当前位置: 首页 > 科技观察

HTTP服务器:一个穷学生的逆袭

时间:2023-03-19 18:25:13 科技观察

刚毕业的时候,国家还在负责分配工作,我哥们张大发被分配到一个叫数据库的大城市,每天坐着高端大气上档次机房内,专门进行了SQL查询优化,工作稳定舒适。隔壁宿舍的小白被送到了编译镇,专门把C源文件编译成EXE程序。虽然累,但技术含量很高,工资高,假期多。我的成绩不是很好,典型的差生,四级补考了两次,被送到了一个不知道名字的村子。据说我要处理HTTP请求。这个村子其实就是一台破旧的电脑,这让我欣慰。是的,我可以上网,还可以时不时和我的哥们交流。但是辅导员说我们都有美好的未来。HTTPServer1.0HTTP是个新东西,能稍微调动一下我的工作兴趣,不会沉沦其中。一上班,操作系统老大就扔给我一大堆文档:“这就是HTTP协议,我两天就看完了!”以我的英文水平,两天不吃不喝睡不下几十页的英文HTTP协议。看不完,死猪不怕开水,慢慢磨。两周后,我终于明白HTTP是怎么回事了:无非是某台电脑上的浏览器向我这破电脑发送了一个预定义的文本(HTTPRequest),然后我在这里处理(通常是到从硬盘中取出一个后缀为html的文件),然后将文件通过文本(HTTPResponse)发回,就这么简单。唯一麻烦的实现是,我必须要求操作系统为我建立一个HTTP层下的TCP连接通道,因为所有的文本数据都必须通过这些TCP通道来接收和发送,这些通道是用套接字建立的。弄清楚原理后,我很快创建了第一个版本的程序,它是这样的:(注:详见文章《张大胖的socket》)你看,这些socket、bind、listen、accept……都是操作系统老大提供的接口,我能做的就是组装它们:先监听80端口,然后进入死循环,如果有连接请求,接受(accept),新建一个socket,最后通过这个socket用于接收和发送http数据。老板给我的程序取了个名字,HttpServer,version1.0。这个名字听起来很高端,我喜欢。我兴冲冲的拿来做实验,程序启动了,我“蹲”在了80端口上,过了一会儿,有一个连接请求。赶快接受吧,新建一个socket,就成功了!接下来,我需要从套接字读取HTTP请求。.但是这个rec??eivecall好慢,我等了100毫秒都没有反应!我被屏蔽了(block)!操作系统老大说:“你别着急,我也在等着从网卡上读取数据呢,读取完以后我会复制给你的。”我很高兴放松和休息。但是操作系统老大却说:“你别说,以后还有很多浏览器发起连接,你不能在这里歇着。”不想休息怎么办?接收呼叫在这里被阻止。Queue,把CPU让给别人用还能干什么?老大说:“哎,你在大学没听说过太多进程吗?你现在明明是单进程,一堵就完了,想办法用多进程,每个进程处理一个请求!”“老大教我忘记多进程并发编程了。HTTP2.0:多进程多进程的思想很简单。当accept连接时,对于这个新的socket,并不在主进程中处理,而是创建一个新的子进程来接管。这样主进程就不会阻塞在receive上,可以继续接受新的连接。我重写了代码并将HTTP服务器升级到V2.0。这一次它运行得更流畅并且可以同时处理许多连接。这时候Web刚刚兴起,我的HTTPServer访问的人不多,每分钟只有几十个连接,我应付自如。因为是新东西,我还有资本向做数据库的小明和做编译的小白吹嘘,告诉他们我是网络高手。几年的时间,Web发展迅速,我手头的那台破旧的机器失败了。我换上了一台性能强大的服务器,搬到了四季如春的机房。现在每秒有几百个连接请求,有的连接持续的时间相当长,所以我经常要创建成百上千个进程来处理,每个进程都会消耗大量的系统资源。显然操作系统老大应接不暇了。他说:“我们不能再这样做了。这么多流程,光是流程切换我就累死了。”“为什么我不为每个Socket连接使用线程而不是进程?”还是得换线程,想办法限制人数。"我怎么限制呢?我只能说同时我只能支持x个连接,其他连接只能排队,这个肯定不是什么好办法。HTTPServer3.0:Selectmodel老大说的:》我们仔细总结了一下,对我来说,一个Socket连接就是一个所谓的文件描述符(FileDescriptor,简称fd,是一个整数),这个fd的背后是一个简单的数据结构,但是我们使用了一个非常重量级的thing'process'来表示对它的读写操作,有点浪费。”我说:“我们为什么不切换回单进程模型呢?但是我们会回到原来的方式,如果接收被阻塞,什么也做不了。”“单一流程并非不可能,但我们必须改变我们的工作方式。“改成什么?”我搞不懂老板想卖什么。“想想你被屏蔽的本质原因,不就是其他浏览器还没有把数据发过来,所以我不能给你,而你又迫不及待想再看,我只好屏蔽你了。”单进程的话,一旦阻塞,就没办法了。”“对,就是这样。”“所以你接受客户端连接后,不能这么着急读取,就这样吧方式。你的每一个socketfd都有一个编号。每次你告诉我一批socket的编号,你就可以阻塞和休息。”【注意:实际上,socketfd的编号在HTTPServer和操作系统,而是一个叫fd_set的数据结构]我问:“这不是和以前一样吗?原来调用receive的时候阻塞了,还是阻塞了。”“听我说,我会检查这些后台有编号的socket,如果我发现这些socket可以读写,我会标记对应的socket,唤醒你去处理这些socket的数据。说说你的socketfds,然后再次进入blocking,如此循环往复。”我有点明白了:“这是我们两个人的一种交流方式。我告诉你我要等什么,然后阻止。万一出了事,你叫醒我,让我做事。””“对,关键是你等我的通知。我把你从阻塞状态唤醒后,你必须遍历所有的socketfds(其实就是fd_set的数据结构),看看谁有mark,谁有marked的就相应处理。我将这种方法称为选择模型。”我用select方法重写了HTTP服务器,抛弃了一个进程一个socket请求的模式,现在可以用一个进程处理所有的socket。HTTPServer4.0:epoll,调用select,运行一段时间当然,效果还不错,我就跟老板说了socketfd,然后等他通知我,有一次不小心问了老板:“我最多可以告诉你多少个socketfd?”“1024.”“也就是说我每个进程最多只能监控1024个sockets?”“是的,你可以考虑多用几个进程。”“这是一种方式,但是“select”方式用的比较多,我发现缺点。最大的问题是我需要把socket的编号(其实就是fd_set数据结构)拷贝给操作系统老大。很耗资源,我阻塞恢复后,需要遍历1000多个socketfds,看看有没有flags需要处理。实际情况是很多socket都不活跃,浏览器有一段时间没有数据发送,这1000多个socket中可能只有几十个需要真正处理,但是我要检查所有的socketfds,这很烦人。老大能告诉我换过的sockets吗??我把这个想法跟老板说了,他说:“嗯,现在访问量越来越大,select方法已经不符合要求了,我们要与时俱进,我想到了一个新的方法叫epoll。。”“看,epoll和select的使用其实差不多,”老大继续说道:“不同的是,我只会告诉你可以读写的socket,你只需要处理这些可以读写的socket准备好。”“看来老板考虑的很周到,这种方式对我来说简单多了。”》再次使用epoll升级了HTTPServer,由于不需要遍历所有的集合,只需要处理变化的和活跃的socket文件描述符,系统的处理能力得到了很大的提升。我的HTTPServer受到了广泛的欢迎,全世界有无数人在使用它。最后,数据库闺蜜小明也知道了,他问我:“大家都说你可以轻松支持数万并发连接.是这样吗?”我谦虚地说:“谢谢,其实我们要优化系统。他说:“太棒了,你的孩子很幸运。”我回答:辅导员不是说我毕业的时候大家前途无量吗。》后记:最近好几个人问我关于select和epoll的问题,其实我几年前写过一篇文章,只是有些小错误,今天整理一下再发上来。