0x14-套接字编程-HTTP服务器(2)
HTTP服务器的结构
- HTTP服务器 本质上就是一个 TCP的接收端 程序
- 但凡一个正常的 TCP 接收端程序,都逃不过那几个流程:
- 创建监听
socket
-> 绑定端口,IP -> 监听socket
-> 接受新连接 -> 处理读写… -> 关闭完成的连接 - 其中前三步比较固定,最多对这个监听用的
socket
,进行一些优化处理,设置一些属性之类的,但那都是固定模式,想想就能明白。硬要说重要的地方,也就是在于是否把socket
设为非阻塞(non-blocking)了。 - 后面几步,每个都是很重要的环节,需要细细设计才行
- 创建监听
所谓非阻塞,我还是不班门弄斧了,请移步 UNIX网络编程-卷1-中文·第三版 127页(英文版160页) 的图
6-6
,清楚的对比了,阻塞,非阻塞,异步,I/O复用的区别和含义。十分建议写网络程序之前,去把这本书的某些章节大致过一遍。
- 对于这章节需要写的这个服务器而言,采用的是经典且流行的 I/O复用+非阻塞套接字(socket)+多线程(线程池) 结构。
- 呐,又出现一个新的知识点,I/O复用,这是什么鬼。
I/O复用
- 我给一个不太严密的解释,那就是 将你这个程序需要等待的地方,集中起来
- 打个比方:
- 假设有100个新连接,被你的监听套接字给成功接受(
accept()
)了 - 这时候并不是所有新连接都立刻有数据可以读,那此时你有两种选择:阻塞,非阻塞。但不论是哪一种都会导致同一个结果
- 阻塞: 那假设你只有一个线程处理这100个连接,万一要是正好处理到这个暂时没有数据的连接,就要一直等待它的数据到来,后面的几十个连接都要闲着;假设有多个线程同时处理,理由还是一样,换汤不换药,而且难道你还能开100个线程去处理吗?那如果更多的连接呢?
- 非阻塞: 比阻塞看起来稍微好一些,因为如果没有数据到来的话,那就直接跳过这个连接,直接去处理下一个连接了,但是你想想,这不就是遍历了吗?万一连接量一大,假设上万,而且只有少数的几个连接有数据活跃,这无用功做的是不是太多了?多开几个线程去平摊压力?那么要开多少比较合适?
- 这时候喜欢偷懒的程序员,自然就不愿意了,于是考虑是否可以有一个,让我们可以在单个线程的情况下还能够只处理那些活跃的连接?
- 这时候出现了所谓的 I/O复用 技术,说是技术,因为它使用的还是同步型的操作(
read, write
),只不过套接字设为非阻塞的了。 - Linux平台下的
epoll
, Unix(包括Mac)平台下的kqueue
, Windows平台下的IOCP
,各平台通用的select
,poll
,还有几个历史实现就不赘述了。 - 最后这两个
select
,poll
在活跃连接明显少于总连接数的情况下,性能比前三个要差许多,故本章使用的是epoll
,(当然还有资料比较多的原因啦
- 假设有100个新连接,被你的监听套接字给成功接受(
说说
epoll
的工作- 首先它帮我们管理着所有的套接字,用来监听这些套接字哪些有了数据,就返回谁。
- 将所有等待,阻塞都集中在了一个地方,那就是
epoll_wait()
调用上 - 而且可以针对不同的事件进行不同的监听,这就是事件驱动这种模式的由来
事件驱动
- 简单来说,就是针对某种事件进行触发的一种编程模式
- 具体来说,假设你在网络编程,正在处理一个套接字,由于TCP是全双工的,意味着这个TCP套接字是可读可写,问题来了,什么时候可读,什么时候可写呢?这就延伸出了事件,读事件,写事件,错误事件等
- 可以通过
epoll_clt()
来设置要监听的事件,当然也可以同时监听多个事件,看你的设计了。
具体的
epoll
接口的详细介绍,可以直接在Linux上,使用man epoll
进行查看手册,这是基本功。epoll_create
,epoll_ctl
,epoll_wait
服务器结构
- 继续回到服务器结构
- 上面简单的讲述了一下什么是 I/O复用,以及将会用到的具体实现
epoll
。那具体说一下,整个程序的流程 - 还是老规矩,写程序之前要先构思,自己在纸上画一画,大概的流程是什么
- 问题: 想要完整处理一个HTTP请求,需要哪些步骤?
- 解析HTTP请求报文
- 创建HTTP回复报文
- 逻辑就这么简单啊,但是加上细节部分,就会稍微麻烦一些了:
- 完整地 从套接字中,读取 HTTP请求报文
- 解析 HTTP请求报文,并判断其有效性
- 生成 HTTP回复报文
- 完整地 通过对应套接字,发送给请求者。
- 在这里我假设,你已经对TCP编程的模型很熟悉了,不熟的可以去顶部看看再回来
- 并发服务器的关键点就在于
- 高效且正确地接收尽可能多的连接
- 高效且正确地处理尽可能多的连接
- 以上忽略了安全性
- 该如何设计?
- 让某个
epoll
用来服务于接收新连接这个环节(accept
) - 让某些
epoll
用来处理这些新连接的事务。 - 这样理论上我们既发挥了单核的极限(epoll),又用上了多核的优势(多个
epoll
)
- 让某个
- 更具体的呢?
- 在主线程里使用单个
epoll
来处理,监听套接字的读事件,也就是接受新连接 - 再开几个线程
epoll
,用来平分处理这些新连接。
- 在主线程里使用单个
- 这样也就是网络编程的一整个流程,如果看到这里你已经大概有了一个程序思路,实际上就已经达到目的了,接下来就是直接上手代码就行
- 还是迷迷糊糊的,就一步一步跟着我,写出这个服务器,会大有脾益。
- 并发服务器的关键点就在于
小经验,在编程中,读往往比写要复杂许多。在网络编程里面亦是。
有图有真相,希望能够自己画。
现在大致有了思路,可以整理整理自己接下来该干什么了
环境准备
- 99%的中国大学学生的操作系统,应该都是 Windows或者Max OS(maxOS),那么建议你直接使用虚拟机进行环境的搭建,可以选择开源免费的
Visual Box
,Windows下也可以使用商业版的VMware
,Mac下有一个更棒的商业版选择Paralelle Desktop
,但是这都是软件,算是无关紧要的。 - 选择一个
Linux
发行版,由于我用的是Debian
系列的Ubuntu 16.04 LTS
,所以我也推荐这个发行版,其他的发行版也许略有差异,不再多说。 - 装好之后,直接进入开发阶段吧。
- IDE可以选择
Clion
或者Kdevelop
。 - 当然你要用
Vim
我也不会阻拦,但是请装好两个插件Nerdtree
和YouCompleteMe
,配合好另一个软件tmux
(简单使用),不然你会想死。 - 除了
Vim
,你也可以选择Visual Studio Code
加装一个C/C++ tools
也是不错的。 - 作为时尚的我,自然选择
Clion
了,简单明了,且还是使用CMake
作为构建工具。
- IDE可以选择
- 想要进行这么底层的网络编程,请准备好
Google
和Unix网络编程卷1
,如果你两个都没有的话,不说了,再见。建议准备一个那玩意儿去访问Google
。