一、TCP基础概念
基础概念:TCP,Transmission Control Protocol(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的【RFC 793】 定义。
面向连接的:客户端与服务端必须建立连接后才可以进行交换数据 UDP 协议就不需要客户端与服务端进行连接
可靠性:体现出 TCP 协议可靠的特点有:应答确认,超时重传,乱序重排,去重,滑动窗口等几个特性,稍后进行详细讲解
流式服务:好比你发送 100 个字节的数据,对方可以分十次接收完,或者你分 10 次发送 100 个字节的数据,对方也可以一次性全部接收,但不能超出对方的接收缓存,否则超出的数据就会被丢弃
发送端执行的写操作次数与接收端执行的读操作次数之间没有任何数量关系,这就是字节流的概念
TCP 连接的建立是端到端系统之间在各自的系统中维护了一些信息,以保留连接状态。
另外,TCP 连接提供的是全双工服务,全双工就是能够同时给对方发送数据
二、TCP报文结构
源端口号 / 目的端口号:表示数据从哪个进程来,到哪个进程去,进行 TCP 通信时,客户端通常使用系统自动选择的临时端口号,而服务器则使用知名服务端口
6个标志位:
URG:标识紧急指针是否有效 ACK:标识确认序号是否有效 PSH:用来提示接收端应用程序立刻将数据从 TCP 缓冲区读走 RST:要求重新建立连接,把含有 RST 标识的报文称为复位报文段 SYN:请求建立连接,我们把含有 SYN 标识的报文称为同步报文段 FIN:通知对端,本端即将关闭,我们把含有 FIN 标识的报文称为结束报文段
16 位校验和:由发送端填充,校验形式有 CRC 校验等,如果接收端不通过,则认为数据有问题,此处的校验和不光包含 TCP 首部,也包含 TCP 数据部分
16 位紧急指针:用来标识哪部分数据是紧急数据
序号与确认号
32 位序号:一次 TCP 通信(从 TCP 连接建立到断开)过程中某一个方向上的字节流的每个字节的编号,一个报文段的序号是该报文首字节的字节流编号
例如,假设主机A上的进程想通过一条 TCP 连接向主机B上的一个进程发送一个数据流。主机A中的 TCP 将隐式地对数据流中的每一个字节进行编号。假定数据流由一个包含 1000 字节的文件组成,MSS的值为 100 字节,数据流的首字节编号为 0 。那么 TCP 将给这个数据流构建10个报文段。第一个报文段的序号就被赋值为 0,第二个报文段序号就被赋值为 100,以此类推,每个序号被填入到相应的 TCP 报文段首部的序号字段中
32 位确认号:用作对另一方发送来的 TCP 报文段的响应。其值是收到的 TCP 报文段的序号值加 1 。
三、TCP 连接机制
正常情况下, TCP 需要经过三次握手建立连接,四次挥手断开连接 那么什么是三次握手?四次挥手呢?
三次握手
大致流程如下,我们称发起连接的为客户端,接受连接的为服务端。起初,双端的状态都处于 CLOSED 状态
第一次:客户端向服务端发起连接 发出 SYN 报文,并给出自己的序号, SYN = 1,seq = x 之后客户端进入 SYN_SEND 状态
第二次:服务端收到 SYN 报文,进入 SYN_RECV 状态并回复一个 SYN + ACK 报文,给出自己的序号和确认序号 SYN = 1,ACK = 1,seq = y,ack = x + 1
第三次:客户端收到报文后,进入 ESTABLISH 状态,并回复一个 ACK 报文,ACK = 1,seq = x + 1,ack = y + 1 服务端收到后也进入ESTABLISH状态,成功建立连接
TCP 协议规定:SYN 报文不能携带数据
数据发送
为什么不用俩次?
主要是为了防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。如果使用的是俩次握手建立连接,假设有这样一种场景,客户端发送的第一个请求连接并没有丢失,只是在网络中滞留的时间太长了,由于 TCP 客户端迟迟没有收到确认报文,以为服务端没有收到,此时重新向服务端发送这条报文,此后客户端和服务端经过俩次握手完成连接,传输数据,然后关闭连接。
此时之前滞留的那一次请求连接,因为网络通畅了,到达了服务端,这个报文本该是失效的,但是俩次握手的机制会让客户端和服务端再次建立连接,这将导致不必要的错误和资源的浪费。
如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。
四次挥手
一开始客户端和服务端都处于 ESTADBLISH 状态,假设客户端去主动断开连接,其过程如下: 第一次:客户端给服务端发送 FIN 报文并给出自己的序号 FIN = 1,seq = u,进入 FIN_WAIT_1 状态
第二次:服务端收到 FIN 报文后,知道了客户端要关闭连接。如果此时服务端还有数据要发送,就先回复客户端一个 ACK 报文,其进入 CLOSED_WAIT 状态
第三次:发送 FIN 报文,发送之后进入 LAST_ACK 状态,如果没有数据发送,就将 FIN + ACK 一起发送
第四次:客户端收到ACK报文后,进入 FIN_WAIT_2 状态,之后再收到 ACK 报文之后,进入 TIME_WAIT 状态,并回复服务端 FIN 报文的 ACK 报文,TIME_WAIT 状态会持续 2*MSL ,期间如果没有报文,客户端就会进入 CLOSED 状态
服务端收到客户端发送的 ACK 报文后,进入 CLOSED 状态
TIME_WAIT ------ 状态为什么客户端要等待 2*MSL的时间呢?
MSL(Maximum Segment Lifetime), TCP 允许不同的实现可以设置不同的MSL值
第一,保证客户端发送的最后一个 ACK 报文能够到达服务器,因为这个 ACK 报文可能丢失,站在服务器的角度看来,我已经发送了 FIN + ACK 报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个 2MSL 时间段内收到这个重传的报文,接着给出回应报文,并且会重启 2MSL 计时器
第二,防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个 2MSL 时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。
意义:
为什么建立连接是三次,关闭连接确是四次
- 建立连接的时候, 服务器在 LISTEN 状态下,收到建立连接请求的 SYN 报文后,把 ACK 和 SYN 放在一个报文里发送给客户端。
- 而关闭连接时,服务器收到对方的 FIN 报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送 FIN 报文给对方来表示同意现在关闭连接,
- 因此,己方 ACK 和 FIN 一般都会分开发送,从而导致多了一次。
如果已经建立了连接, 但是客户端突发故障了怎么办
- TCP 设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。
- 服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为 2 小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔 75 分钟发送一次。
- 若一连发送 10 个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
粘包问题
什么是粘包?
TCP 粘包就是指发送方发送的若干包数据到达接收方时粘成了一包,从接收缓冲区来看,后一包数据的头紧接着前一包数据的尾,出现粘包的原因是多方面的,可能是来自发送方,也可能是来自接收方。
造成粘包原因?
1、在流式服务中出现,send() 与 recv() 不对应,有可能连续俩次 send() 一次 recv() 接受 2、发送端需要等缓冲区满才发送出去,造成粘包 3、接收方不及时接受缓冲区的包,造成多个包接受
如何解决粘包?
- 一次 send() 对应一次 recv() ,就不会粘包
- 加标记符,尾部标记序列
- 规定收到的字符数,定长发送
总结
- 握手和挥手的过程对于程序员来讲是透明的,用户无法干预,是系统自动完成的。
如何验证存在:通过抓包 tcpdump - 可使用 nestat命令查看网络连接,路由表,网络接口信息,使用时如果不带参数,显示活动的 TCP 连接
|