基于epoll的socket服务端通信【Linux】
? epoll是Linux内核中的一种可扩展I/O事件处理机制,最早在Linux2.5.44内核中引入,可以用于代替POSIX select和pll系统调用,并且在具有大量应用程序请求时能够获得较好的性能(此时被监视的文件描述符数量非常大,与旧的select和poll系统调用完成所需O(n)不同,epoll能够在O(1)时间内完成操作,所以性能相当好),epoll与FreeBSD的kqueue类似,都面向用户空间提供了自己的文件描述符来操作。
相关函数介绍
int epoll_create(int size)
函数说明:创建一个树根
参数说明:
- size:最大节点数,此参数在Linux2.6.8已被忽略,但必须传一个大于0的数
返回值:
- 成功:返回一个大于0的文件描述符,代表整个树的树根
- 失败:返回-1,并设置errno值
int epoll_ct(int epfd, int op, int fd, struct epoll_event* event)
函数说明:将要监听的节点fd在epoll树上添加,删除和修改
参数说明:
- epfd:epoll树的根节点
- op:fd在epoll树上的操作。可选值:EPOLL_CTL_ADD(添加)、EPOLL_CTL_MOD(修改)、EPOLL_CTL_DEL(删除)
- fd:要操作的文件描述符
- event
typedef union epoll_data{ void* ptr; int fd; uint32_t u32; uint64_t u64; }epoll_data_t; struct epoll_event{ uint32_t events; //可选值:EPOLLIN(可读事件)、EPOLLOUT(可写事件)、EPOLLERR(异常事件) epoll_data_t data; }
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout)
函数说明:委托内核监听epoll树的事件节点
函数参数:
- epfd:epoll树的根节点
- events:传出参数,是一个结构体数组
- maxevents:events数组的大小
- timeout:
- -1:表示阻塞
- 0:表示不阻塞
- 大于0: 表示阻塞超时时长
epoll_wait()返回的数组中的事件节点的值不会被修改,是当时上epoll树的时候设置的值。
使用epoll模型开发服务器流程:
- 创建socket,得到监听文件描述符ldf------socket()
- 设置端口复用------setsockopt()
- 绑定------bind()
- 监听------listen()
- epoll操作伪代码
//创建一颗epoll树 int epfd = epoll_create(); //将监听文件描述符上树 struct epoll_event ev; ev.events=EPOLLIN; ev.data,fd = lfd; epoll_ctl(epfd, EPOLL_CTL_ADD,lfd,&ev); struct epoll_event events[1024]; while(1) { nready = epoll_wait(epfd, events,1024,-1); if(nready<0){ if(errno==EINTR)//被信号中断 { continue; } break; } for(i=0;i<nready;i++){ sockfd = events[i].data.fd; //有客户端请求到来 if(sockfd==lfd) { cfd = accept(sockfd,NULL,NULL); //将cfd对应的读事件上epoll树 ev.data.fd=cfd; ev.events=EPOLLIN; epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev); continue; } //有客户端发送数据过来 n=read(sockfd, buf,sizeof(buf)); if(n<=0){ close(sockfd); //将sockfd对应的事件节点从epoll树上删除 epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,NULL); perror("read error or client closed"); continue; }else{ write(sockfd,buf,n); } } }
使用epoll开发socket服务端通信代码:
#include<iostream> #include<sys/socket.h> #include<arpa/inet.h> #include<sys/epoll.h> #include<unistd.h> #include<string.h> int main() { int lfd, cfd,sockfd,err_log; struct sockaddr_in serv_add, client_add; struct epoll_event ev; int epfd,nready,n; socklen_t len = sizeof(client_add); struct epoll_event client[1024]; char buf[BUFSIZ]; //创建socket lfd = socket(AF_INET, SOCK_STREAM, 0); //设置文件描述符端口复用 int opt = 1; setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int)); //绑定bind serv_add.sin_family = AF_INET; serv_add.sin_addr.s_addr = htonl(INADDR_ANY); serv_add.sin_port = htons(8888); err_log = bind(lfd, (struct sockaddr*)&serv_add, sizeof(serv_add)); if (err_log == -1) { perror("bind fail!"); exit(-1); } err_log = listen(lfd, 128); if (err_log) { perror("listen fail!"); exit(-1); } //创建一棵epoll树 epfd = epoll_create(1024); if (epfd < 0) { perror("create epoll error"); return -1; } //将lfd上epoll树 ev.data.fd = lfd; ev.events = EPOLLIN; epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev); while (1) { nready = epoll_wait(epfd, client, 1024, -1); if (nready<0) { if (errno = EINTR) { continue; } break; } for (int i = 0; i < nready; i++) { sockfd = client[i].data.fd; //有客户端链接请求过来 if (sockfd == lfd) { cfd = accept(lfd, (struct sockaddr*)&client_add, &len); printf("A new connection is established,ip:%s, port:%d ", inet_ntoa(client_add.sin_addr), htons(client_add.sin_port)); //将cfd上epoll树 ev.data.fd = cfd; ev.events = EPOLLIN; epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev); continue; } //有客户端数据发过来 while (true) { memset(buf, 0x00, sizeof(buf)); n = read(sockfd, buf, sizeof(buf)); if (n <= 0) { perror("client close! "); //从epoll树上删除sockfd epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL); close(sockfd); break; } else { printf("recv:%s",buf); for (int k = 0; k < n; k++) { buf[k] = toupper(buf[k]); } write(sockfd, buf, n); if (n < BUFSIZ) { break; } } } } } close(epfd); close(lfd); return 0; }