gdb调试命令的使用及总结,gdb高级调试技巧_1

  gdb调试命令的使用及总结,gdb高级调试技巧

  Gd调试技巧-全职C-C博客

  epoll的精髓[转贴]引用网址:http://www..com/onlyxp/archive/2007/08/10/851222.html

  在linux的网络编程中,长期以来一直使用select来触发事件。在linux的新内核中,有一种机制可以替代它,这就是epoll。

  与select相比,epoll最大的优点是不会随着监听FD数量的增加而降低效率。因为在内核的select实现中,是通过轮询来处理的,FD轮询的越多,自然花费的时间就越多。并且,在linux/posix_types.h头文件中,有这样一条语句:

  #define __FD_SETSIZE 1024

  Select表示最多可以同时监听1024个FD。当然,这个数字可以通过修改头文件和重新编译内核来扩大,但这似乎并不能解决问题。

  epoll的界面非常简单。总共有三个功能:

  1.int epoll _ create(int size);

  创建一个epoll的句柄,大小用来告诉内核有多少个监听器。这个参数不同于select()中的第一个参数,它给出了fd 1的值,用于最大监听。应当注意,当创建epoll句柄时,它将占用一个fd值。如果在linux下看/proc/process id/fd/就能看到这个fd。因此,在使用epoll之后,必须调用close()来关闭它。否则,fd可能会耗尽。

  2.int epoll_ctl(int epfd,int op,int fd,struct epoll _ event * event);

  epoll的事件注册函数,与select()不同的是,它告诉内核在监听事件时要监听什么类型的事件,而是先在这里注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示操作,由三个宏表示:

  EPOLL_CTL_ADD:在epfd中注册一个新的FD;

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

  Epll _ CTL _ del:从epfd中删除FD;

  第三个参数是需要监控的fd,第四个参数是告诉内核需要监控什么。结构epoll_event的结构如下:

  结构epoll_event {

  __uint32_t事件;/* Epoll事件*/

  epoll_data_t数据;/*用户数据变量*/

  };

  事件可以是以下宏的集合:

  EPOLLIN:表示可以读取对应的文件描述符(包括对面套接字的正常关闭);

  EPOLLOUT:表示可以写入相应的文件描述符;

  EPOLLPRI:表示对应的文件描述符有紧急数据要读取(这里应该表示带外数据来了);

  EPOLLERR:表示相应的文件描述符有错误;

  EPOLLHUP:表示相应的文件描述符被挂起;

  EPOLLET:将EPOLL设置为边沿触发模式,相对于电平触发模式。

  EPOLLONESHOT:只听一次事件。在这个事件之后,如果您仍然需要继续监听这个套接字,您需要再次将这个套接字添加到EPOLL队列中。

  3.int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int time out);

  等待事件生成,类似于select()调用。参数events用于从内核获取事件集合。maxevents告诉内核这个事件有多大。创建epoll_create()时,此maxevents的值不能大于大小。参数timeout为超时时间(毫秒,0会立即返回,-1不确定,也有说永久封锁的)。该函数返回需要处理的事件数量。如果它返回0,则表明它已经超时。

  -

  从man手册中,ET和LT的具体描述如下

  EPOLL事件有两种模式:

  边沿触发(ET)

  触发级别(LT)

  如果有这样一个例子:

  1.我们添加了一个文件句柄(RFD ),用于从管道读取数据到epoll描述符。

  2.此时,2KB数据从管道的另一端写入。

  3.调用epoll_wait(2),它将返回RFD,表明它已准备好进行读取操作。

  4.然后我们读取1KB的数据。

  5.调用epoll_wait(2).

  边沿触发工作模式:

  如果我们在步骤1中向epoll描述符添加RFD时使用了EPOLLET标志,那么在步骤5中调用epoll_wait(2)之后,它可能会挂起,因为剩余的数据仍然存在于文件的输入缓冲区中,并且数据发送方仍然在等待发送数据的反馈消息。只有当被监控的文件句柄上发生事件时,ET工作模式才会报告该事件。因此,在步骤5,调用者可以放弃等待仍然存在于文件输入缓冲区中的剩余数据。在上面的例子中,RFD句柄上将生成一个事件,因为在步骤2中执行了一个写操作,然后该事件将在步骤3中被销毁。因为步骤4中的read操作没有读取空文件的输入缓冲区中的数据,所以不确定我们在步骤5中调用epoll_wait(2)后是否会挂起。当epoll在ET模式下工作时,必须使用非阻塞windows套接字,以避免由于一个文件句柄的阻塞读/写操作而导致处理多个文件描述符的任务不足。最好按照以下方式调用ET模式的epoll接口,避免后面会介绍的可能缺陷。

  I基于非阻塞文件句柄

  Ii只有当read(2)或write(2)返回EAGAIN时,才需要挂起并等待。但是,这并不意味着每次读取()都需要循环读取,直到读取产生EAGAIN才认为事件处理完成。当read()返回的读取数据长度小于请求的数据长度时,可以确定此时缓冲区中没有数据,也可以认为事件处理完成。

  电平触发工作模式

  相反,当在LT模式下调用epoll接口时,它相当于一个相对快速的poll(2),无论后面的数据是否被使用,它们都具有相同的功能。因为即使您在ET模式下使用epoll,当您从多个块接收数据时,您仍然会生成多个事件。调用者可以设置EPOLLONESHOT标志,在epoll_wait(2)收到事件后,与事件相关的文件句柄将从epoll描述符中被禁止。因此,当EPOLLONESHOT被设置时,使用带有EPOLL_CTL_MOD标志的epoll_ctl(2)来处理文件句柄就成为调用者必须要做的事情。

  然后解释ET,LT:

  LT(level triggered)是默认的工作模式,它支持阻塞和非阻塞套接字。这样,内核告诉你一个文件描述符是否准备好了,然后你就可以IO ready FD了。如果你什么都不做,内核会继续通知你,所以这种模式下编程出错的可能性较小。传统的select/poll就是这种模式的代表。

  ET(edge-triggered)是一种高速工作模式,只支持无阻塞套接字。在这种模式下,当描述符从never ready变为ready时,内核会通过epoll告诉您。然后,它将假设您知道文件描述符已准备好,并且您不会再为该文件描述符发送任何就绪通知,直到您做了一些导致该文件描述符不再就绪的事情(例如,当您发送、接收或接收请求,或者发送和接收少于特定量的数据时,它会导致EWOULDBLOCK错误)。但是,请注意,如果fd没有被IO操作(从而导致它再次变为未就绪),内核不会只发送一次。但是在TCP协议中,ET模式的加速效用仍然需要更多的基准确认。

  在很多测试中,我们可以看到,如果空闲连接或死连接不多,epoll的效率不会比select/poll高很多,但是当我们遇到大量空闲连接时(比如WAN环境下有大量慢速连接),就会发现epoll的效率比select/poll高很多。(未经测试)

  此外,当使用epoll的ET模型工作时,当产生EPOLLIN事件时,

  在读取数据时需要考虑的是,如果recv()返回的大小等于请求的大小,那么很可能缓冲区中还有未读取的数据,这也意味着事件还没有被处理,所以需要再次读取:

  while(rs)

  {

  buf len=recv(active events[I]. data . FD,buf,sizeof(buf),0);

  if(缓冲区0)

  {

  //因为是非阻塞模式,当errno为EAGAIN时,表示当前缓冲区没有数据可以读取。

  //这里,算是这个事件处理过的地方。

  if(errno==EAGAIN)

  打破;

  其他

  返回;

  }

  else if(buflen==0)

  {

  //这意味着对端的套接字已经正常关闭。

  }

  if(buflen==sizeof(buf)

  RS=1;//需要重新读取

  其他

  RS=0;

  }

  还有,如果发送方的流量大于接收方的流量(意味着epoll所在的程序读取速度比转发的socket快),由于是非阻塞socket,send()函数返回,但是实际缓冲区中的数据并没有真正发送给接收方。当缓冲区已满时,这种持续的读取和发送将导致EAGAIN错误(请参考man send)。同时,忽略这个请求发送的数据。因此,需要一个封装socket_send()的函数来处理这种情况。这个函数会尽力写数据,然后返回。返回-1表示有错误。在socket_send()中,当写缓冲区已满时(send()返回-1,errno为EAGAIN),它将等待并重试。这种方法并不完美,理论上可能会在socket_send()中阻塞很长时间,但是没有更好的办法。

  ssize_t socket_send(int sockfd,const char* buffer,size_t buflen)

  {

  ssize _ t tmp

  size _ t total=buflen

  const char * p=buffer

  while(1)

  {

  tmp=send(sockfd,p,total,0);

  如果(tmp 0)

  {

  send收到信号后,可以继续写,但是这里返回-1。

  if(errno==EINTR)

  return-1;

  //当套接字非阻塞时,如果返回此错误,则写缓冲区队列已满,

  //在这里等一下,再试一次。

  if(errno==EAGAIN)

  {

  us LEEP(1000);

  继续;

  }

  return-1;

  }

  if((size_t)tmp==total)

  返回buflen

  total-=tmp;

  p=tmp

  }

  返回tmp

  }

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: