服务器概览

客户端和服务器的区别

根据用途,服务器可以分为很多种类,其硬件和操作系统与客户端是有所不同的 。但是,网络相关的部分,如网卡、协议栈、Socket 库等功能和客户端却并无二致。无论硬件和 OS 如何变化,TCP 和 IP 的功能都是一样的,或者说这些功能规格都是统一的 。

不过,它们的功能相同,不代表用法也相同。在连接过程中,客户端发起连接操作,而服务器则是等待连接操作,因此在 Socket 库的用法上还是有一些区别的,即应用程序调用的 Socket 库的程序组件不同

  • 服务器程序分为两个模块,等待连接模块和负责客户端联通的模块。当服务器程序启动并读取配置文件完成初始化操作后,就会运行等待连接模块,这个模块会创建套接字,然后进入等待连接的暂停状态。接下来客户端发起连接时,这个模块会恢复运行并接收连接,然后启动客户端通信模块,并移交完成连接的套接字。

服务器端端套接字和端口号

客户端收发阶段如下:

  • 创建套接字
  • 用管道连接服务器端套接字
  • 收发数据
  • 断开管道并删除套接字

服务端收发阶段如下:

  • 创建套接字
  • 将套接字设置为等待连接状态
    • 绑定端口
    • 监听
  • 接收连接
  • 收发数据
  • 断开管道并删除套接字

服务器的接收操作

网卡将接收到的信号转化为数字信号

网卡的 MAC 模块将网络包从信号还原为数字信息,校验 FCS 并存入缓冲区。

  • 网卡驱动会根据 MAC 头部判断协议类型,并将包交给相应的协议栈。

IP模块的接收操作

当网络包转交到协议栈时,IP 模块会首先开始工作,检查 IP 头部。IP 模块首先会检查 IP 头部的格式是否符合规范,然后检查接收方 IP 地址,看包是不是发给自己的。当服务器启用类似路由器的包转发功能时 ,对于不是发给自己的包,会像路由器一样根据路由表对包进行转发 。

确认包是发给自己的之后,接下来需要检查包有没有被分片 。检查 IP 头部的内容就可以知道是否分片 ,如果是分片的包,则将包暂时存放在内存中,等所有分片全部到达之后将分片组装起来还原成原始包;如果没有分片,则直接保留接收时的样子,不需要进行重组。

  • 协议栈的 IP 模块会检查 IP 头部,(1) 判断是不是发给自己的;(2) 判断网络包是否经过分片;(3) 将包转交给TCP 模块或UDP模块。

TCP模块如何处理连接包

当 TCP 头部中的控制位 SYN 为 1 时,表示这是一个发起连接的包。这时,TCP 模块会执行接受连接的操作,不过在此之前,需要先检查包的接收方端口号,并确认在该端口上有没有与接收方端口号相同且正在处于等待连接状态的套接字。如果指定端口号没有等待连接的套接字,则向客户端返回错误通知的包。

如果存在等待连接的套接字,则为这个套接字复制一个新的副本,并将发送方 IP 地址、端口号、序号初始值、窗口大小等必要的参数写入这个套接字中,同时分配用于发送缓冲区和接收缓冲区的内存空间。然后生成代表接收确认的 ACK 号,用于从服务器向客户端发送数据的序号初始值,表示接收缓冲区剩余容量的窗口大小,并用这些信息生成 TCP 头部,委托 IP 模块发送给客户端

这个包到达客户端之后,客户端会返回表示接收确认的 ACK 号,当这个 ACK 号返回服务器后,连接操作就完成了。

  • 如果收到的是发起连接的包,则 TCP 模块会(1) 确认 TCP 头部的控制位SYN;(2) 检查接收方端口号;(3) 为相应的等待连接套接字复制一个新的副本;(4) 记录发送方 IP 地址和端口号等信息。

TCP模块如何处理数据包

首先,TCP 模块会检查收到的包对应哪一个套接字。在服务器端,可能有多个已连接的套接字对应同一个端口号,因此仅根据接收方端口号无法找到特定的套接字。这时我们需要根据 IP 头部中的发送方 IP 地址和接收方 IP 地址,以及 TCP 头部中的接收方端口号和发送方端口号共 4 种信息,找到上述 4 种信息全部匹配的套接字。

找到 4 种信息全部匹配的套接字之后,TCP 模块会对比该套接字中保存的数据收发状态和收到的包的 TCP 头部中的信息是否匹配,以确定数据收发操作是否正常。具体来说,就是根据套接字中保存的上一个序号和数据长度计算下一个序号,并检查与收到的包的 TCP 头部中的序号是否一致 26 。如果两者一致,就说明包正常到达了。这时,TCP 模块会从包中提出数据,并存放到接收缓冲区中,与上次收到的数据块连接起来。

当收到的数据进入接收缓冲区后,TCP 模块就会生成确认应答的 TCP 头部,并根据接收包的序号和数据长度计算出 ACK 号,然后委托 IP 模块发送给客户端。

收到的数据块进入接收缓冲区,意味着数据包接收的操作告一段落了。接下来,应用程序会调用 Socket 库的 read来获取收到的数据,这时数据会被转交给应用程序。如果应用程序不来获取数据,则数据会被一直保存在缓冲区中,但一般来说,应用程序会在数据到达之前调用 read 等待数据到达,在这种情况下,TCP 模块在完成接收操作的同时,就会执行将数据转交给应用程序的操作。

  • 收到数据包时,TCP 模块会(1) 根据收到的包的发送方 IP 地址、发送方端口号、接收方 IP 地址、接收方端口号找到相对应的套接字;(2) 将数据块拼合起来并保存在接收缓冲区中;(3) 向客户端返回ACK。

TCP模块的断开操作

在 TCP 协议的规则中,断开操作可以由客户端或服务器任何一方发起,具体的顺序是由应用层协议决定的。Web 中,这一顺序随 HTTP 协议版本不同而不同,在 HTTP1.0 中,是服务器先发起断开操作。

这时,服务器程序会调用 Socket 库的 close,TCP 模块会生成一个控制位 FIN 为 1 的 TCP 头部,并委托 IP 模块发送给客户端。当客户端收到这个包之后,会返回一个 ACK 号。接下来客户端调用 close,生成一个 FIN 为 1 的 TCP 头部发给服务器,服务器再返回 ACK 号,这时断开操作就完成了。HTTP1.1 中,是客户端先发起断开操作,这种情况下只要将客户端和服务器的操作颠倒一下就可以了。