引言:## 标题 ##
UNP的第6章讲解IO复用的时候详解了两个函数:select和poll。我们可以用这两个接口以单线程的方式处理多个客户请求。但是,它们都有着自己的缺点。
- select最多支持的描述符为1024(除非你重新编译内核)
- poll虽然没有上限,但是每次一有风吹草动,它都需要你遍历整个描述符集。在第一次看这章的时候,我就在想,这样的时间复杂度是O(N),一定有更加高效的方法!
UNP在第14章的结尾稍稍提起了一下“高级轮询”。使用这样的接口可以解决上述的2个问题,但是不用的操作系统对它的实现方式不同,所以这样的代码应该被认为是不可移植的。
对于linux来说,这样的接口是epoll。
学习资料:
先给出epoll的3个学习资料:
手册:http://man7.org/linux/man-pages/man7/epoll.7.html
例子:https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/
中文:http://blog.csdn.net/xiajun07061225/article/details/9250579
以下是我自己写的小例子,用的是LT模式。
服务器程序:
//utils.h文件可以查看我之前的博客。
#include "../../programe/utils.h"
#include <sys/epoll.h>
#include <stdio.h>
#include <errno.h>
#define MAX_EVENT 64
int main(int argc, char **argv)
{
if(argc != 2)
{
write(2, "port", 4);
return -1;
}
struct epoll_event event, events[MAX_EVENT];//events应该用栈内存还是堆内存?
int listenfd = tcp_listen(argv[1], 5);
if(listenfd < 0)
return -1;
int epollfd = epoll_create1(0);
event.data.fd = listenfd;
event.events = EPOLLIN;
epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &event);
for( ; ; )
{
int i,nready;
nready = epoll_wait(epollfd, events, MAX_EVENT, -1);
for(i=0;i<nready;++i)
{
//new connection
if(events[i].data.fd == listenfd)//new connection
{
int connfd = accept(listenfd, NULL, NULL);
if(connfd == -1)//accept失败
continue;
printf("connection accept, fd=%d\n",connfd);
//将新链接加入epoll的监听。
event.data.fd = connfd;
event.events = EPOLLIN;
epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &event);
}else if(events[i].events & EPOLLIN)//data come in
{
//接受数据
ssize_t count;
char buff[4096];
count = recv(events[i].data.fd, buff, sizeof buff, 0);
printf("read %d character from %d\n", (int)count, events[i].data.fd);
//处理数据
if(count == 0)//eof
{
//客户发完了数据,所以不再需要监听这个套接字。
event.data.fd = events[i].data.fd;
epoll_ctl(epollfd, EPOLL_CTL_DEL, events[i].data.fd, &event);
//shutdown(events[i].data.fd, SHUT_RD);
close(events[i].data.fd);//单线程所以直接关闭即可。
printf("fd=%d shutdown\n",events[i].data.fd);
continue;
}
else if(count < 0)
{//read失败,打印出错时的errno.
printf("read error.\n");
printf("errno:%d\n",errno);
continue;
}
else//echo
{
int nsent;
while(count)
{
nsent = send(events[i].data.fd, buff, count,0);
count -= nsent;
}
}
}else
{
continue;
}
}
}
close(listenfd);
close(epollfd);
}
客户端程序:
#include "../../programe/utils.h"
#include <sys/select.h>
#include <errno.h>
#include <stdio.h>
int main(int argc, char** argv)
{
if(argc != 3)
{
perror("IP&port\n");
}
int sockfd = tcp_connect(argv[1], argv[2]);
if(sockfd < 0)
return -1;
fd_set rset;
char sendLine[4096],recvLine[4096];
int n,eof=0;
for( ; ; )
{
bzero(&sendLine, sizeof(sendLine));
bzero(&recvLine, sizeof(recvLine));
FD_ZERO(&rset);
FD_SET(0, &rset);
FD_SET(sockfd, &rset);
select(sockfd+1, &rset, NULL, NULL, NULL);
if(FD_ISSET(0, &rset))
{
n = read(0,sendLine,sizeof sendLine);
if(n>0)
write(sockfd, sendLine, n);
else
{
if(n == 0)//没有数据可写了。
{
shutdown(sockfd, SHUT_WR);
eof = 1;
}
else if(n < 0)//发生错误,errno?
return -1;
}
}
if(FD_ISSET(sockfd, &rset))
{
n = read(sockfd, recvLine, 4096);
if(n>0)
write(1, recvLine, n);
else if(n == 0)
{
shutdown(sockfd, SHUT_RD);
break;
}
else//错误
{
return -1;
}
}
if(eof == 1)
break;
}
close(sockfd);
}