2019独角兽企业重金招聘Python工程师标准>>>
先贴一段代码,代码很简单要看过epoll如何使用,都应该能看懂。
这是服务端程序:
#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 1000void 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连接就行。
telnet localhost 5555
#!/usr/bin/pythonfrom socket import *
from time import sleephost = gethostname()
addr = (host, 5555)sockCli = socket()sockCli.connect(addr)while True:sockCli.sendall("sadf")data = sockCli.recv(1024)print datasleep(1)sockCli.close()
这段服务端的代码展示的是用,epoll写的一个简单的服务程序, ET非阻塞模式。
关于read的返回值:
1、返回值n=-1, errno = EAGAIN;当返回值等于你要读取的数据时,说明你还有数据要读。 当数据量比较大,多次读数据的时候非阻塞read的返回值为-1,errno值为11(EAGAIN)时,这个 并不是发生了错误,而是数据已经被读完,这个时候停止读数据就好了。阻塞模式不会这样,因为当没有数据的时候,read就阻塞了。
2、返回值n=0, errno = 0。说明客户端已经关闭了。经过测试即使没有数据的时候,阻塞的read就阻塞,非阻塞的read时为第一种情况。只有客户端关闭时返回0(socket fd是这样其他io流为测试) 。这个时候就就不要再注册事件了,从队列中删除这个fd吧。而且当客户端断开时,epoll会主动通知一个EPOLLIN事件。
3、返回值n=-1,errno != EAGAIN;时是错了,看一下错误码是多吧。
关于write的返回值:
1、返回值n=-1,errno = EAGAIN时; 说明系统缓冲区已经满了,不能再写进去了。当write为阻塞时不会返回这个错误,会阻塞掉。
2、返回值n<=0时;客户端断开连接或出错。
何时系统会通知一个写事件:
对于LT 模式,如果注册了EPOLLOUT,只要该socket可写(发送缓冲区)未满,那么就会触发EPOLLOUT。
对于ET模式,如果注册了EPOLLOUT,只有当socket从不可写变为可写的时候,会触发EPOLLOUT。
上面server端的例子,当你要写入的数据比较大的时候,可能就会有困扰了,如何写入的数据量较大,把系统缓冲区写满了,如果write设置是阻塞的,那就会阻塞了。这可能不是我们想要的。
这个时候就需要注册写事件,自己在应用层加个发送缓冲区,需要发送数据的时候,就写到应用层的发送缓冲区。触发write事件的时候,从缓冲区写入socket。