网络软件的数据收发是通过委托给操作系统的网络控制软件(协议栈)以及硬件网卡来实现的。
协议栈是一组网络协议的具体实现,OSI七层模型和TCP/IP五层模型的对比如下:
套接字
在协议栈内部有一块用于存放控制信息的内存空间,这里记录了用于控制通信操作的控制信息,例如通信对象的IP地址、端口号、通信操作的进行状态等。本来套接字就只是一个概念而已,并不存在实体,如果一定要赋予它一个实体,我们可以说这些控制信息就是套接字的实体,或者说存放控制信息的内存空间就是套接字的实体。协议栈是根据套接字中记录的信息来工作的。
协议栈TCP通信过程
- 创建套接字:分配一个套接字所需的内存空间,向其写入初始状态,将表示这个套接字的描述符告知应用程序。
- 连接
- 收发数据
- 断开:套接字会被删除
连接阶段
连接实际上是通信双方交换控制信息,在套接字中记录这些必要信息并准备数据收发的一连串操作。此外,当执行数据收发操作时,我们还需要一块用来临时存放要手法的数据的内存空间,这块内存称为缓冲区,它也是在连接操作的过程中分配的。连接阶段也可以叫做准备阶段。
这个过程是从应用程序调用Socket库的connect方法开始的,需要指定描述符、服务器IP地址和端口号这三个参数。
其实就是TCP三次握手的过程。
负责保存信息的头部
通信操作中使用的控制信息分为两类:
- 头部中记录的信息
- 套接字(协议栈中的内存空间)中记录的信息
TCP头部包含收发方端口号、控制位、窗口等字段,而套接字的控制信息和协议栈的程序本身其实是一体的,因此,“协议栈具体需要哪些信息”会根据协议栈本身的实现方式不同而不同,协议栈中的控制信息通信对方是看不见的,只要在通信时按照规则将必要的信息写入头部、客户端和服务器之间的通信就得以成立。因此不同的操作系统直接可以通信。
收发数据
当控制流程从connect回到应用程序之后,接下来就进入了数据收发阶段了。应用程序调用write将要发送的数据交给协议栈,应用程序在调用write时会指定收发数据的长度。协议栈并不是一收到数据就马上发送出去,而是会将数据存放在内部的发送缓冲区中,并等待应用程序的下一段数据。这样做时有意义的,应用程序交给协议栈的数据长度是由应用程序自己决定的,有的一次传递所有数据,有的逐字节或者逐行传递,协议栈无法控制这些行为。如果协议栈一收到数据就马上发送出去可能会发送大量的小包,造成网络效率低下,因此需要积累一定量的数据再发送。至于积累多少再发送,不同的操作系统实现也不一致。一般都会根据几个要素判断:
- 每个网络包能容纳的数据长度:当应用程序收到的数据长度超过或者接近MSS(最大分段大小)时再发送,可以避免大量小包的问题。
- 事件:当应用程序发送数据的频率不高的时候,如果每次都等到长度接近MSS时再发送,可能会因为等待时间太长而造成延迟。协议栈内部有一个计时器,当经过一定时间之后,就会把网络包发出去。
这两个要素时互相矛盾的,因此怎样才能达到平衡是由协议栈开发者来决定的。同时,如果仅靠协议栈来判断发送时机可能会带来一些问题,因此协议栈也给应用程序保留了控制发送时机的余地,提供了一些控制选项。
//todo: TCP 慢启动、可靠传输