goahead移植的源码有很多BUG ,你能告诉我吗? 谢谢!

&&&&goahead源码分析
goahead_webserver源码分析
嵌到我的页面
<input type="text" readonly="true" value="">
若举报审核通过,可奖励20下载分
被举报人:
举报的资源分:
请选择类型
资源无法下载
资源无法使用
标题与实际内容不符
含有危害国家安全内容
含有反动色情等内容
含广告内容
版权问题,侵犯个人或公司的版权
*详细原因:
VIP下载&&免积分60元/年(1200次)
您可能还需要
数据库下载排行&&&&&&&&GoAhead2.5 源代码分析
正在努力加载播放器,请稍等…
正在努力加载播放器
大小:460.76KB&&所需金币:50
&& & 金币不足怎么办?
下载量:-次 浏览量:239次
贡献时间: 9:00:00
文档标签:
已有-位用户参与评分
同类热门文档
你可能喜欢
看过这篇文档的还看过
阅读:19999&&下载:499
阅读:19999&&下载:353
阅读:4834&&下载:230
阅读:1448&&下载:84
阅读:389&&下载:8
阅读:875&&下载:7
阅读:392&&下载:4
阅读:99&&下载:2
阅读:410&&下载:2
阅读:188&&下载:1
该用户的其他文档
所需财富值:
50文件大小:460.76KB
您当前剩余财富值:&&
举报该文档含有违规或不良信息。
反馈该文档无法正常浏览。
举报该文档为重复文档。
举报该文档侵犯版权。
例: /help.shtmlLinux 系统软件环境配置管理(2)
http://blog.chinaunix.net/uid--id-2904054.html
转载源码分析
一个文本架构图
打开服务器
初化结构注册回调函数等
把结构加入数组
初始化结构的数组
将和回调函数绑定在中
初始化结构的数组
将和回调函数绑定在中
初始化结构把名字和回调函数名放进结构
把结构放进表中
初始化结构把名字和回调函数名放进结构
把结构放进表中
轮询轮询中的
中的几个变量改变中的
如果有新的连接来自就调用
调用初始化结构
把结构加入数组
调用回调函数
&&&&&& |--如果不是新的连接就查找数组调用回调函数
做一些检查
把注册为读事件初始化等更新对应的数组&#20540;等
判断读写操作
查找数组调用和对应的回调函数等
写调用回调函数
跟据查找表调用用户定义的函数
处理默认的请求,包括页面
注册默认的写事件函数
把注册为写事件初始化等更新对应的数组
写数据不包括页面
函数很简短,所以可以对他的代码进行一行一行注释,如下:
首先分配一个大的内存块(字节),以后只要是以开头的对内存操作的函数都是在这个已经分好的内存块上的操作,这些操作在中实现。
初始化用户管理部分,打开服务器,注册处理函数。
用户管理部分在中实现,
服务器的初始化是在和中实现
处理函数在中实现
初始化认证部分
注:在这个文档中对认证不做研究
,函数检查是否有准备好的事件
,函数首先把各个感兴趣的事件()注册给三个集合(读,写,例外),然后调用系统调用,然后更新各个的,表示各个的当前状态。
这两个函数在中实现,他们主要操作的数据是变量中的和,在中定义并主要由该文件中的,和三个函数维护。
该函数处理具体的事件
,调用对进行检查,看是否有事件
,如果有事件,则调用函数,对事件进行处理
该函数在中实现,检查变量,首先把的结果输出,如果有的话,然后看进程是否已对号束,如果结束,就清理该进程。
在函数和中维护。
该函数在中实现,功能是检查变量,断开超时的连接,变量在和中维护
退出时的清理工作,永远不会执行到这里
,找出他们共同的数据结构
,找出对这些数据结构维护(操作)的函数
,从的或者是流程来看程序
,整体架构如何掌握
,分模块,从全局的角度看各个模块的功能
,从函数起,按树型结构一层层分析下去
选择第五种方法:
,模块,专门处理网络链接这一块,有这么几个文件:
和,是(维护)处理链接的数据结构,是(维护)处理链接的。
,对协议数据进行操作(读取和分析),文件
,&对具体数据的操作(),文件
选择第三种方法来看程序:
假设有个请求:从这个请求到服务器的处理,然后返回这样一个过程来看是怎么操作的?
,写一个请求的和一个
,写一个请求的的
因为这次要看通整个代码,所以一下子不知道以什么思路来看。上面是一些想法,不知道从哪里开始分析一个项目的代码,也不知道取舍哪些进行程序结构和功能方面的分析。后来的结果是写出了下面的文字。
函数检查已建立连接的中是否有以下事件,如果检查到一个,就返回,如果没有检查到,就返回零。
(),如果该的标志为(该标志在哪里设置(初始化)的?),则调用函数(该函数后面会解释)关闭该连接,然后返回;
(),如果该当前的事件和他要处理的事件相同,就返回,告诉调用的函数有准备好被处理了;
(),如果该要处理的事件是并且该的缓存中有可读的数据,则调用函数(为什么在这里要调用这个函数,看了下,应该是为了设置,所以这里应可以优化),然后返回,告诉调用的函数有准备好被处理了;
()函数根据传入的参数决定是检查为的(当大于),还是遍历整个(当小于),如果以上个条件中没有一个满足,则返回。
函数是系统调用的外包函数,该函数的主要功能就是监听(?)注册的事件集合,然后修改变量。
流程如下:
在主函数中,对的调用是这样的:
,这样做并没有对的返回&#20540;进行检查,也就是说当返回时,该条件也会满足,从而程序也会往下走,所以,这个地方也是可以优化的。
处理到达的事件,如果传入的参数是小于,则会处理所有的的事件,如果大于,则会处理指定的的事件。下面是主要过程:
函数请看上面的解释,但不明白这里为什么还要用到这个函数,应该也是个可以优化的地方,我现在想到一个过程,应该是这样的:
后注:走完函数的分析后,因为这时仔细看到了更多的代码,上面的这个优化是不行的,因为函数中,是,这几个标志位来判断是否有数据读写。是对已连接的通过改变和来分阶段的去处理数据,
并不是一路执行到底直到把这个连接关闭的。是主要还是用在有新连接到来的时候,有新连接到来才会使这个函数返回真。大致分两个阶段去处理一个连接,是阶段,处理成功,便会设置状态到阶段,却不执行动作,是阶段,执行完后才会结束这个连接。当第一次主循环时,执行的是,所以,如果按上一个代码段,第二次执行循环时,如中没有新连接或数据到来,就不会往下执行了,而已有数据的连接将得不到立即的处理。可以检查已有连接是否有数据准备好读写,所以在这里优化是错误的。
下面看看函数的实现:
()函数首先对的当前事件进行检查,如果是读事件并且是服务器监听上的读事件,说明有新连接到来,于是调用欢迎新连接,并使为,然后马上返回。
()如果当前不是读事件但是该原感兴趣的是读事件并且缓存中确有数据可读,那就置为可读,这一步在函数中有做过,所以这里应该是可以去掉的。
()如果当前是写事件,那就看看该的写缓存中有没有数据,如果有并且有标志就全部输出该写缓存,这是为新的写事件做清理工作。
()调用事件处理函数,该函数指针分别在两个地方进行初始化:
,在函数中注册写事件,该函数在什么时候被调?
& 2,在函数中注册读事件
两处都指向函数。等下解释这个函数。
()把置为。
函数接收一个新的连接,并且调用用户注册的接收函数,这一个过程后,就把对结构的处理转换到了结构。
函数指针在函数中调用函数注册给监听的数据结构,当函数处理新连接时,就会把自已的指针及其他几个属性通过调用函数又给了新的连接,注意,调用完这个后,会更新新的,把该连接和监听连接区别开。
然后,监听用自已的调用到函数,但是传递的第一个参数是新连接的:。这也应该是个技巧吧。
函数功能:
经过函数后,将走出层,来到结构层,以后对读和写的操作都通过操作这个数据结构来完成。
函数处理的读和写事件:
()函数根据决定调用读还是写函数,函数指针在中通过调用进行注册,指向函数。
()函数:
函数处理读事件,我们怀疑这个函数有问题,会导致服务器宕机,因此要重点分析。
我们来看看该函数中用到的结构的四个状态:
先给出一个的头,看起来形象点:
(),该状态是在函数调用函数初始化结构时被设置的,如果是在这个状态,该函数就调用函数分析头的第一行数据,在中,主要会对结构的下面几个数据进行操作:
wp-&protocol = bstrdup(B_L, proto);
wp-&protoVersion = bstrdup(B_L, protoVer);
对几个重要的赋&#20540;:,,等,分析好第一行后,调用函数把做相当于清零的操作,把各个指针都指向这个缓冲的开始处。如果函数调用成功,就转到状态:。按程序的流程,这个时候仍在循环中,马上就会转到状态机,但是会这样吗?
(),首先()以后(经过),除非函数调用不成功,否则就会执行到状态机,这个时候我们就要看看函数了,因为这个函数如果调用不成功,是不会执行到的。
函数分析:
函数功能:接收客户端的数据;
函数返回&#20540;:,表示出错或者请求已经被处理
,表示告诉调用者还有更多的数据待读取
,表示数据已经读好。
函数的处理过程:
注意上图中的两个注解,是读取时,一次最多读个字节,是的数据长度在函数中得到,也就是说如果一个连接首次调用函数,应该执行的是的那条分支。下面试着走一个流程,当函数接收上例头时,会怎么处理。
首先,程序会进入分支,接着会进入函数,我们在这个函数里去走一圈:
①函数从中读取一行,然后返回,如现在读到的是:
明显程序将直接走到输出:这里,并返回,函数的上层调用函数开始进入状态机进行处理,分析头的第一行,分析成功将状态转到,这时第二次调用。
②这里结构除了变量变了,其他都没变,所以还是,还是调用函数从中读取一行,我们这都假设读成功,先不想不成功的事,现在我们到状态机,把读到的数据放入缓存变量中,这样一直读到
这时,缓存中存放了除第一行以外的,好,到这时,还会再调用一次,我们看这回会有什么情况。
另外先说下状态机中为什么会有这一句,是因为在函数以为一行结束标志来返回这一行,但是并没有包括,所以在这里总是会加上一个以便后面的程序做分析。
③接着读,还是走到函数,这时将会读到这一行,在函数中这样处理:因为是不加入缓存的,当读到时,程序这样处理:
这里用得很巧妙,因为是不加入缓存,所以将为,这时理所当然返回了,也就是读完了,我们看当收到的返回&#20540;时,会怎么样处理。
④按上图,程序就会走到这个条件分支,当前状态是,所以会执行函数,这个函数会尽情的分解这个头文件,而马上要起到作用的是在分解时处理的一个变量:。这是根据头中的
得到的。如果头中没有这行,会怎么办?那么首先中不会有这一位,并且也不会被初始化。
下一步进入条件,就是上图①中的入口,程序如下:
如果是,则继续到条件,如果是(如果是标准头,肯定是有这个的),就把,然后置,注意,这里可能是,
如果中没有这一位,则设置和,接着根据的&#20540;大于返回,上面说了有可能等于零的情况,那么会返回(表示没有数据可读了)。我们先看返回给上级函数时的情况。
在中,如果返回,则表明还有数据没读完,这时将会再次调用,这时程序会走进
,那么这里条件将会,于是我们走进上图中的分支,调用函数,这里不讨论出错情况,函数将读完最大长度不超过的数据,然后返回,我们又回到函数。
在函数我们进入状态机,在这个处理模块将读完整个数据,然后送函数进行处理。这时函数算是功得圆满了,置为退出函数。上面就是读客户端数据并且该数据标有长度情况下的整个流程。
⑤在没有长度的情况下,如果有浏览器访问,除非浏览器有,正常是不会出现这种情况的。没长度时,进入那分支,然后一直读,读到出错()或者是读完()为止,我们先看那个分支,如果小于就到条件,如果是读完(走那个公支),那也就是正常结束情况,于是进入条件,如果是状态机,则调用函数进行处理,也就是在这里走完了没有情况下的流程。而我们再看回头看函数中状态机的功能只是把数据读到中。如果不是在状态机,程序将调用函数直接把该连接关闭,为什么呢?如果程序能走到这里,又不是,那更不可能是状态,那只有可能是或状态,只读第一行
而读这一行是不会出现状态的,是如果下面还有数据,出现就说明这个有问题,是如果下面没有数据,应该是以结尾的,如③所分析,返回,不会进入小于的分支,所以这里程序的处理是把这个连接关闭了。
⑥现在函数还有一个分支没有分析,就是在④的情况下,没有进入上图①的接口,这种情况下会发生什么?首先请看下面的头:
程序不进入①是因为这个条件不成立,所以我给出上面的一个头,这时中应该在状态机下的函数中没有被设置为,这里不得不插入一段函数中的代码说明情况:
看来,对于头,并不需要设置一个什么的标志位。
原因说清了,按上图,程序进入函数,函数会怎么处理上面给出的这个头,根据来调用注册好的回调函数,先说,他是指,中的和这些东西,如果一个是这样的:那么他的为,在主函数中,注册了对这个的回调函数为:
即函数。函数通过查找数组会得到和回调函数的对应关系,然后调用回调函数,现在我们进到,在函数分析的第()点中有个疑问,到这里就不再是疑问了。函数的最后调用:
注册该连接写事件的回调函数,并把的感兴趣的事件改为。这样当第二次执行主循环时,想想回到函数,就会调用到写事件的函数,通过指针。
该函数清除执行完的进程。
函数检查超时的连接,如果有超时的连接就会把这个连接清理掉,这里要注意程序中是怎么设置超时的起起始时间的。
在函数中,调用为该连接设置一个时间戳,超时就是相对于这个时间的,但是请想下如果该连接一直没有数据到来的话,仅完成三次握手(不了解内核会不会对这样的连接有个超时机制,如果有,我下面就是白说),因为不可能执行到函数,那么超时机制将对该连接无效,可以想象有很多这样的恶意连接没有被清除将会浪费系统资源,所以当这个连接的时候,就用为该连接设置一个时间戳,这个动作可以放在函数中,这个函数被函数调用,作用是初始化一个新的结构的连接。如果在超时前有数据来,这个时间戳将在函数更改成新的;如果没有新的数据,那超时之后,服务器将断开这个连接,这样并不会影响系统运行,因此是可行的。
可以用这样连接上服务器但是不发任何字符到服务器进行测试。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:4653次
排名:千里之外
转载:12篇
(1)(1)(4)(4)(2)(1)(1)3214人阅读
web开发(2)
1.一个txt文本架构图
&&&&& |--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,
|&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
|--把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();
&*& 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();
#ifdef USER_MANAGEMENT_SUPPORT
&&& umClose();
&*& Close the socket module, report memory leaks and close the memory allocator
&&& websCloseServer();
&&& socketClose();
#ifdef B_STATS
&&& memLeaks();
&&& 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变量。
流程如下:
在主函数中,对socketSelect的调用是这样的:
(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函数的实现:
(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结构。
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()函数功能:
经过websAccept()函数后,将走出socket层,来到webs_t结构层,以后对读和写的操作都通过操作webs_t这个数据结构来完成。
4.5 websSocketEvent()函数分析
websSocketEvent()函数处理socket的读和写事件:
(1)websSocketEvent()
函数根据mask决定调用读还是写函 数,wp-&writeSocket函数指针在websDefaultHandler()中通过调用
websSetRequestSocketHandler()进行注册,指向websDefaultWriteEvent()函数。
(2)websReadEvent()函数:
websReadEvent()函数处理读事件,我们怀疑这个函数有问题,会导致web服务器宕机,因此要重点分析。
我们来看看该函数中用到的wp结构的四个状态:
先给出一个http的post头,看起来形象点:
POST /goform/formTest HTTP/1.1
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 ( 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()函数的处理过程:
意上图中的两个注解,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);
&&&&&&&&&&&&&&
}这里用得很巧妙,因为& &是不加入缓存,所以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-&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& } 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-&
&&&&&& } 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
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 ( 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)为该连接设置一个时间戳,这个动作可以放在
()函数中,这个函数被websAccept()函数调用,作用是初始化一个新的webs_t结构的连接。如果在超时前有数据来,这个时间戳将在
websReadEvent()函数更改成新的;如果没有新的数据,那超时之后,服务器将断开这个连接,这样并不会影响系统运行,因此是可行的。
可以用telnet 192.168.0.50 80这样连接上服务器但是不发任何字符到服务器进行测试。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:17082次
排名:千里之外
原创:11篇
评论:10条}

我要回帖

更多关于 goahead移植 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信