亚洲必赢手机入口哪勾勒一个Web服务器。实现了一个比Nginx速度又快之HTTP服务器。

by admin on 2018年10月5日

近来点滴单月的业余时间在描绘一个亲信项目,目的是当Linux下写一个胜性能Web服务器,名字让Zaver。主体框架和基本功能已形成,还有一对高级功能下会日趋多,代码放在了github。Zaver的框架会于代码量尽量少的状下接近工业水平,而不像有些课本上的toy
server为了教原理而舍了森原来server应该有东西。在本篇文章被,我将一步步地阐明Zaver的设计方案和支出过程中遇见遇到的不方便与对应的缓解智。

当上次的FreeBSD和linux的nginx静态文件性能比测试
后,我萌发了好动手做一个简单的Web
Server来搞懂nginx高性能背后的原理的想法。最后成功实现了一个冲epoll的简易的HTTP服务器,实现了200,404,400,304应,并且性能比nginx高了一点点。本文主要介绍是HTTP服务器的法则及筹划过程。

为什么要双重过去轮子

几乎每个人每天都要要多或少跟Web服务器打交道,比较知名的Web
Server有Apache
Httpd、Nginx、IIS。这些软件跑在众多光机器上也咱提供稳定之劳务,当你打开浏览器输入网址,Web服务器就会见拿信息污染被浏览器然后上现在用户眼前。那既然发生那基本上备的、成熟稳定的web服务器,为什么还要再次造轮子,我看理由有如下几点:

  • 夯实基础。一个优的开发者必须有扎实的功底,造轮子是一个生好的路。学编译器?边看教科书变写一个。学操作系统?写一个原型出来。编程这个圈子只有和谐下手实现了才敢说确会了。现在正值学网编程,所以就是打算写一个Server。

  • 贯彻新成效。成熟的软件或为适应大众的急需导致未会见太考虑而一个人数的与众不同需要,于是只能自己下手实现此奇异要求。关于这一点Nginx做得一定得好了,它提供了受用户从定义之模块来定制自己需要之法力。

  • 协新家容易地操纵成熟软件的架。比如Nginx,虽然代码写得大出彩,但是想全盘看明白她的架,以及它起定义之一部分数据结构,得查相当多的材料及参照书籍,而这些架构和数据结构是为着增强软件的可伸缩性和频率所设计之,无关高并发server的庐山真面目部分,初家会眩晕。而Zaver用最少之代码展示了一个高并发server应有的法,虽然并未Nginx性能强,得到的利益是无Nginx那么复杂,server架构完全暴露在用户面前。

看了部分篇后,我整理出了以下要点:

读本上的server

效仿网编程,第一独例证可能会见是Tcp
echo服务器。大概思路是server会listen在某个端口,调用accept等待客户的connect,等客户连接上时时见面回来一个fd(file
descriptor),从fd里read,之后write同样的数据及这fd,然后还accept,在网上找到一个不行好之代码实现,核心代码是如此的:

while ( 1 ) {

    /*  Wait for a connection, then accept() it  */

    if ( (conn_s = accept(list_s, NULL, NULL) ) < 0 ) {
        fprintf(stderr, "ECHOSERV: Error calling accept()\n");
        exit(EXIT_FAILURE);
    }


    /*  Retrieve an input line from the connected socket
        then simply write it back to the same socket.     */

    Readline(conn_s, buffer, MAX_LINE-1);
    Writeline(conn_s, buffer, strlen(buffer));


    /*  Close the connected socket  */

    if ( close(conn_s) < 0 ) {
        fprintf(stderr, "ECHOSERV: Error calling close()\n");
        exit(EXIT_FAILURE);
    }
}

整体兑现以这里。
如若你还未太亮是序,可以拿它们下充斥至本地编译运行一下,用telnet测试,你见面发觉在telnet里输入什么,马上就会见来得什么。如果您前面还无接触过网编程,可能会见冷不丁领悟到,这同浏览器访问某个网址然后信息显示在屏幕及,整个原理是同一型一样的!学会了是echo服务器是何许行事之事后,在这个基础及进行成一个web
server非常简单,因为HTTP是建于TCP之上的,无非多一些说道的辨析。client在起TCP连接之后发的凡HTTP协议头和(可选的)数据,server接受到多少后先解析HTTP协议头,根据协议前的音信发回相应的多少,浏览器把信展现让用户,一不良呼吁虽做到了。

夫法是有的书让网络编程的业内例程,比如《深入理解计算机体系》(CSAPP)在谈话网络编程的时光就是用是思路实现了一个顶简便易行的server,代码实现以这里,非常紧缺,值得一念,特别是其一server即实现了静态内容还要实现了动态内容,虽然效率不赛,但现已落得教学的目的。之后立刻按照书用事件驱动优化了之server,关于事件驱动会以后面摆。

尽管如此是序会健康工作,但它完全不克投入到工业应用,原因是server在拍卖一个客户要的早晚无法接受别的客户,也就是说,这个程序无法同时满足个别只想获得echo服务的用户,这是力不从心忍受的,试想一下您以就此微信,然后告诉你有人以于是,你不能不等十分人走了下才能够为此。

下一场一个改善之化解方案为提出来了:accept以后fork,父进程继续accept,子进程来处理这fd。这个为是局部课本及之正规示例,代码大概长这么:

/* Main loop */
    while (1) {
        struct sockaddr_in their_addr;
        size_t size = sizeof(struct sockaddr_in);
        int newsock = accept(listenfd, (struct sockaddr*)&their_addr, &size);
        int pid;

        if (newsock == -1) {
            perror("accept");
            return 0;
        }

        pid = fork();
        if (pid == 0) {
            /* In child process */
            close(listenfd);
            handle(newsock);
            return 0;
        }
        else {
            /* Parent process */
            if (pid == -1) {
                perror("fork");
                return 1;
            }
            else {
                close(newsock);
            }
        }
    }

总体代码在
这里。表面上,这个顺序化解了前方只能处理单客户的题目,但基于以下几点主要缘由,还是无法投入工业的高并发使用。

  • 历次来一个总是都fork,开销太怪。任何讲Operating
    System的题都见面写,线程可以了解为轻量级的长河,那进程到底重在什么地方?《Linux
    Kernel
    Development》有相同节(Chapter3)专门讲了调用fork时,系统实际做了呀。地址空间是copy
    on
    write的,所以无造成overhead。但是中间有一个复制父进程页表的操作,这也是怎在Linux下开创进程比创线程开销大的原由,而富有线程都共享一个页表(关于为什么地址空间是COW但页表不是COW的来由,可以考虑一下)。

  • 进程调度器压力最要命。当并发量上来了,系统里有多进程,相当多的工夫用费在决定谁进程是产一个运作过程与上下文切换,这是深勿值得的。

  • 每当heavy
    load下大半个经过消耗太多之内存,在过程下,每一个连续都对应一个独的地点空间;即使在线程下,每一个总是为会占用独立。此外父子进程中需要发出IPC,高并发下IPC带来的overhead不可忽略。

换用线程虽然能够解决fork开销的题目,但是调度器和内存的问题或者无法解决。所以经过与线程在精神上是一律的,被号称process-per-connection
model。因为无法处理高并发而未叫业界使用。

一个雅肯定的改善是用线程池,线程数量稳定,就无地方提到的题目了。基本架构是起一个loop用来accept连接,之后把这个连续分配为线程池中之某部线程,处理完了下是线程又有何不可处理别的连接。看起是单非常好的方案,但在实际情形中,很多连过渡都是丰富连(在一个TCP连接上开展频繁通信),一个线程在收到任务后,处理终结第一批来的多寡,此时会面重新调用read,天喻对方什么时发来新的数量,于是这个线程就被这个read给卡住住了(因为默认情况下fd是blocking的,即只要是fd上从未有过数量,调用read会阻塞住进程),什么都非克干,假设有n个线程,第(n+1)个增长连来了,还是无法处理。

岂惩罚?我们发现问题是产生当read阻塞住了线程,所以解决方案是将blocking
I/O换成non-blocking
I/O,这时候read的做法是要出多少虽然回数据,如果无但读数据就返回-1连把errno设置为EAGAIN,表明下次生多少了自家更来持续读(man
2 read)。

此处来只问题,进程怎么知道者fd什么时候来数量同时可以读了?这里而引出一个要的定义,事件驱动/事件循环。

兑现多起的socket服务器出如此几独办法:

事件驱动(Event-driven)

要是发如此一个函数,在某个fd可以读的下报我,而休是几度地失去调用read,上面的题材无纵缓解了?这种办法叫事件驱动,在linux下可以用select/poll/epoll这些I/O复用的函数来落实(man
7
epoll),因为一旦时时刻刻知道哪些fd是可读之,所以只要拿这个函数放到一个loop里,这个就是叫事件循环(event
loop)。一个示范代码如下:

while (!done)
{
  int timeout_ms = max(1000, getNextTimedCallback());
  int retval = epoll_wait(epds, events, maxevents, timeout_ms);

  if (retval < 0) {
     处理错误
  } else {
    处理到期的 timers

    if (retval > 0) {
      处理 IO 事件
    }
  }
}

在这个while里,调用epoll_wait会晤将经过阻塞住,直到在epoll里的fd发生了立报之风波。这里发生个大好的例子来显示epoll是怎用底。需要注明的是,select/poll不有伸缩性,复杂度是O(n),而epoll的复杂度是O(1),在Linux下工业程序还是用epoll(其它平台产生独家的API,比如当Freebsd/MacOS下用kqueue)来通知进程哪些fd发生了轩然大波,至于缘何epoll比前双方效率高,请参考这里。

事件驱动是兑现大性能服务器的基本点,像Nginx,lighttpd,Tornado,NodeJs都是因事件驱动实现的。

  1. 差不多进程共享一个监听端口

Zaver

组合地方的座谈,我们得出了一个波循环+ non-blocking I/O +
线程池的解决方案,这为是Zaver的主题搭(同步的风波循环+non-blocking
I/O又于称Reactor模型)。
事件循环用作事件通报,如果listenfd上但读,则调用accept,把新建的fd加入epoll中;是屡见不鲜的连fd,将该投入到一个劳动者-消费者队列中,等工作线程来用。
线程池用来举行计算,从一个劳动者-消费者队列里用一个fd作为计量输入,直到读到EAGAIN为止,保存现在的拍卖状态(状态机),等待事件循环对之fd读写事件的产一致浅通报。

bind之后采用fork()创建同客当前过程的正片,并启动子进程。子进程使阻塞式accept、read、write,即这些操作会阻塞线程,直到操作完成才继续执行。缺点是过程中通信速度迟滞,每个过程占用多内存,所以并发数一般受限于经过数。

支出中遇的题目

Zaver的运转架构在上文介绍了,下面将总结一下己当出时撞的局部不便以及部分解决方案。把开发被遇见的不方便记录下是只很好的习惯,如果遇题目查google找到个缓解方案一直照搬过去,不做任何记录,也没思想,那么下次你遇见相同的题目,还是会再同一整个搜索的经过。有时我们如果举行代码的创造者,不是代码的“搬运工”。做笔录定期回顾遇到的问题会见如和谐成长更快。

  • 而拿fd放入生产者-消费者队列中晚,拿到这任务的行事线程还尚未读毕这fd,因为无念了数据,所以这fd可读,那么下一致浅事件循环又回去这个fd,又分开被别的线程,怎么处理?

报经:这里涉及到了epoll的鲜种工作模式,一种于边缘触发(Edge
Triggered),另一样栽让水平触发(Level
Triggered)。ET和LT的命名是不行像的,ET是表示以状态改变时才通知(eg,在边缘上从低电平到高电平),而LT表示以是状态才通知(eg,只要处于低位电平就通报),对应的,在epoll里,ET表示只要有新数据了就算通知(状态的更改)和“只要出新数据”就直会打招呼。

推选个具体的例子:如果某fd上发2kb的数码,应用程序只读了1kb,ET就不见面于产一样不行epoll_wait的下回来,读了之后还要发新数据才返回。而LT每次都见面回来这个fd,只要这fd有数据可读。所以当Zaver里我们要因此epoll的ET,用法之模式是永恒的,把fd设为nonblocking,如果回到某fd可读,循环read直到EAGAIN(如果read返回0,则远端关闭了连续)。

  • 当server和浏览器保持正一个加上连的时段,浏览器突然被关门了,那么server端怎么处理这socket?

答:此时该fd在波循环里会返回一个不过读事件,然后就是为分配为了某线程,该线程read会返回0,代表对方就关闭是fd,于是server端也调整用close即可。

  • 既然如此把socket的fd设置也non-blocking,那么一旦发生一对数据包晚到了,这时候read就会回-1,errno设置为EAGAIN,等待下次读取。这是不怕遇到了一个blocking
    read不曾遇到的问题,我们要以已读到之多少保存下去,并保障一个状态,以代表是否还亟需多少,比如读到HTTP
    Request Header, GET /index.html HTT就算截止了,在blocking
    I/O里设继续read就好,但每当nonblocking
    I/O,我们得保障这个状态,下一致糟糕必须读到’P’,否则HTTP协议分析错误。

报:解决方案是保安一个状态机,在解析Request
Header的时候对应一个状态机,解析Header
Body的时节呢维护一个状态机,Zaver状态机的时刻参考了Nginx在解析header时之兑现,我开了一些简洁和规划及的改变。

  • 岂比好之落实header的分析

报经:HTTP
header有广大,必然发生无数独解析函数,比如解析If-modified-since头与剖析Connection头是分别调用两个例外之函数,所以这里的统筹要是千篇一律种模块化的、易拓展的宏图,可以使开发者很爱地改及概念针对不同header的分析。Zaver的实现方式参考了Nginx的做法,定义了一个struct数组,其中各一个struct存的是key,和相应之函数指针hock,如果条分缕析到的headerKey
== key,就调hock。定义代码如下

zv_http_header_handle_t zv_http_headers_in[] = {
    {"Host", zv_http_process_ignore},
    {"Connection", zv_http_process_connection},
    {"If-Modified-Since", zv_http_process_if_modified_since},
    ...
    {"", zv_http_process_ignore}
};
  • 安存储header

报经:Zaver将兼具header用链表连接了起来,链表的实现参考了Linux内核的夹链表实现(list_head),它提供了同一种植通用的双双链表数据结构,代码非常值得一读,我做了简化和改变,代码在这里。

  • 压力测试

报经:这个产生好多秋的方案了,比如http_load, webbench,
ab等等。我最后摘了webbench,理由是粗略,用fork来套client,代码只来几百执行,出问题可就冲webbench源码定位到底是谁操作而Server挂了。另外为后面提到的一个问题,我仔细看了下Webbench的源码,并且特别推荐C初学者看一样拘留,只出几百尽,但是关乎了令执行参数解析、fork子进程、父子进程之所以pipe通信、信号handler的注册、构建HTTP协议头的技巧等局部编程上的技艺。

  • 故而Webbech测试,Server在测试结束时挂了

答:百思不得其解,不管时间走多长期,并发量开多少,都是于终极webbench结束之随时,server挂了,所以自己猜想肯定是马上一刻发出了什么“事情”。
开班调剂定位错误代码,我为此底凡从log的道,后面的事实证明在这边就不是挺好的措施,在多线程环境下要经看log的艺术固定错误是一样桩比较紧的转业。最后log输出将错定位在往socket里write对方伸手的公文,也就算是网调用挂了,write挂了难道不是回去-1的吧?于是唯一的说明就是是过程接受到了某signal,这个signal使进程挂了。于是用strace重新开展测试,在strace的出口log里发现了问题,系统以write的时段接受到了SIGPIPE,默认的signal
handler是停进程。SIGPIPE产生的本原以,对方都关闭了此socket,但过程还为内写。所以我猜测webbench在测试时间到了后来休会见等server数据的归直接close掉所有的socket。抱在如此的疑虑去看webbench的源码,果然是这么的,webbench设置了一个定时器,在正常测试时间会念取server返回的数据,并正常close;而当测试时间相同到即直接close掉所有socket,不会见念server返回的数目,这就算招了zaver往一个业已让对方关闭的socket里描写多少,系统发送了SIGPIPE。

化解方案为非常简单,把SIGPIPE的信号handler设置也SIG_IGN,意思是忽视该信号即可。

  1. 多线程

不足

眼前Zaver还有很多改善之地方,比如:

  • 今昔初分配内存都是透过malloc的主意,之后会转化外存池的法门
  • 还非支持动态内容,后期起考虑多php的支持
  • HTTP/1.1比较复杂,目前独兑现了几乎独主要的(keep-alive, browser
    cache)的header解析
  • 莫倒连续的过过期还无召开

类多进程,只不过用线程代替了经过。主线程负责accept,为每个请求建立一个线程(或者使用线程池复用线程)。比多进程速度快,占用更不见的内存,稳定性不跟多进程。因为每个线程都出温馨的堆栈空间,其占用的内存还是无法消除的,所以并发数一般受限于线程数。

总结

本文介绍了Zaver,一个布局简单,支持大起的http服务器。基本架构是事件循环

  • non-blocking I/O +
    线程池。Zaver的代码风格参考了Nginx的风骨,所以于可读性上非常高。另外,Zaver资了部署文件和下令执行参数解析,以及到之Makefile和源代码结构,也足以帮其他一个C初学者入门一个类是怎么构建的。目前自己的wiki就用Zaver托管着。

一个阻塞式IO程序的流程示例图:

参考资料

[1]
https://github.com/zyearn/zaver

[2]
http://nginx.org/en/

[3] 《linux多线程服务端编程》

[4]
http://www.martinbroadhurst.com/server-examples.html

[5]
http://berb.github.io/diploma-thesis/original/index.html

[6] <a href=”http://tools.ietf.org/html/rfc2616″
target=”_blank”>rfc2616</a>

[7]
https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/

[8] Unix Network Programming, Volume 1: The Sockets Networking API
(3rd Edition)

亚洲必赢手机入口 1

 

  1. 事件驱动的非阻塞IO(nonblocking I/O)

单线程,将socket设置也非阻塞模式(accept、read、write会立即回。如果就accept完了具有的连日,或读就了缓冲区的多寡,或者写满了缓冲区,会回来-1,而非是进入阻塞状态)。使用select或epoll等体制,同时监听多独IO操作有管事件时有发生。当其中的一个或者多单处Ready状态(即:监听的socket可以accept,tcp连接可以read等)后,立即处理相应的轩然大波,处理完毕后立刻回到监听状态(注意这里的监听是监听IO事件,不是监听端口)。相当给阻塞式IO编程中随机一高居还或回主循环中持续等待,并能够打等待中直接回原处继续执行;而accept、读、写都不再阻塞,阻塞全部移动到了一个多事件监听操作着。

一个非阻塞式IO程序的流水线示例图:

 

亚洲必赢手机入口 2

举例来说,如果当A连接的Read
request的过程遭到,缓冲区数量读毕了,而要还没有完结,直接回到主循环中监听其他事件。而此时要发现其他一个Send了一半的Response连接B变为了不过写状态,则直处理B连接Send
Response事件,从上次B连接写了一半之地方开始,继续写副数据。这样一来,虽然是单线程,但A和B同时展开,互不干扰。

以流程进一步复杂,无法凭线程的库房保存每个连处理进程遭到之各种状态信息,我们需要协调维护其,这种编程方式欲再胜的技艺。比方说,原先我们可以send_response函数中因故有些变量保存发送数据的快,而今日我们只好寻找一块其它的地方,为各级一个一连单独保存之价值了。

nginx即使用事件驱动的非阻塞IO模式工作。

nginx支持多轩然大波机制:跨平台的select,Linux的poll和epoll,FreeBSD的kqueue,Solaris的/dev/poll等。在高并发的情形下,在Linux上运用epoll性能最好,或者说select的性最好差了。

事件机制分为水平触发,或译状态触发(level-triggered)和边缘触发(edge-triggered)。前者是用经过状态表示出事件时有发生,后者通过状态变化代表事件来。打个如来说,使用状态触发的时,只要缓冲区有多少,你尽管可知检测到事件的有。而动边缘触发,你不能不将缓冲区的数目全念毕事后,才会拓展下同样糟糕事件的检测,否则,因为状态一直处在可读状态,没有发生变化,你拿永远完不至是波。显然,后者对编写程序的严谨性要求又强。

select和poll属于前者,epoll同时支持即时片种植模式。值得一提的是,我好测试了转,发现便以20000并作之图景下,epoll使用即时点儿种植模式之前性能差异仍好忽略不计。

除此以外待专注的是,对于正常文件设置非阻塞是不起作用的。

  1. 除此以外还有异步IO,一般以Windows上运,这里就是无出口了。

此外nginx使用了Linux的sendfile函数。和习俗的用户程序自己read和write不同,sendfile接收两个公文描述符,直接在根本中实现复制操作,相比read和write,可以抽内核态和用户态的切换次数,以及数据拷贝的次数。

连下正式开规划。我选择了非阻塞IO,epoll的边缘触发模式。先找找了个比完好的运用epoll的一个socket
server例子作为参照,然后于其的基础上边修改边做试验。

这例子比较简单,而且为不曾体现出非阻塞IO编程。不过通过它们自己询问及了epoll的中坚使用方式。

以促成产出通信,我们用将程序“摊平”。

首先,分析我们的HTTP服务器通信过程用到之变量:

状态

Wait for reading

Wait for writing

次数

变量类型

非本地变量

备注

Accept

Y

N

n

local

   

Read request

Y

N

n

nonlocal

Read buf

 

Open file

N

N

n

nonlocal

文件名

 

Send response header

N

Y

n

nonlocal

Response header buf

 

Read file -> Send response content

N

Y

n*n

nonlocal

Read&write buf

Write pos

fd

Sock

读满read buf或读到EOF,再发

发送时将read buf

Close file

N

N

n

 

fd

 

Close socket

N

N

n

 

sock

 

接下来,定义一个布局用于保存这些变量:

struct process {
    int sock;
    int status;
    int response_code;
    int fd;
    int read_pos;
    int write_pos;
    int total_length;
    char buf[BUF_SIZE];
};

为便利,我一直用一个大局数组装所有的process:

static struct process processes[MAX_PORCESS];

此外定义每个连通信过程中的老三单状态:

#define STATUS_READ_REQUEST_HEADER    0
#define STATUS_SEND_RESPONSE_HEADER    1
#define STATUS_SEND_RESPONSE        2

从此以后,就是按部就班部就班地促成主循环、读取request,解析header,判断文件是否是、检查文件修改时,发送相应的header和content了。

脚仅拿程序中同epoll有关的严重性部分胶出来:

main()函数:

使用epoll_create()创建一个epoll
fd,注意,这里的listen_sock已经设置为nonblocking(我以setNonblocking函数)了:

    efd = epoll_create1 ( 0 );
    if ( efd == -1 )
    {
        ...
    }

    event.data.fd = listen_sock;
    event.events = EPOLLIN | EPOLLET;
    s = epoll_ctl ( efd, EPOLL_CTL_ADD, listen_sock, &event );
    if ( s == -1 )
    {
        ...
    }

    /* Buffer where events are returned */
    events = calloc ( MAXEVENTS, sizeof event );

此的EPOLLIN表示监听“可读”事件。

每当主循环中epoll_wait():

    while ( 1 )
    {
        int n, i;

        n = epoll_wait ( efd, events, MAXEVENTS, -1 );
        if ( n == -1 )
        {
            perror ( "epoll_wait" );
        }
        for ( i = 0; i < n; i++ )
        {
            if ( ( events[i].events & EPOLLERR ) ||
                    ( events[i].events & EPOLLHUP ) )
            {
                fprintf ( stderr, "epoll error\n" );
                close ( events[i].data.fd );
                continue;
            }

            handle_request ( events[i].data.fd );

        }
    }

epoll_wait()会于发出事件后已阻塞,继续执行,并拿来了轩然大波之event的file
descriptor放入events中,返回数组大小。注意的是,这里而循环处理所有的fd。

接通下是第一部分:

void handle_request ( int sock )
{
    if ( sock == listen_sock )
    {
        accept_sock ( sock );
    }
    else
    {
        struct process* process = find_process_by_sock ( sock );
        if ( process != 0 )
        {
            switch ( process->status )
            {
            case STATUS_READ_REQUEST_HEADER:
                read_request ( process );
                break;
            case STATUS_SEND_RESPONSE_HEADER:
                send_response_header ( process );
                break;
            case STATUS_SEND_RESPONSE:
                send_response ( process );
                break;
            default:
                break;
            }
        }
    }
}

根据epoll返回的fd,做不同处理:如果是监听的socket,则accept();否则,根据sock的fd查找相应的process结构体,从中取回状态信息,返回到之前的处理状态中。这样即使能够实现信春哥,死后原地复活之状态回升机制了。

在accept中,将accept出来的连日为设置也非阻塞,然后在process数组中追寻一个还无下的空位,初始化,然后拿此socket存到process结构体中:

struct process* accept_sock ( int listen_sock )
{
    int s;
    // 在ET模式下必须循环accept到返回-1为止
    while ( 1 )
    {
        struct sockaddr in_addr;
        socklen_t in_len;
        int infd;
        char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
        if ( current_total_processes >= MAX_PORCESS )
        {
            // 请求已满,accept之后直接挂断
            infd = accept ( listen_sock, &in_addr, &in_len );
            if ( infd == -1 )
            {
                if ( ( errno == EAGAIN ) ||
                        ( errno == EWOULDBLOCK ) )
                {
                    break;
                }
                else
                {
                    perror ( "accept" );
                    break;
                }
            }
            close ( infd );

            return;
        }

        in_len = sizeof in_addr;
        infd = accept ( listen_sock, &in_addr, &in_len );
        if ( infd == -1 )
        {
            if ( ( errno == EAGAIN ) ||
                    ( errno == EWOULDBLOCK ) )
            {
                break;
            }
            else
            {
                perror ( "accept" );
                break;
            }
        }

        getnameinfo ( &in_addr, in_len,
                      hbuf, sizeof hbuf,
                      sbuf, sizeof sbuf,
                      NI_NUMERICHOST | NI_NUMERICSERV );

        //设置为非阻塞
        s = setNonblocking ( infd );
        if ( s == -1 )
            abort ();
        int on = 1;
        setsockopt ( infd, SOL_TCP, TCP_CORK, &on, sizeof ( on ) );
        //添加监视sock的读取状态
        event.data.fd = infd;
        event.events = EPOLLIN | EPOLLET;
        s = epoll_ctl ( efd, EPOLL_CTL_ADD, infd, &event );
        if ( s == -1 )
        {
            perror ( "epoll_ctl" );
            abort ();
        }
        struct process* process = find_empty_process_for_sock ( infd );
        current_total_processes++;
        reset_process ( process );
        process->sock = infd;
        process->fd = NO_FILE;
        process->status = STATUS_READ_REQUEST_HEADER;
    }
}

老三独不等状态对应三独例外函数进行处理,我就不都粘了,以read_request为例:

void read_request ( struct process* process )
{
    int sock = process->sock, s;
    char* buf=process->buf;
    char read_complete = 0;

    ssize_t count;

    while ( 1 )
    {
        count = read ( sock, buf + process->read_pos, BUF_SIZE - process->read_pos );
        if ( count == -1 )
        {
            if ( errno != EAGAIN )
            {
                handle_error ( process, "read request" );
                return;
            }
            else
            {
                //errno == EAGAIN表示读取完毕
                break;
            }
        }
        else if ( count == 0 )
        {
            // 被客户端关闭连接
            cleanup ( process );
            return;
        }
        else if ( count > 0 )
        {
            process->read_pos += count;
        }
    }

    int header_length = process->read_pos;
    // determine whether the request is complete
    if ( header_length > BUF_SIZE - 1 )
    {
    process->response_code = 400;
    process->status = STATUS_SEND_RESPONSE_HEADER;
    strcpy ( process->buf, header_400 );
    send_response_header ( process );
    handle_error ( processes, "bad request" );
    return;
    }
    buf[header_length]=0;
    read_complete = ( strstr ( buf, "\n\n" ) != 0 ) || ( strstr ( buf, "\r\n\r\n" ) != 0 );

    if ( read_complete )
    {
        // ...

        //解析之后,打开文件,把文件描述符存入process,然后进入发送header状态
        process->status = STATUS_SEND_RESPONSE_HEADER;
        //修改此sock的监听状态,改为监视写状态
        event.data.fd = process->sock;
        event.events = EPOLLOUT | EPOLLET;
        s = epoll_ctl ( efd, EPOLL_CTL_MOD, process->sock, &event );
        if ( s == -1 )
        {
            perror ( "epoll_ctl" );
            abort ();
        }
        //发送header
        send_response_header ( process );
    }
}

亚洲必赢手机入口 3

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图