基于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] = '\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的回调触发时机是这样的:
- 当输入缓冲区的数据大于或等于输入低水位时,读取回调就会被调用。默认情况下,输入低水位的值是 0,也就是说,只要 socket 变得可读,就会调用读取回调。
- 当输出缓冲区的数据小于或等于输出低水位时,写入回调就会被调用。默认情况下,输出低水位的值是 0,也就是说,只有当输出缓冲区的数据都发送完了,才会调用写入回调。因此,默认情况下的写入回调也可以理解成为 write complete callback。
- 当连接建立、连接关闭、连接超时或者连接发生错误时,则会调用事件回调。
参考:http://senlinzhan.github.io/2017/08/20/libevent-buffer/
http://www.wangafu.net/~nickm/libevent-book/Ref6_bufferevent.html
这里提到的demo,会有几个问题:
- client提前退出时,server端继续写数据会收到sigpipe信号,然后直接退出。
- echo_read_cb读回调,如果读的数据比较大,可能会触发多次,然而我们需要在数据结束时再同时处理,这里同样需要判断一下数据是否已经读取结束;
- 如果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相关推荐
- 【技术应用】java基于UNIX域套接字(unix domain socket)连接mysql数据库
前言 Unix domain socket 又叫 IPC(inter-process communication 进程间通信)socket,用于实现同一主机上的进程间通信. socket 原本是为网络 ...
- UNIX Domain Socket(UDS)是什么?同一台主机间进程间通信
文章目录 概述 流程介绍 概述 Linux下进程通讯方式有很多,比较典型的有套接字,平时比较常用的套接字是基于TCP/IP协议的,适用于两台不同主机上两个进程间通信, 通信之前需要指定IP地址. 但是 ...
- 网络协议之:socket协议详解之Unix domain Socket
文章目录 简介 什么是Unix domain Socket 使用socat来创建Unix Domain Sockets 使用ss命令来查看Unix domain Socket 使用nc连接到Unix ...
- 网络协议之socket协议详解之Unix domain Socket
简介 之前的文章我们讲到了Socket中的Stream Socket和Datagram Socket,和有连接的Stream Socket不同,Datagram Socket是无连接的.有连接的Str ...
- unix domain socket 浅析
unix domain socket unix domain socket 是在socket架构上发展起来的用于同一台主机的进程间通讯(IPC: Inter-Process Communication ...
- Unix Domain Socket 域套接字实现
主要注意流程: STREAM SOCKET: Server : socket() ---> bind() ---> listen() ---> accept() Client: ...
- unix网络编程之UNIX Domain Socket IPC (sockaddr_un )
socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket.虽然网络socket也可用于同一台主机的进程间通讯(通过loop ...
- Unix domain socket 简介(进程间通信,进程通信)
Unix domain socket 又叫 IPC(inter-process communication 进程间通信) socket,用于实现同一主机上的进程间通信.socket 原本是为网络通讯设 ...
- Unix domain socket IPC
UNIX Domain socket 虽然网络socket也可用于同一台主机的进程间通讯(通过lo地址127.0.0.1),但是unix domain socket用于IPC更有效率:不需要经过网络协 ...
最新文章
- 运用家居收纳储物空间 小空间变出大身材
- 饼图大小调整_Excel图表变形计:个性化的创意饼图,原来可以很快做出来!
- Java:String和Date、Timestamp之间的转换
- STM32-GPIO的配置和使用
- python死循环_怎么避免Python程序出现死循环(无限循环)?
- 人工神经网络—神经元的数学模型
- POI-HSSF表格
- Java的运算符-取整,取绝对值,取余数
- 设计师如何吸引用户注意力?
- Cloud一分钟 |小米瞄上电纸书市场;员工《卫报》开专栏控诉亚马逊;拼多多Q3财报:总收入33.724亿元...
- “大白兔”迎来60岁生日 各类衍生品受青睐
- 【算法】判断一个点是否在多边形之内
- LM2596的肖特基二极管选择的注意点
- python模拟鼠标 事件
- 英语写作——必备的200条句子【写作必备!!!】
- Java毕设项目OA办公系统
- SpringBoot与拦截器
- 达梦数据库DCA培训课程总结
- Allegro·芯片GND引脚铺铜问题及解决方案)
- 每个专业UI设计师需要的4种职业技能!