/*
poll 在已连接的套接字中遍历
epoll_wait 返回的都是活跃的套接字,所以减少了很多无效的套接字
Poll模型 :
每次调用 poll函数的时候。都需要把监听套接字与已连接套接字所感兴趣的事件数组 拷贝到内核。
LT模式 :
Write EPOLLOUT 事件
高电平 writebuf内核有空闲空间,我们就说他处于高电平状态,也就是一直处于活跃状态。此时可能会产生busy waitting loop
低电平 当writebuf内核没有空闲空间,我们就说他处于低电平状态,没有激活。
Read EPOLLIN 事件
xxx
ET模型 边缘触发
在该模式下,一开始我们就关注EPOLLIN事件和EPOLLOUT事件 , 此时writbuf一直处于高电平,不会触发EPOLLOUT事件.
而EPOLLIN可能会产生,因为开始Readbuf是空的,如果在 epoll_wait前,readbuf有数据了,那么就有unreadablr--->readable,
也就是产生了EPOLLLIN事件,这也是为什么监听socket 能够接收到外来请求的原因。
关注EPOLLIN 和 EPOLLOUT事件后,我们也没必要取消他们的关注,只有到断开他们的socketfd时 , 我们才需要取消。
需要注意的是,在读写时,如果是读,一定要读到EAGAIN,写也是要写到EAGAIN
socket从unreadable变为readable或从unwritable变为writable
有些人说从readable变为unreadable或者writable变为unwritable时也会触发事件。
我个人觉得第一种合理一点。
*/
/*
正确的读法
n = 0;
while ((nread = read(fd, buf + n, BUFSIZ-1)) > 0) {
n += nread;
}
if (nread == -1 && errno != EAGAIN) {
perror("read error");
}
*/
/*
正确的写法
int nwrite, data_size = strlen(buf);
n = data_size;
while (n > 0) {
nwrite = write(fd, buf + data_size - n, n);
if (nwrite < n) {
if (nwrite == -1 && errno != EAGAIN) {
perror("write error");
}
break;
}
n -= nwrite;
}
*/
poll 在已连接的套接字中遍历
epoll_wait 返回的都是活跃的套接字,所以减少了很多无效的套接字
Poll模型 :
每次调用 poll函数的时候。都需要把监听套接字与已连接套接字所感兴趣的事件数组 拷贝到内核。
LT模式 :
Write EPOLLOUT 事件
高电平 writebuf内核有空闲空间,我们就说他处于高电平状态,也就是一直处于活跃状态。此时可能会产生busy waitting loop
低电平 当writebuf内核没有空闲空间,我们就说他处于低电平状态,没有激活。
Read EPOLLIN 事件
xxx
ET模型 边缘触发
在该模式下,一开始我们就关注EPOLLIN事件和EPOLLOUT事件 , 此时writbuf一直处于高电平,不会触发EPOLLOUT事件.
而EPOLLIN可能会产生,因为开始Readbuf是空的,如果在 epoll_wait前,readbuf有数据了,那么就有unreadablr--->readable,
也就是产生了EPOLLLIN事件,这也是为什么监听socket 能够接收到外来请求的原因。
关注EPOLLIN 和 EPOLLOUT事件后,我们也没必要取消他们的关注,只有到断开他们的socketfd时 , 我们才需要取消。
需要注意的是,在读写时,如果是读,一定要读到EAGAIN,写也是要写到EAGAIN
socket从unreadable变为readable或从unwritable变为writable
有些人说从readable变为unreadable或者writable变为unwritable时也会触发事件。
我个人觉得第一种合理一点。
*/
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netnet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/epoll.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <vector.h>
#include <algorithm>
#include <iostream>
typedef std::vector<struct epoll_event> EventList ;
#define ERR_EXIT(m) \
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0);
int main ( void ) {
//防止进程由于客户端的关闭而使服务器进程退出
signal(SIGPIPE ,SIG_IGN) ;
//防止僵死进程的发生
signal(SIGCHLD,SIG_IGN);
//备胎描述符
int idlefd = open("/dev/null",O_RDONLY | O_CLOEXEC) ;
//监听描述符
int listenfd ;
if((listenfd =socket(PF_INET,SOCK_STRAM|SOCK_NONBLOCK|SOCK_CLOSEXEC,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 ;
//socketfd 重用
if(setsocketopt(listenfd,SOL_SOCKET,SO_REUSERADDR,&on,sizeof(n))<0)
{
ERR_EXIT("setsocketopt");
}
if( bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) <0)
ERR_EXIT("bind");
if(listen(listen,SOMAXCONN) <0)
ERR_EXIT("listen");
std::vector<int> clients;
int epollfd;
// 函数返回一个epoll专用的描述符epfd,epfd引用了一个新的epoll机制例程(instance.)。
epollfd = epoll_create1(EPOLL_CLOEXEC);
//事件结构体
struct epoll_event event ;
event.data.fd = listenfd ; //关注listenfd
event.events = EPOLLIN ; // /* | EPOLLET*/; 默认是LT模型
// 把lisenfd 添加到epollfd 中进行管理
//如果是poll的话,就不用这样了,poll直接使用一个数组就行了
/*函数声明: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
71 - 78 就交给epollfd 内核帮我们做了,并且只有epoll_ctl才会拷贝到内核,一次拷贝就行了,以后就不用拷贝了,
如果我们使用poll 那么每次循环都要拷贝到内核里面去,大大降低了效率
*/
epoll_ctl(epollfd , EPOLL_CTL_ADD,listenfd,&event);
//事件列表,初始为16 个
EventList events(16) ;
struct sockaddr_in peeraddr ;
socklen_t peerlen ;
int connfd ;
int nready ;
while(1){
/*函数声明:int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)
该函数用于轮询I/O事件的发生;
参数:
epfd:由epoll_create 生成的epoll专用的文件描述符;
epoll_event:用于回传代处理事件的数组,要处理的事件都会存储在events里面了
maxevents:每次能处理的事件数;
timeout:等待I/O事件发生的超时值(单位我也不太清楚);-1相当于阻塞,0相当于非阻塞。一般用-1即可
返回发生事件数。
-1 表示等待到至少一个事件发生*/
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 ) //nothing to happened
continue ;
//
if((size_t)nready == events.size())
events.resize(events.size()*2) ;
for (int i = 0; i < nready; ++i)
{
//如果是监听socket,则accetp
if (events[i].data.fd == listenfd)
{
peerlen = sizeof( peeraddr ) ;
// 这里的处理还不够好,因为accept4每一次只能到tcp就绪队列里面拿出一个就绪socketfd , 有可能这个队列不止一个,
//在并发的时候这是必然的, 所以最后是while掉accept4,把里面的就绪socketfd全部拿出来,而不时只拿一个socketfd。
//下面的代码可根据需求进行改进
connfd =::accept4(listenfd,(struct sockaddr *)&peeraddr,&peerlen,SOCK_NONBLOCK|SOCK_CLOSEXEC) ;
if( connfd == -1 )
{
if(errno == EMFILE){
close(idlefd) ;
idlefd = accept(listenfd,NULL,NULL) ;
close(idlefd) ;
idlefd = open("/dev/null",O_RDONLY|O_CLOEXEC) ;
}
}
else
ERR_EXIT("accept4") ;
std::cout<<"ip="<<inet_ntoa(peeraddr.sin_addr)<<" port="<<ntohs(peeraddr.sin_port)<<std::endl;
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)
{
//下面的这些都要改进
connfd = events[i].data.fd;
if (connfd < 0)
continue;
char buf[1024] = {0};
int ret = read(connfd, buf, 1024);
if (ret == -1)
ERR_EXIT("read");
if (ret == 0)
{
std::cout<<"client close"<<std::endl;
close(connfd);
event = events[i];
epoll_ctl(epollfd, EPOLL_CTL_DEL, connfd, &event);
clients.erase(std::remove(clients.begin(), clients.end(), connfd), clients.end());
continue;
}
std::cout<<buf;
write(connfd, buf, strlen(buf));
}
}
}
}
/*
正确的读法
n = 0;
while ((nread = read(fd, buf + n, BUFSIZ-1)) > 0) {
n += nread;
}
if (nread == -1 && errno != EAGAIN) {
perror("read error");
}
*/
/*
正确的写法
int nwrite, data_size = strlen(buf);
n = data_size;
while (n > 0) {
nwrite = write(fd, buf + data_size - n, n);
if (nwrite < n) {
if (nwrite == -1 && errno != EAGAIN) {
perror("write error");
}
break;
}
n -= nwrite;
}
*/