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] = '\0';
47                 printf("%s\n", 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] = '\0';
11             printf("recv:%s\n", buf);
12         } else if (rc == 0) {
13             break;
14         } else if (rc < 0 && (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)) {
15             //printf("errno %d\n", 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\n", 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] = '\0';
57         if (rc < end_mark_len) break;
58         if (strncmp(buf + rc - end_mark_len, end_mark, end_mark_len) == 0) {
59             printf("%s\n", 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\n", 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\n");
6         bufferevent_free(bev);
7     }
8 }

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

转载于:https://www.cnblogs.com/linyx/p/9966237.html

基于libevent和unix domain socket的本地server相关推荐

  1. 【技术应用】java基于UNIX域套接字(unix domain socket)连接mysql数据库

    前言 Unix domain socket 又叫 IPC(inter-process communication 进程间通信)socket,用于实现同一主机上的进程间通信. socket 原本是为网络 ...

  2. UNIX Domain Socket(UDS)是什么?同一台主机间进程间通信

    文章目录 概述 流程介绍 概述 Linux下进程通讯方式有很多,比较典型的有套接字,平时比较常用的套接字是基于TCP/IP协议的,适用于两台不同主机上两个进程间通信, 通信之前需要指定IP地址. 但是 ...

  3. 网络协议之:socket协议详解之Unix domain Socket

    文章目录 简介 什么是Unix domain Socket 使用socat来创建Unix Domain Sockets 使用ss命令来查看Unix domain Socket 使用nc连接到Unix ...

  4. 网络协议之socket协议详解之Unix domain Socket

    简介 之前的文章我们讲到了Socket中的Stream Socket和Datagram Socket,和有连接的Stream Socket不同,Datagram Socket是无连接的.有连接的Str ...

  5. unix domain socket 浅析

    unix domain socket unix domain socket 是在socket架构上发展起来的用于同一台主机的进程间通讯(IPC: Inter-Process Communication ...

  6. Unix Domain Socket 域套接字实现

    主要注意流程: STREAM SOCKET: Server :  socket() --->  bind() ---> listen()  ---> accept() Client: ...

  7. unix网络编程之UNIX Domain Socket IPC (sockaddr_un )

    socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket.虽然网络socket也可用于同一台主机的进程间通讯(通过loop ...

  8. Unix domain socket 简介(进程间通信,进程通信)

    Unix domain socket 又叫 IPC(inter-process communication 进程间通信) socket,用于实现同一主机上的进程间通信.socket 原本是为网络通讯设 ...

  9. Unix domain socket IPC

    UNIX Domain socket 虽然网络socket也可用于同一台主机的进程间通讯(通过lo地址127.0.0.1),但是unix domain socket用于IPC更有效率:不需要经过网络协 ...

最新文章

  1. 运用家居收纳储物空间 小空间变出大身材
  2. 饼图大小调整_Excel图表变形计:个性化的创意饼图,原来可以很快做出来!
  3. Java:String和Date、Timestamp之间的转换
  4. STM32-GPIO的配置和使用
  5. python死循环_怎么避免Python程序出现死循环(无限循环)?
  6. 人工神经网络—神经元的数学模型
  7. POI-HSSF表格
  8. Java的运算符-取整,取绝对值,取余数
  9. 设计师如何吸引用户注意力?
  10. Cloud一分钟 |小米瞄上电纸书市场;员工《卫报》开专栏控诉亚马逊;拼多多Q3财报:总收入33.724亿元...
  11. “大白兔”迎来60岁生日 各类衍生品受青睐
  12. 【算法】判断一个点是否在多边形之内
  13. LM2596的肖特基二极管选择的注意点
  14. python模拟鼠标 事件
  15. 英语写作——必备的200条句子【写作必备!!!】
  16. Java毕设项目OA办公系统
  17. SpringBoot与拦截器
  18. 达梦数据库DCA培训课程总结
  19. Allegro·芯片GND引脚铺铜问题及解决方案)
  20. 每个专业UI设计师需要的4种职业技能!

热门文章

  1. python 字符编码问题
  2. Dispatcher与UI线程交互
  3. linux源码包卸载方式
  4. HTTP 状态代码及其定义
  5. 基于python的一个运维自动化的项目(进度更新)【已开源】
  6. nginx特定的 404页面利于seo
  7. 转:ASP.NET状态保存方法
  8. node 压缩模块速成
  9. ActiveMQ5.14.5配置参数详解
  10. /dev/rdsk 与 /dev/dsk区别