epoll 服务端 ET模式
windows下IOCP, linux下 epoll。
epoll模型其实也是一个同步模型,ET是epoll里面的一种模式,叫 边缘触发。 个人理解,类似于 windows下的事件选择模型。代码如下:
#include <unistd.h> #include <sys/epoll.h> #include <arpa/inet.h> #include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <string.h> #include <fcntl.h> #include <netinet/in.h> #include <errno.h> #define MAX_BACKLOG 256 #define MAX_EVENTS 1024 // 如EPOLL的作者Davide Libenzi所说,如果你对一fd同时注册EPOLLIN | EPOLLOUT事件, // 即使发送缓冲区并非由满变空,也会触发EPOLLOUT事件 // 使用epoll的最好是用ATM模式,当真正需要用到EPOLLOUT时才注册。我理解的ATM模式就是读、写、读、写这样的循环。 // ET模式下的读写 // 只要可读, 就一直读, 直到返回 0, 或者 errno = EAGAIN // 只要可写, 就一直写, 直到数据发送完, 或者 errno = EAGAIN // 保证对非阻塞的套接字写够请求的字节数才返回 ssize_t socket_write(int sockfd, const char* buffer, size_t buflen) { ssize_t tmp = 0; size_t total = buflen; const char* p = buffer; while (1) { tmp = write(sockfd, p, total); if(tmp < 0) { // 当send收到信号时,可以继续写,但这里返回-1. if(errno == EINTR) { return -1; } // 当socket是非阻塞时,如返回此错误,表示写缓冲队列已满,在这里做延时后再重试. if(errno == EAGAIN) { usleep(1000); continue; } return -1; } if((size_t)tmp == total) { return buflen; } total -= tmp; p += tmp; } return tmp; // 返回已写的字节数 } // 设定socket句柄为非阻塞 static int SetNonBlock(int nFd) { int nOldOpt = fcntl(nFd, F_GETFL, 0); int nNewOpt = nOldOpt | O_NONBLOCK; return fcntl(nFd, F_SETFL, nNewOpt); } // 为套接字追加事件 //static void AddEvent(int nEpfd, int nFd) //{ // struct epoll_event event; // event.data.fd = nFd; // //event.events = EPOLLIN | EPOLLOUT | EPOLLHUP | EPOLLET; // event.events = EPOLLIN | EPOLLOUT | EPOLLET; // epoll_ctl(nEpfd, EPOLL_CTL_ADD, nFd, &event); //} static void add_event(int epollfd,int fd,int state) { struct epoll_event ev; ev.events = state | EPOLLET; ev.data.fd = fd; epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&ev); } static void delete_event(int epollfd,int fd,int state) { struct epoll_event ev; ev.events = state | EPOLLET; ev.data.fd = fd; epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,&ev); } static void modify_event(int epollfd,int fd,int state) { struct epoll_event ev; ev.events = state | EPOLLET; ev.data.fd = fd; epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&ev); } // 主函数 int main(int argc, char const *argv[]) { if (argc != 2) { printf("usage: CMD Port\n"); exit(-1); } int nPort = atoi(argv[1]); if (nPort < 0) { printf("Port Invalid\n"); exit(-1); } int nSvrFd = socket(AF_INET, SOCK_STREAM, 0); // 设置非阻塞 SetNonBlock(nSvrFd); // 绑定地址 struct sockaddr_in addr; //bzero(&addr,sizeof(addr)); memset(&addr,0x00, sizeof(addr)); addr.sin_port = htons(nPort); inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr); addr.sin_family = (AF_INET); if (0 != bind(nSvrFd, (struct sockaddr*)&addr, sizeof(addr))) { perror("Bind Failure:"); exit(-1); } //监听端口 if (0 != listen(nSvrFd, MAX_BACKLOG)) { perror("Listen Failure:"); exit(-1); } // 创建epoll句柄 int nEpfd = epoll_create(1024); struct epoll_event events[MAX_EVENTS]; // AddEvent(nEpfd, nSvrFd); add_event(nEpfd,nSvrFd,EPOLLIN); while (1) { //等待事件到来 int nReadyNums = epoll_wait(nEpfd, events, MAX_EVENTS, -1); int nClientFd = -1; struct sockaddr_in cliaddr; socklen_t cliaddrlen = sizeof(cliaddr); for (int i = 0; i < nReadyNums; ++i) { if (events[i].data.fd == nSvrFd) { // accept放到线程中,可以使用while循环 // 多个连接同时到达,服务器的 TCP 就绪队列瞬间积累多个就绪 // 连接,由于是边缘触发模式,epoll 只会通知一次,accept 只处理一个连接,导致 TCP 就绪队列中剩下的连接都得不到处理。 // 解决办法是用 while 循环抱住 accept 调用,处理完 TCP 就绪队列中的所有连接后再退出循环。 while ((nClientFd = accept(nSvrFd,(struct sockaddr*)&cliaddr,&cliaddrlen)) > 0) // if ((nClientFd = accept(nSvrFd,(struct sockaddr*)&cliaddr,&cliaddrlen)) > 0) { printf("建立连接\n"); //设置为非阻塞 SetNonBlock(nClientFd); //添加事件监听 // AddEvent(nEpfd, nClientFd); add_event(nEpfd,nClientFd,EPOLLIN); } } else { // 处理FD事件 if (events[i].events & EPOLLIN) { int n = 0; int nread = 0; char buf[BUFSIZ]; memset(buf, 0x00, sizeof(buf)); while ((nread = read(events[i].data.fd, buf + n, BUFSIZ-1)) > 0) { n += nread; } if (nread == -1 && errno != EAGAIN) { perror("read error"); } printf("read----%s\n", buf); // 直接去写,不需要EPOLLOUT事件 const char* p = "epoll_server.cpp answer....\n"; socket_write(events[i].data.fd, p, strlen(p) + 1 ); // 后面代码无效,ET模式下,EPOLLOUT事件不响应或者出错,没搞通,实际代码中也不会用到,忽略。 continue; // modify_event(nEpfd,nClientFd,EPOLLOUT); // continue; // 强制触发一次 struct epoll_event ev; int fd = events[i].data.fd; ev.events = events[i].events | EPOLLOUT; if(-1 == epoll_ctl(nEpfd, EPOLL_CTL_MOD, fd, &ev)) { perror("epoll_ctl: mod"); } } // 可写 if (events[i].events & EPOLLOUT) { const char* p = "epoll_server.cpp answer...."; socket_write(events[i].data.fd, p, strlen(p) + 1 ); //modify_event(nEpfd,nClientFd,EPOLLIN); // 强制触发一次 // struct epoll_event ev; // int fd = events[i].data.fd; // ev.events = events[i].events | EPOLLIN; // if(-1 == epoll_ctl(nEpfd, EPOLL_CTL_MOD, fd, &ev)) // { // perror("epoll_ctl: mod"); // } } } } } }
使用telnet 命令模拟客户端进行测试,结果如下:
服务端:
客户端:
比较好的linux 网络编程文章 http://www.cnblogs.com/Anker/p/3265058.html