select和poll被鄙视得厉害, 因为有了linux epoll的存在。 网上讲解select/poll/epoll的例子不胜枚举, 各种比喻, 各种图示。 其实, epoll并没有那么玄乎。 本文中, 我们不进行画图讲解, 也尽量避免过多的文字描述, 只是初步来感受一下epoll.
服务端代码为:
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <malloc.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <fcntl.h>
#include <fcntl.h>
#include <sys/poll.h>
#include <sys/epoll.h>
#define BACKLOG 100
int main()
{
int iListenSock = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
inet_aton("0.0.0.0", &addr.sin_addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
int iOpt = 1;
setsockopt(iListenSock, SOL_SOCKET, SO_REUSEADDR, &iOpt, sizeof(iOpt)); // 标配
bind(iListenSock, (sockaddr*)&addr, sizeof(addr));
listen(iListenSock, BACKLOG);
epoll_event ev;
ev.data.fd = iListenSock;
ev.events = EPOLLIN;
epoll_event events[BACKLOG + 1];
int epollFD = epoll_create(BACKLOG + 1); // 告诉内核监测的数目, 返回的epollFD为epoll管理句柄
epoll_ctl(epollFD, EPOLL_CTL_ADD, iListenSock, &ev); // 将ev和对应的iListenSock添加到epoll句柄,用于被epollFD管理
while(1)
{
int timeoutMS = -1; // 永不超时
int nfds = epoll_wait(epollFD, events, BACKLOG + 1, timeoutMS); // events和nfds是一对输出值
printf("nfds is %d\n", nfds);
for(int i = 0; i < nfds; i++)
{
if(events[i].data.fd == iListenSock) // 用于监听客户端连接的socket
{
int iConnSock = accept(iListenSock, NULL, NULL);
if (iConnSock < 0)
{
continue;
}
ev.data.fd = iConnSock;
ev.events = EPOLLIN;
epoll_ctl(epollFD, EPOLL_CTL_ADD, iConnSock, &ev); // 将ev和对应的iConnSock添加到epoll句柄,用于被epollFD管理
printf("new sock came, fd is %d\n", iConnSock);
}
else
{
int iConnSock = events[i].data.fd; // 用于通信的socket
char szBuf[1024] = {0};
int recvLen = recv(iConnSock, szBuf, sizeof(szBuf) - 1, 0);
if (recvLen > 0)
{
printf("recv data [%s] from fd [%d]\n", szBuf, iConnSock);
}
else if(0 == recvLen)
{
ev.data.fd = iConnSock;
epoll_ctl(epollFD, EPOLL_CTL_DEL, iConnSock, &ev);
close(iConnSock);
printf("connection closed, local fd is [%d]\n", iConnSock);
}
else
{
ev.data.fd = iConnSock;
epoll_ctl(epollFD, EPOLL_CTL_DEL, iConnSock, &ev);
close(iConnSock);
printf("recv error, local fd is [%d]\n", iConnSock);
}
}
}
}
close(epollFD);
close(iListenSock);
return 0;
}
客户端代码为:
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <malloc.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <fcntl.h>
#include <fcntl.h>
int main()
{
int sockClient = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addrSrv;
addrSrv.sin_addr.s_addr = inet_addr("127.0.0.1");
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(8888);
connect(sockClient, ( const struct sockaddr *)&addrSrv, sizeof(struct sockaddr_in));
char szSendBuf[100] = "this is me";
while(1)
{
send(sockClient, szSendBuf, strlen(szSendBuf) + 1, 0);
scanf("%s", szSendBuf);
}
close(sockClient);
return 0;
}
makefile代码为:
all: server client
server: server.o
g++ -o server server.o
client: client.o
g++ -o client client.o
server.o: server.cpp
g++ -c server.cpp
client.o:client.cpp
g++ -c client.cpp
clean:
rm -f server client *.o
编译链接后, 先启动服务端, 然后在同一机器上启动三个不同的客户端, 此时,在服务端界面, 结果如下:
xxxxxx:~/network> ./server
nfds is 1
new sock came, fd is 5
nfds is 1
recv data [this is me] from fd [5]
nfds is 1
new sock came, fd is 6
nfds is 1
recv data [this is me] from fd [6]
nfds is 1
new sock came, fd is 7
nfds is 1
recv data [this is me] from fd [7]
本文只是epoll的一个简单开始, 比较基础, 后面我们还会进行更多的介绍, 便于深入理解其思路。