网络通信客户端模块的编写


1.设置socket选项/tcp建立和释放连接的状态CLOSE_TIME,TIME_WAITE/recv,send的异常处理
// socket提供活保功能,也就是心跳
SO_KEEPALIVE
SO_KEEPALIVE_VALS
// 马上重新使用端口建立连接,避免WAIT_TIME时间内2-4分钟内不能绑定端口的设定。
SO_REUSEADDR
// closesocket时候不要再接受和发送数据了,因为这种临界数据不要了,避免因为需要发送数据网络又有问题半天不关闭。
SO_LINGER:
struct  linger {
        u_short l_onoff;                /* option on/off */
        u_short l_linger;               /* linger time */
};

设置 l_onoff为非0,l_linger为0,则套接口关闭时TCP夭折连接,TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个RST给对方,而不是通常的四分组终止序列,这避免了TIME_WAIT状态
linger Linger;
 Linger.l_onoff = 1;
 Linger.l_linger = 0;
 setsockopt(
  m_Socket,
  SOL_SOCKET,
  SO_LINGER,
  (const char*)&Linger,
  sizeof(linger));
// 关闭nagle算法,nagle算法是会发送时候缓存小数据包,攒多了才一次发送,加法启动,剩法减速窗口的慢启动和流量控制算法
// 网络游戏中通常需要马上发送数据,而将该算法关闭,启用nodelay选项。
TCP_NODELAY
//  FIONBIO:允许或禁止套接口s的非阻塞模式。
/*

u_long mode = 0;
ioctlsocket(s,FIONBIO,&mode);
控制为阻塞方式。

u_long mode = 1;
ioctlsocket(s,FIONBIO,&mode);
控制为非阻塞方式。

*/

// 查看网络连接状态和分析网络连接问题工具
// 1.netstat命令
netstat 
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
// 2.wireshark开源数据包分析工具

2.网络数据缓存的管理-IOCP或epoll模式收发数据/基础缓存附加链表缓存/抽象类数据管理分析接收的直接发送/抽象类直接小端模式发送小端模式截取转换/接收或者发送准备好的数据在每帧处理
从网络recv来的数据,send将要发出去的数据,不能简单的从一块读缓存和一块写缓存来存放。因为这样很容易导致一下子接收到很多的数据而缓存溢出,或者一下子写很多数据而缓存溢出。
强壮良好的做法,是用两个链表。一个读链表,一个写链表。读链表的内存节点的大小先从之前的内存节点里面取得,如果内存大小不够那么申请新的内存节点大小,是一次recv到的数据的大小申请一个这么大的节点,插入读链表管理队列。
写链表,先从写链表节点里面获取一块足够大的节点,如果没有那么申请该消息大小的节点。存在弊端,频繁的申请内存和释放内存比较影响程序的性能。
也可以用2块足够大的内存存放网络消息,但是这样也有弊端,就是产生溢出数据会丢失。

总结:应用程序应该为网络接收和发送同时开辟一块理论上比较够大的基础缓存大小;同时分配两个附加的链表。当基础接收或者发送缓存被用完的时候,那么启用附加的链表,new出消息节点大小的缓存。
       接收完毕if(nRet <= 0),接收线程需要解析这些网络消息,解析时候可以用map找到消息处理函数(回调函数可以用统一的消息基类指针作为函数参数定义统一的函数指针),对消息进行回调,回调的网络消息函数会在客户端备份网络传输过来的数据,所有回调处理完毕,这个时候如果有附加链表节点就可以释放接收缓存的附加节点链表了,基础接收缓存不处理。
从而接收线程不需要下面多余的拷贝new,delete内存和游戏中process或者update每帧处理接收消息,不仅带来游戏网络延迟也带来了很多的性能开销。但是前面的只是空想,因为还要确定子线程中可以调用父线程的处理函数?,或者在主函数中使用recv接收数据可以做到,但却获取不到epoll,IOCP带来的事件异步高性能客户端的好处。
所以,还是需要子线程中有效的拷贝数据,按照一个一个消息的大小进行解析放置到管理队列中(需要new拷贝,拷贝完以后,接收缓存节点部分就可以释放了,基础部分还是保留),然后在程序中process处理,处理完所有接收消息和他们的回调之后,删掉拷贝new出来的节点数据。
      发送数据,可以写到应用程序的发送缓存,基础块和附加链表;直接主程序中不缓存发送是不好的设计。
    主程序process中,先处理接收缓存完,然后处理发送缓存。
     recv或send得到的数据如果用抽象类指针转换,那么可以不用类型转换可以得到小端模式的数据类型?还需要验证,抽象数据类情况下是否还需要下面的转换函数:
整型:
ntohs
ntohl
ntohll
字符串用char*,unsiged char*都可以。
float ,double用字符串形式来表示。

3.windows使用事件选择模型/linux使用epoll模型-window/linux网络通信模型和水平触发/边缘触发
1).都在子线程中程,WSACreateEvent()返回事件类型,且用WSAEventSelect关联和检测客户端socket和事件。
 2).子线程中用WSAWaitForMultipleEvents检测到客户端socket事件(根据配置时间是限时的,当0时间是非阻塞的),
       且返回事件的id,通过index = ret - WSA_WAIT_EVENT_0获取对应的客户端socket;用WSAEnumNetworkEvents获取事件类型进行操作。

m_Event[1] = WSACreateEvent();
 WSAEventSelect(m_Socket, m_Event, FD_READ | FD_CLOSE);
dwIndex = WSAWaitForMultipleEvents(2, m_Event[1], false, WSA_INFINITE, false);
  switch(dwIndex - WSA_WAIT_EVENT_0)
  {
  case 1:
   {
    WSAEnumNetworkEvents(m_Socket, m_Event[1], &NetworkEvents);
    if(NetworkEvents.lNetworkEvents & FD_READ)
    {
     if(NetworkEvents.iErrorCode[FD_READ_BIT] != 0)
     {
   
      closesocket(pClient->m_Socket);
      pClient->m_Socket = INVALID_SOCKET;
      return 0;
     }

// 一直接受完为止,不断recv
  }

if(NetworkEvents.lNetworkEvents & FD_CLOSE)
    {
     closesocket(pClient->m_Socket);
     pClient->m_Socket = INVALID_SOCKET;
     return 0;
    }
}//1

if(NetworkEvents.lNetworkEvents & FD_CLOSE)
    {
     closesocket(pClient->m_Socket);
     pClient->m_Socket = INVALID_SOCKET;
     return 0;
    }
}// switch

// linux下epoll模型
linux下epoll记得设置为边缘事件,关闭socket.

工作模式

  epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:

  LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。

  ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。

  ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。


1) int epoll_create(int size)
该 函数生成一个epoll专用的文件描述符。它其实是在内核申请一空间,用来存放你想关注的socket fd上是否发生以及发生了什么事件。size就是你在这个epoll fd上能关注的最大socket fd数。随你定好了。只要你有空间。使用完后需要关闭。

返回值:
       On success, these system calls return a nonnegative file descriptor.
       On error, -1 is returned, and errno is set to indicate the error.
ERRORS         top
       EINVAL size is not positive.
       EINVAL (epoll_create1()) Invalid value specified in flags.
       EMFILE The per-user limit on the number of epoll instances imposed by
              /proc/sys/fs/epoll/max_user_instances was encountered.  See
              epoll(7) for further details.
       ENFILE The system limit on the total number of open files has been
              reached.
       ENOMEM There was insufficient memory to create the kernel object.


2)  int epoll_ctl(int epfd, intop, int fd, struct epoll_event*event);// 注册触发类型和读写事件等

   epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。

           第一个参数是epoll_create()的返回值,

           第二个参数表示动作,用三个宏来表示:

           EPOLL_CTL_ADD:       注册新的fd到epfd中;

          EPOLL_CTL_MOD:      修改已经注册的fd的监听事件;

           EPOLL_CTL_DEL:        从epfd中删除一个fd;

         第三个参数是需要监听的fd,

          第四个参数是告诉内核需要监听什么事件。

返回值:

成功时,epoll_ctl() 返回零。错误时,epoll_ctl() 返回 -1 并把 errno 设置为合适的值

错误

EBADF

    epfd 或 fd 不是一个有效的文件描述符。

EEXIST

    op 是 EPOLL_CTL_ADD,并且提供的文件描述符 fd 已经存在于 epoll 实例中。

EINVAL

    epfd 不是一个 epoll 文件描述符,或者 fd 与 epfd 相同,或者请求的操作 op 不被本接口支持。

ENOENT

    op 是 EPOLL_CTL_MOD 或 EPOLL_CTL_DEL,但 fd 在相应的 epoll 实例中不存在。

ENOMEM

    没有足够的内存来处理请求的 op 控制操作。

ENOSPC

    试图注册(EPOLL_CTL_ADD)一个新文件描述符到一个 epoll 实例时达到了 /proc/sys/fs/epoll/max_user_watches 的限制。参看 epoll(7) 了解更多细节。

EPERM

    目标文件 fd 不被 epoll 支持。 


3) int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout) // events是回传的处理事件的socket

该函数用于轮询I/O事件的发生;
参数:
epfd: 由epoll_create 生成的epoll专用的文件描述符;
epoll_event: 用于回传代处理事件的数组;
maxevents: 每次能处理的事件数;
timeout: 等待I/O事件发生的超时值(单位我也不太清楚);-1相当于阻塞,0相当于非阻塞。一般用-1即可
返回发生事件数。
epoll_wait运行的原理是
等侍注册在epfd上的socket fd的事件的发生,如果发生则将发生的sokct fd和事件类型放入到events数组中。
并 且将注册在epfd上的socket fd的事件类型给清空, 所以如果下一个循环你还要关注这个socket fd的话,则需要用epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev)来重新设置socket fd的事件类型。这时不用EPOLL_CTL_ADD,因为socket fd并未清空,只是事件类型清空。这一步非常重要。

返回值:

成功时,epoll_wait() 返回就绪的请求的 I/O 个数,如果在 timeout 毫秒里没有文件描述符就绪返回零。发生错误时,epoll_wait() 返回 -1 同时把 errno 设置为合适的值。

错误值:

EBADF

epfd 不是一个有效的文件描述符。

EFAULT

events 指向的内存没有写权限。

EINTR

在请求事件就绪或 timeout 到期之前,这个调用被信号处理器中断,参看 signal(7)。

EINVAL

epfd 不是一个 epoll 文件描述符,或 maxevents 小于或等于零。

4)close(m_fdEpoll); 释放资源后需要关闭epoll专用的文件描述符// epoll服务器端
#include <iostream>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#define MAXLINE 10
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 5555
#define INFTIM 1000
void setnonblocking(int sock)
{
 int opts;
 opts=fcntl(sock,F_GETFL);
 if(opts<0)
 {
  perror("fcntl(sock,GETFL)");
  exit(1);
 }
 opts = opts | O_NONBLOCK;
 if(fcntl(sock,F_SETFL,opts)<0)
 {
  perror("fcntl(sock,SETFL,opts)");
  exit(1);
 }
}
int main()
{
 int i, maxi, listenfd, connfd, sockfd, epfd, nfds;
 ssize_t n;
 char line[MAXLINE];
 socklen_t clilen;
 struct epoll_event ev,events[20]; //声明epoll_event结构体的变量, ev用于注册事件, events数组用于回传要处理的事件
 epfd=epoll_create(256); //1. 生成用于处理accept的epoll专用的文件描述符, 指定生成描述符的最大范围为256
 struct sockaddr_in clientaddr;
 struct sockaddr_in serveraddr;
 listenfd = socket(AF_INET, SOCK_STREAM, 0);
 setnonblocking(listenfd); //把用于监听的socket设置为非阻塞方式
 ev.data.fd=listenfd; //设置与要处理的事件相关的文件描述符
 ev.events=EPOLLIN | EPOLLET; //2.设置要处理的事件类型,事件类型为边缘触发(IO就绪了只通知一次,只关心活跃的IO)
 epoll_ctl(epfd, EPOLL_CTL_ADD,listenfd,&ev); //2. 注册epoll触发或者读写事件
 bzero(&serveraddr, sizeof(serveraddr));
 serveraddr.sin_family = AF_INET;
 char *local_addr="200.200.200.204";
 inet_aton(local_addr,&(serveraddr.sin_addr));
 serveraddr.sin_port=htons(SERV_PORT);  //或者htons(SERV_PORT);
 bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));
 listen(listenfd, LISTENQ);
 maxi = 0;
 for( ; ; ) {
  nfds=epoll_wait(epfd,events,20,500); //3.等待epoll监听读写事件等
  for(i=0; i < nfds; ++i) //处理所发生的所有事件
  {
   if(events[i].data.fd == listenfd)    /**监听事件**/
   {
    connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);
    if(connfd<0){
     perror("connfd<0");
     exit(1);
    }
    setnonblocking(connfd); //把客户端的socket设置为非阻塞方式
    char *str = inet_ntoa(clientaddr.sin_addr);
    std::cout<<"connect from "<_u115 ? tr<<std::endl;
    ev.data.fd = connfd; //设置用于读操作的文件描述符
    ev.events = EPOLLIN | EPOLLET; //设置用于注测的读操作事件
    epoll_ctl(epfd,EPOLL_CTL_ADD,connfd, &ev); //2. 注册epoll触发或者读写事件
   }
   else if(events[i].events & EPOLLIN)     /**读事件**/
   {
    if ( (sockfd = events[i].data.fd) < 0) continue;
    if ( (n = read(sockfd, line, MAXLINE)) < 0) {
     if (errno == ECONNRESET) {
      close(sockfd);
      events[i].data.fd = -1;
     } else
     {
      std::cout<<"readline error"<<std::endl;
     }
    } else if (n == 0) {
     close(sockfd);
     events[i].data.fd = -1;
    }
    ev.data.fd = sockfd; //设置用于写操作的文件描述符
    ev.events = EPOLLOUT | EPOLLET; //设置用于注册写操作事件
    epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev); //2. 修改epoll触发或者读写事件
   }
   else if(events[i].events & EPOLLOUT)    /**写事件**/
   {
    sockfd = events[i].data.fd;
    write(sockfd, line, n);
    ev.data.fd=sockfd; //设置用于读操作的文件描述符
    ev.events=EPOLLIN | EPOLLET; //设置用于注册的读操作事件
    epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //2. 修改epoll触发或者读写事件
   }
  }
 }
}

// epoll客户端
unsigned int ClientWorkerThread(void *pParam);
void EpollClient::Process()
{
 // linux设置接收和发送超时, 解决连接超时的bug  连接成功后将超时设置清除,防止在影响后续逻辑
 struct timeval nTimeout = {3, 0};
 if (SOCKET_ERROR == setsockopt(m_Socket, SOL_SOCKET, SO_SNDTIMEO, (char*)&nTimeout, sizeof(nTimeout)))
 {
  int l_error = errno;
  printf("setsockopt send timeout error[%d]\n", l_error);
  return false;
 } // 超时修改
 bool ret = false;
 if(connect(m_Socket, (sockaddr *)&Addr, sizeof(Addr)) == SOCKET_ERROR)
 {
  return false;
  /*
  // 超时事件设置为3秒
  timeval tm;
  int error=-1;
  fd_set set;
  tm.tv_sec = 3;
  tm.tv_usec = 0;
  int len=sizeof(error);
  FD_ZERO(&set);
  FD_SET(m_Socket, &set);
  if( select(m_Socket+1, NULL, &set, NULL, &tm) > 0)
  {
   if(FD_ISSET(m_Socket,&set))
   {
    getsockopt(m_Socket, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len);
    if(error == 0)
    ret = true;
   }
  }
  */
 }
 else
 {
  ret = true;
  // linux设置接收和发送超时, 解决连接超时的bug 连接成功后将超时设置清除,防止在影响后续逻辑 11929
  struct timeval nClearTimeout = {0, 0};
  if (SOCKET_ERROR == setsockopt(m_Socket, SOL_SOCKET, SO_SNDTIMEO, (char*)&nClearTimeout, sizeof(nClearTimeout)))
  {
   int l_error = errno;
   printf("setsockopt send timeout error[%d]\n", l_error);
   closesocket(m_Socket);
   m_Socket = INVALID_SOCKET;
   return false;
  }
 }
 // 设置为非阻塞模式
 unsigned long ul = 1;
 if(ioctlsocket(m_Socket, FIONBIO, &ul) == SOCKET_ERROR)
 {
  closesocket(m_Socket);
  m_Socket = INVALID_SOCKET;
  return false;
 }
 if (!ret)
 {
  closesocket(m_Socket);
  m_Socket = INVALID_SOCKET;
  return false;
 }
 // 1.创建eopll,最多处理EPOLL_BACK_STORE_SIZE个连接
 m_fdEpoll = epoll_create(10);
 if(m_fdEpoll == -1)
  return FALSE;
 epoll_event ee;
 //设置与要处理的事件相关的文件描述符
 ee.data.fd = m_Socket;
 ee.data.ptr = this;
 //设置要处理的事件类型(IN 接受新连接)
 ee.events = EPOLLOUT;
 //2.注册epoll事件
 if(epoll_ctl(m_fdEpoll, EPOLL_CTL_ADD, m_Socket, &ee) == -1)
  return FALSE;
 m_bQuit = false;
 m_pThread = new CFBKernal_Thread();
 if(!m_pThread->Create(1024 * 1024, & ClientWorkerThread, this))
 {
  m_bQuit = true;
  return false;
 }
}
bool EpollClient::EpollCtl(unsigned int uEvent)
 {
  epoll_event ee;
  ee.data.ptr = this;
  ee.events = uEvent;
  this->m_CurEvent=uEvent;
  if(-1 == epoll_ctl(m_fdEpoll, EPOLL_CTL_MOD, m_Socket, &ee))
  {
   return false;
  }
  return true;
 };
unsigned int  ClientWorkerThread(void *pParam)
 {
  EpollClient* pClient = (EpollClient*) pParam;
  epoll_event events[100];
  pClient->m_bThreadRun = true;
  for(;;)
  {
   if (pClient->m_bQuit)
   {
    if(pClient->m_Socket != INVALID_SOCKET)
    {
     closesocket(pClient->m_Socket);
     pClient->m_Socket = INVALID_SOCKET;
    }
    printf("FBNetwork_Client_WorkerThread quirt! ");
    fflush(stdout);
    pClient->m_bThreadRun = false;
    return 0;
   }
   if(pClient->m_Socket == INVALID_SOCKET)
   {
    printf("client socket is invalid :%s\n", strerror(errno));
    pClient->m_bThreadRun = false;
    return 0;
   }
   if(!pClient->m_SendBuf.Empty()&&!(pClient->m_CurEvent&EPOLLOUT))
    pClient->EpollCtl(EPOLLOUT|EPOLLET);
   // 3.epoll_wait阻塞或非阻塞等待socket事件
   int nfds = epoll_wait(pClient->m_fdEpoll, events, 100, 10);
   if(nfds == -1)
   {
    closesocket(pClient->m_Socket);
    pClient->m_Socket = INVALID_SOCKET;
    printf("epoll_wait error nfds = (%d)! ", nfds);
    fflush(stdout);
    pClient->m_bThreadRun = false;
    return 0;
   }
   // 得到触发事件的socket数量
   for(int i = 0; i < nfds; i++)
   {
    EpollClient* pConn = (EpollClient *) events[i].data.ptr;
    assert(pConn != NULL);
    if(pConn->m_Socket == INVALID_SOCKET)
    {
     printf("client socket is invalid :%s\n", strerror(errno));
     pClient->m_bThreadRun = false;
     return 0;
    }
    // 读取事件,读取接收缓存的数据
    if(events[i].events & EPOLLIN)
    {
     while(1)
     {
      // 收完为止
      unsigned int nFreeSize = 0;
      char * pBuff = pConn->m_RBuffer.GetFreeData(nFreeSize);
      if (pBuff == NULL)
      {
       if (!pConn->m_RBuffer.MarkData(0))
       {
        printf("read date faild read buff too larg (%d)  Close()!!!!!!!!!", pConn->m_RBuffer.GetBlockCount());
        fflush(stdout);
        closesocket(pConn->m_Socket);
        pConn->m_Socket = INVALID_SOCKET;
        pClient->m_bThreadRun = false;
        return 0;
       }
       pBuff = pConn->m_RBuffer.GetFreeData(nFreeSize);
       assert(pBuff != NULL);
      }
      // 收完为止
      int nRet = recv(
       pConn->m_Socket,
       pBuff,
       nFreeSize, 0);
      if(nRet <= 0)
      {
       pConn->m_NetworkMessagesQueue.ClearNode();
       pConn->AnalyseRecvBuffer();
       if((nRet == 0)||(errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN))
       {
        printf("nRet = %d, nFreeSize = %d, pBuff = %d", nRet, nFreeSize, (int)pBuff);
        printf("read date faild(%d)(%s)  Close()!!!!!!!!!", errno, strerror(errno));
        fflush(stdout);
        closesocket(pConn->m_Socket);
        pConn->m_Socket = INVALID_SOCKET;
        pClient->m_bThreadRun = false;
        return 0;
       }
       break;
      }
      else
      {
       if (!pConn->m_RBuffer.MarkData(nRet))
       {
        printf("read date faild read buff too larg (%d)  Close()!!!!!!!!!", pConn->m_RBuffer.GetBlockCount());
        fflush(stdout);
        closesocket(pConn->m_Socket);
        pConn->m_Socket = INVALID_SOCKET;
        pClient->m_bThreadRun = false;
        return 0;
       }
      }
     }
    }
    // 写事件,发送发送缓存的数据
    else if(events[i].events & EPOLLOUT)
    {
     bool flag = true;
     while(flag)
     {
      unsigned int size = 0;
      char * pData = pConn->m_SendBuf.GetData(size);
      if (pData == NULL)
      {
       break;
      }
      if (size == 0)
      {
       pConn->m_SendBuf.PopData(NULL,size);
       pData = pConn->m_SendBuf.GetData(size);
       if (size == 0)
       {
        // 所有的数据都发完了
        break;
       }
      }
      int nRet = send(pConn->m_Socket, pData, size, 0);
      if(nRet <= 0)
      {
       if(errno==EINTR||errno== EWOULDBLOCK||errno == EAGAIN)
       {
        break;
       }
       else
       {
        printf("send date faild(%d)(%s)  Close()!!!!!!!!!", errno, strerror(errno));
        fflush(stdout);
        closesocket(pConn->m_Socket);
        pConn->m_Socket = INVALID_SOCKET;
        printf("EPOLL ERROR!!!\n");
        fflush(stdout);
        pClient->m_bThreadRun = false;
        return 0;
       }
      }
      else
       pConn->m_SendBuf.PopData(NULL,nRet);
     }
    }
    else
    {
     // 错误打印
     bool bUnKnow = true;
     if (events[i].events & EPOLLPRI)
     {
      printf("client EPOLLPRI error \n");
      bUnKnow = false;
     }
     if (events[i].events & EPOLLERR)
     {
      printf("client EPOLLERR error \n");
      bUnKnow = false;
     }
     if (events[i].events & EPOLLHUP)
     {
      printf("client EPOLLHUP error \n");
      bUnKnow = false;
     }
     if (events[i].events & EPOLLET)
     {
      printf("client EPOLLET error \n");
      bUnKnow = false;
     }
     if (bUnKnow)
     {
      printf("client UNKNOW error \n");
     }
     closesocket(pConn->m_Socket);
     pConn->m_Socket = INVALID_SOCKET;
     printf("client EPOLL ERROR!!!\n");
     fflush(stdout);
     pClient->m_bThreadRun = false;
     return 0;
    }
    if (pConn != NULL)
    {
     if(pConn->m_SendBuf.Empty())
      pConn->EpollCtl(EPOLLIN|EPOLLET);
     else
      pConn->EpollCtl(EPOLLOUT|EPOLLET);
    }
   }
  }
  printf("network_client thread exit!!!!! \n");
  fflush(stdout);
  closesocket(pClient->m_Socket);
  pClient->m_Socket = INVALID_SOCKET;
  pClient->m_bThreadRun = false;
  return 0;
}
void EpollClient::Release(void)
{
 if(m_fdEpoll == 0)
  return;
 m_bQuit = true;
 // 等待线程退出
 while(m_bThreadRun)
  Sleep(10);
 // 需要关闭epoll_create的
 close(m_fdEpoll);
 shutdown(m_fdListen, 2);
 close(m_fdListen);
 m_fdEpoll = 0;
}

// 网络上其它的例子,来自: http://www.cnblogs.com/Anker/p/3263780.html

服务器代码如下所示:

复制代码
  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <string.h>
  4 #include <errno.h>
  5  6 #include <netinet/in.h>
  7 #include <sys/socket.h>
  8 #include <arpa/inet.h>
  9 #include <sys/epoll.h>
 10 #include <unistd.h>
 11 #include <sys/types.h>
 12 13#define IPADDRESS   "127.0.0.1"
 14#define PORT        8787
 15#define MAXSIZE     1024
 16#define LISTENQ     5
 17#define FDSIZE      1000
 18#define EPOLLEVENTS 100
 19 20//函数声明
 21//创建套接字并进行绑定 22staticint socket_bind(constchar* ip,int port);
 23//IO多路复用epoll 24staticvoid do_epoll(int listenfd);
 25//事件处理函数 26staticvoid 27 handle_events(int epollfd,struct epoll_event *events,int num,int listenfd,char *buf);
 28//处理接收到的连接 29staticvoid handle_accpet(int epollfd,int listenfd);
 30//读处理 31staticvoid do_read(int epollfd,int fd,char *buf);
 32//写处理 33staticvoid do_write(int epollfd,int fd,char *buf);
 34//添加事件 35staticvoid add_event(int epollfd,int fd,int state);
 36//修改事件 37staticvoid modify_event(int epollfd,int fd,int state);
 38//删除事件 39staticvoid delete_event(int epollfd,int fd,int state);
 40 41int main(int argc,char *argv[])
 42{
 43int  listenfd;
 44     listenfd = socket_bind(IPADDRESS,PORT);
 45    listen(listenfd,LISTENQ);
 46    do_epoll(listenfd);
 47return0;
 48}
 49 50staticint socket_bind(constchar* ip,int port)
 51{
 52int  listenfd;
 53struct sockaddr_in servaddr;
 54     listenfd = socket(AF_INET,SOCK_STREAM,0);
 55if (listenfd == -1)
 56    {
 57         perror("socket error:");
 58         exit(1);
 59    }
 60     bzero(&servaddr,sizeof(servaddr));
 61     servaddr.sin_family = AF_INET;
 62     inet_pton(AF_INET,ip,&servaddr.sin_addr);
 63     servaddr.sin_port = htons(port);
 64if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1)
 65    {
 66         perror("bind error: ");
 67         exit(1);
 68    }
 69return listenfd;
 70}
 71 72staticvoid do_epoll(int listenfd)
 73{
 74int epollfd;
 75struct epoll_event events[EPOLLEVENTS];
 76int ret;
 77char buf[MAXSIZE];
 78     memset(buf,0,MAXSIZE);
 79//创建一个描述符 80     epollfd = epoll_create(FDSIZE);
 81//添加监听描述符事件 82    add_event(epollfd,listenfd,EPOLLIN);
 83for ( ; ; )
 84    {
 85//获取已经准备好的描述符事件 86         ret = epoll_wait(epollfd,events,EPOLLEVENTS,-1);
 87        handle_events(epollfd,events,ret,listenfd,buf);
 88    }
 89    close(epollfd);
 90}
 91 92staticvoid 93 handle_events(int epollfd,struct epoll_event *events,int num,int listenfd,char *buf)
 94{
 95int i;
 96int fd;
 97//进行选好遍历 98for (i = 0;i < num;i++)
 99    {
100         fd = events[i].data.fd;
101//根据描述符的类型和事件类型进行处理102if ((fd == listenfd) &&(events[i].events & EPOLLIN))
103            handle_accpet(epollfd,listenfd);
104elseif (events[i].events & EPOLLIN)
105            do_read(epollfd,fd,buf);
106elseif (events[i].events & EPOLLOUT)
107            do_write(epollfd,fd,buf);
108    }
109}
110staticvoid handle_accpet(int epollfd,int listenfd)
111{
112int clifd;
113struct sockaddr_in cliaddr;
114    socklen_t  cliaddrlen;
115     clifd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddrlen);
116if (clifd == -1)
117         perror("accpet error:");
118else119    {
120         printf("accept a new client: %s:%d\n",inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);
121//添加一个客户描述符和事件122        add_event(epollfd,clifd,EPOLLIN);
123    }
124}
125126staticvoid do_read(int epollfd,int fd,char *buf)
127{
128int nread;
129     nread = read(fd,buf,MAXSIZE);
130if (nread == -1)
131    {
132         perror("read error:");
133        close(fd);
134        delete_event(epollfd,fd,EPOLLIN);
135    }
136elseif (nread == 0)
137    {
138         fprintf(stderr,"client close.\n");
139        close(fd);
140        delete_event(epollfd,fd,EPOLLIN);
141    }
142else143    {
144         printf("read message is : %s",buf);
145//修改描述符对应的事件,由读改为写146        modify_event(epollfd,fd,EPOLLOUT);
147    }
148}
149150staticvoid do_write(int epollfd,int fd,char *buf)
151{
152int nwrite;
153     nwrite = write(fd,buf,strlen(buf));
154if (nwrite == -1)
155    {
156         perror("write error:");
157        close(fd);
158        delete_event(epollfd,fd,EPOLLOUT);
159    }
160else161        modify_event(epollfd,fd,EPOLLIN);
162     memset(buf,0,MAXSIZE);
163}
164165staticvoid add_event(int epollfd,int fd,int state)
166{
167struct epoll_event ev;
168     ev.events = state;
169     ev.data.fd = fd;
170     epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&ev);
171}
172173staticvoid delete_event(int epollfd,int fd,int state)
174{
175struct epoll_event ev;
176     ev.events = state;
177     ev.data.fd = fd;
178     epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,&ev);
179}
180181staticvoid modify_event(int epollfd,int fd,int state)
182{
183struct epoll_event ev;
184     ev.events = state;
185     ev.data.fd = fd;
186     epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&ev);
187 }
复制代码

客户端也用epoll实现,控制STDIN_FILENO、STDOUT_FILENO、和sockfd三个描述符,程序如下所示:

复制代码
  1 #include <netinet/in.h>
  2 #include <sys/socket.h>
  3 #include <stdio.h>
  4 #include <string.h>
  5 #include <stdlib.h>
  6 #include <sys/epoll.h>
  7 #include <time.h>
  8 #include <unistd.h>
  9 #include <sys/types.h>
 10 #include <arpa/inet.h>
 11 12#define MAXSIZE     1024
 13#define IPADDRESS   "127.0.0.1"
 14#define SERV_PORT   8787
 15#define FDSIZE        1024
 16#define EPOLLEVENTS 20
 17 18staticvoid handle_connection(int sockfd);
 19staticvoid 20 handle_events(int epollfd,struct epoll_event *events,int num,int sockfd,char *buf);
 21staticvoid do_read(int epollfd,int fd,int sockfd,char *buf);
 22staticvoid do_read(int epollfd,int fd,int sockfd,char *buf);
 23staticvoid do_write(int epollfd,int fd,int sockfd,char *buf);
 24staticvoid add_event(int epollfd,int fd,int state);
 25staticvoid delete_event(int epollfd,int fd,int state);
 26staticvoid modify_event(int epollfd,int fd,int state);
 27 28int main(int argc,char *argv[])
 29{
 30int                 sockfd;
 31struct sockaddr_in  servaddr;
 32     sockfd = socket(AF_INET,SOCK_STREAM,0);
 33     bzero(&servaddr,sizeof(servaddr));
 34     servaddr.sin_family = AF_INET;
 35     servaddr.sin_port = htons(SERV_PORT);
 36     inet_pton(AF_INET,IPADDRESS,&servaddr.sin_addr);
 37     connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
 38//处理连接 39    handle_connection(sockfd);
 40    close(sockfd);
 41return0;
 42}
 43 44 45staticvoid handle_connection(int sockfd)
 46{
 47int epollfd;
 48struct epoll_event events[EPOLLEVENTS];
 49char buf[MAXSIZE];
 50int ret;
 51     epollfd = epoll_create(FDSIZE);
 52    add_event(epollfd,STDIN_FILENO,EPOLLIN);
 53for ( ; ; )
 54    {
 55         ret = epoll_wait(epollfd,events,EPOLLEVENTS,-1);
 56        handle_events(epollfd,events,ret,sockfd,buf);
 57    }
 58    close(epollfd);
 59}
 60 61staticvoid 62 handle_events(int epollfd,struct epoll_event *events,int num,int sockfd,char *buf)
 63{
 64int fd;
 65int i;
 66for (i = 0;i < num;i++)
 67    {
 68         fd = events[i].data.fd;
 69if (events[i].events & EPOLLIN)
 70            do_read(epollfd,fd,sockfd,buf);
 71elseif (events[i].events & EPOLLOUT)
 72            do_write(epollfd,fd,sockfd,buf);
 73    }
 74}
 75 76staticvoid do_read(int epollfd,int fd,int sockfd,char *buf)
 77{
 78int nread;
 79     nread = read(fd,buf,MAXSIZE);
 80if (nread == -1)
 81    {
 82         perror("read error:");
 83        close(fd);
 84    }
 85elseif (nread == 0)
 86    {
 87         fprintf(stderr,"server close.\n");
 88        close(fd);
 89    }
 90else 91    {
 92if (fd == STDIN_FILENO)
 93            add_event(epollfd,sockfd,EPOLLOUT);
 94else 95        {
 96            delete_event(epollfd,sockfd,EPOLLIN);
 97            add_event(epollfd,STDOUT_FILENO,EPOLLOUT);
 98        }
 99    }
100}
101102staticvoid do_write(int epollfd,int fd,int sockfd,char *buf)
103{
104int nwrite;
105     nwrite = write(fd,buf,strlen(buf));
106if (nwrite == -1)
107    {
108         perror("write error:");
109        close(fd);
110    }
111else112    {
113if (fd == STDOUT_FILENO)
114            delete_event(epollfd,fd,EPOLLOUT);
115else116            modify_event(epollfd,fd,EPOLLIN);
117    }
118     memset(buf,0,MAXSIZE);
119}
120121staticvoid add_event(int epollfd,int fd,int state)
122{
123struct epoll_event ev;
124     ev.events = state;
125     ev.data.fd = fd;
126     epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&ev);
127}
128129staticvoid delete_event(int epollfd,int fd,int state)
130{
131struct epoll_event ev;
132     ev.events = state;
133     ev.data.fd = fd;
134     epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,&ev);
135}
136137staticvoid modify_event(int epollfd,int fd,int state)
138{
139struct epoll_event ev;
140     ev.events = state;
141     ev.data.fd = fd;
142     epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&ev);
143 }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值