?接上一篇:【网络面试必问(8)】防火墙原理、正向代理、反向代理、缓存服务器、负载均衡和内容分发服务器
?这一篇,我们看一下请求消息的终点,Web服务器的程序结构。我们都知道,服务器会同时接收多个客户端的请求,做出响应并返回程序结果。但是仅一个服务器程序就处理多个客户端的请求是很难得,因此服务器会启用多个程序或者多个线程来实现这种一对多的关系。
?服务器的程序会分成两个模块,即等待连接模块和与客户端通讯模块,简称连接模块和通讯模块。服务器在启动之后,连接模块(a)会首先创建套接字,并且进入等待连接的状态,这时候,客户端就可以向客户端发起连接了。当连接模块监听到有客户端的连接请求进来之后,首先会把套接字和客户端的连接起来,然后就会把这个套接字移交给连接模块(b),当然这个连接模块可能之前没有新创建的,也可能是事先已经准备好的。同时,操作系统还会记录套接字和客户端的对应关系。
?接下来的通讯操作,实际上是通讯模块(b)和客户端在通讯,连接模块(a)会继续监听来自客户端的请求,如有新的请求过来,会继续执行上面的操作。所以通讯(b)模块会存在多个,并且和多个客户端交互。
?这里其实还有一个问题,上面我们提到了套接字,每当有新的连接进来,就会创建一个套接字,其实要说明的是这个套接字其实是连接模块套接字拷贝的副本,因为服务器应用程序启动之初就已经创建了监听的套接字,所有链接模块的套接字都是这个套接字的副本,并且记录和自己通讯的客户端控制信息。但是有一个问题是,前面我们说过,不同套接字应该对应不同的端口号,但是对于多个套接字副本端口号其实是一样的。这样一来,客户端比如要找80端口的应用,协议栈分析TCP头部中接收方端口号为80,应该找那个套接字副本呢?
?对应的解决办法其实就是,根据客户端的IP地址和端口号、服务器的IP地址和端口号这4个信息共同来确定一个套接字,这样即使服务器套接字端口号相同,也可以根据这四个信息确定唯一的套接字了。这些匹配信息其实会维护在操作系统协议栈中,只要请求过来,查表就可以了。
?当然,有这么多信息才能确定一个套接字,我们就需要用套接字描述符来代表这4类信息了,简单直接。而且最重要的,在程序启动之初,等待连接的套接字中,是没有客户端的IP地址和端口号的。
?服务器接收电信号的过程和客户端发送的过程相反,网卡会接收信号,然后将其还原成数字信息。接下来需要根据包末尾的帧校验序列(FCS)来校验错误,如果校验失败,需要丢弃此包。接下来,MAC模块会检查MAC模块中的MAC地址信息是不是发给自己的,不是的话也要丢弃。确认没问题后,就会把这些信息存放到网卡缓冲区中。
?接下来,网卡就会读取包的信息,并根据MAC头部的信息来判断协议类型,并将包交给它处理,这里假设是以太网的类型,表示IP协议,就会调用TCP/IP协议栈来执行。
?协议栈的IP模块会检查IP的头部,判断是不是发送给自己的,否则的话丢弃,另外还会检查是否分片等操作。最后再读取IP头部的协议号字段,确定是转交给TCP模块还是UDP模块。
?TCP模块的执行操作,我们前面其实已经说到过,这里就简单概述一下,如果接收到的发起连接的包,会检查TCP头部的控制位SYN,检查接收方的端口号,创建套接字的副本,然后和将发送方的IP地址和端口号信息记录到套接字里面。
?如果是数据收发阶段,TCP模块会根据收到的包发送方IP地址和端口号,接收方的IP地址和端口号,找到对应的套接字,然后对比收到的包和之前报的发送状态是不是对应的上,如果能对应上就会把数据块拼接起来并且缓存起来,然后向客户端返回ACK包。
?如果是断开阶段,服务器程序会调用Socket的close()函数,TCP模块会生成FIN=1的头部,并且委托IP模块发送给客户端,之后进行四次挥手的操作执行断开连接的操作。当断开连接操作完成后,经过一段时间,套接字就会被删除。