libevent核心思想:epoll反应堆模型

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_36750623/article/details/83547803
一.Linux下的I/O复用与epoll详解

与select/poll不同的是,epoll采用回调函数机制,epoll只关心“活跃”的连接,无需遍历全部的文件描述符

一.为什么引出epoll?
1.select的缺点
1.select所用到的FD_SET是有限的

/linux/posix_types.h:
#define __FD_SETSIZE         1024
1
2
3.select/poll都要进行不断的将fd集合在内核空间和用户空间的来回拷贝
2.内核中实现 select是用轮询方法,即每次检测都会遍历所有FD_SET中的句柄,显然,select函数执行时间与FD_SET中的句柄个数有一个比例关系,即 select要检测的句柄数越多就会越费时

2.epoll高效的奥秘(实现原理)
三大关键因素:mmap/红黑树/链表
(1) epoll_create:epoll是通过内核与用户空间mmap同一块内存映射区实现的。mmap将用户空间的一块地址和内核空间的一块地址映射到物理内存地址,使得这块物理内存对内核和用户均可见,减少用户态和内核态之间的数据交换。
(2) epoll_ctl:红黑树将存储epoll所监听的套接字,当epoll_ctl添加/删除一个套接字时,实际上是在红黑树上进行节点的插入/删除。
注意:当使用epoll_ctl函数将事件添加到红黑树上后,会完成更为关键的异步(那就是该事件都会与相应的设备驱动程序建立回调关系)
(3) epoll_wait:一旦有事件发生,就会调用注册的回调函数ep_poll_callback,该回调函数的作用是这个事件添加到就绪双向链表rdlist中。调用epoll_wait时,epoll_wait只需要检查双向链表rdlist中是否有存在注册的事件
epoll_wait的工作流程:
1.epoll_wait调用ep_poll,当rdlist为空(无就绪fd)时挂起当前进程,直到rdlist不空时进程才被唤醒
2.当有就绪fd发生时,将调用ep_poll_callback,它将相应fd对应epitem加入rdlist,导致rdlist不为空,进程被唤醒,epoll_wait将返回
3.ep_events_transfer函数将双向链表rdlist中的epitem拷贝到txlist中,并将双向链表rdlist清空
4.ep_send_event函数(很关键),它扫描txlist中的每个epitem,调用其关联fd对应的poll方法。此时对poll的调用仅仅是取得fd上较新的events(防止之前events被更新),之后将取得的events和相应的fd发送到用户空间(封装在struct epoll_event,从epoll_wait返回)。==之后如果这个epitem对应的fd是LT模式监听且取得的events是用户所关心的,则将其重新加入回rdlist(图中蓝线),否则(ET模式)不在加入rdlist。

二.epoll函数API
int epoll_create(int size); //哈希表
int epoll_create1(int flags); //红黑树

int epoll_ctl ( int epfd, int op, int fd, struct epoll_event *event );

参数
epfd:epoll_create的返回值
fd:要操作的文件描述符
op:操作类型 EPOLL_CTL_ADD/EPOLL_CTL_MOD/EPOLL_CTL_DEL
event:指定事件,它是epoll_event结构指针类型
其中,epoll_event—>每一个文件描述符都有一个对应的epoll_event结构,该结构为 :

struct epoll_event{
     __unit32_t events;    // epoll事件类型:EPOLLET / EPOLLONESHOT
     epoll_data_t data;    // 存储用户数据
};
其中,epoll_data_t定义:
    typedef union epoll_data{
        void* ptr;  //自定义的结构体(最常用)
        int fd;     //指定事件所从属的目标文件描述符 
        uint32_t u32;
        uint64_t u64;
    } epoll_data_t;
1
2
3
4
5
6
7
8
9
10
11
int epoll_wait ( int epfd, struct epoll_event* events, int maxevents, int timeout );

返回值:成功时返回就绪的文件描述符的个数,失败时返回-1并设置errno
参数
timeout:指定epoll的超时时间,单位是毫秒。
maxevents:指定最多监听多少个事件
events:传出参数,是一个数组,epoll_wait函数返回后,所有就绪的事件
三.使用epoll接口的一般操作流程为:
(1)使用epoll_create()创建一个epoll对象,该对象与epfd关联,后续操作使用epfd来使用这个epoll对象,这个epoll对象才是红黑树,epfd作为描述符只是能关联而已。
(2)调用epoll_ctl()向epoll对象中进行增加、删除等操作。
(3)调用epoll_wait()可以阻塞(或非阻塞或定时) 返回待处理的事件集合。
(3)处理事件。

/*
 *  -[  一般epoll接口使用描述01  ]-
 */
int main(void)
{
 /* 
  *   此处省略网络编程常用初始化方式(从申请到最后listen)
  *   并且部分的错误处理省略,我会在后面放上所有的源码,这里只放重要步骤
  *   部分初始化也没写
  */ 
  // [1] 创建一个epoll对象
  ep_fd = epoll_create(OPEN_MAX);       /* 创建epoll模型,ep_fd指向红黑树根节点 */
  listen_ep_event.events  = EPOLLIN;    /* 指定监听读事件 注意:默认为水平触发LT */
  listen_ep_event.data.fd = listen_fd;  /* 注意:一般的epoll在这里放fd */ 
  // [2] 将listen_fd和对应的结构体设置到树上
  epoll_ctl(ep_fd, EPOLL_CTL_ADD, listen_fd, &listen_ep_event);

  while(1) { 
      // [3] 为server阻塞(默认)监听事件,ep_event是数组,装满足条件后的所有事件结构体
      n_ready = epoll_wait(ep_fd, ep_event, OPEN_MAX, -1); 
      for(i=0; i<n_ready; i++) {
         temp_fd = ep_event[i].data.fd;

         if(ep_event[i].events & EPOLLIN){
            if(temp_fd == listen_fd) {  //说明有新连接到来
               connect_fd = accept(listen_fd, (struct sockaddr *)&client_socket_addr, &client_socket_len);
               // 给即将上树的结构体初始化
               temp_ep_event.events  = EPOLLIN;
               temp_ep_event.data.fd = connect_fd;
               // 上树
               epoll_ctl(ep_fd, EPOLL_CTL_ADD, connect_fd, &temp_ep_event);
             }
             else {                      //cfd有数据到来
               n_data = read(temp_fd , buf, sizeof(buf));
               if(n_data == 0)  {        //客户端关闭
                   epoll_ctl(ep_fd, EPOLL_CTL_DEL, temp_fd, NULL) //下树
                   close(temp_fd);
                }
                else if(n_data < 0) {}

                do {
                   //处理数据
                 }while( (n_data = read(temp_fd , buf, sizeof(buf))) >0 ) ;
             }
          }
         else if(ep_event[i].events & EPOLLOUT){
                //处理写事件
         }
         else if(ep_event[i].events & EPOLLERR) {
                //处理异常事件
         }
      }      
   }
  close(listen_fd);
  close(ep_fd);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
四. 水平触发LT / 边缘触发ET
evt.events  = EPOLLIN | EPOLLET;   /*边沿触发 */
evt.events  = EPOLLIN | EPOLLIN;   /*水平触发 */
1
2
1.ET和LT在本质上的区别
[1] 水平触发LT

从图中可以看到:只要有数据,LT方式epoll_wait就会返回
1.如果用户在监听epoll事件,当内核有事件的时候,会拷贝给用户态事件,但是如果用户只处理了一次,那么剩下没有处理的会在下一次epoll_wait再次返回该事件。
2.这样如果用户永远不处理这个事件,就导致每次都会有该事件从内核到用户的拷贝,耗费性能,但是水平触发相对安全,最起码事件不会丢掉,除非用户处理完毕。

[2]边缘触发ET

从图中可以看到:尽管还有数据未被处理,但是ET方式epoll_wait也不会返回
1.边缘触发,相对跟水平触发相反,当内核有事件到达, 只会通知用户一次,至于用户处理还是不处理,以后将不会再通知。
2.这样减少了拷贝过程,增加了性能,但是相对来说,如果用户马虎忘记处理,将会产生事件丢的情况。

2.ET和LT的区别
if ET:当且仅当有新到来的数据,epoll_wait才返回
if LT:只要有数据,epoll_wait就返回


3.那么,为什么说边沿触发(ET) 的效率更高呢?*
(1) 边沿触发只在数据到来的一刻才触发,很多时候服务器在接受大量数据时会先接受数据头部(水平触发在此触发第一次,边沿触发第一次)。
(2) 接着服务器通过解析头部决定要不要接这个数据。此时,如果不接受数据,水平触发需要手动清除,而边沿触发可以将清除工作交给一个定时的清除程序去做,自己立刻返回。
(3) 如果接受,两种方式都可以用while接收完整数据。

4.边缘触发ET的使用技巧:epoll + 非阻塞fd+ET
举例说明:Client向Server一次性发送10个字节的数据;服务器一次接受5个字节的数据,下面Server使用两种方式去读取数据:
方式1:阻塞+LT触发模式
代码分析:读取10字节的数据:(1)先执行step1的epoll_wait,再执行step2的read读取5个字节;(2)再执行step1的epoll_wait,再执行step2的read读取5个字节
结论:读取10个字节,需要调用2次epoll_wait

while (1){
    epoll_wait(epfd, resevent, maxi+1, -1);   //step1
    if (resevent[0].data.fd == connfd){
        len = read(connfd, buf, 5);       //step2
        write(STDOUT_FILEND, buf, len);
    }
}
1
2
3
4
5
6
7
方式2:非阻塞+ET触发模式+while(read)
代码分析:(1)先执行step1的epoll_wait,再执行step2的read2读取5个字节(2)继续调用step2的read读取5个字节
结论:读取10个字节,只需要调用1次epoll_wait

先用fcntl将连接的套接字connfd设置为非阻塞O_NOBLOCK
while (1){
    epoll_wait(epfd, resevent, maxi+1, -1); 
    if (resevent[0].data.fd == connfd){
        while ((len = read(connfd, buf, 5))){//非阻塞读,有数据就轮询读,直到读完缓冲区中所有的数据
            write(STDOUT_FILEND, buf, len);
        }
    }
}
1
2
3
4
5
6
7
8
9
总结:采用[非阻塞fd+边缘触发ET+while循环读]的方式,比采用[阻塞fd+水平触发LT]的方式调用epoll_wait的次数大大减少!效率更高

二. libevent核心思想:epoll反应堆模型
一.epoll的struct epoll_event结构体
自定义结构体

struct epoll_event{
     __unit32_t events;    // epoll事件类型:EPOLLET / EPOLLONESHOT
     epoll_data_t data;    // 存储用户数据
};
其中,epoll_data_t定义:
    typedef union epoll_data{
        void* ptr;  //自定义的结构体(最常用)
        int fd;     // 一般
        uint32_t u32;
        uint64_t u64;
    } epoll_data_t;
1
2
3
4
5
6
7
8
9
10
11
还记得每一个在红黑树上的文件描述符所对应的结构体epoll_event吗?
1.一般在epoll_event结构体中的联合体data上传入的是文件描述符fd本身
2.但是在epoll模型中,传入联合体的是一个自定义结构体指针,该结构体的基本结构至少包括:

struct my_events {  
    int        m_fd;                             //监听的文件描述符
    void       *m_arg;                           //泛型参数
    void       (*call_back)(void *arg);          //回调函数
    /*
     *  你可以在此处封装更多的数据内容
     *  例如用户缓冲区、节点状态、节点上树时间等等
     */
};
/*
 * 注意:用户需要自行开辟空间存放my_events类型的数组,并在每次上树前用epoll_data_t里的  
 *      ptr指向一个my_events元素。
 */
1
2
3
4
5
6
7
8
9
10
11
12
13
根据该模型,在程序中可以让所有的事件都拥有自己的回调函数,只需要使用ptr传入即可。示例代码:

while(1) {
   int n_ready = epoll_wait(ep_fd, events, MAX_EVENTS, 1000); /* 监听红黑树, 1秒没事件满足则返回0 */ 
   if (n_ready > 0) {
      for (i=0; i<n_ready; i++) 
         events[i].data.ptr->call_back(/* void *arg */); //调用回调函数
    }
    else
    /*  
     * 这里可以做很多很多其他的工作,例如定时清除没读完的不要的数据
     *     也可以做点和数据库有关的设置
     *     玩大点你在这里搞搞分布式的代码也可以
     */
}
1
2
3
4
5
6
7
8
9
10
11
12
13
二.epoll反应堆模型
1.传统的epoll服务器模型
监听可读事件(ET) ⇒ 数据到来 ⇒ 触发读事件 ⇒
epoll_wait()返回 ⇒ read消息 ⇒ write回射信息 ⇒ 继续epoll_wait()
⇒ 直到程序停止前都是这么循环

2.epoll反应堆服务器模型
监听可读事件(ET) ⇒ 数据到来 ⇒ 触发读事件 ⇒
epoll_wait()返回 ⇒
read完数据; 节点下树; 设置监听写事件和对应写回调函数; 节点上树(可读事件回调函数内)

⇒ 监听可写事件(ET) ⇒ 对方可读 ⇒ 触发事件 ⇒
epoll_wait()返回 ⇒
write数据; 节点下树; 设置监听读事件和对应可读回调函数; 节点上树(可写事件回调函数内)
⇒ 直到程序停止前一直这么交替循环

3.为什么epoll反应堆模型要这样设计?
①如此频繁的在红黑树上增添/删除节点是不是浪费CPU资源?
答:epoll反应堆模型中,对于同一个socket而言,完成收发信息至少占用两个树上的位置。而传统的epoll服务器模型中,完成收发信息只需要一个树上位置。任何一种设计方式都会浪费CPU资源,关键看浪费的值不值,此处的耗费能否换来更大的收益是决定是否浪费的标准。

②为什么要可读以后设置可写?然后一直交替?
服务器向客户端write数据,并不一定能write成功,原因有二
(1) 滑动窗口机制
服务器向客户端write数据,假设刚好此时客户端的接收滑动窗口满,将导致当前服务器将阻塞在send函数处,导致服务器程序阻塞。
解决方案:设置可写事件,当客户端的接收缓冲区有空闲时,将导致该socket可写,在可写回调函数中调用send函数
(2) SIGPIPE信号
客户端send完数据后,突然由于异常停止,这将导致一个FIN发送给服务器。如果服务器不设置可写事件监听,那么服务器在read完数据后,直接向没有读端的套接字中写入数据,TCP协议栈将会给服务器发送RST分节+SIGPIPE信号,导致服务器进程终止。

三.epoll反应堆模型代码
Server
#include <stdio.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

#define MAX_EVENTS  1024
#define SERVER_PORT 8888

struct my_events {
    int        m_fd;                                         //监听的文件描述符
    int        m_event;                                      //监听的事件  
    void       *m_arg;                                       //泛型参数
    void       (*call_back)(int fd, int event, void *arg);   //回调函数
    char       m_buf[BUFSIZ];
    int        m_buf_len;
    int        m_status;                                     //是否在红黑树上, 1->在, 0->不在
    time_t     m_lasttime;                                   //最后放入红黑树的时间
};

int                    ep_fd;                                //红黑树根
struct my_events       ep_events[MAX_EVENTS];   

/*初始化监听socket*/
void initlistensocket(int ep_fd, unsigned short port);
/*将结构体成员变量初始化*/
void eventset(struct my_events *my_ev, int fd, void (*call_back)(int fd, int event, void *arg), void *event_arg);
/*向红黑树添加 文件描述符和对应的结构体*/
void eventadd(int ep_fd, int event, struct my_events *my_ev);
/*从红黑树上删除 文件描述符和对应的结构体*/
void eventdel(int ep_fd, struct my_events *ev);
/*发送数据*/
void senddata(int client_fd, int event, void *arg);
/*接收数据*/
void recvdata(int client_fd, int event, void *arg);
/*回调函数: 接收连接*/
void acceptconnect(int listen_fd, int event, void *arg);


int main(void)
{
   unsigned short port = SERVER_PORT;

   ep_fd = epoll_create(MAX_EVENTS);                         //创建红黑树,返回给全局变量ep_fd;
   if (ep_fd <= 0)
      printf("create ep_fd in %s error: %s \n", __func__, strerror(errno));
  
   /*初始化监听socket*/
   initlistensocket(ep_fd, port);

   int checkpos = 0;
   int i;
   struct epoll_event events[MAX_EVENTS]; //epoll_wait的传出参数(数组:保存就绪事件的文件描述符)
   while (1)
   {
      /*超时验证,每次测试100个连接,60s内没有和服务器通信则关闭客户端连接*/
      long now = time(NULL); //当前时间
      for (i=0; i<100; i++,checkpos++) //一次循环检测100个,使用checkpos控制检测对象
      {
         if (checkpos == MAX_EVENTS-1)
             checkpos = 0;
         if (ep_events[i].m_status != 1) //不在红黑树上
             continue;

         long spell_time = now - ep_events[i].m_lasttime; //客户端不活跃的时间
         if (spell_time >= 60) //如果时间超过60s
         {
             printf("[fd= %d] timeout \n", ep_events[i].m_fd);  
             close(ep_events[i].m_fd); //关闭与客户端连接
             eventdel(ep_fd, &ep_events[i]); //将客户端从红黑树摘下
         }     
      }
      
      /*监听红黑树,将满足条件的文件描述符加至ep_events数组*/ 
      int n_ready = epoll_wait(ep_fd, events, MAX_EVENTS, 1000); //1秒没事件满足则返回0
      if (n_ready < 0)
      {
          printf("epoll_wait error, exit \n");
          break;
      }

      for (i=0; i<n_ready; i++)
      {
           //将传出参数events[i].data的ptr赋值给"自定义结构体ev指针"
           struct my_events *ev = (struct my_events *)(events[i].data.ptr); 
           if ((events[i].events & EPOLLIN) && (ev->m_event & EPOLLIN))  //读就绪事件
               ev->call_back(ev->m_fd, events[i].events, ev->m_arg);
           if ((events[i].events & EPOLLOUT) && (ev->m_event & EPOLLOUT)) //写就绪事件
               ev->call_back(ev->m_fd, events[i].events, ev->m_arg);
      }
   }
   return 0;
}     

/*初始化监听socket*/
void initlistensocket(int ep_fd, unsigned short port)
{
    int                  listen_fd;
    struct sockaddr_in   listen_socket_addr;

    printf("\n initlistensocket() \n");  

    int opt = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//端口复用

    /*申请一个socket*/
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    fcntl(listen_fd, F_SETFL, O_NONBLOCK); //将socket设置为非阻塞模式,好处自行百度
    /*绑定前初始化*/
    bzero(&listen_socket_addr, sizeof(listen_socket_addr));
    listen_socket_addr.sin_family      = AF_INET;
    listen_socket_addr.sin_port        = htons(port);
    listen_socket_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    /*绑定*/
    bind(listen_fd, (struct sockaddr *)&listen_socket_addr, sizeof(listen_socket_addr));
    /*设置监听上限*/
    listen(listen_fd, 128);

    /*将listen_fd初始化*/
    eventset(&ep_events[MAX_EVENTS-1], listen_fd, acceptconnect, &ep_events[MAX_EVENTS-1]);    
    /*将listen_fd挂上红黑树*/
    eventadd(ep_fd, EPOLLIN, &ep_events[MAX_EVENTS-1]);

    return ;
}

/*将结构体成员变量初始化*/
void eventset(struct my_events *my_ev, int fd, void (*call_back)(int, int, void *), void *event_arg)
{
   my_ev->m_fd       = fd;
   my_ev->m_event    = 0; //开始不知道关注的是什么事件,因此设置为0
   my_ev->m_arg      = event_arg;
   my_ev->call_back  = call_back;
   
   my_ev->m_status   = 0; //0表示没有在红黑树上
   my_ev->m_lasttime = time(NULL);//调用eventset函数的绝对时间
   return ;
}

/*向红黑树添加文件描述符和对应的结构体*/
void eventadd(int ep_fd, int event, struct my_events *my_ev)
{
  int op;
  struct epoll_event epv;
  epv.data.ptr = my_ev;
  epv.events   = my_ev->m_event = event; //EPOLLIN或EPOLLOUT

  if (my_ev->m_status == 0)
  {
    op = EPOLL_CTL_ADD;
  }
  else
  {
    printf("\n add error: already on tree \n");
    return ;
  }
  
  if (epoll_ctl(ep_fd, op, my_ev->m_fd, &epv) < 0) //实际添加/修改
  {
     printf("\n event add/mod false [fd= %d] [events= %d] \n", my_ev->m_fd, my_ev->m_event);
  }
  else
  {
     my_ev->m_status = 1;
     printf("\n event add ok [fd= %d] [events= %d] \n", my_ev->m_fd, my_ev->m_event);
  }

  return ;
}
/*从红黑树上删除 文件描述符和对应的结构体*/
void eventdel(int ep_fd, struct my_events *ev)
{
  if(ev->m_status != 1)
     return ;

  epoll_ctl(ep_fd, EPOLL_CTL_DEL, ev->m_fd, NULL);
  ev->m_status = 0;
 
  return ;
}

/*回调函数: 接收连接*/
void acceptconnect(int listen_fd, int event, void *arg)
{
  int                 connect_fd;
  int                 i;
  int                 flag=0;
  char                str[BUFSIZ];
  struct sockaddr_in  connect_socket_addr;
  socklen_t           connect_socket_len;

  if ( (connect_fd=accept(listen_fd, (struct sockaddr *)&connect_socket_addr, &connect_socket_len)) <0 )
  {
     if (errno != EAGAIN && errno != EINTR)
        {/*暂时不处理*/}
     printf("\n %s: accept, %s \n", __func__, strerror(errno));
     return ;
  }
  
  do
  {
    for(i=0; i<MAX_EVENTS; i++) //从全局数组ep_events中找一个空闲位置i(类似于select中找值为-1的位置)
        if(ep_events[i].m_status == 0) 
           break;
    if(i >= MAX_EVENTS)
     {
        printf("\n %s : max connect [%d] \n", __func__, MAX_EVENTS);
        break;
     }      
    
    /* 设置非阻塞 */
    if((flag = fcntl(connect_fd, F_SETFL, O_NONBLOCK)) <0)
    {
       printf("\n %s: fcntl nonblocking false, %s \n", __func__, strerror(errno));
       break;
    }

    eventset(&ep_events[i], connect_fd, recvdata, &ep_events[i]);
    eventadd(ep_fd, EPOLLIN, &ep_events[i]);

  }while(0);

   printf("\n new connection [%s:%d]  [time:%ld]  [pos:%d] \n", inet_ntop(AF_INET, &connect_socket_addr.sin_addr, str, sizeof(str)), 
                                ntohs(connect_socket_addr.sin_port), ep_events[i].m_lasttime, i);
   return ;
}

/*接收数据*/
void recvdata(int client_fd, int event, void *arg)
{
  int              len;
  struct my_events *ev = (struct my_events *)arg;

  len = recv(client_fd, ev->m_buf, sizeof(ev->m_buf), 0);
  //1.将ep_fd从红黑树拿下
  eventdel(ep_fd, ev);                                      

  if (len >0)
  {
      ev->m_buf_len      = len;
      ev->m_buf[len] = '\0'; //手动添加结束标记
      printf("\n Client[%d]: %s \n", client_fd, ev->m_buf);

      eventset(ev, client_fd, senddata, ev); //2.设置client_fd对应的回调函数为senddata
      eventadd(ep_fd, EPOLLOUT, ev); //3.将ep_fd放上红黑树,监听写事件EPOLLOUT
  }
  else if (len == 0)
  {
      close(ev->m_fd);
      eventdel(ep_fd, ev);
      printf("\n [Client:%d] disconnection \n", ev->m_fd);
  }
  else
  {
      close(ev->m_fd);
      eventdel(ep_fd, ev);
      printf("\n error: [Client:%d] disconnection\n", ev->m_fd);
  }
 
  return ;
}

/*发送数据*/
void senddata(int client_fd, int event, void *arg)
{
  int              len; 
  struct my_events *ev = (struct my_events *)arg;
 
  len = send(client_fd, ev->m_buf, ev->m_buf_len, 0);   //回写

  if (len > 0)
  {
     printf("\n send[fd=%d], [len=%d] %s \n", client_fd, len, ev->m_buf);
     eventdel(ep_fd, ev);  //1.将ep_fd从红黑树拿下
     eventset(ev, client_fd, recvdata, ev); //2.设置client_fd对应的回调函数为recvdata
     eventadd(ep_fd, EPOLLIN, ev); //3.将ep_fd放上红黑树,监听读事件EPOLLIN 
  }
  else
  {
     close(ev->m_fd);
     eventdel(ep_fd, ev);
     printf("\n send[fd=%d] error \n", client_fd);
  }
  return ;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
Client
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>


#define MAX_LINE (1024)
#define SERVER_PORT (7778)

void setnoblocking(int fd)
{
  int opts=0;
  opts=fcntl(fd,F_GETFL);
  opts=opts|O_NONBLOCK;
  fcntl(fd,F_SETFL);
}

int main(int argc,char* argv[])
{
  int sockfd;
  char recvbuf[MAX_LINE+1]={0};

  struct sockaddr_in server_addr;

  /*
  if(argc!=2)
  {
    fprintf(stderr,"usage ./cli <SERVER_IP> \n");
    exit(0);
  }
  */

  if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0)
  {
    fprintf(stderr,"socket error");
    exit(0);
  }

  bzero(&server_addr,sizeof(server_addr));
  server_addr.sin_family=AF_INET;
  server_addr.sin_port=htons(SERVER_PORT);
  server_addr.sin_addr.s_addr=inet_addr("127.0.0.1");  
  /*
  if(inet_pton(AF_INET,argv[1],&server_addr.sin_addr)<=0)
  {
    fprintf(stderr,"inet_pton error for %s",argv[1]);
    exit(0);
  }
  */
  if(connect(sockfd,(struct sockaddr*)&server_addr,sizeof(server_addr))<0)
  {
    perror("connect");
    fprintf(stderr,"connect error\n");
    exit(0);
  }
  setnoblocking(sockfd);

  char input[100];
  int n=0;
  int count=0;

  while(fgets(input,100,stdin)!=NULL)
  {
    printf("[send]:%s\n",input);
    n=send(sockfd,input,strlen(input),0);
    if(n<0)
    {
      perror("send");
    }
    n=0;
    count=0;

    while(1)
    {
      n=read(sockfd,recvbuf+count,MAX_LINE);
      if(n==MAX_LINE)
      {
        count+=n;
        continue;
      }
      else if(n<0)
      {
        perror("recv");
        break;
      }
      else 
      {
        count+=n;
        recvbuf[count]='\0';
        printf("[recv]:%s\n",recvbuf);
        break;
      }
    }
  }

  return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102

————————————————
版权声明:本文为CSDN博主「guojawee」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_36750623/article/details/83547803

转载于:https://my.oschina.net/u/4000302/blog/3100295

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值