zoukankan      html  css  js  c++  java
  • 基于libevent和unix domain socket的本地server

    https://www.pacificsimplicity.ca/blog/libevent-echo-server-tutorial

    根据这一篇写一个最简单的demo。然后开始写client。

    client调优

    client最初的代码如下:

     1 #include <sys/socket.h>
     2 #include <sys/un.h>
     3 #include <stdio.h>
     4 #include <stdlib.h>
     5 #include <unistd.h>
     6 #include <sys/socket.h>
     7 #include <fcntl.h>
     8 #include <errno.h>
     9 
    10 int main(int argc, char *argv[]) {
    11     struct sockaddr_un addr;
    12     int fd,rc;
    13 
    14     if ( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
    15         perror("socket error");
    16         exit(-1);
    17     }
    18 
    19     const char *socket_path = "/tmp/mysocket";
    20     memset(&addr, 0, sizeof(addr));
    21     addr.sun_family = AF_UNIX;
    22     strcpy(addr.sun_path, socket_path);
    23 
    24     if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
    25         perror("connect error");
    26         exit(-1);
    27     }
    28 
    29     char sendbuf[8145] = {0};
    30     rc = 8145;
    31     
    32     {
    33         if (write(fd, sendbuf, rc) != rc) {
    34             if (rc > 0) fprintf(stderr,"partial write");
    35             else {
    36                 perror("write error");
    37                 exit(-1);
    38             }
    39         }        
    40     }
    41     
    42 
    43     char buf[1024] = {0};
    44 
    45     while ((rc = read(fd, buf, 1024)) > 0) {
    46         buf[rc] = '';
    47                 printf("%s
    ", buf);
    48     }
    49 
    50     close(fd);
    51 
    52     return 0;
    53 }            

    代码很简单,会发现有个问题,read这里会阻塞住不退出。

    因为这是阻塞IO,读不到数据时会阻塞。有没办法可以知道服务端已经写完了呢?如果用非阻塞的是不是有不一样的返回码呢。又试了下非阻塞版。

     1 int val = fcntl(fd, F_GETFL, 0);
     2 fcntl(fd, F_SETFL, val|O_NONBLOCK);// 设置为非阻塞
     3 
     4 //...
     5 
     6 char buf[1024] = {0};
     7 while (true) {
     8         rc = read(fd, buf, 1024);
     9         if (rc > 0) {
    10             buf[rc] = '';
    11             printf("recv:%s
    ", buf);
    12         } else if (rc == 0) {
    13             break;
    14         } else if (rc < 0 && (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)) {
    15             //printf("errno %d
    ", errno);
    16             continue;
    17         } else {
    18             break;
    19         }
    20     }

    这时就会出现一直跑到第15行这里,errno一直是EWOULDBLOCK/EAGAIN。

    非阻塞模式下返回值 <0时并且 (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情况下认为连接是正常的, 继续发送。

    https://blog.csdn.net/qq_14821541/article/details/52028924

    好吧,问题同样没有解决。实际上网络通信server端可能会出现很多情况,写得慢、网络慢或者server挂了等,为了鲁棒性,一个比较通用的策略就是超时。如果超了时间就直接退出。

    1 struct timeval tv;
    2 tv.tv_sec = 3;
    3 tv.tv_usec = 0;
    4 setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));

    用阻塞+超时,这样就可以正常退出了。不过还是没解决正常情况下的退出。

    一个简单的思路就是服务端写完了数据,在数据的最终加上一个mark,标识已经写完了,client读到这个mark,就直接退出。

     1 #include <sys/socket.h>
     2 #include <sys/un.h>
     3 #include <stdio.h>
     4 #include <stdlib.h>
     5 #include <unistd.h>
     6 #include <sys/socket.h>
     7 #include <fcntl.h>
     8 #include <errno.h>
     9 
    10 int main(int argc, char *argv[]) {
    11     struct sockaddr_un addr;
    12     int fd,rc;
    13 
    14     if ( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
    15         perror("socket error");
    16         exit(-1);
    17     }
    18 
    19     struct timeval tv;
    20     tv.tv_sec = 3;
    21     tv.tv_usec = 0;
    22     setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
    23 
    24     const char *socket_path = "/tmp/mysocket";
    25     memset(&addr, 0, sizeof(addr));
    26     addr.sun_family = AF_UNIX;
    27     strcpy(addr.sun_path, socket_path);
    28 
    29     if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
    30         perror("connect error");
    31         exit(-1);
    32     }
    33 
    34     const char *end_mark = "$@%^&~";
    35     int end_mark_len = strlen(end_mark);
    36 
    37     char sendbuf[8145] = {0};
    38     rc = 8145;
    39     memcpy(sendbuf + rc - end_mark_len, end_mark, end_mark_len);
    40     
    41     printf("%s %d
    ", sendbuf, rc);
    42 
    43     {
    44         if (write(fd, sendbuf, rc) != rc) {
    45             if (rc > 0) fprintf(stderr,"partial write");
    46             else {
    47                 perror("write error");
    48                 exit(-1);
    49             }
    50         }    
    51     }
    52     
    53 
    54     char buf[1024] = {0};    
    55     while ((rc = read(fd, buf, 1024)) > 0) {
    56         buf[rc] = '';
    57         if (rc < end_mark_len) break;
    58         if (strncmp(buf + rc - end_mark_len, end_mark, end_mark_len) == 0) {
    59             printf("%s
    ", buf);
    60             break;
    61         }
    62     }
    63 
    64     close(fd);
    65 
    66     return 0;
    67 }

    server调优

    https://www.pacificsimplicity.ca/blog/libevent-echo-server-tutorial

    前面我们用bufferevent_setcb来设置回调函数,libevent的回调触发时机是这样的:

    1. 当输入缓冲区的数据大于或等于输入低水位时,读取回调就会被调用。默认情况下,输入低水位的值是 0,也就是说,只要 socket 变得可读,就会调用读取回调。
    2. 当输出缓冲区的数据小于或等于输出低水位时,写入回调就会被调用。默认情况下,输出低水位的值是 0,也就是说,只有当输出缓冲区的数据都发送完了,才会调用写入回调。因此,默认情况下的写入回调也可以理解成为 write complete callback。
    3. 当连接建立、连接关闭、连接超时或者连接发生错误时,则会调用事件回调。

    参考:http://senlinzhan.github.io/2017/08/20/libevent-buffer/

    http://www.wangafu.net/~nickm/libevent-book/Ref6_bufferevent.html

    这里提到的demo,会有几个问题:

    1. client提前退出时,server端继续写数据会收到sigpipe信号,然后直接退出。
    2. echo_read_cb读回调,如果读的数据比较大,可能会触发多次,然而我们需要在数据结束时再同时处理,这里同样需要判断一下数据是否已经读取结束;
    3. 如果client提前退出,即使忽略了sigpipe信号 ,但是链接依旧不会关闭;

     第一个问题,是因为连接建立,若某一端关闭连接,而另一端仍然向它写数据,第一次写数据后会收到RST响应,此后再写数据,内核将向进程发出SIGPIPE信号,通知进程此连接已经断开。而SIGPIPE信号的默认处理是终止程序。解决方案就是直接忽略SIGPIPE信号。

    1 signal(SIGPIPE, SIG_IGN);

    第二个问题,同样用一个mark来标记读取结束。这里用到evbuffer_peek来获取整个buffer内存而不是copy出来再查。

    http://www.wangafu.net/~nickm/libevent-book/Ref7_evbuffer.html

     1 bool CheckReadFinished(struct evbuffer *input) {
     2     const int limit_vec = 10;
     3 
     4     struct evbuffer_iovec v[limit_vec];
     5     int n = evbuffer_peek(input, -1, NULL, v, limit_vec);
     6     if (n <= 0) {
     7         return false;
     8     }
     9 
    10     int end_mark_len = strlen(end_mark);
    11     for (unsigned i = n - 1; i >= 0; --i) {
    12         size_t len = v[i].iov_len;
    13         if (len >= end_mark_len) {
    14             return strncmp((char*)(v[i].iov_base) + (len - end_mark_len), end_mark, end_mark_len) == 0;
    15         } else {
    16             if (strncmp((char*)(v[i].iov_base), end_mark + (end_mark_len - len), len) != 0) {
    17                 return false;
    18             }
    19             end_mark_len -= len;
    20         }
    21     }
    22     return false;
    23 }

    这里直接用了limit_vec来限制大小,如果超出buff大小就认为是错误的。

     1 static void echo_read_cb(struct bufferevent *bev, void *ctx) {
     2     struct evbuffer *input = bufferevent_get_input(bev);
     3     struct evbuffer *output = bufferevent_get_output(bev);
     4     
     5     if (CheckReadFinished(input)) {
     6         size_t len = evbuffer_get_length(input);
     7         printf("we got some data: %d
    ", len);
     8         evbuffer_add_printf(output, end_mark);
     9     }
    10 }

    第三个问题,client异常退出是避免不了的,所以要有容错机制,同样是采用超时来容错。

     1 static void
     2 accept_conn_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *address, int socklen, void *ctx) {
     3     evutil_make_socket_nonblocking(fd);
     4 
     5     struct event_base *base = evconnlistener_get_base(listener);
     6     struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
     7     bufferevent_setcb(bev, echo_read_cb, echo_write_cb, echo_event_cb, NULL);
     8 
     9     // 设置超时,然后断开链接
    10     struct timeval read_tv = {2, 0}, write_tv = {3, 0};
    11     bufferevent_set_timeouts(bev, &read_tv, &write_tv);
    12 
    13     bufferevent_enable(bev, EV_READ | EV_WRITE);
    14 }

    然后在BEV_EVENT_TIMEOUT事件触发时free掉evbuff。因为我们指定了BEV_OPT_CLOSE_ON_FREE,所以这时候就会断掉连接。

    1 static void echo_event_cb(struct bufferevent *bev, short events, void *ctx) {
    2     if (events & BEV_EVENT_ERROR)
    3         perror("Error from bufferevent");
    4     if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
    5         printf("free event
    ");
    6         bufferevent_free(bev);
    7     }
    8 }

    这里我们也可以看到,正常情况下,当client读取结束之后会close(fd),这时就会触发BEV_EVENT_EOF事件,同样是会关掉服务端的连接。

  • 相关阅读:
    C++ 在dynamic_cast&lt;&gt;用法
    Solr入门指南
    Android学习4、Android该Adapter
    在这些形式的验证码
    智课雅思词汇---四、clos和cap和ced是什么意思
    js插件---强大的图片裁剪Cropper
    Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported
    js插件---图片裁剪photoClip
    base64格式的图片数据如何转成图片
    smarty课程---最最最简单的smarty例子
  • 原文地址:https://www.cnblogs.com/linyx/p/9966237.html
Copyright © 2011-2022 走看看