1、用select实现的并发服务器,能达到的并发数,受两个方面的限制
(1)一个进程能打开的最大文件描述符限制。这个可以通过调整内核参数完成。(可用ulimit -n number命令进行修改)
(2) select中的fd_set集合容量的限制(FD_SETSIZE),这需要重新编译内核
2、用poll实现的并发服务器,能达到的并发数,受一个方面的限制
(1)一个进程能打开的最大文件描述符限制。这个可以通过调整内核参数完成。(可用ulimit -n numbern命令进行修改)
注意:系统所有打开的最大文件描述个数也是有限的。可以用命令cat /proc/sy/fs/file-max 个数是49041(我当前内存是512M,如果是1G最大个数将达到10万个左右,10G的话井竟达到百万左右)
select和poll的共同特点是:两者内核都要遍历所有文件描述,直到找到发生事件的文件描述符。(随着描述符的增多,遍历的效率也会降低,或成指数降低或成线性降低)
epoll的接口非常简单,一共就三个函数:
1、epoll_create函数
函数声明:int epoll_create(int size)
该 函数生成一个epoll专用的文件描述符。它其实是在内核申请一空间,用来存放你想关注的socket fd上是否发生以及发生了什么事件。size就是你在这个epoll fd上能关注的最大socket fd数。随你定好了。只要你有空间。可参见上面与select之不同2.
2、epoll_ctl函数
函数声明:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
该函数用于控制某个epoll文件描述符上的事件,可以注册事件,修改事件,删除事件。
参数:
epfd:由 epoll_create 生成的epoll专用的文件描述符;
op:要进行的操作例如注册事件,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修 改、EPOLL_CTL_DEL 删除
fd:关联的文件描述符;
event:指向epoll_event的指针;
如果调用成功返回0,不成功返回-1
用到的数据结构
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
如:
struct epoll_event ev;
//设置与要处理的事件相关的文件描述符
ev.data.fd=listenfd;
//设置要处理的事件类型
ev.events=EPOLLIN|EPOLLET;
//注册epoll事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
常用的事件类型:
EPOLLIN :表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET:表示对应的文件描述符有事件发生;
3、epoll_wait函数
函数声明:int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)
该函数用于轮询I/O事件的发生;
参数:
epfd:由epoll_create 生成的epoll专用的文件描述符;
epoll_event:用于回传代处理事件的数组;
maxevents:每次能处理的事件数;
timeout:等待I/O事件发生的超时值(单位我也不太清楚);-1相当于阻塞,0相当于非阻塞。一般用-1即可
返回发生事件数。
epoll模式的模式分为EPOLLLT和EPOLLET
EPOLLLT
1、完全靠kernel epoll驱动,应用程序只需要处理从epoll_wait返回的fds,这些fds我们认为它们处于就绪状态
EPOLLET
1、此模式下,系统仅仅通知应用程序哪些fds变成了就绪状态,一旦fd变成就绪状态,epoll将不再关注这个fd的任何状态信息,(从epoll队列移除)直到应用程序通过读写操作触发EAGAIN状态,epoll认为这个fd又变为空闲状态,那么epoll又重新关注这个fd的状态变化(重新加入epoll队列)。
2、随着epoll_wait的返回,队列中的fds是在减少的,所以在大并发的系统中,EPOLLET更有优势。但是对程序员的要求也更高。
//下面是具体事例代码
<span style="font-size:18px;">#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <poll.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <vector>
#include <algorithm>
#include <sys/epoll.h>
typedef std::vector<struct epoll_event> EventList;
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
void activate_nonblock(int fd) {
int ret = 0;
int flags = fcntl(fd, F_GETFL);
if (flags == -1)
ERR_EXIT("fcntl");
flags |= O_NONBLOCK;
ret = fcntl(fd, F_SETFL, flags);
if (ret == -1)
ERR_EXIT("fcntl");
}
ssize_t readn(int fd, void *buf, size_t count) {
size_t nleft = count;
ssize_t nread;
char *bufp = (char*) buf;
while (nleft > 0) {
if ((nread = read(fd, bufp, nleft)) < 0) {
if (errno == EINTR)
continue;
return -1;
} else if (nread == 0)
return count - nleft;
bufp += nread;
nleft -= nread;
}
return count;
}
ssize_t writen(int fd, const void *buf, size_t count) {
size_t nleft = count;
ssize_t nwritten;
char *bufp = (char*) buf;
while (nleft > 0) {
if ((nwritten = write(fd, bufp, nleft)) < 0) {
if (errno == EINTR)
continue;
return -1;
} else if (nwritten == 0)
continue;
bufp += nwritten;
nleft -= nwritten;
}
return count;
}
ssize_t recv_peek(int sockfd, void *buf, size_t len) {
while (1) {
int ret = recv(sockfd, buf, len, MSG_PEEK);
if (ret == -1 && errno == EINTR)
continue;
return ret;
}
}
ssize_t readline(int sockfd, void *buf, size_t maxline) {
int ret;
int nread;
char *bufp = (char*) buf;
int nleft = maxline;
while (1) {
ret = recv_peek(sockfd, bufp, nleft);
if (ret < 0)
return ret;
else if (ret == 0)
return ret;
nread = ret;
int i;
for (i = 0; i < nread; i++) {
if (bufp[i] == '\n') {
ret = readn(sockfd, bufp, i + 1);
if (ret != i + 1)
exit(EXIT_FAILURE);
return ret;
}
}
if (nread > nleft)
exit(EXIT_FAILURE);
nleft -= nread;
ret = readn(sockfd, bufp, nread);
if (ret != nread)
exit(EXIT_FAILURE);
bufp += nread;
}
return -1;
}
void echo_srv(int conn) {
char recvbuf[1024];
while (1) {
memset(recvbuf, 0, sizeof(recvbuf));
int ret = readline(conn, recvbuf, 1024);
if (ret == -1)
ERR_EXIT("readline");
if (ret == 0) {
printf("client close\n");
break;
}
fputs(recvbuf, stdout);
writen(conn, recvbuf, strlen(recvbuf));
}
}
void handle_sigchld(int sig) {
/* wait(NULL);*/
while (waitpid(-1, NULL, WNOHANG) > 0)
;
}
void handle_sigpipe(int sig) {
printf("recv a sig=%d\n", sig);
}
int main(void) {
int count = 0;
signal(SIGPIPE, handle_sigpipe);
signal(SIGCHLD, handle_sigchld);
int listenfd;
if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
ERR_EXIT("socket");
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY );
int on = 1;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
ERR_EXIT("setsockopt");
if (bind(listenfd, (struct sockaddr*) &servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("bind");
if (listen(listenfd, SOMAXCONN) < 0)
ERR_EXIT("listen");
std::vector<int> clients;
int epollfd = 0;
epollfd = epoll_create1(EPOLL_CLOEXEC);
//声明epoll_event结构体的变量,event用于注册事件,
struct epoll_event event;
//设置与要处理的事件相关的文件描述符
event.data.fd = listenfd;
//设置要处理的事件类型
event.events = EPOLLIN | EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &event);
EventList events(16);
struct sockaddr_in peeraddr;
socklen_t peerlen;
int conn = 0;
int i = 0;
int nready = 0;
while (1) {
//等待epoll事件的发生
nready = epoll_wait(epollfd, &*events.begin(),
static_cast<int>(events.size()), -1);
if (nready == -1) {
if (errno == EINTR)
continue;
ERR_EXIT("epoll_wait");
}
if (nready == 0)
continue;
if ((size_t) nready == events.size())
events.resize(events.size() * 2);
//处理所发生的所有事件
for (i = 0; i < nready; i++) {
//如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。
if (events[i].data.fd == listenfd) {
peerlen = sizeof(peeraddr);
//accept这个连接
conn = accept(listenfd, (struct sockaddr*) &peeraddr, &peerlen);
if (conn == -1)
ERR_EXIT("accept");
printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr),
ntohs(peeraddr.sin_port));
printf("count = %d\n", ++count);
clients.push_back(conn);
//把socket设置为非阻塞方式
activate_nonblock(conn);
event.data.fd = conn;
event.events = EPOLLIN | EPOLLET;
//将新的fd添加到epoll的监听队列中
epoll_ctl(epollfd, EPOLL_CTL_ADD, conn, &event);
} else if (events[i].events & EPOLLIN) {//接收到数据,读socket
conn = events[i].data.fd;
if (conn < 0)
continue;
char recvbuf[1024] = { 0 };
int ret = readline(conn, recvbuf, 1024);
if (ret == -1)
ERR_EXIT("readline");
if (ret == 0) {
printf("client close\n");
close(conn);
event = events[i];
//删除sockfd上要处理的事件为EPOLIN
epoll_ctl(epollfd, EPOLL_CTL_DEL, conn, &event);
clients.erase(
std::remove(clients.begin(), clients.end(), conn),
clients.end());
}
fputs(recvbuf, stdout);
writen(conn, recvbuf, strlen(recvbuf));
}
}
}
return 0;
}
</span>