热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

转载goaheadwebserver源码分析

1.一个txt文本架构图main()     |     |--websOpenServer()     |            |--websOpenListen()     

1.一个txt文本架构图

main()

      |

      |--websOpenServer()

      |             |-- websOpenListen()

      |                           |--socketOpenConnection()

      |                                           |--打开webServer服务器

      |                                           |--初化socket_t结构(注册websAccept()回调函数(socket_t sp->accept= websAccept)等)

      |                                           |--把socket_t结构加入数组socketList

      |            

      |

      |--websUrlHandlerDefine()

      |                |--初始化websUrlHandlerType结构的websUrlHandler数组

      |                |--将urlPrefix和回调函数绑定在websUrlHandler[websUrlHandlerMax]中

      |

      |--websUrlHandlerDefine(websDefaultHandler)

      |                |--初始化websUrlHandlerType结构的websUrlHandler数组

      |                |--将urlPrefix和回调函数绑定在websUrlHandler[websUrlHandlerMax]中

      |         

      |

      |

      |--websFormDefine()

      |               |--初始化symbol table结构sym_t,把名字和回调函数名放进sym_t结构

      |               |--把sym_t结构放进hash表中

      |

      |--websAspDefine()

      |               |--初始化symbol table结构sym_t,把名字和回调函数名放进sym_t结构

      |               |--把sym_t结构放进hash表中

      |

      |

      |(main loop)

   ----|--socketReady(-1) || socketSelect(-1, 1000)

   ^                  |--轮询socketList        |--轮询socketList中的handlerMask

   |  |                |--中的几个变量        |--改变socketList中的currentEvents

   |  |

   |  |--socketProcess()

   ^  |              |--轮询socketList[]

   |  |               |--socketReady()

   |  |               |--socketDoEvent()

   |  |                                |--如果有新的连接(来自listenfd)就调用socketAccept()

   |  |                                                    |--调用socketAlloc()初始化socket_t结构

   |  |                                                    |--把socket_t结构加入 socketList数组

   |  |                                                    |--调用socket_t sp->accept()回调函数

   |  |

   |  |                                |--如果不是新的连接就查找socketList数组调用socket_t sp->handler()回调函数

   |  |

   |  |

   --|

 

websAccept()

     |--做一些检查

     |--socketCreateHandler(sid, SOCKET_READABLE, websSocketEvent, (int) wp)

     |            |--把sid注册为读事件,初始化socket_t sp->handler = websSocketEvent等, 更新对应的socketList数组(handlerMask值等)

    

websSocketEvent()

     |--判断读写操作

     |--读websReadEvent()

     |                |--websUrlHandlerRequest()

     |                                     |--查找wbsUrlHandler数组,调用和urlPrefix对应的回调函数(websFormHandler(),websDefaultHandler()等)

     |

     |--写,调用(wp->writeSocket)回调函数

 

 

websFormHandler()

    |--跟据formName查找hash表,调用用户定义的函数

 

websDefaultHandler()

    |--处理默认的URL请求,包括asp页面

    |--websSetRequestSocketHandler()

    |                                              |--注册默认的写事件函数wp->writeSocket = websDefaultWriteEvent

    |                                              |--socketCreateHandler(wp->sid, SOCKET_WRITABLE, websSocketEvent, (int) wp)

    |                                                          |--把sid注册为写事件,初始化socket_t sp->handler = websSocketEvent等, 更新对应的socketList数组

    

websDefaultWriteEvent()

    |

|--写数据,不包括asp页面

 

2.跟着main走

Main函数很简短,所以可以对他的代码进行一行一行注释,如下:

int main(int argc, char** argv)

{

/*

 *  Initialize the memory allocator. Allow use of malloc and start

 *  with a 60K heap.  For each page request approx 8KB is allocated.

 *  60KB allows for several concurrent page requests.  If more space

 *  is required, malloc will be used for the overflow.

 */

/*

首先分配一个大的内存块(60*1024字节),以后只要是以b开头的对内存操作的函数都是在这个已经分好的内存块上的操作,这些操作在Balloc.c中实现。

*/

    bopen(NULL, (60 * 1024), B_USE_MALLOC);

/*

忽略SIGPIPE信号

*/

    signal(SIGPIPE, SIG_IGN);

 

/*

 *  Initialize the web server

初始化用户管理部分,打开web服务器,注册URL处理函数。

用户管理部分在um.c中实现,

Web服务器的初始化是在default.c和webs.c中实现

url处理函数在handler.c中实现

 */

    if (initWebs() <0) {

        return -1;

    }

/*

初始化Ssl认证部分

注:在这个文档中对ssl认证不做研究

*/

#ifdef WEBS_SSL_SUPPORT

    websSSLOpen();

#endif

 

/*

 *  Basic event loop. SocketReady returns true when a socket is ready for

 *  service. SocketSelect will block until an event occurs. SocketProcess

 *  will actually do the servicing.

 */

/*

主循环

*/

    while (!finished) {

/*

1,socketReady()函数检查是否有准备好的sock事件

2,socketSelect()函数首先把各个sock感兴趣的事件(sp->handlerMask)注册给三个集合(读,写,例外),然后调用select系统调用,然后更新各个sock的sp->currentEvents,表示各个sock的当前状态。

这两个函数在sockGen.c中实现,他们主要操作的数据是socket_t变量socketList中的handlerMask和currentEvents,socketList在sock.c中定义并主要由该文件中的socketAlloc,socketFree和socketPtr三个函数维护。

*/

if (socketReady(-1) || socketSelect(-1, 1000)) {

/*

该函数处理具体的sock事件

1,调用socketReady(sid)对socketList[sid]进行检查,看是否有sock事件

2,如果有sock事件,则调用socketDoEvent()函数,对事件进行处理

*/

            socketProcess(-1);

        }

        /*

该函数在cgi.c中实现,检查cgiRec变量cgilist,首先把cgi的结果输出,如果有的话,然后看cgi进程是否已对号束,如果结束,就清理该cgi进程。

Cgilist在函数websCgiHandler和websCgiCleanup中维护。

*/

        websCgiCleanup();

/*

该函数在websuemf.c中实现,功能是检查sched_t变量sched,断开超时的连接,sched变量在emfSchedCallback和emfUnschedCallback中维护

*/

        emfSchedProcess();

    }

/*

退出时的清理工作,永远不会执行到这里

*/

#ifdef WEBS_SSL_SUPPORT

    websSSLClose();

#endif

 

#ifdef USER_MANAGEMENT_SUPPORT

    umClose();

#endif

 

/*

 *  Close the socket module, report memory leaks and close the memory allocator

 */

    websCloseServer();

    socketClose();

#ifdef B_STATS

    memLeaks();

#endif

    bclose();

    return 0;

}

 

3.一些想法

1,  找出他们共同的数据结构

2,  找出对这些数据结构维护(操作)的函数

3,  从http的get或者是post流程来看程序

4,  整体架构如何掌握

5,  分模块,从全局的角度看各个模块的功能

6,  从main函数起,按树型结构一层层分析下去

 

 

选择第五种方法:

1,  sock模块,专门处理网络链接这一块,有这么几个文件:

sock.c和sockGen.c,sock.c是(维护)处理链接的socket_t数据结构,sockGen.c是(维护)处理链接的。

2,  对http协议数据进行操作(读取和分析),webc.c文件

3,  对具体数据的操作(asp,form…),handler.c文件

 

选择第三种方法来看程序:

假设有个http请求:从这个http请求到服务器的处理,然后返回这样一个过程来看goahead是怎么操作的?

1,写一个http请求的url和一个head

1,  写一个http请求的post的head

 

注:

因为这次要看通整个goahead代码,所以一下子不知道以什么思路来看。上面是一些想法,不知道从哪里开始分析一个项目的代码,也不知道取舍哪些进行程序结构和功能方面的分析。后来的结果是写出了下面的文字。

 

 

4.goahead mainloop源码分析

 

4.1 socketReady(-1)函数分析

socketReady函数检查已建立连接的socket中是否有以下事件,如果检查到一个,就返回1,如果没有检查到,就返回零。

(1)           sp->flags & SOCKET_CONNRESET,如果该socket的flag标志为SOCKET_CONNRESET(该标志在哪里设置(初始化)的?),则调用函数socketCloseConnection(该函数后面会解释)关闭该socket连接,然后返回0;

(2)           sp->currentEvents & sp->handlerMask,如果该socket当前的事件和他要处理的事件相同,就返回1,告诉调用socketReady的函数有socket准备好被处理了;

(3)           sp->handlerMask & SOCKET_READABLE && socketInputBuffered(sid) > 0,如果该socket要处理的事件是SOCKET_READABLE并且该socket的缓存中有可读的数据,则调用socketSelect函数(为什么在这里要调用这个函数,看了下socketSelect,应该是为了设置sp->currentEvents |= SOCKET_READABLE,所以这里应可以优化),然后返回1,告诉调用socketReady的函数有socket准备好被处理了;

(4)           socketReady函数根据传入的参数sid决定是检查id为sid的socket(当sid大于0),还是遍历整个socketList(当sid小于0),如果以上3个条件中没有一个满足,则返回0。

 

4.2 socketSelect(-1, 1000)函数分析

socketSelect函数是系统调用select的外包函数,该函数的主要功能就是监听(?)注册的socket事件集合,然后修改sp->currentEvents变量。

流程如下:

 转载goahead webserver源码分析

在主函数中,对socketSelect的调用是这样的:

if (socketReady(-1) || socketSelect(-1, 1000)),这样做并没有对socketSelect的返回值进行检查,也就是说当socketSelect返回-1时,该条件也会满足,从而程序也会往下走,所以,这个地方也是可以优化的。

 

4.3 socketProcess(-1)函数分析

socketProcess处理到达的socket事件,如果传入的参数是小于0,则会处理所有的socket的事件,如果大于0,则会处理指定的socket的事件。下面是主要过程:

if (socketReady(sid)) {

                     socketDoEvent(sp);

              }

socketReady()函数请看上面的解释,但不明白这里为什么还要用到这个函数,应该也是个可以优化的地方,我现在想到一个过程,应该是这样的:

If(socketSelect ()){

/*

If(socketReady())

   socketDoEvent();

*/

socketProcess();

}

后注:走完websGetInput()函数的分析后,因为这时仔细看到了更多的代码,上面的这个优化是不行的,因为socketReady(int sid)函数中,是sp->currentEvents,sp->handlerMask这几个标志位来判断是否有数据读写。socketDoEvent()是对已连接的socket通过改变sp->currentEvents和sp->handlerMask来分阶段的去处理数据,

并不是一路执行到底直到把这个连接关闭的。socketSelect ()是主要还是用在有新连接到来的时候,有新连接到来才会使这个函数返回真。socketDoEvent大致分两个阶段去处理一个连接,1是READ阶段,READ处理成功,便会设置状态到WRITE阶段,却不执行WRITE动作,2是WRITE阶段,WRITE执行完后才会结束这个连接。当第一次主循环时,socketDoEvent()执行的是READ,所以,如果按上一个代码段,第二次执行循环时,如socketSelect ()中没有新连接或数据到来,就不会往下执行了,而已有数据的连接将得不到立即的处理。socketReady(sid)可以检查已有连接是否有数据准备好读写,所以在这里优化是错误的。

下面看看socketDoEvent函数的实现:

转载goahead webserver源码分析

(1)socketDoEvent函数首先对socket的当前事件进行检查,如果是读事件并且是服务器监听socket上的读事件,说明有新连接到来,于是调用socketAccept()欢迎新连接,并使currentEvents为0,然后马上返回。

(2)如果当前不是读事件但是该socket原感兴趣的是读事件并且socket缓存中确有数据可读,那就置currentEvents为可读,这一步在socketReady函数中有做过,所以这里应该是可以去掉的。

(3)如果当前是写事件,那就看看该socket的写缓存中有没有数据,如果有并且有SOCKET_FLUSHING标志就全部输出该写缓存,这是为新的写事件做清理工作。

(4)调用事件处理函数sp->handler,该函数指针分别在两个地方进行初始化:

     1,在websDefaultHandler()函数中注册写事件,该函数在什么时候被调?

     2,在websAccept()函数中注册读事件

     两处都指向websSocketEvent()函数。等下解释这个函数。

(5)把currentEvents置为0。

 

4.4 socketAccept()函数分析

socketAccept()函数接收一个新的连接,并且调用用户注册的接收函数,这一个过程后,就把对socket_t结构的处理转换到了webs_t结构。

转载goahead webserver源码分析

Sp->accept函数指针在socketOpenConnection()函数中调用socketAlloc()函数注册给监听socket的socket_t数据结构,当socketAccept()函数处理新连接时,就会把自已的Sp->accept指针及其他几个属性通过调用socketAlloc(sp->host, sp->port, sp->accept, sp->flags)函数又给了新的连接,注意,调用完这个后,会更新新的nsp->flags &= ~SOCKET_LISTENING,把该连接和监听连接区别开。

然后,监听socket用自已的Sp->accept调用到websAccept()函数,但是传递的第一个参数是新连接的id:nid。这也应该是个技巧吧。

websAccept()函数功能:

 转载goahead webserver源码分析

经过websAccept()函数后,将走出socket层,来到webs_t结构层,以后对读和写的操作都通过操作webs_t这个数据结构来完成。

4.5 websSocketEvent()函数分析

websSocketEvent()函数处理socket的读和写事件:

 转载goahead webserver源码分析

(1)websSocketEvent()函数根据mask决定调用读还是写函数,wp->writeSocket函数指针在websDefaultHandler()中通过调用websSetRequestSocketHandler()进行注册,指向websDefaultWriteEvent()函数。

(2)websReadEvent()函数:

websReadEvent()函数处理读事件,我们怀疑这个函数有问题,会导致web服务器宕机,因此要重点分析。

转载goahead webserver源码分析

我们来看看该函数中用到的wp结构的四个状态:

先给出一个http的post头,看起来形象点:

POST /goform/formTest HTTP/1.1

Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*

Referer: http://192.168.90.50/forms.asp

Accept-Language: zh-cn

Content-Type: application/x-www-form-urlencoded

Accept-Encoding: gzip, deflate

User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; CIBA)

Host: 192.168.90.50

Content-Length: 32

Connection: Keep-Alive

Cache-Control: no-cache

 

name=adfs&address=fdsafdsa&ok=OK

 

(1)WEBS_BEGIN,该状态是在websAccept()函数调用wbesAlloc()函数初始化wp结构时被设置的,如果是在这个状态,该函数就调用websParseFirst()函数分析http头的第一行数据,在websParseFirst()中,主要会对wp结构的下面几个数据进行操作:

wp->flags |= WEBS_POST_REQUEST;

wp->query = bstrdup(B_L, query);

       wp->host = bstrdup(B_L, host);

       wp->path = bstrdup(B_L, path);

       wp->protocol = bstrdup(B_L, proto);

       wp->protoVersion = bstrdup(B_L, protoVer);

对wp->flags几个重要的赋值:WEBS_CGI_REQUEST,WEBS_ASP,WEBS_LOCAL_PAGE等,分析好第一行后,调用ringqFlush()函数把wp->header做相当于清零的操作,把各个指针都指向这个缓冲的开始处。如果websParseFirst()函数调用成功,就转到WEBS_HEADER状态:wp->state = WEBS_HEADER。按程序的流程,这个时候仍在循环中,马上就会转到WEBS_HEADER状态机,但是会这样吗?

(2)WEBS_HEADER,首先(1)以后(经过WEBS_BEGIN),除非websGetInput() ()函数调用不成功,否则就会执行到WEBS_HEADER状态机,这个时候我们就要看看websGetInput()函数了,因为这个函数如果调用不成功,是不会执行到WEBS_HEADER的。

websGetInput()函数分析:

函数功能:接收客户端的数据;

函数返回值:-1,表示出错或者请求已经被处理

0,  表示告诉调用者还有更多的数据待读取

1,  表示数据已经读好。

websGetInput()函数的处理过程:

 转载goahead webserver源码分析

注意上图中的两个注解,1是读取socket时,一次最多读256个字节,2是post的数据长度在websParseRequest(wp)函数中得到,也就是说如果一个连接首次调用websGetInput()函数,应该执行的是==0的那条分支。下面试着走一个流程,当websGetInput()函数接收上例post头时,会怎么处理。

首先,程序会进入==0分支,接着会进入socketGets()函数,我们在这个函数里去走一圈:

①socketGets()函数从socket中读取一行,然后返回,如现在读到的是:POST /goform/formTest HTTP/1.1

明显程序将直接走到//输出:这里,并返回1,websGetInput()函数的上层调用函数websReadEvent()开始进入状态机WEBS_BEGIN进行处理,分析post头的第一行,分析成功将状态转到WEBS_HEADER,这时第二次调用websGetInput()。

②这里wp结构除了state变量变了,其他都没变,所以len还是0,还是调用socketGets()函数从socket中读取一行,我们这都假设读成功,先不想不成功的事,现在我们到WEBS_HEADER状态机,把读到的数据放入wp->header缓存变量中,这样一直读到

Authorization: Basic YWRtaW46YWRtaW4=

这时,wp->header缓存中存放了除第一行以外的head,好,到这时,websReadEvent()还会再调用一次websGetInput(),我们看这回会有什么情况。

另外先说下WEBS_HEADER状态机中为什么会有ringqPutStr(&wp->header, T(" "));这一句,是因为在socketGets()函数以” ”为一行结束标志来返回这一行,但是并没有包括” ”,所以在这里总是会加上一个以便后面的程序做分析。

③接着读,还是走到socketGets()函数,这时将会读到” ”这一行,在socketGets()函数中这样处理:因为’ ’是不加入缓存的,当读到’ ’时,程序这样处理:

if (c == ' ') {

               len = ringqLen(lq);

               if (len > 0) {           

                    *buf = ballocAscToUni((char *)lq->servp, len);

               } else {

                    *buf = NULL;

               }

              

               ringqFlush(lq);

               return len;

}这里用得很巧妙,因为’ ’是不加入缓存,所以len = ringqLen(lq)将为0,这时理所当然返回0了,也就是读完head了,我们看当websGetInput()收到socketGets()的0返回值时,会怎么样处理。

④按上图,程序就会走到wp->state这个条件分支,当前状态是WEBS_HEADER,所以会执行websParseRequest()函数,这个函数会尽情的分解这个头文件,而马上要起到作用的是在分解时处理的一个变量:wp->flags |= WEBS_CLEN。 这是根据头中的Content-Length: 116

得到的。如果头中没有这行,会怎么办?那么首先wp->flags 中不会有WEBS_CLEN这一位,并且wp->clen也不会被初始化。

下一步进入wp->flags条件,就是上图①中的入口,程序如下:

if (wp->flags & WEBS_POST_REQUEST) {

                                   if (wp->flags & WEBS_CLEN) {

                                          wp->state = WEBS_POST_CLEN;

                                          clen = wp->clen;

                                   } else {

                                          wp->state = WEBS_POST;

                                          clen = 1;

                                   }

                                   if (clen > 0) {

                                          return 0;

                                   }

                                   return 1;

}如果是WEBS_POST_REQUEST,则继续到条件wp->flags,如果是WEBS_CLEN(如果是标准http头,肯定是有这个的),就把wp->state = WEBS_POST_CLEN,然后置clen = wp->clen,注意,这里clen可能是0,Content-Length: 0

如果wp->flags中没有WEBS_CLEN这一位,则设置wp->state = WEBS_POST;和clen = 1,接着根据clen的值大于0返回0,上面说了有可能等于零的情况,那么会返回1(表示没有数据可读了)。我们先看websGetInput()返回0给上级函数websReadEvent()时的情况。

在websReadEvent()中,如果websGetInput()返回0,则表明还有数据没读完,这时将会再次调用websGetInput(),这时程序会走进

if (wp->state == WEBS_POST_CLEN) {

              len = (wp->clen > WEBS_SOCKET_BUFSIZ) ? WEBS_SOCKET_BUFSIZ : wp->clen;

       } else {

              len = 0;

},那么这里条件len将会>0,于是我们走进上图中的len>0分支,调用socketRead()函数,这里不讨论出错情况,socketRead()函数将读完最大长度不超过WEBS_SOCKET_BUFSIZ的post数据,然后websGetInput()返回1,我们又回到websReadEvent()函数。

在websReadEvent()函数我们进入WEBS_POST_CLEN状态机,在这个处理模块将读完整个post数据,然后送websUrlHandlerRequest(wp)函数进行处理。这时websReadEvent()函数算是功得圆满了,置done为1退出函数。上面就是读客户端post数据并且该数据标有长度情况下的整个流程。

 

⑤在没有长度的情况下,如果有浏览器访问,除非浏览器有bug,正常是不会出现这种情况的。没长度时len=0,websGetInput()进入socketGets()那分支,然后一直读,读到出错(nbytes<0)或者是读完(nbytes=0)为止,我们先看<0那个分支,如果小于0就到条件EOF,如果是socket读完(走Y那个公支),那也就是正常结束情况,于是进入条件wp->state,如果是WEBS_POST状态机,则调用websUrlHandlerRequest(wp)函数进行处理,也就是在这里走完了post没有len情况下的流程。而我们再看回头看websReadEvent()函数中WEBS_POST状态机的功能只是把数据读到wp->header中。如果不是在WEBS_POST状态机,程序将调用websDone(wp, 0)函数直接把该连接关闭,为什么呢?如果程序能走到这里,又不是WEBS_POST,那更不可能是WEBS_POST_LEN状态,那只有可能是WEBS_BEGIN或WEBS_HEADER状态,WEBS_BEGIN只读第一行

POST /goform/wirelessAdvanced HTTP/1.1

而读这一行是不会出现EOF状态的,1是如果下面还有数据,出现EOF就说明这个head有问题,2是如果下面没有数据,应该是以” ”结尾的,如③所分析,socketGets()返回0,websGetInput()不会进入小于0的分支,所以这里程序的处理是把这个连接关闭了。

if (wp->state == WEBS_POST) {

                                   websUrlHandlerRequest(wp);

                            } else {

                                   websDone(wp, 0);

}

⑥现在websGetInput()函数还有一个分支没有分析,就是在④的情况下,没有进入上图①的接口,这种情况下会发生什么?首先请看下面的http头:

GET /forms.asp HTTP/1.1

Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*

Accept-Language: zh-cn

Accept-Encoding: gzip, deflate

User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; CIBA)

Host: 192.168.90.50

Connection: Keep-Alive

 

程序不进入①是因为wp->flags & WEBS_POST_REQUEST这个条件不成立,所以我给出上面的一个GET头,这时wp->flags中应该在WEBS_BEGIN状态机下的websParseFirst()函数中没有被设置为WEBS_POST_REQUEST,这里不得不插入一段websParseFirst()函数中的代码说明情况:

if (gstrcmp(op, T("GET")) != 0) {

              if (gstrcmp(op, T("POST")) == 0) {

                     wp->flags |= WEBS_POST_REQUEST;

              } else if (gstrcmp(op, T("HEAD")) == 0) {

                     wp->flags |= WEBS_HEAD_REQUEST;

              } else {

                     websError(wp, 400, T("Bad request type"));

                     return -1;

              }

}看来,对于GET头,wp->flags并不需要设置一个WEBS_GET_XXX什么的标志位。

原因说清了,按上图,程序进入websUrlHandlerRequest(wp)函数,websUrlHandlerRequest(wp)函数会怎么处理上面给出的这个GET头,websUrlHandlerRequest(wp)根据urlPrefix来调用注册好的回调函数,先说urlPrefix,他是指/goform/xxx,/cgi_bin/yyy中的/goform和/cgi_bin这些东西,如果一个URL是这样的:/forms.asp那么他的urlPrefix为””,在主main()函数中,注册了对这个urlPrefix的回调函数为:websUrlHandlerDefine(T(""), NULL, 0, websDefaultHandler,

              WEBS_HANDLER_LAST);

即websDefaultHandler()函数。websUrlHandlerRequest(wp)函数通过查找websUrlHandler[]数组会得到urlPrefix和回调函数的对应关系,然后调用回调函数,现在我们进到websDefaultHandler(),在socketProcess(-1)函数分析的第(4)点中有个疑问,到这里就不再是疑问了。websDefaultHandler()函数的最后调用:

websSetRequestSocketHandler(wp, SOCKET_WRITABLE, websDefaultWriteEvent);注册该wp连接写事件的回调函数,并把wp的感兴趣的事件handlerMask改为SOCKET_WRITABLE。这样当第二次执行主循环时,想想回到websSocketEvent()函数,就会调用到写事件的函数websDefaultWriteEvent(),通过wp->writeSocket指针。

4.6 websCgiCleanup()函数分析

该函数清除执行完的CGI进程。

4.7 emfSchedProcess()函数分析

 

emfSchedProcess()函数检查超时的连接,如果有超时的连接就会把这个连接清理掉,这里要注意程序中是怎么设置超时的起起始时间的。

在websReadEvent()函数中,调用websSetTimeMark(wp)为该连接设置一个时间戳,超时就是相对于这个时间的,但是请想下如果该连接一直没有数据到来的话,仅完成三次握手(不了解内核会不会对这样的连接有个超时机制,如果有,我下面就是白说),因为不可能执行到websReadEvent()函数,那么超时机制将对该连接无效,可以想象有很多这样的恶意连接没有被清除将会浪费系统资源,所以当accept这个连接的时候,就用websSetTimeMark(wp)为该连接设置一个时间戳,这个动作可以放在websAlloc ()函数中,这个函数被websAccept()函数调用,作用是初始化一个新的webs_t结构的连接。如果在超时前有数据来,这个时间戳将在websReadEvent()函数更改成新的;如果没有新的数据,那超时之后,服务器将断开这个连接,这样并不会影响系统运行,因此是可行的。

可以用telnet 192.168.0.50 80这样连接上服务器但是不发任何字符到服务器进行测试。

 


推荐阅读
  • C++ 异步编程中获取线程执行结果的方法与技巧及其在前端开发中的应用探讨
    本文探讨了C++异步编程中获取线程执行结果的方法与技巧,并深入分析了这些技术在前端开发中的应用。通过对比不同的异步编程模型,本文详细介绍了如何高效地处理多线程任务,确保程序的稳定性和性能。同时,文章还结合实际案例,展示了这些方法在前端异步编程中的具体实现和优化策略。 ... [详细]
  • Python 伦理黑客技术:深入探讨后门攻击(第三部分)
    在《Python 伦理黑客技术:深入探讨后门攻击(第三部分)》中,作者详细分析了后门攻击中的Socket问题。由于TCP协议基于流,难以确定消息批次的结束点,这给后门攻击的实现带来了挑战。为了解决这一问题,文章提出了一系列有效的技术方案,包括使用特定的分隔符和长度前缀,以确保数据包的准确传输和解析。这些方法不仅提高了攻击的隐蔽性和可靠性,还为安全研究人员提供了宝贵的参考。 ... [详细]
  • 本文详细解析了 Android 系统启动过程中的核心文件 `init.c`,探讨了其在系统初始化阶段的关键作用。通过对 `init.c` 的源代码进行深入分析,揭示了其如何管理进程、解析配置文件以及执行系统启动脚本。此外,文章还介绍了 `init` 进程的生命周期及其与内核的交互方式,为开发者提供了深入了解 Android 启动机制的宝贵资料。 ... [详细]
  • 在Cisco IOS XR系统中,存在提供服务的服务器和使用这些服务的客户端。本文深入探讨了进程与线程状态转换机制,分析了其在系统性能优化中的关键作用,并提出了改进措施,以提高系统的响应速度和资源利用率。通过详细研究状态转换的各个环节,本文为开发人员和系统管理员提供了实用的指导,旨在提升整体系统效率和稳定性。 ... [详细]
  • 在优化Nginx与PHP的高效配置过程中,许多教程提供的配置方法存在诸多问题或不良实践。本文将深入探讨这些常见错误,并详细介绍如何正确配置Nginx和PHP,以实现更高的性能和稳定性。我们将从Nginx配置文件的基本指令入手,逐步解析每个关键参数的最优设置,帮助读者理解其背后的原理和实际应用效果。 ... [详细]
  • 本文探讨了如何利用Java代码获取当前本地操作系统中正在运行的进程列表及其详细信息。通过引入必要的包和类,开发者可以轻松地实现这一功能,为系统监控和管理提供有力支持。示例代码展示了具体实现方法,适用于需要了解系统进程状态的开发人员。 ... [详细]
  • 尽管我们尽最大努力,任何软件开发过程中都难免会出现缺陷。为了更有效地提升对支持部门的协助与支撑,本文探讨了多种策略和最佳实践,旨在通过改进沟通、增强培训和支持流程来减少这些缺陷的影响,并提高整体服务质量和客户满意度。 ... [详细]
  • 利用树莓派畅享落网电台音乐体验
    最近重新拾起了闲置已久的树莓派,这台小巧的开发板已经沉寂了半年多。上个月闲暇时间较多,我决定将其重新启用。恰逢落网电台进行了改版,回忆起之前在树莓派论坛上看到有人用它来播放豆瓣音乐,便萌生了同样的想法。通过一番调试,终于实现了在树莓派上流畅播放落网电台音乐的功能,带来了全新的音乐享受体验。 ... [详细]
  • 基址获取与驱动开发:内核中提取ntoskrnl模块的基地址方法解析
    基址获取与驱动开发:内核中提取ntoskrnl模块的基地址方法解析 ... [详细]
  • 本文详细解析了使用C++实现的键盘输入记录程序的源代码,该程序在Windows应用程序开发中具有很高的实用价值。键盘记录功能不仅在远程控制软件中广泛应用,还为开发者提供了强大的调试和监控工具。通过具体实例,本文深入探讨了C++键盘记录程序的设计与实现,适合需要相关技术的开发者参考。 ... [详细]
  • 服务器部署中的安全策略实践与优化
    服务器部署中的安全策略实践与优化 ... [详细]
  • 本文全面解析了 Python 中字符串处理的常用操作与技巧。首先介绍了如何通过 `s.strip()`, `s.lstrip()` 和 `s.rstrip()` 方法去除字符串中的空格和特殊符号。接着,详细讲解了字符串复制的方法,包括使用 `sStr1 = sStr2` 进行简单的赋值复制。此外,还探讨了字符串连接、分割、替换等高级操作,并提供了丰富的示例代码,帮助读者深入理解和掌握这些实用技巧。 ... [详细]
  • 为了确保iOS应用能够安全地访问网站数据,本文介绍了如何在Nginx服务器上轻松配置CertBot以实现SSL证书的自动化管理。通过这一过程,可以确保应用始终使用HTTPS协议,从而提升数据传输的安全性和可靠性。文章详细阐述了配置步骤和常见问题的解决方法,帮助读者快速上手并成功部署SSL证书。 ... [详细]
  • Nginx 反向代理配置与应用指南
    本文详细介绍了 Nginx 反向代理的配置与应用方法。首先,用户可以从官方下载页面(http://nginx.org/en/download.html)获取最新稳定版 Nginx,推荐使用 1.14.2 版本。下载并解压后,通过双击 `nginx.exe` 文件启动 Nginx 服务。文章进一步探讨了反向代理的基本原理及其在实际应用场景中的配置技巧,包括负载均衡、缓存管理和安全设置等,为用户提供了一套全面的实践指南。 ... [详细]
  • JavaScript XML操作实用工具类:XmlUtilsJS技巧与应用 ... [详细]
author-avatar
石某俊
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有