非阻塞socket与epoll
阻塞socket。
–阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。
–对于文件操作read,fread函数调用会将线程阻塞。
–对于socket,accept与recv、recvfrom函数调用会将线程阻塞。
–为了避免整个进程被阻塞后挂起,所以在阻塞模式下,往往需要采用多线程技术。
–一个进程中可并发的线程总数是有限的,在处理大量客户端sokcet连接(比如上万个client socket),通过线程并发处理socket并不方便,效率也不高。
非阻塞socket。
–非阻塞调用是指调用立刻返回。
–在非阻塞模式下,accept与recv、recvfrom函数调用会立刻返回。
–在nonblocking状态下调用accept函数,如果没有客户端socket连接请求,那么accept函数返回-1,同时errno值为11。
–在nonblocking状态下调用recv、recvfrom函数,如果没有数据,函数返回-1,同时errno值为11。如果socket已经关闭,函数返回0。
–在nonblocking状态下对一个已经关闭的socket调用send函数,将引发一个SIGPIPE信号,进程必须捕捉这个信号,因为SIGPIPE系统默认的处理方式是关闭进程。
fcntl函数调用
fcntl函数可以将文件或者socket描述符设置为阻塞或者非阻塞状态
int fcntl(int fd, int cmd, ... /* arg */ );
参数fd为要设置的文件描述符或者socket。
参数cmd, F_GETFL为得到目前状态, F_SETFL为设置状态。
宏定义O_NONBLOCK代表非阻塞,0代表阻塞。
返回值为描述符当前状态。
fcntl函数调用设置非阻塞例子 int opts = fcntl(st, F_GETFL); if (opts < 0) { printf("fcntl failed %s\n", strerror(errno)); } opts = opts | O_NONBLOCK; if (fcntl(st, F_SETFL, opts) < 0) { printf("fcntl failed %s\n", strerror(errno)); }
fcntl函数调用设置阻塞例子 if (fcntl(st, F_SETFL, 0) < 0) { printf("fcntl failed %s\n", strerror(errno)); }
在非阻塞模式下,如何能知道accept与recv有数据返回呢?
epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。
epoll的系统调用函数:
–epoll_create
•epoll_create用来创建一个epoll文件描述符。
–epoll_ctl
•epoll_ctl用来添加/修改/删除需要侦听的文件描述符及其事件。
–epoll_wait。
•epoll_wait接收发生在被侦听的描述符上的,用户感兴趣的IO事件。
–epoll文件描述符用完后,需要用close关闭。
–每次添加/修改/删除文件描述符都需要调用epoll_ctl,所以要尽量少地调用epoll_ctl。
epoll_create:
–int epoll_create(int size);
–epoll_create创建一个epoll的句柄。
–参数size指定epoll所支持的最大句柄数。
–函数会返回一个新的epoll句柄,之后的所有操作将通过这个句柄来进行操作。
–在用完句柄之后,需要用close()来关闭这个创建出来的epoll句柄。
epoll_ctl:
–int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
–参数epfd是epoll_create()的返回值。
–参数op表示动作,用三个宏来表示:
•EPOLL_CTL_ADD:注册新的fd到epfd中;
•EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
•EPOLL_CTL_DEL:从epfd中删除一个fd;
–参数fd是需要监听的socket描述符。
–参数event通知内核需要监听什么事件。
struct epoll_event结构如下: typedef union epoll_data { void *ptr; int fd; __uint32_t u32; __uint64_t u64; } epoll_data_t; struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ };
events定义:
–EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
–EPOLLOUT:表示对应的文件描述符可以写;
–EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
–EPOLLERR:表示对应的文件描述符发生错误;
–EPOLLHUP:表示对应的文件描述符被挂断;
–EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的;
–EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
关于ET、LT两种工作模式:
LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.
–在LT模式中,内核通知一个文件描述符是否就绪了,然后可以对这个就绪的fd进行IO操作。
–如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。
ET (edge-triggered)是高速工作方式,只支持no-block socket。
–在ET模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。
–ET模式会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。
–如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知。
ET和LT的区别:
–LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读,则不断的通知你。
–ET则只在事件发生之时通知。可以简单理解为LT是水平触发,而ET则为边缘触发。
–LT模式只要有事件未处理就会触发,而ET则只在高低电平变换时(即状态从1到0或者0到1)触发。
epoll_wait:
–int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
–参数epfd是epoll_create()的返回值。
–参数events是一个epoll_event*的指针,当epoll_wait这个函数操作成功之后,epoll_events里面将储存所有的读写事件。
–参数maxevents是当前需要监听的所有socket句柄数。
–参数timeout是 epoll_wait的超时,为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件范围,正整数表示等这么长的时间。
–一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以保证一些效率,如果是和主逻辑在同一个线程的话,则可以用0来保证主循环的效率。
epoll_wait范围之后应该是一个循环,遍历所有的事件。
epoll例子
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/epoll.h> #include <netinet/in.h> #include <arpa/inet.h> ssize_t socket_recv(int st) { char buf[1024]; memset(buf, 0, sizeof(buf)); ssize_t rc = recv(st, buf, sizeof(buf), 0); if (rc <= 0) { printf("recv failed %s\n", strerror(errno)); } else { printf("recv %s\n", buf); send(st, buf, rc, 0); } return rc; } int socket_accept(int listen_st) { struct sockaddr_in client_addr; socklen_t len = sizeof(client_addr); memset(&client_addr, 0, sizeof(client_addr)); int client_st = accept(listen_st, (struct sockaddr *) &client_addr, &len); if (client_st < 0) printf("accept failed %s\n", strerror(errno)); else printf("accept by %s\n", inet_ntoa(client_addr.sin_addr)); return client_st; } void setnonblocking(int st) //将socket设置为非阻塞 { int opts = fcntl(st, F_GETFL); if (opts < 0) { printf("fcntl failed %s\n", strerror(errno)); } opts = opts | O_NONBLOCK; if (fcntl(st, F_SETFL, opts) < 0) { printf("fcntl failed %s\n", strerror(errno)); } } int socket_create(int port) { int st = socket(AF_INET, SOCK_STREAM, 0); int on = 1; if (setsockopt(st, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { printf("setsockopt failed %s\n", strerror(errno)); return 0; } struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(st, (struct sockaddr *) &addr, sizeof(addr)) == -1) { printf("bind port %d failed %s\n", port, strerror(errno)); return 0; } if (listen(st, 300) == -1) { printf("listen failed %s\n", strerror(errno)); return 0; } return st; } int main(int arg, char *args[]) { if (arg < 2) return -1; int iport = atoi(args[1]); int listen_st = socket_create(iport); if (listen_st == 0) return -1; struct epoll_event ev, events[100]; //声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件 int epfd = epoll_create(100); //生成用于处理accept的epoll专用的文件描述符 setnonblocking(listen_st); //把socket设置为非阻塞方式 ev.data.fd = listen_st; //设置与要处理的事件相关的文件描述符 ev.events = EPOLLIN | EPOLLERR | EPOLLHUP; //设置要处理的事件类型 epoll_ctl(epfd, EPOLL_CTL_ADD, listen_st, &ev); //注册epoll事件 int st = 0; while (1) { int nfds = epoll_wait(epfd, events, 100, -1); //等待epoll事件的发生 if (nfds == -1) { printf("epoll_wait failed %s\n", strerror(errno)); break; } int i; for (i = 0; i < nfds; i++) { if (events[i].data.fd < 0) continue; if (events[i].data.fd == listen_st) //监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。 { st = socket_accept(listen_st); if (st >= 0) { setnonblocking(st); ev.data.fd = st; ev.events = EPOLLIN | EPOLLERR | EPOLLHUP; //设置要处理的事件类型 epoll_ctl(epfd, EPOLL_CTL_ADD, st, &ev); continue; } } if (events[i].events & EPOLLIN) //socket收到数据 { st = events[i].data.fd; if (socket_recv(st) <= 0) { close(st); events[i].data.fd = -1; } } if (events[i].events & EPOLLERR) //socket错误 { close(st); events[i].data.fd = -1; } if (events[i].events & EPOLLHUP) //socket错误 { close(st); events[i].data.fd = -1; } } } close(epfd); return 0; }
搜索
复制