必威电竞外围网站什么勾勒一个Web服务器。实现了一个比Nginx速度又快的HTTP服务器。

近年来简单只月之业余时间在写一个私人项目,目的是以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

相关文章

admin

网站地图xml地图