TCP连接的建立和终止
TCP建立连接:三路握手
- 服务器做好接收外来连接的准备:依次调用socket、bind、listen
- 客户端通过调用connect主动发起连接(这导致TCP客户发送一个
SYN
分节) - 服务端通过调用accept被动接受连接:1.服务端确认客户端发来的SYN分节(ACK),2.发送自己的SYN分节(1和2是在同一个分节)
- 客户端确认服务端SYN分节(ACK)
TCP终止连接:四次挥手
- 某个进程首先调用close,称之为主动关闭。上图是TCP客户端主动关闭。close的调用会导致TCP客户端发送一个
FIN
分节,表示数据发送完毕。 - 接收到这个FIN分节的端执行被动关闭。上图是TCP服务端被动关闭。服务端接收到FIN分节后,调用read会返回0(读到的是
文件结束符EOF
)。同时服务端需要对客户端发来的FIN分节进行确认(发送ACK分节)。此时连接处于半关闭
状态,即:在连接中客户端不再向服务端传输任何数据(服务端也不能再从客户端收到数据),但是服务端仍然可以往客户端发送数据(客户端仍然可以从服务端接收数据)。 - (当服务端发送完数据后)服务端调用close,同样会发送FIN分节。
- 客户端需要对服务端发来的FIN分节进行确认(ACK)。此时TCP连接终止完成(其实还未完,后面再讲)。
当TCP的客户端或者服务端进程在(自愿或者非自愿)终止时,都会给任何打开的TCP连接发出一个FIN分节。
TCP状态转换
一个例子:
TIME_WAIT状态
执行主动关闭的那一端在关闭连接的最后时,需要等待一段时间(对应就是TIME_WAIT状态)。这个状态维持的时间是2 ×MSL。MSL是任何IP数据包能够在互联网中存活的最长时间。
TIME_WAIT状态存在的两个理由:
- 可靠地实现TCP连接的终止
如上图2-5 TCP连接的分组交换。
- 假设图中最后一个ACK分节(客户端确认服务端的FIN分节)丢失时。
- 若没有TIME_WAIT状态(即客户端在发送完一个ACK分节时,立即关闭连接)。此时服务端由于没有收到客户端对FIN分节的确认(ACK),进行超时重传,重发FIN分节,而此时客户端已经关闭,然后客户端在接收这个FIN分节后,回复一个
RST
分节,服务端收到RST分节时,将其解析为一个错误,TCP连接终止失败。- 有了TIME_WAIT状态之后,客户端在发送完最后一个ACK分节后,会等待2MSL。
- 一个MSL是为了让ACK分节有足够的时间送到服务端或者丢失
- ACK分节如果送到服务端,就顺利终止TCP连接。服务端进入CLOSED状态,客户端依然是TIME_WAIT状态,不过2MSL过后就关闭连接
- 如果ACK分节丢失,过了一个MSL后,服务端重发FIN分节
- 另一个MSL是为让服务端超时重传的FIN分节送到客户端或者丢失)
- 如果服务端超时重传的FIN分节送到客户端,客户端收到FIN分节后,发出ACK分节后又重新计时
- 如果服务端超时重传的FIN分节丢失,然后又超时重传
- 允许老的重复分节在网络中消逝
如上图2-5 TCP连接的分组交换。
- 假设图中的四次挥手都顺利进行。
- 假如没有TIME_WAIT状态。有一种可能:假设场景TCP客户端套接字(12.12.12.12,12),服务端套接字(13,13,13,13,13)。客户端在发送最后一个ACK分节后立即关闭连接。然后过一段时间在相同的IP和端口之间建立另一个连接(即客户端和服务端的套接字都不变)。此时在连接关闭之前所发送的分节(属于上一个连接的)(无论是那个方向)到了任意一端,收到这个分节的这一端,
误以为
是新连接的对端发来的,从而造成串话
。- 假如有了TIME_WAIT状态。上面旧连接的分节将会在2MSL中丢弃(因为连接已经关闭,即使送到任意一端也会被丢弃或者在途中消逝)。所以TCP默认不给处于TIME_WAIT状态的连接发起新的化身(可以通过设置改变)。从而保证了每建立一个TCP连接时,来自该连接先前连接的重复分组都已消逝了。
端口
端口号是16位符号整数(unsigned short int)。整体范围是0~2^16 - 1(65535)。
- 公认端口(Well Known Ports):从0到1023,它们紧密绑定(binding)于一些服务。通常这些端口的通讯明确表明了某种服务的协议。例如:80端口实际上总是HTTP通讯。
- 注册端口(Registered Ports):从1024到49151。它们松散地绑定于一些服务。也就是说有许多服务绑定于这些端口,这些端口同样用于许多其它目的。例如:许多系统处理动态端口从1024左右开始。
- 动态和/或私有端口(Dynamic and/or Private Ports):从49152到65535。理论上,不应为服务分配这些端口。实际上,机器通常从1024起分配动态端口。但也有例外:SUN的RPC端口从32768开始。
**注意:**关于端口的解析:TCP 和 UDP 可以使用同一个端口吗? - 知乎 (zhihu.com)
缓冲区大小及限制
- IP数据报的最大大小。IPv4的最大大小是65535字节,包括首部,表示总长度的字段占16位。
- 硬件MTU(maximum transmission unit,最大传输单元)。如以太网MTU为1500字节。
- 路径MTU
- TCP的MSS(maximum segment size,最大分节大小)
TCP输出
- 套接字发送缓冲区大小可以通过setsockopt的SO_SNDBUF设置
- write的返回值:从应用进程缓冲区拷贝到套接字发送缓冲区的字节数。
- write调用成功仅仅代表数据从应用缓冲区拷贝到内核缓存区,可以重新使用应用缓冲区,不代表数据真正发送
- write阻塞条件:当套接字发送缓冲区容不下调用write所传入的len字节就会阻塞,使得应用进程被投入睡眠
- 套接字缓冲区的数据就是如图,加上TCP头,IP头,然后进入数据链路的输出队列进行排队输出。如果队列满,则直接丢弃该分组(当然还有TCP重传那套)
UDP输出
与TCP输出的一点就是套接字发送缓冲区是不存在的,调用sendto,成功返回就代表数据从应用进程缓冲区进入到数据链路的输出队列。