第二步 传输TCP/IP数据
创建套接字
协议栈的结构
套接字:通信控制信息
套接字记录着通信对象的IP地址与端口号、通信的状态等等;总之,套接字中记录了用于控制通信的各种信息,协议栈根据这些信息判断下一步操作。
socket的内部实现
以下所示是更详细的收发数据示意图:
连接服务器
连接:通信双方交换控制信息
所谓建立连接,就是为通信双方互相交换控制信息,在套接字中记录这些必要信息并准备数据收发的一系列操作。
保存控制信息的头部
在数据包的头部,存放着客户端与服务器相互联络所需的控制信息,这些信息在TCP协议中进行了定义,如下表所示。
在连接阶段,通信双方只交换控制信息,此时网络包的结构如下图:
通信双方建立连接之后,通信双方开始互相发送数据,此时的网络包结构如下图:
连接的具体操作
服务器端 | 方向 | 客户端 |
---|---|---|
< | 创建TCP头部,其中包含表示开始数据收发操作的控制信息; 对TCP头部进行初始化设置(源IP与目的IP、源端口与目的端口、SYN位置1、序号设置与窗口大小设置等等); 将TCP头部传给IP层并委托IP层发送给服务器端。 |
|
服务器端接收到数据,经以太层、IP层解析之后到达TCP层; TCP层根据头部的目的端口号找到连接此请求的套接字,并向该套接字中写入相应的信息;套接字的状态改为正在连接; 服务器端也是发送一个TCP头部给客户端(操作与客户端的操作相同,但是有些地方的设置要反过来,比如TCP头部的源IP与目的IP、源端口与目的端口之类);此外,还要将ACK位置为1,表示已经接收到相应的网络包。 |
> | |
< | 客户端接收到数据,经以太层、IP层解析之后到达TCP层; TCP层通过接收到的TCP头部的SYN位确认连接服务器的操作是否成功;若SYN位为1,(表示连接成功),此时TCP模块会向套接字中写入服务器的IP地址,端口号等信息;并将套接字的状态设置为连接完毕; 同样地,客户端也需要将TCP头部的ACK位置1并将TCP头部传给IP层并委托IP层发送给服务器端。 |
|
服务器收到客户端第二次发送的网络包,连接操作全部完成。 |
此后,套接字进入了随时可以收发数据的状态。到这里,也就是connect执行完毕。
收发数据
请求消息进入协议栈
协议栈收到数据后,先将其存放在内部的发送缓冲区,继续等待应用程序后续的数据;协议栈是否发送数据有以下两个判断要素:
- 缓存的数据包长度是否接近MSS(最大分段大小)。MSS=MTU-ip_head_len-tcp_head_len,MTU(最大传输单元)一般为1500;
- 等待时间。即使数据包长度还未接近MSS,但是已经等待了一定时间,为避免造成发送延迟,也会将数据包发送出去;
应用程序可以控制数据包发送的时机(长度优先或时间优先),若应用程序未指定,则由协议栈自行决定。
对长数据进行拆分
当应用程序送入协议栈的数据长度大于MSS,数据以MSS个字节为单位进行分组,在每组数据前加上TCP头部后送入IP层,委托IP将数据发送。
网络包的接收确认
每发送一个网络包,都需要进行确认操作。首先,在客户端(发送方)与服务器端(接收方)建立连接时,客户端(发送方)向服务器(接收方)发送的tcp头部使用一个32位的随机数作为序列号;在(发送方)TCP模块拆分数据时,每一数据块前所加的TCP头部的序列号为在上述序列号(32位的随机数)的基础上加上数据块的位置。服务器端(接收方)在收到网络包后,会计算ACK值(序列号+data_len+1);
服务器端(接收方)创建一个TCP头部(ACK的值为上述ACK的值,ACK位置1),然后发送给客户端(发送方);客户端(发送方)在没有收到某个数据包对应的ACK号之前,数据包会一直保存在发送缓冲区中,如果没有收到某个数据包对应的ACK号,TCP模块会重新发送此数据包。
以下是TCP数据包传输的示意图(简便起见,没有考虑32随机数的初始序列号)。
同时,TCP的数据收发是双向的,即服务器端作为发送方、客户端作为接收方,也会为网络包的接收确认做上述操作。
如下是TCP数据双向传输的示意图:
ACK号等待时间
TCP协议采用动态调整的策略来设置ACK号的等待时间。策略是:在TCP模块发送数据时持续测量ACK号的等待时间,若ACK号的返回变慢,则增大ACK号的等待时间,若ACK号的返回变快,则减小ACK号的等待时间。
滑动窗口
接收方告知发送方自己最多能接收的数据,然后发送方根据这个值对发送的数据量进行控制。以下是一个TCP滑动窗口的示例。
ACK与窗口信息的合并发送
Q1:接收方何时要向发送方发送窗口的更新信息?
答:应用程序从接收缓冲区取出数据后。
Q2:接收方何时要向发送方发送ACK号?
答:收到数据包确认无误后。
为了提高网络的效率,可考虑将这两种信息放入同一tcp头部进行发送。这时,在等待过程中(一种情形是刚刚收到了tcp数据包并确认无误,等待应用程序从接收缓冲区取出数据;另一种情形是应用程序从接收缓冲区取出了数据,等待数据包的到来与确认),窗口信息或ACK号会有多个值,此时只需要传递最终结果即可。