Select两个限制:
1,一个进程能够打开的文件描述符个数室友限制的
2, FD_SETSIZE限制
Poll限制:
1,一个进程能够打开的最大文件描述符是有限的
通过ulimit –n numbe可以更改这个上限,但是系统能够打开的最大文件描述的个数是有限制的(内存有关),通过cat /proc/sys/fs/file-max可以查看最大限制。
Select和poll共同点:
内核要遍历所有文件描述符,直达找到发生事件的文件描述符。
当并发数很高时,因为遍历要花费大量时间,效率并不会很高,因此需要使用epoll来改进。
int epoll_create(int size)需要指定最大的并发数,内部用哈希表实现,size其实是哈希表容量
Int epoll_create1(int flags)内部用红黑树实现,容量更大
Int epoll_ctl(int epollfd, int op, int fds, epoLL_event);//控制某个opoll监控的文件描述符上的事件,注册,修改,删除,其值是如下的宏:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:
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 */
};
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
epoll好处
1. 相比于select与poll,epoll最大的好处就在于它不会随着监听数fd数目的增长而降低效率。
2. 内核中select与poll的实现是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。
3. Epoll的实现是基于回调的,如果fd有期望的事件就通过毁掉函数将其加入到eploo就绪队列中,也就是说它只关心活跃的fd,与fd数目无关。
4. 内核用户空间内存拷贝问题,select/poll采用了内存拷贝方式,而epoll采用的是共享内存方式。
5. Epoll不仅会告诉应用程序有IO事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息应用程序就能直接定位到事件,而不必遍历整个fd集合。
Epoll的两种模式:
1. 水平触发,默认模式。
1,一个进程能够打开的文件描述符个数室友限制的
2, FD_SETSIZE限制
Poll限制:
1,一个进程能够打开的最大文件描述符是有限的
通过ulimit –n numbe可以更改这个上限,但是系统能够打开的最大文件描述的个数是有限制的(内存有关),通过cat /proc/sys/fs/file-max可以查看最大限制。
Select和poll共同点:
内核要遍历所有文件描述符,直达找到发生事件的文件描述符。
当并发数很高时,因为遍历要花费大量时间,效率并不会很高,因此需要使用epoll来改进。
int epoll_create(int size)需要指定最大的并发数,内部用哈希表实现,size其实是哈希表容量
Int epoll_create1(int flags)内部用红黑树实现,容量更大
Int epoll_ctl(int epollfd, int op, int fds, epoLL_event);//控制某个opoll监控的文件描述符上的事件,注册,修改,删除,其值是如下的宏:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:
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 */
};
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
epoll好处
1. 相比于select与poll,epoll最大的好处就在于它不会随着监听数fd数目的增长而降低效率。
2. 内核中select与poll的实现是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。
3. Epoll的实现是基于回调的,如果fd有期望的事件就通过毁掉函数将其加入到eploo就绪队列中,也就是说它只关心活跃的fd,与fd数目无关。
4. 内核用户空间内存拷贝问题,select/poll采用了内存拷贝方式,而epoll采用的是共享内存方式。
5. Epoll不仅会告诉应用程序有IO事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息应用程序就能直接定位到事件,而不必遍历整个fd集合。
Epoll的两种模式:
1. 水平触发,默认模式。
2. EPOLLLT边沿触发,这种模式靠内核中的epoll驱动,应用程序只需要处理从epoll_wait返回的fds,这些fds我们认为它们处于就绪状态。在此模式下,系统仅仅通知了那些fds变成了就绪状态,一旦fd变成就绪状态,epoll将不再关注这个fd的任何信息(从epoll队列移除),直到应用程序通过读写操作触发EAGAIN状态,epoll认为这个fd又变为空闲状态,那么epoll又重新关注这个fd的状态变化(重新加入epoll队列)。随着epoll_wait的返回,队列中的fds是在减少的,所以在大并发系统中,EPOLLET更有优势,但是对程序员的要求也更高。在此模式下,如果一次未处理完数据,就会被阻塞出问题,假设有个可读事件,对方发送了2K数据,但是一次read只读了1K,然后调用epoll_wait去监听状态,就会无法处理那个事件,因为没有新的可读可写去触发该事件,这个需要程序员来维护。
服务器程序:
#include<sys/epoll.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#include<vector>
#include<algorithm>
#define ERR_EXIT(m) do{perror(m);exit(EXIT_FAILURE);}while(0)
typedef std::vector<struct epoll_event>Eventlist;
void do_service(int connfd)
{
char recvbuf[1024];
int n;
while (1)
{
memset(&recvbuf, 0, sizeof(recvbuf));
int ret = read(connfd, recvbuf, sizeof(recvbuf));
if (ret == -1)
{
ERR_EXIT("read");
}
else if (ret == 0)
{
printf("client cloase\n");
break;
}
fputs(recvbuf, stdout);
write(connfd, recvbuf, strlen(recvbuf));
}
}
void handle_sigchild(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)
{
// signal(SIGPIPE,SIG_IGN);
signal(SIGPIPE,handle_sigpipe);
//signal(SIGCHLD, SIG_IGN);
signal(SIGCHLD,handle_sigchild);
int listenfd;
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 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("listten");
/*while (1)
{
int connfd;
struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
if ((connfd = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)ERR_EXIT("accept");
printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
pid_t pid = fork();
if (pid == -1)ERR_EXIT("fork");
if (pid == 0)
{
close(listenfd);
do_service(connfd);
exit(EXIT_SUCCESS);
}
if (pid > 0)
{
close(connfd);
}
}*/
std::vector<int>clients;
int epollfd = epoll_create1(EPOLL_CLOEXEC);
struct epoll_event event;
event.data.fd = listenfd;
event.events = EPOLLIN | EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &event);
Eventlist events(16);
int nready = 0;
while (1)
{
nready = epoll_wait(epollfd, &*events.begin(), static_cast<int>(events.size()), -1);
if (nready == -1)
{
if (errno == EINTR)continue;//被信号中断
ERR_EXIT("select");
}
if (nready == 0)continue;
if ((size_t)nready == events.size())events.resize(events.size() * 2);
for (int i = 0; i < nready; i++)
{
if (events[i].data.fd == listenfd)//有新的套接口了
{
int connfd;
struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
if ((connfd = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)ERR_EXIT("accept");
printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
clients.push_back(connfd);
event.data.fd = connfd;
event.events = EPOLLIN | EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &event);
}
else if (events[i].events&EPOLLIN)
{
int connfd = events[i].data.fd;
if (connfd < 0)continue;
char recvbuf[1024];
memset(&recvbuf, 0, sizeof(recvbuf));
int ret = read(connfd, recvbuf, sizeof(recvbuf));
if (ret == -1)
{
ERR_EXIT("read");
}
else if (ret == 0)
{
printf("client close\n");
close(connfd);
event = events[i];
epoll_ctl(epollfd, EPOLL_CTL_DEL, connfd, &event);
clients.erase(std::remove(clients.begin(), clients.end(), connfd), clients.end());
}
fputs(recvbuf, stdout);
write(connfd, recvbuf, strlen(recvbuf));
if (--nready <= 0)break;//检测到的事件已经处理完了,重新监听
}
}
}
close(listenfd);
}