基于libevent和unix domain socket的本地server
https://www.pacificsimplicity.ca/blog/libevent-echo-server-tutorial
根据这一篇写一个最简单的demo。然后开始写client。
client调优
client最初的代码如下:
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <errno.h> int main(int argc, char *argv[]) {
struct sockaddr_un addr;
int fd,rc; if ( (fd = socket(AF_UNIX, SOCK_STREAM, )) == -) {
perror("socket error");
exit(-);
} const char *socket_path = "/tmp/mysocket";
memset(&addr, , sizeof(addr));
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, socket_path); if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -) {
perror("connect error");
exit(-);
} char sendbuf[] = {};
rc = ; {
if (write(fd, sendbuf, rc) != rc) {
if (rc > ) fprintf(stderr,"partial write");
else {
perror("write error");
exit(-);
}
}
} char buf[] = {}; while ((rc = read(fd, buf, )) > ) {
buf[rc] = '\0';
printf("%s\n", buf);
} close(fd); return ;
}
代码很简单,会发现有个问题,read这里会阻塞住不退出。
因为这是阻塞IO,读不到数据时会阻塞。有没办法可以知道服务端已经写完了呢?如果用非阻塞的是不是有不一样的返回码呢。又试了下非阻塞版。
int val = fcntl(fd, F_GETFL, );
fcntl(fd, F_SETFL, val|O_NONBLOCK);// 设置为非阻塞 //... char buf[] = {};
while (true) {
rc = read(fd, buf, );
if (rc > ) {
buf[rc] = '\0';
printf("recv:%s\n", buf);
} else if (rc == ) {
break;
} else if (rc < && (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)) {
//printf("errno %d\n", errno);
continue;
} else {
break;
}
}
这时就会出现一直跑到第15行这里,errno一直是EWOULDBLOCK/EAGAIN。
非阻塞模式下返回值 <0时并且 (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情况下认为连接是正常的, 继续发送。
https://blog.csdn.net/qq_14821541/article/details/52028924
好吧,问题同样没有解决。实际上网络通信server端可能会出现很多情况,写得慢、网络慢或者server挂了等,为了鲁棒性,一个比较通用的策略就是超时。如果超了时间就直接退出。
struct timeval tv;
tv.tv_sec = ;
tv.tv_usec = ;
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
用阻塞+超时,这样就可以正常退出了。不过还是没解决正常情况下的退出。
一个简单的思路就是服务端写完了数据,在数据的最终加上一个mark,标识已经写完了,client读到这个mark,就直接退出。
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <errno.h> int main(int argc, char *argv[]) {
struct sockaddr_un addr;
int fd,rc; if ( (fd = socket(AF_UNIX, SOCK_STREAM, )) == -) {
perror("socket error");
exit(-);
} struct timeval tv;
tv.tv_sec = ;
tv.tv_usec = ;
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)); const char *socket_path = "/tmp/mysocket";
memset(&addr, , sizeof(addr));
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, socket_path); if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -) {
perror("connect error");
exit(-);
} const char *end_mark = "$@%^&~";
int end_mark_len = strlen(end_mark); char sendbuf[] = {};
rc = ;
memcpy(sendbuf + rc - end_mark_len, end_mark, end_mark_len); printf("%s %d\n", sendbuf, rc); {
if (write(fd, sendbuf, rc) != rc) {
if (rc > ) fprintf(stderr,"partial write");
else {
perror("write error");
exit(-);
}
}
} char buf[] = {};
while ((rc = read(fd, buf, )) > ) {
buf[rc] = '\0';
if (rc < end_mark_len) break;
if (strncmp(buf + rc - end_mark_len, end_mark, end_mark_len) == ) {
printf("%s\n", buf);
break;
}
} close(fd); return ;
}
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信号。
signal(SIGPIPE, SIG_IGN);
第二个问题,同样用一个mark来标记读取结束。这里用到evbuffer_peek来获取整个buffer内存而不是copy出来再查。
http://www.wangafu.net/~nickm/libevent-book/Ref7_evbuffer.html
bool CheckReadFinished(struct evbuffer *input) {
const int limit_vec = ;
struct evbuffer_iovec v[limit_vec];
int n = evbuffer_peek(input, -, NULL, v, limit_vec);
if (n <= ) {
return false;
}
int end_mark_len = strlen(end_mark);
for (unsigned i = n - ; i >= ; --i) {
size_t len = v[i].iov_len;
if (len >= end_mark_len) {
return strncmp((char*)(v[i].iov_base) + (len - end_mark_len), end_mark, end_mark_len) == ;
} else {
if (strncmp((char*)(v[i].iov_base), end_mark + (end_mark_len - len), len) != ) {
return false;
}
end_mark_len -= len;
}
}
return false;
}
这里直接用了limit_vec来限制大小,如果超出buff大小就认为是错误的。
static void echo_read_cb(struct bufferevent *bev, void *ctx) {
struct evbuffer *input = bufferevent_get_input(bev);
struct evbuffer *output = bufferevent_get_output(bev);
if (CheckReadFinished(input)) {
size_t len = evbuffer_get_length(input);
printf("we got some data: %d\n", len);
evbuffer_add_printf(output, end_mark);
}
}
第三个问题,client异常退出是避免不了的,所以要有容错机制,同样是采用超时来容错。
static void
accept_conn_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *address, int socklen, void *ctx) {
evutil_make_socket_nonblocking(fd); struct event_base *base = evconnlistener_get_base(listener);
struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev, echo_read_cb, echo_write_cb, echo_event_cb, NULL); // 设置超时,然后断开链接
struct timeval read_tv = {, }, write_tv = {, };
bufferevent_set_timeouts(bev, &read_tv, &write_tv); bufferevent_enable(bev, EV_READ | EV_WRITE);
}
然后在BEV_EVENT_TIMEOUT事件触发时free掉evbuff。因为我们指定了BEV_OPT_CLOSE_ON_FREE,所以这时候就会断掉连接。
static void echo_event_cb(struct bufferevent *bev, short events, void *ctx) {
if (events & BEV_EVENT_ERROR)
perror("Error from bufferevent");
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
printf("free event\n");
bufferevent_free(bev);
}
}
这里我们也可以看到,正常情况下,当client读取结束之后会close(fd),这时就会触发BEV_EVENT_EOF事件,同样是会关掉服务端的连接。
基于libevent和unix domain socket的本地server的更多相关文章
- 【转】PHP实现系统编程(四)--- 本地套接字(Unix Domain Socket)
原文:http://blog.csdn.net/zhang197093/article/details/78143687?locationNum=6&fps=1 --------------- ...
- Unix domain socket
转载:http://www.cnblogs.com/chekliang/p/3222950.html socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是 ...
- (unix domain socket)使用udp发送>=128K的消息会报ENOBUFS的错误
一个困扰我两天的问题, Google和Baidu没有找到解决方法! 此文为记录这个问题,并给出原因和解决方法. 1.Unix domain socket简介 unix域协议并不是一个实际的协议族,而是 ...
- monitor a local unix domain socket like tcpdump
Can I monitor a local unix domain socket like tcpdump? - Super User https://superuser.com/questions/ ...
- [dev][socket] unix domain socket删除socket文件
问题 在使用unix domain socket的时候,bind之后,会在本地路径里 产生一个与path对应的socket文件. 如何正确的在用完socket之后,对其销毁呢? 方案 使用 unlin ...
- Unix domain socket IPC
UNIX Domain socket 虽然网络socket也可用于同一台主机的进程间通讯(通过lo地址127.0.0.1),但是unix domain socket用于IPC更有效率:不需要经过网络协 ...
- [apue] 作为 daemon, 启动 Unix Domain Socket 侦听失败?
前段时间写一个传递文件句柄的小 demo,有 server 端.有 client 端,之间通过 Unix Domain Socket 通讯. 在普通模式下,双方可以正常建立连接,当server端作为d ...
- Envoy 基础教程:使用 Unix Domain Socket(UDS) 与上游集群通信
Envoy Proxy 在大多数情况下都是作为 Sidecar 与应用部署在同一网络环境中,每个应用只需要与 Envoy(localhost)交互,不需要知道其他服务的地址.然而这并不是 Envoy ...
- 网络协议之:socket协议详解之Unix domain Socket
目录 简介 什么是Unix domain Socket 使用socat来创建Unix Domain Sockets 使用ss命令来查看Unix domain Socket 使用nc连接到Unix do ...
随机推荐
- nginx目录路径重定向[转]
如果希望域名后边跟随的路径指向本地磁盘的其他目录,而不是默认的web目录时,需要设置nginx目录访问重定向. 应用场景:dashidan.com/image自动跳转到dashidan.com/fol ...
- JAVA多线程提高六:java5线程并发库的应用_线程池
前面我们对并发有了一定的认识,并且知道如何创建线程,创建线程主要依靠的是Thread 的类来完成的,那么有什么缺陷呢?如何解决? 一.对比new Threadnew Thread的弊端 a. 每次ne ...
- SpringCloud(四)服务发现与消费:以ribbon为例
说明: ribbon是spring-cloud中作为服务消费者的一种角色,客户端可以通过它来对服务提供者的服务进行消费, 比如本例中是服务提供者注册到注册中心,服务提供者提供了一个服务接口,返回一个h ...
- Nodejs文件监控chokidar
最近有个需求是扫描用例,用例是放在svn上,如果每次扫描都去遍历目录的话会有占用太多的io,所以想着用文件监控,有文件变化时只对该文件进行操作. Nodejs里的 chokidar 模块可以更好的对文 ...
- 网络流入门--最大流算法Dicnic 算法
感谢WHD的大力支持 最早知道网络流的内容便是最大流问题,最大流问题很好理解: 解释一定要通俗! 如右图所示,有一个管道系统,节点{1,2,3,4},有向管道{A,B,C,D,E},即有向图一张. ...
- 【SRM20】数学场
第一题 n个m位二进制,求异或值域总和. [题解]异或值域--->使用线性基,解决去重问题. m位二进制--->拆位,每位根据01数量可以用组合数快速统计总和. #include<c ...
- 【洛谷P2015】二叉苹果树
题目描述 有一棵苹果树,如果树枝有分叉,一定是分2叉(就是说没有只有1个儿子的结点) 这棵树共有N个结点(叶子点或者树枝分叉点),编号为1-N,树根编号一定是1. 我们用一根树枝两端连接的结点的编号来 ...
- SMTP暴力破解
这里实现一个SMTP的暴力破解程序,实验搭建的是postfix服务器,猜解用户名字典(user.txt)和密码字典(password.txt)中匹配的用户名密码对, 程序开发环境是: WinXP VC ...
- 天梯赛 L2-006 树的遍历 (二叉树)
给定一棵二叉树的后序遍历和中序遍历,请你输出其层序遍历的序列.这里假设键值都是互不相等的正整数. 输入格式: 输入第一行给出一个正整数N(<=30),是二叉树中结点的个数.第二行给出其后序遍历序 ...
- Tensorflow中使用TFRecords高效读取数据--结合Attention-over-Attention Neural Network for Reading Comprehension
原文链接:https://arxiv.org/pdf/1607.04423.pdf 本片论文主要讲了Attention Model在完形填空类的阅读理解上的应用. 转载:https://blog.cs ...