引言:## 標題 ##
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);
}