Bootstrap

EPOLLIN和EPOLLOUT究竟什么时候触发?

server.cpp

 

#include <stdio.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <arpa/inet.h>
#include <iostream>

using namespace std;

#define MAXLINE 1024
#define SERV_PORT 8877

//发生了致命错误,输出错误后立即退出
void error_quit(const char *str)   
{   
 perror(str);
 exit(1);   
}  

int main(void)     
{     
 int listenfd, connfd, sockfd, epfd;   
 int i, res, maxi, nfds;   
 ssize_t n = 10;     
 char buf[MAXLINE] = "world";     
 socklen_t clilen;     
 struct sockaddr_in cliaddr;     
 struct sockaddr_in servaddr;    

 //声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件     
 struct epoll_event ev, events[256];     

 //创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大     
 epfd = epoll_create(256);    
 if( -1 == epfd )
  error_quit("epoll_create error");

 //创建用于TCP协议的套接字       
 listenfd = socket(AF_INET, SOCK_STREAM, 0);     
 memset(&servaddr, 0, sizeof(servaddr));     
 servaddr.sin_family = AF_INET;     
 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);     
 servaddr.sin_port = htons(SERV_PORT);     

 //把socket和socket地址结构联系起来      
 res = bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));   
 if( -1 == res )
  error_quit("bind error");

 //开始监听LISTENQ端口      
 res = listen(listenfd, INADDR_ANY);   
 if( -1 == res )
  error_quit("listen error");

 //设置与要处理的事件相关的文件描述符和事件   
 ev.data.fd = listenfd;     
 /*
 events可以是以下几个宏的集合:
 EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
 EPOLLOUT:表示对应的文件描述符可以写;
 EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
 EPOLLERR:表示对应的文件描述符发生错误;
 EPOLLHUP:表示对应的文件描述符被挂断;
 EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
 EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
 */
 ev.events = EPOLLIN|EPOLLET;     

 //注册epoll事件     
 epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd,&ev);     
 maxi = 0;   

 while(1)   
 {     
  //等待epoll事件的发生     
  //返回需要处理的事件数目nfds,如返回0表示已超时。     
  nfds = epoll_wait(epfd, events, 20, 500);     

  //处理所发生的所有事件     
  for(i=0; i < nfds; ++i)     
  {     
   //如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。     
   if(events[i].data.fd == listenfd)     
   {     
    connfd = accept(listenfd,(struct sockaddr *)&cliaddr, &clilen); 
    if( -1 == connfd )
     error_quit("accept error");   

    //注册用于读操作的文件描述符和事件   
    ev.data.fd = connfd;     
    ev.events =  EPOLLIN|EPOLLOUT|EPOLLET|EPOLLERR|EPOLLHUP;       
    res = epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);    
    if( -1 == res )
     error_quit("epoll_ctl error");
   }
   //如果有数据发送   
   else if(events[i].events & EPOLLOUT)    
   {   
    cout << "EPOLLOUT event"  << endl;
    sockfd = events[i].data.fd;   
    res = write(sockfd, buf, 10 );
    cout << "write " << res << " bytes. errno" << errno <<  ", str is "
     << strerror( errno ) << endl;
    //注册用于读操作的文件描述符和事件   
    /*ev.data.fd = sockfd;   
    ev.events = EPOLLIN|EPOLLET;   
    res = epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);   
    if( -1 == res )
     error_quit("epoll_ctl error");
     */
   }
   //如果是已经连接的用户,并且收到数据,那么进行读入。     
   else if(events[i].events & EPOLLIN)     
   {
    cout << "EPOLLIN event"  << endl;
    sockfd = events[i].data.fd;   
    if ( sockfd < 0 )     
     continue;     
    n = read(sockfd, buf, MAXLINE);   
    if ( n < 0)      
    {       
     // Connection Reset:你连接的那一端已经断开了,   
     //而你却还试着在对方已断开的socketfd上读写数据!     
     if (errno == ECONNRESET)     
     {     
      close(sockfd);     
      events[i].data.fd = -1;     
     }      
     else     
      error_quit("read error");   
    }      
    //如果读入的数据为空     
    else if ( n == 0 )    
    {     
     close(sockfd);     
     events[i].data.fd = -1;     
    }
    /*
    else   
    {   
     //注册用于写操作的文件描述符和事件   
     ev.data.fd = sockfd;   
     ev.events = EPOLLOUT|EPOLLET;    
     res = epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);   
     if( -1 == res )
      error_quit("epoll_ctl error");
    }
    */    
        
   }    //如果有数据发送   
   else if(events[i].events & EPOLLERR)    
   {   
    cout << "EPOLLERR event"  << endl;
   }
   else if(events[i].events & EPOLLHUP)    
   {   
    cout << "EPOLLHUP event"  << endl;
   }
  }   
 }   
 return 0;     
}    

 

client.cpp

 

 #include <stdio.h>
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/un.h>

using namespace std;

int main ( int argc, char ** argv )
{

 if ( argc < 5  )
 {
  cout << "Usage : ip port  kind need_crlf  str, like : 172.27.201.182 39752 tcp Y   hello"  << endl;

  return -1;

 }
 int iRet = -1;
 string sHost = argv[1];

 int iPort = atoi( argv[2] );

 string sKind = argv[3];
 string sNeedCRLF = argv[4];
 string  sIn  = argv[5];
 
 int sock_fd = -1, serverlen;
 struct sockaddr_in server_addr;

 if ( sKind == "udp" )
 { 
  sock_fd = socket( AF_INET, SOCK_DGRAM, 0 );
 }
 else if ( sKind == "tcp" )
 {
  sock_fd = socket( AF_INET, SOCK_STREAM, 0 );
 }
 
 if ( sock_fd == -1 )
 {

  cout <<  "create socket failed !!" << endl;

  return -1;

 }

 bzero( &server_addr, sizeof ( struct sockaddr_in ) );

 server_addr.sin_family = AF_INET;
 server_addr.sin_port = htons( iPort );
 server_addr.sin_addr.s_addr = inet_addr( sHost.c_str() );

 serverlen = sizeof ( server_addr );

 
 if ( sNeedCRLF == "Y"  )
 {
  sIn = sIn + "\r\n";
 }
 int iLen = sIn.length() ;
 if ( sKind == "tcp" )
 {
  iRet = connect( sock_fd, (struct sockaddr *)&server_addr, serverlen );
  if ( iRet != 0 )
  {
   cout << "connect failed"  << endl;
   return -1;
  }
 }
 cout <<  "connect to server " << sHost << ":" << iPort << endl;
 sleep( 10 );
 
 if  ( sendto( sock_fd, sIn.c_str(), iLen, 0, (struct sockaddr *)&server_addr, serverlen ) < 0)
 {

  cout <<  "send to server failed !!" << endl;

  close( sock_fd );

  return -2;
 }
 cout <<  "send to server " << sHost << ":" << iPort << endl;
 char sBuffer[1024] ;
 sleep( 10 );
 int iSize = ::recvfrom( sock_fd, sBuffer, 1024 , 0,
  (struct sockaddr*)&server_addr, (socklen_t*)&serverlen);
 cout << "recv return: " << iSize << " bytes " << endl;
 cout << sBuffer << endl;
 sleep( 10 );
 close( sock_fd );

 cout <<  " send finished, close socket. " << endl;

 return 0;

}
开一个窗口执行server

./server

再开一个窗口执行client

./client  10.6.208.181 8877 tcp Y hello

 

 

这时server端会打印


EPOLLOUT event
write 10 bytes. errno0, str is Success

 过10秒


EPOLLOUT event
write 10 bytes. errno0, str is Success

 过10秒

 

 下面三行同时打印

EPOLLOUT event
write 10 bytes. errno0, str is Success
EPOLLIN event

 

可见 如果同时监听EPOLLIN|EPOLLOUT,客户端connect的时候会产生一次EPOLLOUT|EPOLLIN;客户端send的时候会产生一次EPOLLOUT|EPOLLIN;客户端recv的时候没有任何事件产生,客户端关闭时会产生EPOLLOUT|EPOLLIN|EPOLLHUP|EPOLLERR四个事件

 

 

 

 

 

 

 

;