tcp简介
什么是 TCP ?
TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。
面向连接:一定是「一对一」才能连接,不能像 UDP 协议可以一个主机同时向多个主机发送消息,也就是一对多是无法做到的;
可靠的:无论的网络链路中出现了怎样的链路变化,TCP 都可以保证一个报文一定能够到达接收端;
字节流:消息是「没有边界」的,所以无论我们消息有多大都可以进行传输。并且消息是有序的,当前一个消息没有收到的时候,即使它先收到了后面的字节,那么也不能扔给应用层去处理,同时对重复的报文会自动丢弃。
为什么需要 TCP 协议?
IP层是不可靠的,它不保证网络包的交付、不保证网络包的按序交付、也不保证网络包中的数据的完整性。
如果需要保障网络数据包的可靠性,那么就需要由上层(传输层)的 TCP 协议来负责。
因为 TCP 是一个工作在传输层的可靠数据传输的服务,它能确保接收端接收的网络包是无损坏、无间隔、非冗余和按序的。
TCP报文段的首部格式
TCP报文段分为首部和数据部分。首部的前20个字节是固定的,后面根据需要可增加字节的选项。
TCP报文段的首部格式
字段意义如下:
-
源端口和目的端口:各占2个字节,分别写入源端口号和目的端口号。 -
序号:占4字节,指明本报文段所发送的数据的第一个字节的序号。TCP连接中传送的字节流中的每一个字节都按顺序编号。 -
确认号:占4字节,指明期望收到对方下一个报文段第一个数据字节的序号。若确认号,则表明:到序号为止的所有数据都已正确收到。 -
数据偏移:占4位,指明数据起始处距离报文段起始处有多远。实际上是指出TCP报文段的首部长度。 -
保留:保留为今后使用,但目前应置为0。 -
控制位:置1有效,置0无效) -
紧急URG(URGent):告诉系统此报文段有紧急数据,应尽快传送(相当于高优先级的数据)。与紧急指针字段匹配使用。 -
确认ACK(ACKnowledgment):TCP规定,在连接建立后所有传送的报文段都必须把ACK置1。 -
推送PSH(PuSH):指明本报文段希望尽快交付。用于交互式通信时,可直接创建报文并发送,不用等待缓存填满。 -
复位RST(ReSeT):表明TCP连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后重新建立运输连接。 -
同步SYN(YNchronization)emsp;:在连接建立时用来同步序号。对放若同意建立连接,则在报文段中使SYN=1和ACK=1。 -
终止FIN(FINis):用来释放连接。 -
窗口:占2字节。窗口指的是发送本报文段一方的接收窗口。之所有有这个限制,是因为接收方的数据缓存空间是有限的。本字段的值可以作为接收方设置其发送窗口的依据。窗口字段明确指出了现在允许对方发送的数据量。窗口值经常在动态变化着。 -
检验和:占2字节。同UDP。 -
紧急指针:占2字节。紧急指针仅在URG=1时有意义,它指出了紧急数据末尾在报文段中的位置。 -
选项:长度可变,最长可达40字节。没有使用时,TCP的首部长度为20字节。 -
最大报文段长度MSS(Maximum Segment Size):MSS是每一个TCP报文段中的数据字段的最大长度。默认值为536字节长。 -
窗口扩大选项:占3个字节。其中有一个字节表示移位值S,新窗口值从16位扩大到,用于获得高吞吐率的应用情形。 -
时间戳选项:占10个字节。其中最主要的字段是时间戳值字段(4字节)和时间戳回送回答字段(4字节)。此选项有两个功能:第一,用来计算往返时间RTT;第二,用来处理TCP序号超过的情况。
TCP连接的建立和终止
什么是TCP连接?
我们来看看 RFC 793 是如何定义连接的:
Connections:
The reliability and flow control mechanisms described above require that TCPs initialize and maintain certain status information for each data stream. The combination of this information, including sockets, sequence numbers, and window sizes, is called a connection.
简单来说就是,用于保证可靠性和流量控制维护的某些状态信息,这些信息的组合,包括Socket、序列号和窗口大小称为连接。
所以我们可以知道,建立一个 TCP 连接是需要客户端与服务器端达成上述三个信息的共识。
Socket:由 IP 地址和端口号组成;
序列号:用来解决乱序问题等;
窗口大小:用来做流量控制。
TCP的连接建立
TCP连接的建立采用客户服务器方式。主动发起连接建立的应用进程叫做客户(client),被动等待连接建立的应用进程叫做服务器(server)。
具体过程
- 客户进程A创建首先传输控制模块TCB,然后在打算建立TCP连接时,向服务器B发出连接请求报文段(首部中同步位SYN=1,初始序号seq=x)。这时,TCP客户进程进入SYN-SENT(同步已发送)状态。
- B收到连接请求报文段后,如果同意建立连接,则向A发送确认报文段(同部位SYN=1,确认位ACK=1,确认号ack=x+1,初始序号seq=y)。这时,TCP服务器进入SYN-RCVD(同步收到)状态。
- A收到B的确认后,还要向B发送确认报文段(确认位ACK=1,确认号ack=y+1,序号seq=x+1)。A进入ESTABLISHED(已建立连接)状态。
- B收到A的确认后,也进入ESTABLISHED状态。
总结:请求连接则置同步位SYN=1,确认连接就置确认位ACK=1(TCP规定,SYN报文段不能携带数据,但要消耗一个序号;ACK报文段可以携带数据,但如果不携带数据则不消耗序号)。
TCP的连接释放
具体过程
- A的应用进程先向B发送连接释放报文段(终止控制位FIN=1,序号seq=u),并停止发送数据,进入FIN-WAIT-1(终止等待1)状态。
- B收到连接释放报文段后即发出确认报文段(确认位ACK=1,序号seq=v,确认号ack=u+1),然后B进入CLOSE-WAIT(关闭等待)状态。此时A到B方向的连接已经释放,B若要发送数据,A仍要接收。
- A收到来自B的确认后,就进入FIN-WAIT-2(终止等待2)状态,等待B发出的连接释放报文段。
- B若没有要向A发送的数据了,就发送连接释放报文段(终止位FIN=1,序号seq=w,确认号ack=u+1),然后B就进入LAST-ACK(最后确认)状态,等待A的确认。
- A收到B的连接释放报文段后,必须对此发出确认(ACK=1,ack=w+1,seq=u+1)。然后进入TIME-WAIT(时间等待)状态。此时,TCP连接还没有释放掉。必须经过2倍最长报文段寿命(MSL)时间后(通常MSL=2分钟),TCP连接结束。
总结:请求释放连接则置FIN=1,确认释放则置ACK=1。
为什么要等待2MSL时间?
- 为了保证A发送的最后一个ACK报文段能够到达B。
- 防止“已失效的连接请求报文段”出现在本连接中。
保活计时器: ??客户端主机出现故障时,服务器会白白等待下去。为此需要设置保活计时器。服务器每收到一次客户的数据,就重置计时器,时间通常是两小时。若两小时没有收到客户端的数据,服务器就发送一个探测报文段,以后每隔75秒发送一次。若一连发送10个探测报文段后仍无客户的响应,服务器就认为客户端出现了故障,接着的关闭这个连接。
TCP的有限状态机
TCP涉及连接建立和连接终止的操作可以用状态转换图(state transition diagram)来说明。
TCP为一个连接定义了11种状态,并且TCP规则规定如何基于当前状态及在该状态下所接收的分节从一个状态转换到另一个状态。举例来说,当某个应用进程在CLOSED状态下执行主动打开时,TCP将发送一个SYN,且新的状态是SYN_SENT。如果这个TCP接着接收到一个带ACK的SYN,它将发送一个ACK,且新的状态是ESTABLISHED。这个最终状态是绝大多数数据传送发生的状态。
自ESTABLISHED状态引出的两个箭头处理连接的终止。如果某个应用进程在接收到一个FIN之前调用close(主动关闭),那就转换到FIN_WAIT_1状态。但如果某个应用进程在ESTABLISHED状态期间接收到一个FIN(被动关闭),那就转换到CLOSE_WAIT状态。
我们用粗实线表示通常的客户状态转换,用粗虚线表示通常的服务器状态转换。
图中还注明存在两个我们未曾讨论的转换:一个为同时打开(simultaneous open),发生在两端几乎同时发送SYN并且这两个SYN在网络中交错的情形下,另一个为同时关闭(simultaneous close),发生在两端几乎同时发送FIN的情形下。
● 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: 这个状态要好好解释一下,其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是: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报文。但是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可用状态了;
粗实线箭头表示对客户进程的正常变迁。粗虚线箭头表示对服务器进程的正常变迁。另一种细线箭头表示异常变迁。
tcp中的socket编程
建立TCP连接
-
服务器必须准备好接受外来的连接。这通常通过调用socket、bind和listen这3个函数来完成,我们称之为被动打开(passive open)。 -
客户通过调用connect发起主动打开(active open)。这导致客户TCP发送一个SYN(同步)分节,它告诉服务器客户将在(待建立的)连接中发送的数据的初始序列号。通常SYN分节不携带数据,其所在IP数据报只含有一个IP首部、一个TCP首部及可能有的TCP选项。 -
服务器必须确认(ACK)客户的SYN,同时自己也得发送一个SYN分节,它含有服务器将在同一连接中发送的数据的初始序列号。服务器在单个分节中发送SYN和对客户SYN的ACK(确认)。 -
客户必须确认服务器的SYN。 这种交换至少需要三个分组,因此称之为三次握手。
终止TCP连接
- 某个应用进程首先调用close,我们称该端执行主动关闭(active close)。该端的TCP于是发送一个FIN分节,表示数据发送完毕。
- 接收到这个FIN的对端执行被动关闭(passive close)。这个FIN由TCP确认。它的接收也作为一个文件结束符(end-of-file)传递给接收端应用进程(放在已排队等候该应用进程接收的任何其他数据之后),因为FIN的接收意味着接收端应用进程在相应连接上再无额外数据可接收。
- 一段时间后,接收到这个文件结束符的应用进程将调用close关闭它的套接字。这导致它的TCP也发送一个FIN。
- 接收这个最终FIN的原发送端TCP(即执行主动关闭的那一端)确认这个FIN。
既然每个方向都需要一个FIN和一个ACK,因此通常需要4个分节。我们使用限定词“通常”是因为:某些情形下步骤1的FIN随数据一起发送;另外,步骤2和步骤3发送的分节都出自执行被动关闭那一端,有可能被合并成一个分节。
观察分组
下图展示一个完整的TCP连接所发生的实际分组交换情况,包括连接建立、数据传送和连接终止3个阶段。图中还展示了每个端点所历经的TCP状态。
例中的客户通告一个值为536的MSS(表明该客户只实现了最小重组缓冲区大小),服务器通告一个值为1460的MSS(以太网上IPv4的典型值)。
一旦建立一个连接,客户就构造一个请求并发送给服务器。这里我们假设该请求适合于单个TCP分节(即请求大小小于服务器通告的值为1460字节的MSS)。服务器处理该请求并发送一个应答,我们假设该应答也适合于单个分节(本例即小于536字节)。
图中使用粗箭头表示这两个数据分节。注意,服务器对客户请求的确认是伴随其应答发送的。这种做法称为捎带(piggybacking),它通常在服务器处理请求并产生应答的时间少于200 ms时发生。如果服务器耗用更长时间,譬如说1 s,那么我们将看到先是确认后是应答。
参考:https://yyf.zone/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/page/3/
参考:《UNIX网络编程卷一》
|