先贴一段代码,代码很简单要看过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 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连接就行。
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非阻塞模式。
关于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。