目录
1 TCP 协议简介
1.1 TCP 协议简介
1.2 TCP 的建立连接
1.3 TCP 终止连接
1.4 TCP 报文结构
1.5 lwIP 的 TCP 报文首部数据结构
1.6 lwIP 的 TCP 连接状态图
1 TCP 协议简介
1.1 TCP 协议简介
TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
TCP 为了保证数据包传输的可靠行,会给每个包一个序号,同时此序号也保证了发送到接收端主机能够按序接收。然后接收端主机对成功接收到的数据包发回一个相应的确认字符( ACK, Acknowledgement),如果发送端主机在合理的往返时延( RTT) 内未收到确认字符ACK,那么对应的数据包就被认为丢失并将被重传。 TCP 协议,它是基于连接的一种传输层协议,在发送数据之前要求系统需要在不可靠的信道上建立可靠连接,我们称之为“三次握手”。 建立连接完成之后客户端与服务器才能互发数据, 不需要发送数据时,可以断开连接,这里我们称之为“四次挥手”。
1.2 TCP 的建立连接
握手之前主动打开连接的客户端结束 CLOSED 阶段,被动打开的服务器端也结束CLOSED 阶段,并进入 LISTEN 阶段。随后开始“三次握手”:
① TCP 服务器进程先创建传输控制块 TCB,时刻准备接受客户进程的连接请求,此时服务器就进入了 LISTEN(监听)状态。
② TCP 客户进程也是先创建传输控制块 TCB,然后向服务器发出连接请求报文,这是报文首部中的同部位 SYN=1,同时选择一个初始序列号 seq=x ,此时, TCP 客户端进程进入了SYN-SENT(同步已发送状态)状态。 TCP 规定, SYN 报文段(SYN=1 的报文段)不能携带数据,但需要消耗掉一个序号。
③ TCP 服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该ACK=1, SYN=1,确认号是 ack=x+1,同时也要为自己初始化一个序列号 seq=y,此时, TCP服务器进程进入了 SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。
④ TCP 客户进程收到确认后,还要向服务器给出确认。确认报文的 ACK=1, ack=y+1,自己的序列号 seq=x+1,此时, TCP 连接建立,客户端进入 ESTABLISHED(已建立连接)状态。 TCP 规定, ACK 报文段可以携带数据,但是如果不携带数据则不消耗序号。
当服务器收到客户端的确认后也进入 ESTABLISHED 状态,此后双方就可以开始通信了。这就是“三次握手”的过程,如下图所示。
1.3 TCP 终止连接
建立一个连接需要三次握手而终止一个连接需要四次挥手,终止连接有以下过程。
(1) 第一次挥手:客户端发送释放报文,并停止发送数据。释放数据报文首部, FIN=1,其序列号为 seq=u,此时,客户端进入 FIN-WAIT1(等待服务器应答 FIN 报文)。
(2) 第二次挥手:服务器收到客户端的 FIN 报文后,发出确认报文 ACK=1、 ack=u+1,并携带自己的序列号 seq=v。此时,服务器进入 CLOSE-WAIT(关闭等待) 状态。客户端收到服务端确认请求,此时,客户端进入 FIN-WAIT2(终止等待 2)状态,等待服务器发送连接释放报文。
(3) 第三次挥手:服务器向客户端发送连接释放报文 FIN=1、 ack=u+1,此时,服务器进入了 LAST-ACK(最后确认)等待客户端的确认。客户端接收到服务器的连接释放报文后,必须发送确认 ack=1、 ack=w+1,客户端的序列号为 seq=u+1,此时,客户端进入 TIME-WAIT(时间等待)。
(4) 第四次挥手:服务器接收到客户端的确认报文,立刻进入 CLOSED 状态。
这四次挥手就是终止 TCP 协议连接,如下图所示:
1.4 TCP 报文结构
在传输层中, TCP 的数据包称为数据段, TCP 报文段与 UDP 报文段一样都是封装在 IP 数据报中发送。 TCP 首部包含建立与断开、数据确认、窗口大小通告、数据发送相关的所有标志和控制信息, TCP 报文结构如下图所示:
(1) 源、目标端口号字段:占 16 比特。 TCP 协议通过使用”端口”来标识源端和目标端的应用进程。端口号可以使用 0 到 65535 之间的任何数字。在收到服务请求时,操作系统动态地为客户端的应用程序分配端口号。在服务器端,每种服务在”众所周知的端口”( Well-Know Port)为用户提供服务。
(2) 序列号字段:占 32 比特。用来标识从 TCP 源端向 TCP 目标端发送的数据字节流,它表示在这个报文段中的第一个数据字节。
(3) 确认号字段:占 32 比特。只有 ACK 标志为 1 时,确认号字段才有效。它包含目标端所期望收到源端的下一个数据字节。
(4) 头部长度字段:占 4 比特。给出头部占 32 比特的数目。没有任何选项字段的 TCP 头部长度为 20 字节;最多可以有 60 字节的 TCP 头部。
(5) 标志位字段(U、 A、 P、 R、 S、 F):占 6 比特。各比特的含义如下:
① URG: 紧急指针有效。
② ACK: 为 1 时,确认序号有效。
③ PSH: 为 1 时,接收方应该尽快将这个报文段交给应用层。
④ RST: 为 1 时,重建连接。
⑤ SYN: 为 1 时,同步程序,发起一个连接。
⑥ FIN: 为 1 时,发送端完成任务,释放一个连接。
(6) 窗口大小字段:占 16 比特。此字段用来进行流量控制。单位为字节数,这个值是本机期望一次接收的字节数。
(7) TCP 校验和字段:占 16 比特。对整个 TCP 报文段,即 TCP 头部和 TCP 数据进行校验和计算,并由目标端进行验证。
(8) 紧急指针字段:占 16 比特。它是一个偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。
(9) 选项字段:占 32 比特。可能包括”窗口扩大因子”、”时间戳”等选项。
上述的内容讲解的是 TCP 首部信息,这些信息被封装在一个 IP 数据报中, 该数据报结构如下图所示。
1.5 lwIP 的 TCP 报文首部数据结构
实现 TCP 协议的文件有 tcp.h、 tcp.c、 tcp_in.c 和 tcp_out.c,这四个文件实现了 TCP 协议全部数据结构和函数, 其中 tcp.c 文件包含了与 TCP 编程、 TCP 定时器相关的函数,而tcp_in.c 文件包含了 TCP 报文段输入处理函数, 而 tcp_out.c 文件包含了 TCP 报文输出处理函数, 当然 tcp.h 定义了宏和结构体。 首先我们看一下 TCP 首部结构,这个结构为 tcp_hdr, 如下源码所示:
struct tcp_hdr {
PACK_STRUCT_FIELD(u16_t src); /* 源端口 */
PACK_STRUCT_FIELD(u16_t dest); /* 目的端口 */
PACK_STRUCT_FIELD(u32_t seqno); /* 序号 */
PACK_STRUCT_FIELD(u32_t ackno); /* 确认序号 */
PACK_STRUCT_FIELD(u16_t _hdrlen_rsvd_flags); /* 首部长度+保留位+标志位 */
PACK_STRUCT_FIELD(u16_t wnd); /* 窗口大小 */
PACK_STRUCT_FIELD(u16_t chksum); /* 校验位 */
PACK_STRUCT_FIELD(u16_t urgp); /* 紧急指针 */
} PACK_STRUCT_STRUCT;
可见, lwIP 使用 tcp_hdr 结构体描述 TCP 首部各个字段,值得注意的是,该结构体的_hdrlen_rsvd_flags 变量用来描述下图黄色部分的内容。
1.6 lwIP 的 TCP 连接状态图
发送端与接收端发送的指令会进入不同的状态, 因此, lwIP 在 tcpbase.h 文件中定义了枚举类型 tcp_state,它是用来描述 TCP 的状态,该枚举tcp_state 如下源码所示:
enum tcp_state {
CLOSED = 0, /* 关闭状态 */
LISTEN = 1, /* 监听状态 */
SYN_SENT = 2, /* 发送请求连接 */
SYN_RCVD = 3, /* 接收请求连接 */
ESTABLISHED = 4, /* 连接状态已建立 */
FIN_WAIT_1 = 5, /* 程序已关闭该连接 */
FIN_WAIT_2 = 6, /* 另一端已关闭连接 */
CLOSE_WAIT = 7, /* 等待程序关闭连接 */
CLOSING = 8, /* 两端同时收到对方的关闭请求 */
LAST_ACK = 9, /* 服务器等待对方接收关闭操作 */
TIME_WAIT = 10 /* 关闭成功 */
}
如果 TCP 需要建立连接,则系统需要三次握手;如果 TCP 中断连接,则系统需要四次挥手。