epoll_ctl 默认的模式是水平模式。
epoll使用共享内存,使用内核和用户使用一块内存,使得这块物理内存对内核和对用户均可见,减少用户态和内核态之间的数据交换。内核可以直接看到epoll监听的句柄,效率高。
epoll 使用红黑树保存监听的套接字,当添加或者删除一个套接字时(epoll_ctl),都在红黑树上去处理,红黑树本身插入和删除性能比较好,时间复杂度O(logN)。
客户端connect,会产生EPOLLIN事件,
TCP-NODELAY 标志-代表发送的返回不汇总发送,而是接受消息立即发送。这种是块,但是增加带宽压力。
epoll 的大概逻辑是将有效的描述符放入epoll.
{1. epoll_wait 先等待listen描述符是否产生接受从cli端发来的新的connect 链接。新的connnect 对应的EPOLL 的事件 是EPOLLIN。
2 . accept 接受新的listen链接,将返回的有效描述符rd存放到epoll_ctl.
3. epoll_wait 对rd 描述符进行处理,判断是否有新的EPOLLIN 可以读,写等操作
}
EPOLLOUT事件:
EPOLLOUT事件只有在连接时触发一次,表示可写,其他时候想要触发,那你要先准备好下面条件:
1.某次write,写满了发送缓冲区,返回错误码为EAGAIN。
2.对端读取了一些数据,又重新可写了,此时会触发EPOLLOUT。
简单地说:EPOLLOUT事件只有在不可写到可写的转变时刻,才会触发一次,所以叫边缘触发,这叫法没错的!
其实,如果你真的想强制触发一次,也是有办法的,直接调用epoll_ctl重新设置一下event就可以了,event跟原来的设置一模一样都行(但必须包含EPOLLOUT),关键是重新设置,就会马上触发一次EPOLLOUT事件。
EPOLLIN事件:
EPOLLIN事件则只有当对端有数据写入时才会触发,所以触发一次后需要不断读取所有数据直到读完EAGAIN为止。否则剩下的数据只有在下次对端有写入时才能一起取出来了。
现在明白为什么说epoll必须要求异步socket了吧?如果同步socket,而且要求读完所有数据,那么最终就会在堵死在阻塞里。
可以为每一个listen 返回的fd ,产生epoll_wait 等操作。
int
tcp_service(constchar * in_port,constchar * in_ip )
{
if( in_port ==NULL | in_ip ==NULL )
{
printf ( "port or ip is null\n");
return E_FAIL;
}
int port = atoi( in_port);
int listenfd,rwfd, epfd,okfds,i,ret;
constchar *ip = in_ip;
struct sockaddr_in address, local;
struct epoll_event events[MAX_EVENTS_NUM];
int address_len;
pid_t pid;
char logname[STR_MAX_LEN];
int log_fd;
struct stat stat_buf;
bzero( &address,sizeof(address) );
if( port <1024 || port >65535)
{
printf( "prot can not be more than 65535 and \
can not be less than 1024");
return E_FAIL;
}
address.sin_family = AF_INET;
inet_pton( AF_INET, ip, &address.sin_addr );
address.sin_port = htons( port );
listenfd = socket( AF_INET, SOCK_STREAM,0);
if( listenfd <0 )
{
perror("socket wrong" );
return E_FAIL;
}
setskopt(listenfd);
ret = bind( listenfd, (struct sockaddr*)&address,sizeof(address));
if( ret <0 )
{
if( errno == EADDRNOTAVAIL )
{
printf("ip is wrong");
return E_FAIL;
}
elseif( errno == EADDRINUSE )
{
printf("prot is uesd" );
return E_FAIL;
}
perror("bind wrong");
return E_FAIL;
}
//添加描述符,LISTEN_NUM 是listen 队列等待的最大数量,listen队列的描述符需要由accept函数获取,如果没有获取当等待队列大于 LISTEN_NUM,listen就会报错。
ret = listen( listenfd, LISTEN_NUM );
if( ret <0 )
{
perror("listen is wrong");
return E_FAIL;
}
epfd = epoll_create( EPOLL_CREAT_NUM );
if( epfd <0 )
{
perror( "epoll_creat is wrong");
return E_FAIL;
}
epoll_addfd( epfd, listenfd );
signal(SIGCHLD,SIG_IGN);
while(1)
{
printf( "parent is doing\n");
okfds = epoll_wait(epfd, events, MAX_EVENTS_NUM, -1);
if( okfds <0 )
{
perror("epoll_wait is wrong");
return E_FAIL;
}
for ( i=0; i<okfds; i++)
{
if(events[i].data.fd == listenfd)
{
address_len =sizeof(address);
rwfd = accept(listenfd,(struct sockaddr*)&address,
&address_len );
if(rwfd <0 )
{
perror("accept wrong");
return E_FAIL;
}
epoll_addfd(epfd, rwfd);
printf("events.fd=[%d]\n",events[i].data.fd);
printf("listen=[%d]\n", listenfd);
printf("rwfd=[%d]\n", rwfd);
}elseif ( events[i].events & EPOLLIN)
{
pid=fork();
if(pid <0 )
{
perror("fork is wrong");
return E_FAIL;
}
if( pid ==0 )
{
printf("clid events.fd=[%d]\n",events[i].data.fd);
char buf[STR_MIN_LEN];
close(listenfd);
memset(logname,0x00,sizeof(logname));
memset(buf,0x00,sizeof(buf));
sprintf( logname,"fd%d",events[i].data.fd);
log_fd =open( logname, O_CREAT|O_RDWR|O_APPEND,0666);
if( log_fd <0 )
{
printf( "open is wrong\n");
return E_FAIL;
}
printf("log_fd=[%d]\n", log_fd);
while(1)
{
memset(buf,0x00,sizeof(buf));
ret=recv(events[i].data.fd,buf,sizeof(buf),0);
if(ret <0)
{
if(errno == EAGAIN || errno == EWOULDBLOCK)
break;
close(events[i].data.fd);
break;
}
if( ret ==0)
{
close(events[i].data.fd);
}
printf("being child:%s\n",buf);
}
exit(0);
}
printf("par rwfd=[%d]\n", rwfd);
close(events[i].data.fd);
close(rwfd);
continue;
}
}
}
close(listenfd);
close(epfd);
return E_OK;
}