• 用libevent实现的echo服务器及telnet客户端


    以下代码在vs 2010编译通过,使用的libevent版本是:libevent-2.0.22,win7环境测试通过。

    服务器实现:

    1 流程图:

     2 代码:

    // my_telnet.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    
    #include <string.h>
    #include <errno.h>
    #include <stdio.h>
    #include <signal.h>
    
    #ifndef WIN32
    #include <netinet/in.h>
    # ifdef _XOPEN_SOURCE_EXTENDED
    #  include <arpa/inet.h>
    # endif
    #include <sys/socket.h>
    #endif
     
    #include "event2/bufferevent.h"
    #include "event2/buffer.h"
    #include "event2/listener.h"
    #include "event2/util.h"
    #include "event2/event.h"
    #include "event2/util.h"
    
    #include <WinSock2.h>
    //#include "MySimpleLog.h"
    
    
    static const int PORT = 9995;
    
    //读取一行输入并返回
    static void conn_readcb(struct bufferevent *bev, void *user_data)
    {
        //MY_SIMPLE_LOG_DEBUG("conn_readcb!
    ");
    
        //只有读到一行完整行才进行处理 所以这里需要检查缓冲区是否存在换行符
        //如果存在才往下走
        struct evbuffer *input = bufferevent_get_input(bev);
        struct evbuffer_ptr ptrInput;
        if ((ptrInput = evbuffer_search(input , "
    ", 1, NULL)).pos == -1)
        {
            return;
        }
    
        char line[1024] = {0};
        int nRead = 0; //已读取字节数
        int nExpect = ptrInput.pos + 1; //期望读取的字节数
    
        //把这一行读取出来(如果大于1024则需要分次读取)
        while (nRead < nExpect)
        {
            int nLeft = nExpect - nRead;
            int nReadOnce = min(nLeft, sizeof(line) - 1);
    
            int n = bufferevent_read(bev, line, nReadOnce);
            if (n <= 0)
            {
                //MY_SIMPLE_LOG_ERROR("expect to read %d bytes,but get %d.", nReadOnce, n);
                break;
            }
            line[n] = '';
            //把待发送数据添加到发送缓冲中,这里不会立即发送数据,要等conn_readcb返回后才会发送的
            bufferevent_write(bev, line, n);
            nRead += nReadOnce;
            //MY_SIMPLE_LOG_DEBUG("n = %d nRead = %d nExpect = %d! line = [%s]", n, nRead, nExpect, line);
        }
    
        //启动写事件,发送缓冲内容 //写事件是默认开启的,这里不需要额外设置
        //bufferevent_enable(bev, EV_WRITE | EV_PERSIST);
        
        //为避免粘包,这里要判断缓冲里面是否还有剩余数据,如果有要特别处理
        //这是因为libevent是边缘触发的,而不是水平触发
        if (evbuffer_get_length(input) != 0)
        {
            //本来是想直接激活读事件
            //但是发现要访问bufferevent未公开的成员,就不这样搞了
            //event_active(&(bev->ev_read), EV_READ, 1);
            conn_readcb(bev, user_data);
        }
    }
    
    static void conn_writecb(struct bufferevent *bev, void *user_data)
    {
        //MY_SIMPLE_LOG_DEBUG("conn_writecb!");
        struct evbuffer *output = bufferevent_get_output(bev);
        if (evbuffer_get_length(output) == 0) 
        {
            //MY_SIMPLE_LOG_DEBUG("buffer writed
    ");
            //bufferevent_free(bev);
        }
    }
    
    
    
    static void conn_writecb_once(struct bufferevent *bev, void *user_data)
    {
        //MY_SIMPLE_LOG_DEBUG("conn_writecb_once!");
        struct evbuffer *output = bufferevent_get_output(bev);
        if (evbuffer_get_length(output) == 0) 
        {
            //MY_SIMPLE_LOG_DEBUG("flushed free
    ");
            bufferevent_free(bev);
        }
    }
    
    static void conn_eventcb(struct bufferevent *bev, short events, void *user_data)
    {
        //MY_SIMPLE_LOG_DEBUG("conn_eventcb!
    ");
    
        //判断是因为什么原因调用了此函数
        if (events & BEV_EVENT_EOF) 
        {
            //MY_SIMPLE_LOG_DEBUG("Connection closed.
    ");
        } 
        else if (events & BEV_EVENT_ERROR) 
        {
            //MY_SIMPLE_LOG_DEBUG("Got an error on the connection: %s
    ",
            //    strerror(errno));/*XXX win32*/
        }
        else if ((events & BEV_EVENT_TIMEOUT) && (events & BEV_EVENT_READING))
        {
            //发生读超时
    
    
            //超时事件发生时,会禁止相应读/写事件,需要重新注册
            //这里由于业务逻辑上认为超时就要关闭socket,就没必要这样做了
            //bufferevent_enable(bev, EV_READ | EV_PERSIST);
    
            //发送一个超时消息给客户端,然后就关闭bufferevent
            const char time_out_msg[] = "time out!
    ";
            //MY_SIMPLE_LOG_INFOR(time_out_msg);
            int n = bufferevent_write(bev, time_out_msg, sizeof(time_out_msg) - 1);
            //MY_SIMPLE_LOG_DEBUG("bufferevent_write ret %d send %d", n, int(sizeof(time_out_msg) - 1));
    //        据说这个版本bufferevent_flush没实现 实测总是返回0
    //         n = bufferevent_flush(bev, EV_WRITE, BEV_FINISHED);
    //         //MY_SIMPLE_LOG_DEBUG("bufferevent_flush ret %d", n);
    
            //等写完超时信息就退出
            bufferevent_setcb(bev, NULL, conn_writecb_once, NULL, user_data);
            return;
        }
        /* None of the other events can happen here, since we haven't enabled
         * timeouts */
        bufferevent_free(bev);
    }
    
    //ctrl + c处理函数
    static void signal_cb(evutil_socket_t sig, short events, void *user_data)
    {
        //MY_SIMPLE_LOG_DEBUG("signal_cb!
    ");
    
        printf("to exit in 2 seconds.
    ");
        struct event_base *base = (struct event_base *)user_data;
        struct timeval delay = { 2, 0 };
    
        //MY_SIMPLE_LOG_DEBUG("Caught an interrupt signal; exiting cleanly in two seconds.
    ");
    
        event_base_loopexit(base, &delay);
    }
    
    //新连接到来处理函数
    static void listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
        struct sockaddr *sa, int socklen, void *user_data)
    {
        //MY_SIMPLE_LOG_DEBUG("listener_cb!
    ");
        struct event_base *base = (struct event_base *)user_data;
        struct bufferevent *bev;
    
        //将该连接设置成非阻塞
        evutil_make_socket_nonblocking(fd);
        //BEV_OPT_CLOSE_ON_FREE参数让bufferevent被删除时会自动清理对应的socket
        bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
        if (!bev) 
        {
            //MY_SIMPLE_LOG_ERROR("Error constructing bufferevent!");
            event_base_loopbreak(base);
            return;
        }
        //MY_SIMPLE_LOG_DEBUG("new connect!
    ");
    
        //开始设置各个事件的回调函数
        //最后一个参数是各个回调函数的user_data 这里不需要用到,填NULL
        //除了读写事件外,其它很多事件都是会回调conn_eventcb
        bufferevent_setcb(bev, conn_readcb, conn_writecb, conn_eventcb, NULL);
    
        //EV_PERSIST不设置的话读事件只会触发一次
        bufferevent_enable(bev, EV_READ | EV_PERSIST);
        //默认情况下bufferevent自动监听可读事件,如有需要可以关闭
        //关闭的情况下bufferevent_write调用后,不会真正往socket写,只保留在缓冲区
        //bufferevent_disable(bev, EV_WRITE);
        struct timeval delay = { 10, 0 };
        //读超时10秒 写超时无限
        bufferevent_set_timeouts(bev, &delay, NULL);
    }
    
    /*
    基本流程:
    1 新建event_base
    2 生成监听socket
    3 将
    */
    int main(int argc, char **argv)
    {
        struct event_base *base;
        struct evconnlistener *listener;
        struct event *signal_event;
    
        struct sockaddr_in sin;
    
    #ifdef WIN32
        WSADATA wsa_data;
        WSAStartup(0x0201, &wsa_data);
    #endif
    
        //MY_SIMPLE_LOG_INIT(//MY_SIMPLE_LOG_LEVEL_DEBUG);
    
        //初始化event_base 这个是全局唯一的
        base = event_base_new();
        if (!base) 
        {
            //MY_SIMPLE_LOG_ERROR("Could not initialize libevent!
    ");
            return 1;
        }
        //MY_SIMPLE_LOG_DEBUG("all inited");
        
        //往event_base新增一个event,监听连接事件
        memset(&sin, 0, sizeof(sin));
        sin.sin_family = AF_INET;
        sin.sin_port = htons(PORT);
        
        listener = evconnlistener_new_bind(base, listener_cb, (void *)base,
            LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE, -1,
            (struct sockaddr*)&sin,
            sizeof(sin));
    
        if (!listener) 
        {
            //MY_SIMPLE_LOG_ERROR("Could not create a listener!
    ");
            event_base_free(base);
            return 1;
        }
        //MY_SIMPLE_LOG_INFOR("listener created");
    
        //evutil_make_socket_nonblocking(listener); //监听socket不应该设置成非阻塞
        //监听ctrl+c信号
        signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);
    
        if (!signal_event || event_add(signal_event, NULL)<0) 
        {
            //MY_SIMPLE_LOG_ERROR("Could not create/add a signal event!
    ");
            evconnlistener_free(listener);
            event_base_free(base);
            return 1;
        }
        //MY_SIMPLE_LOG_INFOR("signal_event created");
    
        //MY_SIMPLE_LOG_INFOR("beging to dispatch");
        printf("beging to dispatch...
    ");
        //开始监听连接 这是一个内置循环的函数 event_base_loopexit函数调用才会返回
        event_base_dispatch(base);
        //MY_SIMPLE_LOG_INFOR("end dispatch
    ");
    
        //清理资源
        evconnlistener_free(listener);
        event_free(signal_event);
        event_base_free(base);
    
        //MY_SIMPLE_LOG_DEBUG("done
    ");
        return 0;
    }

    客户端实现:

    客户端实现较简单,就不附流程图了,直接贴代码

    // my_telnet.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    
    #include <string.h>
    #include <errno.h>
    #include <stdio.h>
    #include <io.h>
    #include <signal.h>
    
    #ifndef WIN32
    #include <netinet/in.h>
    # ifdef _XOPEN_SOURCE_EXTENDED
    #  include <arpa/inet.h>
    # endif
    #include <sys/socket.h>
    #else
    #include <Ws2tcpip.h>
    #endif
     
    #include "event2/bufferevent.h"
    #include "event2/buffer.h"
    #include "event2/listener.h"
    #include "event2/util.h"
    #include "event2/event.h"
    #include "event2/util.h"
    #include <WinSock2.h>
    #include <conio.h>  // _kbhit(), _getch()
    //#include "MySimpleLog.h"
    #include <assert.h>
    static const int PORT = 9995;
    
    int tcp_connect_server(const char* server_ip, int port);
    
    
    void cmd_msg_cb(int fd, short events, void* arg);
    void server_msg_cb(struct bufferevent* bev, void* arg);
    void event_cb(struct bufferevent *bev, short event, void *arg);
    void signal_cb(evutil_socket_t sig, short events, void *user_data);
    
    typedef struct 
    {
        struct bufferevent* bev;
        struct evbuffer *buf;
    }MyTelnetParam;
    
    int main(int argc, char** argv)
    {
        if( argc < 3 )
        {
            //两个参数依次是服务器端的IP地址、端口号
            fprintf(stderr, "please input 2 parameter
    ");
            return -1;
        }
    
    
    #ifdef WIN32
        WSADATA wsa_data;
        WSAStartup(0x0201, &wsa_data);
    #endif
    
        //MY_SIMPLE_LOG_INIT(//MY_SIMPLE_LOG_LEVEL_DEBUG);
    
        struct event_base *base = event_base_new();
    
        //创建bufferevent 暂时不设置对应的socket
        struct bufferevent* bev = bufferevent_socket_new(base, -1,
            BEV_OPT_CLOSE_ON_FREE);
        assert(bev != NULL);
        /*
        //监听终端输入事件 这是linux的做法 win上面不能这样玩
        struct event* ev_cmd = event_new(base, _fileno(stdin),
            EV_READ | EV_PERSIST,
            cmd_msg_cb, (void*)bev);
    
    
        event_add(ev_cmd, NULL);
        */
        struct evbuffer *buf = evbuffer_new();
        assert(buf != NULL);
        MyTelnetParam param = {bev, buf};
        
        //监听终端输入事件 用超时事件 这里不用evtimer_new 因为那个事件只能触发一次
        struct event *ev_cmd = event_new(base, -1, EV_PERSIST, cmd_msg_cb, (void *)&param);
        assert(event_new != NULL);
        //每100微秒检测一次键盘输入情况 如果有输入则读取输入并发送给服务器
        //时间太短CPU占用太高,太长用户感觉有延迟
        struct timeval wait_time = {0, 100};
        event_add(ev_cmd, &wait_time);
    
    
        //监听ctrl+c信号
        struct event *signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);
        assert(signal_event != NULL);
        event_add(signal_event, NULL);
    
        //服务器ip信息初始化
        struct sockaddr_in server_addr;
        memset(&server_addr, 0, sizeof(server_addr) );
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(atoi(argv[2]));
        //inet_aton(argv[1], &server_addr.sin_addr);
        server_addr.sin_addr.s_addr = inet_addr(argv[1]);
    
        //连接服务器,并将socket设置到bufferevent
        bufferevent_socket_connect(bev, (struct sockaddr *)&server_addr,
            sizeof(server_addr));
    
    
        bufferevent_setcb(bev, server_msg_cb, NULL, event_cb, NULL);
        bufferevent_enable(bev, EV_READ | EV_PERSIST);
    
    
    
        event_base_dispatch(base);
        //这将自动close套接字和free读写缓冲区
        bufferevent_free(bev);
        event_free(ev_cmd);
        event_free(signal_event);
        evbuffer_free(buf);
        event_base_free(base);
    
    #ifdef WIN32
        WSACleanup();
    #endif
    
        //MY_SIMPLE_LOG_DEBUG("finished 
    ");
        return 0;
    }
    
    void cmd_msg_cb(int fd, short events, void* arg)
    {
        MyTelnetParam *param = (MyTelnetParam *)arg;
        struct bufferevent *bev = param->bev;
        struct evbuffer *buf = param->buf;
        
        //如果有键盘输入,则读取一个字符  如果不加这判断直接getch有可能会阻塞回调
        if (_kbhit())
        {
            char cInput = EOF;
            do
            {
                //读取键盘输入并回显 不用getchar 因为getchar要等用户按回车才能获取
                int nInput = (char) _getch();
                cInput = (char) nInput;
                evbuffer_add(buf, &cInput, 1);
    
                //这里最好用putch 不用putchar 因为有时是读取到非可视化字符,如方向键
                putch(nInput);
    
                //读满一行就先返回 同时换行
                if (cInput == '
    ')
                {
                    //实际测试发现,按回车时用getch只能读取到一次按键
    
                    cInput = '
    ';
                    evbuffer_add(buf, &cInput, 1);
                    putch(cInput);
                    break;
                }     
            } while (_kbhit());
    
            //将buf整理成连续的内存
            //size_t nLen = evbuffer_get_length(buf);
            //evbuffer_pullup(buf, nLen);
    
            //往socket输出读取到的字节并移除buf现有字节
            bufferevent_write_buffer(bev, buf);
            evbuffer_drain(buf, evbuffer_get_length(buf));
        }
    }
    
    //ctrl + c处理函数
    static void signal_cb(evutil_socket_t sig, short events, void *user_data)
    {
        //MY_SIMPLE_LOG_DEBUG("signal_cb!
    ");
    
        printf("to exit in 2 seconds.
    ");
        struct event_base *base = (struct event_base *)user_data;
        struct timeval delay = { 2, 0 };
    
        //MY_SIMPLE_LOG_DEBUG("Caught an interrupt signal; exiting cleanly in two seconds.
    ");
    
        event_base_loopexit(base, &delay);
    }
    
    void server_msg_cb(struct bufferevent* bev, void* arg)
    {
        char msg[1024 * 4];
    
        size_t len = bufferevent_read(bev, msg, sizeof(msg) - 1);
        msg[len] = '';
    
        printf("recv %d:[%s]
    ", (int)len, msg);
    }
    
    
    void event_cb(struct bufferevent *bev, short event, void *arg)
    {
        if (event & BEV_EVENT_EOF)
            printf("connection closed
    ");
        else if (event & BEV_EVENT_ERROR)
            printf("some other error
    ");
        else if( event & BEV_EVENT_CONNECTED)
        {
            printf("the client has connected to server
    ");
            return ;
        }
    
        // 不等目前队列中所有回调事件完成,立即退出循环 
        // 如果等待回调事件完成再退出要用event_base_loopexit
        // 这里不能用event_base_loopexit 不然如果用户操作太快,cmd_msg_cb会报错
        event_base_loopbreak(bufferevent_get_base(bev));
    }
  • 相关阅读:
    利用vuex 做个简单的前端缓存
    EFcore 解决 SQLite 没有datetime 类型的问题
    dotnet 清理 nuget 缓存
    .net 5 单文件模式发布异常 CodeBase is not supported on assemblies loaded from a single-file bundle
    ubuntu 开启ip转发的方法
    Vue-ECharts 6 迁移记录
    System.Text.Json 5.0 已增加支持将Enum 由默认 Number类型 转换为String JsonStringEnumConverter
    Windows 10 LTSC 2019 正式版轻松激活教程
    Mac 提示Permission denied
    苹果手机代理 charles 提示(此链接非私人连接)
  • 原文地址:https://www.cnblogs.com/kingstarer/p/6629562.html
Copyright © 2020-2023  润新知