Linux下各种网络编程模型

[精华] Linux下各类TCP网络服务器的实现源代码 
--------------------------------------------------------------------------------

http://www.chinaunix.net
作者:zhoulifa  发表于:2006-07-11 22:53:24 
【发表评论】【查看原文】【Linux讨论区】【关闭】  
大家都知道各类网络服务器程序的编写步骤,并且都知道网络服务器就两大类:循环服务和并发服务。这里附上源代码来个小结吧。 
首先,循环网络服务器编程实现的步骤是这样的: 
  
这种服务器模型是典型循环服务,如果不加上多进程/线程技术,此种服务吞吐量有限,大家都可以看到,如果前一个连接服务数据没有收发完毕后面的连接没办法处理。所以一般有多进程技术,对一个新连接启用一个新进程去处理,而监听socket继续监听。 
/************关于本文档******************************************** 
*filename: Linux下各类TCP网络服务器的实现源代码 
*purpose: 记录Linux下各类tcp服务程序源代码 
*wrote by: zhoulifa(
zhoulifa@163.com
) 周立发(
http://zhoulifa.bokee.com

Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言 
*date time:2006-07-04 22:00:00 
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途 
* 但请遵循GPL 
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力 
*********************************************************************/ 
一个循环TCP服务源代码(因为用fork进行多进程服务了,所以这种服务现实中也有用)如下:


/*----------------------源代码开始--------------------------------------------*/ 
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
/********************************************************************* 
*filename: cycletcpserver.c 
*purpose: 循环tcp服务端程序 
*tidied by: zhoulifa(
[email=zhoulifa@163.com]zhoulifa@163.com[/email]
) 周立发(
[url=http://zhoulifa.bokee.com]http://zhoulifa.bokee.com[/url]

Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言 
*date time:2006-07-04 22:00:00 
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途 
* 但请遵循GPL 
*Thanks to: Google.com 
*********************************************************************/ 
int main(int argc, char ** argv) 

    int sockfd,new_fd; /* 监听socket: sock_fd,数据传输socket: new_fd */ 
    struct sockaddr_in my_addr; /* 本机地址信息 */ 
    struct sockaddr_in their_addr; /* 客户地址信息 */ 
    unsigned int sin_size, myport, lisnum; 
    if(argv[1])  myport = atoi(argv[1]); 
    else myport = 7838; 
    if(argv[2])  lisnum = atoi(argv[2]); 
    else lisnum = 2; 
    if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) { 
        perror("socket"); 
        exit(1); 
    } 
    my_addr.sin_family=PF_INET; 
    my_addr.sin_port=htons(myport); 
    my_addr.sin_addr.s_addr = INADDR_ANY; 
    bzero(&(my_addr.sin_zero), 0); 
    if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) { 
        perror("bind"); 
        exit(1); 
    } 
    if (listen(sockfd, lisnum) == -1) { 
        perror("listen"); 
        exit(1); 
    } 
    while(1) { 
        sin_size = sizeof(struct sockaddr_in); 
        if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1) { 
            perror("accept"); 
            continue; 
        } 
        printf("server: got connection from %s\n",inet_ntoa(their_addr.sin_addr)); 
        if (!fork()) { /* 子进程代码段 */ 
            if (send(new_fd, "Hello, world!\n", 14, 0) == -1) { 
                perror("send"); 
                close(new_fd); 
                exit(0); 
            } 
        } 
        close(new_fd); /*父进程不再需要该socket*/ 
        waitpid(-1,NULL,WNOHANG);/*等待子进程结束,清除子进程所占用资源*/ 
    } 

/*----------------------源代码结束--------------------------------------------*/

一个测试客户端代码如下:


/*----------------------源代码开始--------------------------------------------*/ 
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#define MAXDATASIZE 100 /*每次最大数据传输量 */ 
/********************************************************************* 
*filename: cycletcpclient.c 
*purpose: 循环tcp客户端程序 
*tidied by: zhoulifa(
[email=zhoulifa@163.com]zhoulifa@163.com[/email]
) 周立发(
[url=http://zhoulifa.bokee.com]http://zhoulifa.bokee.com[/url]

Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言 
*date time:2006-07-04 22:20:00 
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途 
* 但请遵循GPL 
*Thanks to: Google.com 
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力 
*********************************************************************/ 
int main(int argc, char *argv[]) 

    int sockfd, numbytes; 
    char buf[MAXDATASIZE]; 
    struct hostent *he; 
    struct sockaddr_in their_addr; 
    unsigned int myport; 
    if(argv[2]) myport = atoi(argv[2]); 
    else myport = 7838; 
    if (argc != 3) { 
        fprintf(stderr,"usage: %s hostname port\n", argv[0]); 
        exit(1); 
    } 
    if((he=gethostbyname(argv[1]))==NULL) { 
        herror("gethostbyname"); 
        exit(1); 
    } 
    if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) { 
        perror("socket"); 
        exit(1); 
    } 
    their_addr.sin_family=PF_INET; 
    their_addr.sin_port=htons(myport); 
    their_addr.sin_addr = *((struct in_addr *)he->h_addr); 
    bzero(&(their_addr.sin_zero),0); 
    if (connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1) { 
        perror("connect"); 
        exit(1); 
    } 
    if ((numbytes=recv(sockfd, buf, MAXDATASIZE, 0)) == -1) { 
        perror("recv"); 
        exit(1); 
    } 
    buf[numbytes] = 0; 
    printf("Received: %s\n",buf); 
    close(sockfd); 
    return 0; 

/*----------------------源代码结束--------------------------------------------*/

用gcc cycletcpserver.c -o tcpserver和gcc cycletcpclient.c -o tcpclient分别编译上述代码后运行情况如下: 
服务端运行显示:

QUOTE:

[email=administrator@ubuzlf:/data/example/c$]administrator@ubuzlf:/data/example/c$[/email]
./tcpserver 
server: got connection from 127.0.0.1 
server: got connection from 127.0.0.1 
server: got connection from 127.0.0.1

客户端运行显示:

QUOTE:

[email=administrator@ubuzlf:/data/example/c$]administrator@ubuzlf:/data/example/c$[/email]
./tcpclient 127.0.0.1 7838 
Received: Hello, world! 
[email=administrator@ubuzlf:/data/example/c$]administrator@ubuzlf:/data/example/c$[/email]
./tcpclient 127.0.0.1 7838 
Received: Hello, world! 
[email=administrator@ubuzlf:/data/example/c$]administrator@ubuzlf:/data/example/c$[/email]
./tcpclient 127.0.0.1 7838 
Received: Hello, world!

不得不说的一个概念性问题:阻塞与非阻塞 
在阻塞服务中,当服务器运行到accept语句而没有客户连接服务请求到来,那么会发生什么情况? 这时服务器就会停止在accept语句上等待连接服务请求的到来;同样,当程序运行到接收数据语句recv时,如果没有数据可以读取,则程序同样会停止在接收语句上。这种情况称为阻塞(blocking)。 
但如果你希望服务器仅仅注意检查是否有客户在等待连接,有就接受连接;否则就继续做其他事情,则可以通过将 socket设置为非阻塞方式来实现:非阻塞socket在没有客户在等待时就使accept调用立即返回 。 
通过设置socket为非阻塞方式,可以实现“轮询”若干socket。当企图从一个没有数据等待处理的非阻塞socket读入数据时,函数将立即返回,并且返回值置为-1,并且errno置为EWOULDBLOCK。但是这种“轮询”会使CPU处于忙等待方式,从而降低性能。考虑到这种情况,假设你希望服务器监听连接服务请求的同时从已经建立的连接读取数据,你也许会想到用一个accept语句和多个recv()语句,但是由于accept及recv都是会阻塞的,所以这个想法显然不会成功。 
调用非阻塞的socket会大大地浪费系统资源。而调用select()会有效地解决这个问题,它允许你把进程本身挂起来,而同时使系统内核监听所要求的一组文件描述符的任何活动,只要确认在任何被监控的文件描述符上出现活动,select()调用将返回指示该文件描述符已准备好的信息,从而实现了为进程选出随机的变化,而不必由进程本身对输入进行测试而浪费CPU开销。 
其次,并发服务器,在上述cycletcpserver.c中,由于使用了fork技术也可以称之为并发服务器,但这种服务器并不是真正意义上的IO多路复用的并发服务器,并且由于没有处理阻塞问题,实际应用有各种各样的问题。 
一个典型IO多路复用的单进程并发服务器流程如下: 
/*IO多路复用并发服务流程图*/ 
 
下面是一个演示IO多路复用的源程序,是一个端口转发程序,但它的用处相当大,实际应用中的各类代理软件或端口映射软件都是基于这样的代码的,比如Windows下的WinGate、WinProxy等都是在此基础上实现的。源代码如下:


/*----------------------源代码开始--------------------------------------------*/ 
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
static int forward_port; 
#undef max 
#define max(x,y) ((x) > (y) ? (x) : (y)) 
/*************************关于本文档************************************ 
*filename: tcpforwardport.c 
*purpose: 演示了select的用法,这是一个极好的代理软件核心,专门作端口映射用 
*tidied by: zhoulifa(
[email=zhoulifa@163.com]zhoulifa@163.com[/email]
) 周立发(
[url=http://zhoulifa.bokee.com]http://zhoulifa.bokee.com[/url]

Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言 
*date time:2006-07-05 19:00:00 
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途 
* 但请遵循GPL 
*Thanks to: Paul Sheer 感谢Paul Sheer在select_tut的man手册里提供了这份源代码 
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力 
*********************************************************************/ 
static int listen_socket (int listen_port) { 
    struct sockaddr_in a; 
    int s; 
    int yes; 
    if ((s = socket (AF_INET, SOCK_STREAM, 0)) = 0) {   \ 
        shutdown (fd1, SHUT_RDWR);  \ 
        close (fd1);  \ 
        fd1 = -1;     \ 
    }   \ 

#define SHUT_FD2 { \ 
    if (fd2 >= 0) {   \ 
        shutdown (fd2, SHUT_RDWR);  \ 
        close (fd2);  \ 
        fd2 = -1;     \ 
    }   \ 

#define BUF_SIZE 1024 
int main (int argc, char **argv) { 
    int h; 
    int fd1 = -1, fd2 = -1; 
    char buf1[BUF_SIZE], buf2[BUF_SIZE]; 
    int buf1_avail, buf1_written; 
    int buf2_avail, buf2_written; 
    if (argc != 4) { 
        fprintf (stderr, "Usage\n\tfwd   \n"); 
        exit (1); 
    } 
    signal (SIGPIPE, SIG_IGN); 
    forward_port = atoi (argv[2]); 
    /*建立监听socket*/ 
    h = listen_socket (atoi (argv[1])); 
    if (h  0 && buf1_avail  0 && buf2_avail  0 && buf2_avail - buf2_written > 0) { 
            FD_SET (fd1, &wr); 
            nfds = max (nfds, fd1); 
        } 
        if (fd2 > 0 && buf1_avail - buf1_written > 0) { 
            FD_SET (fd2, &wr); 
            nfds = max (nfds, fd2); 
        } 
        /*把有异常数据的socket两个一起放入select的异常句柄列表里*/ 
        if (fd1 > 0) { 
            FD_SET (fd1, &er); 
            nfds = max (nfds, fd1); 
        } 
        if (fd2 > 0) { 
            FD_SET (fd2, &er); 
            nfds = max (nfds, fd2); 
        } 
        /*开始select*/ 
        r = select (nfds + 1, &rd, &wr, &er, NULL); 
        if (r == -1 && errno == EINTR) continue; 
        if (r  0) 
        if (FD_ISSET (fd1, &er)) { 
            char c; 
            errno = 0; 
            r = recv (fd1, &c, 1, MSG_OOB); 
            if (r  0) 
        if (FD_ISSET (fd2, &er)) { 
            char c; 
            errno = 0; 
            r = recv (fd2, &c, 1, MSG_OOB); 
            if (r  0) 
        if (FD_ISSET (fd1, &rd)) { 
            r = read (fd1, buf1 + buf1_avail, BUF_SIZE - buf1_avail); 
            if (r  0) 
        if (FD_ISSET (fd2, &rd)) { 
            r = read (fd2, buf2 + buf2_avail, BUF_SIZE - buf2_avail); 
            if (r  0) 
        if (FD_ISSET (fd1, &wr)) { 
            r = write (fd1, buf2 + buf2_written, buf2_avail - buf2_written); 
            if (r  0) 
        if (FD_ISSET (fd2, &wr)) { 
            r = write (fd2, buf1 + buf1_written, buf1_avail - buf1_written); 
            if (r fd2?(fd1+1):(fd2+1), &readfds, &writefds, NULL, &timeout); 
        if(ret h_addr); 
    ret = connect(serversock, (struct sockaddr *)&sin, sizeof(sin)); 
    if(ret == -1)    { 
        sprintf(errorMessage, "ConnectSCPServer connect() error! return:-3, errno=%d, errortext:'%s' %s", errno, strerror(errno), GetCurrentTime(0, 0)); 
        close(serversock); 
        return -3; 
    } 
    return serversock; 

/*----------------------源代码代码结束--------------------------------------------*/

3、发送数据函数Send


/*----------------------源代码代码开始--------------------------------------------*/ 
int 
Send(int sock, char * buf, size_t size, int flag, int timeout)    { 
/*************************关于本函数************************************ 
*function_name: Send 
*参数说明:sock整数型socket,buf待发送的内容,size要发送的大小,flag发送选项,timeout超时时间值 
*purpose: 用来通过一个socket在指定时间内发送数据 
*tidied by: zhoulifa(
[email=zhoulifa@163.com]zhoulifa@163.com[/email]
) 周立发(
[url=http://zhoulifa.bokee.com]http://zhoulifa.bokee.com[/url]

Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言 
*date time:2006-07-05 20:58:00 
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途 
* 但请遵循GPL 
*Thanks to: Paul Sheer 感谢Paul Sheer在select_tut的man手册里提供了这份源代码 
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力 
*Note:要使用此函数需要自定义一个全局变量char errorMessage[1024];并包含自己编写的GetCurrentTime.h头文件 
*********************************************************************/ 
    int i = 0, ret = 0, intretry = 0; 
    struct timeval tival; 
    fd_set writefds; 
    int maxfds = 0; 
    tival.tv_sec = timeout; 
    tival.tv_usec = 0; 
    FD_ZERO(&writefds); 
    if(sock > 0) { 
        FD_SET(sock, &writefds); 
        maxfds=((sock > maxfds)?sock:maxfds); 
    } 
    else    { 
        sprintf(errorMessage, "Send socket:%d error! return:-2 %s", sock, GetCurrentTime(0, 0)); 
        return -2; 
    } 
    ret = select(maxfds + 1, NULL, &writefds, NULL, &tival); 
    if(ret  0) { 
        FD_SET(sock, &readfds); 
        maxfds=((sock > maxfds)?sock:maxfds); 
    } 
    else    { 
        sprintf(errorMessage, "Recv socket:%d error! return:-2 %s", sock, GetCurrentTime(0, 0)); 
        return -2; 
    } 
    ret = select(maxfds + 1, &readfds, NULL, NULL, &tival); 
    if(ret  sock_new) { 
fprintf(stderr, "接收客户端连接失败\n"); 
continue; 



}
--------------------------------------------------------------------------------
zhoulifa 回复于:2006-07-06 08:34:30
Very good!又长见识了。 
safedead , 不介意收藏你的示意代码吧?
--------------------------------------------------------------------------------
japonensis 回复于:2006-07-06 09:00:53
好人呢
--------------------------------------------------------------------------------
zhoulifa 回复于:2006-07-06 22:36:38
正如我昨天在“Linux下各类TCP网络服务器的实现源代码”(
[url=http://zhoulifa.bokee.com/5345930.html]http://zhoulifa.bokee.com/5345930.html[/url]
)一文中提到的那样,I/O多路复用技术在比较多的TCP网络服务器中有使用,即比较多的用到select函数。 
感谢chinaunix.net上朋友safedead(
[url=http://bbs.chinaunix.net/viewpro.php?uid=407631]http://bbs.chinaunix.net/viewpro.php?uid=407631[/url]
)提醒,我今天仔细研究了一下,证实了在2.6内核中的新的I/O技术epoll。 

1、为什么select是落后的? 
首先,在Linux内核中,select所用到的FD_SET是有限的,即内核中有个参数__FD_SETSIZE定义了每个FD_SET的句柄个数,在我用的2.6.15-25-386内核中,该值是1024,搜索内核源代码得到: 
include/linux/posix_types.h:#define __FD_SETSIZE        1024 
也就是说,如果想要同时检测1025个句柄的可读状态是不可能用select实现的。或者同时检测1025个句柄的可写状态也是不可能的。 
其次,内核中实现select是用轮询方法,即每次检测都会遍历所有FD_SET中的句柄,显然,select函数执行时间与FD_SET中的句柄个数有一个比例关系,即select要检测的句柄数越多就会越费时。 
当然,在前文中我并没有提及poll方法,事实上用select的朋友一定也试过poll,我个人觉得select和poll大同小异,个人偏好于用select而已。 

/************关于本文档******************************************** 
*filename: Linux 2.6内核中提高网络I/O性能的新方法epoll 
*purpose: 补充“Linux下各类TCP网络服务器的实现源代码”一文的不足之处 
*wrote by: zhoulifa(
[email=zhoulifa@163.com]zhoulifa@163.com[/email]
) 周立发(
[url=http://zhoulifa.bokee.com]http://zhoulifa.bokee.com[/url]

Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言 
*date time:2006-07-06 22:30:00 
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途 
* 但请遵循GPL 
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力 
*********************************************************************/ 

2、2.6内核中提高I/O性能的新方法epoll 

epoll是什么?按照man手册的说法:是为处理大批量句柄而作了改进的poll。要使用epoll只需要这三个系统调用:epoll_create(2), epoll_ctl(2), epoll_wait(2)。 
当然,这不是2.6内核才有的,它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44) 

以下文章转自滕昱的Web Log 
[url=http://mechgouki.spaces.msn.com/blog/PersonalSpace.aspx]http://mechgouki.spaces.msn.com/blog/PersonalSpace.aspx[/url]

[QUOTE] 
/*********************************引用开始******************************/ 
Linux2.6内核epoll介绍---我的blog 2005/3/30 

[作者]:滕昱,2005/3/30,0.1版本 

[版权声明]:此文档遵循GNU自由文档许可证(GNU Free Documentation License).任何人可以自由复制,分发,修改,不过如果方便,请注明出处和作者:) 

(1)导言: 

首先,我强烈建议大家阅读Richard Stevens著作《TCP/IP Illustracted Volume 1,2,3》和《UNIX Network Programming Volume 1,2》。虽然他离开我们大家已经5年多了,但是他的书依然是进入网络编程的最直接的道路。其中的3卷的《TCP/IP Illustracted》卷1是必读-如果你不了解tcp协议各个选项的详细定义,你就失去了优化程序重要的一个手段。卷2,3可以选读一下。比如卷2 讲解的是4.4BSD内核TCP/IP协议栈实现----这个版本的协议栈几乎影响了现在所有的主流os,但是因为年代久远,内容不一定那么vogue. 在这里我多推荐一本《The Linux Networking Architecture--Design and Implementation of Network Protocols in the Linux Kernel》,以2.4内核讲解Linux TCP/IP实现,相当不错.作为一个现实世界中的实现,很多时候你必须作很多权衡,这时候参考一个久经考验的系统更有实际意义。举个例子,linux内核中sk_buff结构为了追求速度和安全,牺牲了部分内存,所以在发送TCP包的时候,无论应用层数据多大,sk_buff最小也有272的字节. 

其实对于socket应用层程序来说,《UNIX Network Programming Volume 1》意义更大一点.2003年的时候,这本书出了最新的第3版本,不过主要还是修订第2版本。其中第6章《I/O Multiplexing》是最重要的。Stevens给出了网络IO的基本模型。在这里最重要的莫过于select模型和Asynchronous I/O模型.从理论上说,AIO似乎是最高效的,你的IO操作可以立即返回,然后等待os告诉你IO操作完成。但是一直以来,如何实现就没有一个完美的方案。最著名的windows完成端口实现的AIO,实际上也是内部用线程池实现的罢了,最后的结果是IO有个线程池,你应用也需要一个线程池...... 很多文档其实已经指出了这带来的线程context-switch带来的代价。 

在linux 平台上,关于网络AIO一直是改动最多的地方,2.4的年代就有很多AIO内核patch,最著名的应该算是SGI那个。但是一直到2.6内核发布,网络模块的AIO一直没有进入稳定内核版本(大部分都是使用用户线程模拟方法,在使用了NPTL的linux上面其实和windows的完成端口基本上差不多了)。2.6内核所支持的AIO特指磁盘的AIO---支持io_submit(),io_getevents()以及对Direct IO的支持(就是绕过VFS系统buffer直接写硬盘,对于流服务器在内存平稳性上有相当帮助)。 

所以,剩下的select模型基本上就是我们在linux上面的唯一选择,其实,如果加上no-block socket的配置,可以完成一个"伪"AIO的实现,只不过推动力在于你而不是os而已。不过传统的select/poll函数有着一些无法忍受的缺点,所以改进一直是2.4-2.5开发版本内核的任务,包括/dev/poll,realtime signal等等。最终,Davide Libenzi开发的epoll进入2.6内核成为正式的解决方案 

(2)epoll的优点 

支持一个进程打开大数目的socket描述符(FD) 

select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显然太少了。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。 

IO效率不随FD数目增加而线性下降 

传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是"活跃"的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对"活跃"的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用 callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个"伪"AIO,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境,epoll并不比select/poll有什么效率,相反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。 

使用mmap加速内核与用户空间的消息传递。 

这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话,一定不会忘记手工 mmap这一步的。 

内核微调 

这一点其实不算epoll的优点了,而是整个linux平台的优点。也许你可以怀疑linux平台,但是你无法回避linux平台赋予你微调内核的能力。比如,内核TCP/IP协议栈使用内存池管理sk_buff结构,那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大小--- 通过echo XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手的数据包队列长度),也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网卡驱动架构。 

(3)epoll的使用 

令人高兴的是,2.6内核的epoll比其2.5开发版本的/dev/epoll简洁了许多,所以,大部分情况下,强大的东西往往是简单的。唯一有点麻烦是epoll有2种工作方式:LT和ET。 

LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表. 

ET (edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。 

epoll只有epoll_create,epoll_ctl,epoll_wait 3个系统调用,具体用法请参考
[url=http://www.xmailserver.org/linux-patches/nio-improve.html]http://www.xmailserver.org/linux-patches/nio-improve.html[/url]
, 

[url=http://www.kegel.com/rn/]http://www.kegel.com/rn/[/url]
也有一个完整的例子,大家一看就知道如何使用了 

(4)Leader/follower模式线程pool实现,以及和epoll的配合 

.....未完成,主要是要避免过多的epoll_ctl调用,以及尝试使用EPOLLONESHOT加速...... 

(5)benchmark 

.......未完成 
/*********************************引用结束******************************/ 
[/QUOTE] 
3、epoll的使用方法 
这是epoll的man手册提供的一个例子,这段代码假设一个非阻塞的socket监听listener被建立并且一个epoll句柄kdpfd已经提前用epoll_create建立了: 
[CODE] 
       struct epoll_event ev, *events; 

       for(;;) { 
           nfds = epoll_wait(kdpfd, events, maxevents, -1);/*wait for an I/O event. All notes here added by zhoulifa(
[url=http://zhoulifa.bokee.com]http://zhoulifa.bokee.com[/url]
) on 2006-7-6 22:10:00*/ 

           for(n = 0; n  sock_new) { 
                                fprintf(stderr, "接收客户端连接失败\n"); 
                                continue; 
                        } 
                } 
        } 
}

对照safedead和前面的一份代码,我想大家一定是明白了的。 

5、参考文档 
Improving (network) I/O performance ... 
http://www.xmailserver.org/linux-patches/nio-improve.html
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值