理论上来说,确定一条链路,只要四元组(源IP、源端口号、目的 IP、目的端口号)。而计算机之间的通信链路都是通过 socket 来建立的,socket 本身是传输层的一个抽象,为应用层之间的网络通信提供接口。所以只要这五元组中的一个不同,就可以创建不同的socket / 连接。传输层头部表示端口号的字段为 2 个字节,所以对应一台单机服务器,比如 http 服务器,自身的 ip 地址固定了,使用的端口为 80,最大能支持的连接数为 2^(32 + 16),32表示目的地址的4字节的 ip,16 表示目的计算机的 端口号可能情况。这是一个非常大的数字。
对于TCP协议,服务器首相创建的是侦听套接字,在 accept 之后,返回的是新的 socket ,这个 socket 才是表示连接的用四元组唯一表征的套接字。
一下例子来源网络,同一个 IP 和 端口 自己和自己连接。
In [1]: import socket
...: s = socket.socket()
...: s.bind(('127.0.0.1', 520))
...: s.connect(('127.0.0.1', 520))
...: s.send(b'I love you.')
...:
Out[1]: 11
In [2]: s.recv(1024)
Out[2]: b'I love you.'
socket.socket 的原型,这里的形如 <XXX.YYY: N> 都是枚举类型:
socket.socket(family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, proto=0, fileno=None)
枚举类型可以如下方式进行定义:
In [1]: from enum import Enum
...: class Color(Enum):
...: red = 1
...: green = 2
...: blue = 3
...:
...: Color.red
...:
Out[1]: <Color.red: 1>
In [2]: type(Color.red)
Out[2]: <enum 'Color'>
附上 TCP 11个状态的转换图:
需要注意的地方:
1、半打开状态:指一端 A 异常终止连接了,而另一方 B 却不知情。这样在 B 发送数据给 A 时,A 会回复 RST 包来重新建立连接。
2、同时打开:通常 TCP 通过三次握手建立连接,这样通信双方实际在建立过程中,有四次报文交互,而不是三次。
A,B在接收到对方的SYN 报文之后从SYN_SENT状态进入SYN_RECD状态,都发送一个SYN, ACK 在接收 ACK 之后建立连接。
3、同时关闭:同时关闭时会出现罕见的 CLOSING 状态。
4、关于 TIME_WAIT 状态的2MLS超时:
先来复习一下:MSL 是 Maximum Segment Lifetime,表示报文的最大生存时间,单位是时间,是一个配置值。IP 头中有 TTL,是 time to live,这个虽说也能间接的表示生存时间,但其实表示的是数据包进过的路由器的跳数:每进过一个路由器,该值减1.RTT 是 round-trip time,表示数据往返一趟的时间,是通过算法来估算出来的。
所以这里为啥要等待2倍的报文最大生存时间:
原因在于:1、四次挥手的最后一个 Ack 对方没有收到,所以这时对端在判定 Ack 丢失之后,会重新发送四次挥手的第三个动作 FIN 给本端,所以需要等到 2 倍的生存时间。 2、为了使双方建立一条和本次连接相同的四元组的连接时,不会收到之前的 FIN 而导致复位。
另外,FIN_WAIT_2 是没有超时的,如果对端不配合发送 FIN,FIN_WAIT_2 将会一直保持直到系统关闭,过多的FIN_WAIT_2 可能会导致系统崩溃。
记住:只有主动关闭的才会有 TIME_WAIT 状态,被动关闭的是 CLOSE_WAIT 状态。
TCP 是基于流的可靠传输,没有边界(UDP有消息边界),可靠的意思是确保数据可靠的到达对方的协议栈,至于应用层是否读取到该数据就是另外回事了。