先贴一段代码,代码很简单要看过epoll如何使用,都应该能看懂。python
这是服务端程序:socket
#include <sys/socket.h> #include <sys/epoll.h> #include <netinet/in.h> #include <arpa/inet.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <errno.h> #include <stdlib.h> #include <string.h> #define MAXLINE 10 #define OPEN_MAX 100 #define LISTENQ 20 #define SERV_PORT 5555 #define INFTIM 1000 void setnonblocking(int sock) { int opts; opts = fcntl(sock, F_GETFL); if(opts < 0) { perror("fcntl(sock, GETFL)"); exit(1); } opts = opts | O_NONBLOCK; if(fcntl(sock, F_SETFL, opts) < 0) { perror("fcntl(sock, SETFL, opts)"); exit(1); } } int main(int argc, char *argv[]) { printf("epoll socket begins.\n"); int i, maxi, listenfd, connfd, sockfd, epfd, nfds, on = 1; ssize_t n; char line[MAXLINE]; socklen_t clilen; struct epoll_event ev, events[20]; epfd = epoll_create(256); struct sockaddr_in clientaddr; struct sockaddr_in serveraddr; listenfd = socket(AF_INET, SOCK_STREAM, 0); setnonblocking(listenfd); ev.data.fd = listenfd; ev.events = EPOLLIN | EPOLLET; epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev); setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(int)); bzero(&serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; char *local_addr = "0.0.0.0"; inet_aton(local_addr, &(serveraddr.sin_addr)); serveraddr.sin_port = htons(SERV_PORT); int ret = bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)); if(ret < 0) { printf("%s\n", "bind error!"); exit(-1); } listen(listenfd, LISTENQ); maxi = 0; for(; ;) { nfds = epoll_wait(epfd, events, 20, 500); for(i = 0; i < nfds; ++i) { if(events[i].data.fd == listenfd) { printf("accept connection, fd is %d\n", listenfd); connfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clilen); if(connfd < 0) { perror("connfd < 0"); exit(1); } setnonblocking(connfd); char *str = inet_ntoa(clientaddr.sin_addr); printf("connect from %s\n", str); ev.data.fd = connfd; ev.events = EPOLLIN | EPOLLET; epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev); } else if(events[i].events & EPOLLIN) { sockfd = events[i].data.fd; n = read(sockfd, line, MAXLINE); if(n < 0 && errno == EAGAIN) { printf("%s\n", "数据已经读完,没有更多的数据能够读了。"); } else if(n == 0) //errno { printf("%s\n", "客户端断开链接。"); epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, &ev); } else if(n <= 0) { printf("%s\n错误代码为%d", "读出错。", errno); epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, &ev); } else { printf("%s\n", line); memset(line, 0, MAXLINE); n = write(sockfd, "hello client!", strlen("hello client!")); if(n < 0 && errno == EAGAIN) { printf("%s\n", "系统缓冲区数据已写满。"); } else if(n <= 0) { printf("%s\n错误代码为%d", "客户端断开链接或出错。", errno); } else { ev.data.fd = sockfd; ev.events = EPOLLIN | EPOLLET; epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev); } } } else if(events[i].events & EPOLLOUT) { } } } }
客户端程序:测试
能够不用代码写客户端,用telnet链接就行。code
telnet localhost 5555
#!/usr/bin/python from socket import * from time import sleep host = gethostname() addr = (host, 5555) sockCli = socket() sockCli.connect(addr) while True: sockCli.sendall("sadf") data = sockCli.recv(1024) print data sleep(1) sockCli.close()
这段服务端的代码展现的是用,epoll写的一个简单的服务程序, ET非阻塞模式。server
关于read的返回值:队列
一、返回值n=-1, errno = EAGAIN;当返回值等于你要读取的数据时,说明你还有数据要读。 当数据量比较大,屡次读数据的时候非阻塞read的返回值为-1,errno值为11(EAGAIN)时,这个 并非发生了错误,而是数据已经被读完,这个时候中止读数据就行了。阻塞模式不会这样,由于当没有数据的时候,read就阻塞了。事件
二、返回值n=0, errno = 0。说明客户端已经关闭了。通过测试即便没有数据的时候,阻塞的read就阻塞,非阻塞的read时为第一种状况。只有客户端关闭时返回0(socket fd是这样其余io流为测试) 。这个时候就就不要再注册事件了,从队列中删除这个fd吧。并且当客户端断开时,epoll会主动通知一个EPOLLIN事件。get
三、返回值n=-1,errno != EAGAIN;时是错了,看一下错误码是多吧。string
关于write的返回值:it
一、返回值n=-1,errno = EAGAIN时; 说明系统缓冲区已经满了,不能再写进去了。当write为阻塞时不会返回这个错误,会阻塞掉。
二、返回值n<=0时;客户端断开链接或出错。
什么时候系统会通知一个写事件:
对于LT 模式,若是注册了EPOLLOUT,只要该socket可写(发送缓冲区)未满,那么就会触发EPOLLOUT。
对于ET模式,若是注册了EPOLLOUT,只有当socket从不可写变为可写的时候,会触发EPOLLOUT。
上面server端的例子,当你要写入的数据比较大的时候,可能就会有困扰了,如何写入的数据量较大,把系统缓冲区写满了,若是write设置是阻塞的,那就会阻塞了。这可能不是咱们想要的。
这个时候就须要注册写事件,本身在应用层加个发送缓冲区,须要发送数据的时候,就写到应用层的发送缓冲区。触发write事件的时候,从缓冲区写入socket。