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的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。