TCP原理及应用

1、定义:

传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。

2、主要特点:

2.1、面向连接

通信双方在进行数据交互前需要建立连接,连接的建立和释放涉及到三次握手、四次挥手过程。  

2.2、可靠的数据传输

1、分段机制:TCP将需要传输的数据分成合适的报文段,然后逐个传输

2、定时器的超时和重传机制:TCP对每个发送的报文段都设置一个定时器等待目标端返回确认收到的响应,如果未收到,则超时重传

3、确认报文机制:接受方收到报文后会返回确认报文告诉发送方收到报文

4、校验和机制:TCP保持首部和数据的校验和,接收方将校验段的校验和,如果错误将丢弃不确认等待发送方超时重传

5、有序机制:TCP将对报文段进行排序,保证报文段有序性

6、去重机制:TCP将对报文段进行去重

7、流控机制:由于接收方和发送的TCP缓冲区容量不一致和处理速度,需要对发送方的流量进行控制,防止网络阻塞

2.3、基于字节流

报文按照8bit的字节流进行传递

3、报文结构

TCP报文格式

3.1、各字段含义:

第一行:源端口和目的端口字段

网络中进程通信需要IP + PORT来确定唯一进程。因为IP地址是有IP协议涉及,所以在TCP传输中只需要确定接收方和目标方的端口号便可以建立连接,双方的进程才能正常通信

TCP 源端口(Source Port):源计算机上的应用程序的端口号,占 16 位(bit)。

TCP 目的端口(Destination Port):目标计算机的应用程序端口号,占 16 位(bit)。

第二行/第三行:序列号和确认号

用于标识发送的字节流和期望接收的字节流

TCP会为报文数据段中的每个字节编个序号,发布的时候,序列号表示当前TCP报文数据段第一个字节的序号,确认号表示当前已收到的,有效的数据中的最大字节序号。

第四行:

TCP 首部长度(Header Length):指数据段中的“数据”部分起始处距离 TCP 数据段起始处的字节偏移量,占 4 位。其实这里的“数据偏移”也是在确定 TCP 数据段头部分的长度,告诉接收端的应用程序,数据从何处开始。

保留字段(Reserved):占 4 位,为 TCP 将来的发展预留空间,一般全部为 0。

标志位字段

CWR、ECE:告知发送方,我方拥堵,你降低一下窗口发送速率,CWR主管通知,ECE用于标记状态

URG(Urgent):该位设为 1,表示包中有需要紧急处理的数据,对于需要紧急处理的数据,与后面的紧急指针有关;

ACK:除了最初建立连接时的 SYN 包之外该位必须设为 1,表示 TCP 确认号(Acknowledgment Number,ACK Number)有效

PSH(Push):告知接收方是否马上将数据提交给上层,PSH=1,马上提交不是缓存起来

RST:是否释放连接,重新建立连接, RST=1 需要重新连接

SYN:在建立连接时使用,用来同步序号。

当 SYN=1,ACK=0 时,表示这是一个请求建立连接的报文段;

当 SYN=1,ACK=1 时,表示对方同意建立连接。

SYN=1 时,说明这是一个请求建立连接或同意建立连接的报文。只有在前两次握手中 SYN 才为 1。

FIN:标记数据是否发送完毕。如果 FIN=1,表示数据已经发送完成,可以释放连接。

窗口大小(Window Size):占 16 位。用于TCP 流量控制,表示目前还有多少空间,能接收多少数据量

第五行

校验位(TCP Checksum):占 16 位。它用于确认传输的数据是否有损坏。发送端基于数据内容校验生成一个数值,接收端根据接收的数据校验生成一个值。两个值必须相同,才能证明数据是有效的。如果两个值不同,则丢掉这个数据包。Checksum 是根据伪头 + TCP 头 + TCP 数据三部分进行计算的。

紧急指针(Urgent Pointer):仅当前面的 URG 控制位为 1 时才有意义。它指出本数据段中为紧急数据的字节数,占 16 位。当所有紧急数据处理完后,TCP 就会告诉应用程序恢复到正常操作。即使当前窗口大小为 0,也是可以发送紧急数据的,因为紧急数据无须缓存。

第六行:可选项字段

可选项(Option):可以自定义头部字段,长度不定,但长度必须是 32bits 的整数倍

4、连接过程

4.1、建立连接(三次握手)


第一次握手:建立连接时,客户端发送 syn 包(syn=j)到服务器,并进入 SYN_SEND 状态,等待服务器确认;

第二次握手:服务器收到 syn 包,必须确认客户的 SYN(ack=j+1),同时自己也发送一个 SYN 包(syn=k),即 SYN+ACK 包,此时服务器进入 SYN_RECV 状态;

第三次握手:客户端收到服务器的 SYN+ACK 包,向服务器发送确认包 ACK(ack=k+1),此包发送完毕,客户端和服务器进入 ESTABLISHED 状态,完成三次握手。

4.1、释放连接(四次握手)


1)客户端 A 发送一个 FIN,用来关闭客户 A 到服务器 B 的数据传送;

2)服务器 B 收到这个 FIN,它发回一个 ACK,确认序号为收到的序号加 1。和 SYN 一样,一个 FIN 将占用一个序号;

3)服务器 B 关闭与客户端 A 的连接,发送一个 FIN 给客户端 A;

4)客户端 A 发回 ACK 报文确认,并将确认序号设置为收到序号加 1。

5、TCP通信过程中各步骤的状态


CLOSED: 关闭状态(初始状态);

LISTEN: 表示服务器端的某个 SOCKET 处于监听状态,可以接收连接了;

SYN_RCVD: 这个状态表示接收到了 SYN 报文,在正常情况下,这个状态是服务器端的SOCKET 在建立 TCP 连接时的三次握手会话过程中的一个中间状态,很短暂,基本上用 netstat 你是很难看到这种状态的,除非你特意写了一个客户端测试程序,故意将三次 TCP 握手过程中最后一个 ACK 报文不予发送。因此这种状态时,当收到客户端的 ACK 报文后,它会进入到 ESTABLISHED 状态。

SYN_SENT: 这个状态与 SYN_RCVD 相呼应,当客户端 SOCKET 执行 CONNECT 连接时,它首先发送 SYN 报文,因此也随即它会进入到了 SYN_SENT 状态,并等待服务端的发送三次握手中的第 2 个报文。SYN_SENT 状态表示客户端已发送 SYN 报文。

ESTABLISHED:这个容易理解了,表示连接已经建立了。

FIN_WAIT_1: 该状态实际上是当 SOCKET 在 ESTABLISHED 状态时,它想主动关闭连接,向对方发送了 FIN 报文,此时该 SOCKET 即进入到 FIN_WAIT_1 状态。而当对方回应 ACK 报文后,则进入到 FIN_WAIT_2 状态,当然在实际的正常情况下,无论对方何种情况下,都应该马 上回应 ACK 报文,所以 FIN_WAIT_1 状态一般是比较难见到的,而 FIN_WAIT_2 状态还有时常常可以用 netstat 看到。

FIN_WAIT_2:上面已经详细解释了这种状态,实际上 FIN_WAIT_2 状态下的 SOCKET,表示半连接,也即有一方要求 close 连接,但另外还告诉对方,我暂时还有点数据需要传送给你,稍后再关闭连接。

TIME_WAIT: 表示收到了对方的 FIN 报文,并发送出了 ACK 报文,就等 2MSL 后即可回到 CLOSED 可用状态了。如果 FIN_WAIT_1 状态下,收到了对方同时带 FIN 标志和ACK 标志的报文时,可以直接进入到 TIME_WAIT 状态,而无须经过 FIN_WAIT_2 状态。

CLOSING(图中没有标志这种状态): 表示你发送 FIN 报文后,并没有收到对方的 ACK 报文,反而却也收到了对方的 FIN 报文。什么情况下会出现此种情况呢?其实细想一下,也不难得出结论:那就是如果双方几乎在同时close一个 SOCKET 的话,那么就出现了双方同时发送 FIN 报文的情况,也即会出现 CLOSING 状态,表示双方都正在关闭 SOCKET 连接。

CLOSE_WAIT: 这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方 close 一个 SOCKET 后发送 FIN 报文给自己,你系统毫无疑问地会回应一个 ACK 报文给对方,此时则进入到 CLOSE_WAIT 状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以 close 这个 SOCKET,发送 FIN 报文给对方,也即关闭连接。所以你在 CLOSE_WAIT 状态下,需要完成的事情是等待你去关闭连接。

LAST_ACK: 这个状态还是比较容易好理解的,它是被动关闭一方在发送 FIN 报文后,最后等待对方的 ACK 报文。当收到 ACK 报文后,也即可以进入到 CLOSED 可用状态了。

6、TCP网络编程


6.1、connect()函数:

对于客户端的 connect() 函数,该函数的功能为客户端主动连接服务器,建立连接是通过三次握手,而这个连接的过程是由内核完成,不是这个函数完成的,这个函数的作用仅仅是通知 Linux 内核,让 Linux 内核自动完成TCP三次握手连接,最后把连接的结果返回给这个函数的返回值(成功连接为0, 失败为-1)。

通常的情况,客户端的 connect() 函数默认会一直阻塞,直到三次握手成功或超时失败才返回(正常的情况,这个过程很快完成)。

6.2、listen()函数:

int listen(int sockfd, int backlog);

主要作用就是将套接字( sockfd )变成被动的连接监听套接字(被动等待客户端的连接),至于参数 backlog 的作用是设置内核中连接队列的长度(下文解释),TCP 三次握手也不是由这个函数完成,listen()的作用仅仅告诉内核一些信息。

这里需要注意的是,listen()函数不会阻塞,它主要做的事情为,将该套接字和套接字对应的连接队列长度告诉 Linux 内核,然后,listen()函数就结束。这样的话,当有一个客户端主动连接(connect()),Linux 内核就自动完成TCP三次握手,将建立好的链接自动存储到队列中,如此重复。


6.3、backlog参数:

告诉内核连接队列的长度。

内核为任何一个给定的监听套接口维护两个队列:

1、未完成连接队列(incomplete connection queue),当来自客户的 SYN 到达时,TCP 在未完成连接队列中创建一个新项,然后响应以三次握手的第二个分节:服务器的 SYN 响应,其中稍带对客户 SYN 的 ACK(即SYN+ACK),这一项一直保留在未完成连接队列中,直到三次握手的第三个分节(客户对服务器 SYN 的 ACK )到达或者该项超时为止(曾经源自Berkeley的实现为这些未完成连接的项设置的超时值为75秒)。

2、已完成连接队列(completed connection queue),每个已完成 TCP 三次握手过程的客户对应其中一项。这些套接口处于 ESTABLISHED 状态。

未完成连接队列长度由/proc/sys/net/ipv4/tcp_max_syn_backlog指定,默认为2048。

已完成连接队列长度由/proc/sys/net/core/somaxconn和使用listen函数时传入的参数,二者取最小值。默认为128。在Linux内核2.4.25之前,是写死在代码常量SOMAXCONN,在Linux内核2.4.25之后,在配置文件/proc/sys/net/core/somaxconn中直接修改,或者在 /etc/sysctl.conf 中配置net.core.somaxconn = 128。

6.4、accept()函数:

accept()函数功能是,从处于 established 状态的连接队列头部取出一个已经完成的连接,如果这个队列没有已经完成的连接,accept()函数就会阻塞,直到取出队列中已完成的用户连接为止。

7、常用套接字选项

7.1、TCP_NODELY:表示立即发送数据

    TCP_NODELAY选项是用来控制是否开启Nagle算法(该算法要求一个TCP连接上最多只能有一个未被确认的报文长度小于MSS小分组,在该小分组的确认到来之前,不能发送其他小分组。)

开启 Nagle算法与未开启的区别

7.2、SO_RESUSEADDR:表示是否允许重用Socket所绑定的本地地址

一般来说,一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用。

7.3、SO_TIMEOUT:表示接收数据时的等待超时时间

7.4、SO_SNFBUF/SO_RCVBUF:表示发送数据/接收数据的缓冲区大小

参考资料:

1、百度百科 - TCP (传输控制协议)

2、TCP协议 - 面向连接

3、一篇看懂 | TCP原理详细图解(特别推荐)

4、TCP报文详解

5、TCP/IP 详解卷一 - TCP CWR、ECE、URG、ACK、PSH、RST、SYN、FIN控制位

6、Understanding TCP Sequence and Acknowledgment Numbers

7、Linux网络编程——浅谈 TCP 三次握手和四次挥手

8、TCP 通信过程中各步骤的状态

9、TCP网络编程中connect()、listen()和accept()三者之间的关系(特别推荐)

10、TCP/IP协议中backlog参数

11、设置Socket的选项

12、详解Socket编程---TCP_NODELAY选项

你可能感兴趣的:(TCP原理及应用)