本文来自依云's Blog,转载请注明。
人们对 TCP 的误解
因为我们的教育总是只教人「怎么做」,而根本不管「为什么这么做」,所以造成了很多误解。
今天(恰巧是今天)看到有人在 SegmentFault 上问「TCP server 为什么一个端口可以建立多个连接?」。提问者认为 client 端就不能使用相同的本地端口了。理论上来说,确定一条链路,只要五元组(源IP、源端口号、目标IP、目标端口号、协议)唯一就可以了,所以这不应该是技术限制。而实际上,Linux 3.9 之后确实可以让客户端使用相同的地址来连接不同的目标,只不过要提前跟内核说好而已。
当然,你不能使用同一个 socket,不然调用connect
连接的时候会报错:
[Errno 106] (EISCONN) Transport endpoint is already connected
man 2 connect
里说了:
Generally, connection-based protocol sockets may successfully connect() only once; connectionless protocol sockets may use connect() multiple times to change their association.
想也是,一个 socket 连接到多个目标,那发送的时候到底发给谁呢?TCP 又不像 UDP 那样无状态的,以前做过什么根本不管。
那用多个 socket 就可以了嘛。服务端其实也一直是用多个 socket 来处理多个连接的不是么,每次accept
都生成个新的 socket。
>>> import socket >>> s = socket.socket() # since Linux 3.9, 见 man 7 socket >>> s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) >>> s2 = socket.socket() >>> s2.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) >>> s.bind(('127.0.0.1', 12345)) >>> s2.bind(('127.0.0.1', 12345)) # 都可以使用同一本地地址来连接哦 >>> s.connect(('127.0.0.1', 80)) >>> s2.connect(('127.0.0.1', 4321))
连上去之后 netstat 的输出(4568 进程是上边这个程序,另两个进程一个是 nginx,另一个是我的另一个 Python 程序):
>>> netstat -npt | grep 12345 (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) tcp 0 0 127.0.0.1:4321 127.0.0.1:12345 ESTABLISHED 18284/python3 tcp 0 0 127.0.0.1:12345 127.0.0.1:4321 ESTABLISHED 4568/python3 tcp 0 0 127.0.0.1:80 127.0.0.1:12345 ESTABLISHED - tcp 0 0 127.0.0.1:12345 127.0.0.1:80 ESTABLISHED 4568/python3
当然你要是连接相同的地址会报错的:
OSError: [Errno 99] Cannot assign requested address
那个五元组已经被占用啦。
同时创建连接:恰巧你也在这里
有时候,我们不能一个劲地等待。主动出击也是可以的,即便对方并没有在等待。
这个在 TCP 里叫「simultaneous open」,用于 TCP 打洞。但是比起 UDP 打洞难多了,因为那个「simultaneous」字眼:必须同时调用connect
,双方的 SYN 包要交叉,早了或者晚了都是会被拒绝的。
所以手工就办不到啦,在本地测试也不容易办到。我本地的系统时间是使用 NTP 同步的,再用一个时钟也和 NTP 同步的 VPS 就可以啦,我这里延迟 80ms 左右,足够那两个 SYN 「在空中会面」了。以下是代码:
#!/usr/bin/env python3 import time import sys import socket import datetime def wait_until(t): deadline = t.timestamp() to_wait = deadline - time.time() time.sleep(to_wait) s = socket.socket() s.bind(('', 1314)) if sys.argv[1] == 'local': ip = 'VPS 的地址' else: ip = '我的地址' t = datetime.datetime(2015, 8, 19, 22, 14, 30) wait_until(t) s.connect((ip, 1314)) s.send(b'I love you.') print(s.recv(1024))
当然,我是公网 IP。在内网里包就不容易进来啦。
然后双方在约定的时间之前跑起来即可,结果是这样子的:
# 本地 >>> python3 t.py local b'I love you.' # VPS 上 >>> python3 t.py remote b'I love you.'
一个人也可以建立 TCP 连接呢
如果你没有 VPS,或者没有公网 IP,也是有活动可以参与的哦。即使只有一个 socket,也可以自己连接到自己的:
>>> import socket >>> s = socket.socket() >>> s.bind(('127.0.0.1', 1314)) >>> s.connect(('127.0.0.1', 1314)) >>> s.send(b'I love you.') 11 >>> s.recv(1024) b'I love you.'
netstat 输出:
>>> netstat -npt | grep 1314 tcp 0 0 127.0.0.1:1314 127.0.0.1:1314 ESTABLISHED 8050/python
Aug 19, 2015 10:49:39 PM 标题好寂寞啊...
Aug 19, 2015 11:26:07 PM
真会玩!
Aug 20, 2015 09:06:00 PM
最后那个……真会玩。
另,祝幸福。
Aug 20, 2015 09:09:16 PM
所以最后无聊地一个人玩DIY了么?
——另外第一次听说一个socket还可以连接自己
Aug 20, 2015 11:59:12 PM
谢谢。
我也是查 simultaneous open 的时候在 StackOverflow 上看到能这么玩的。
Aug 21, 2015 12:31:07 AM
七夕节发这个感觉很别致啊!...
【一个人可以连多个。。一个人可以连自己。。
Aug 25, 2015 10:42:37 PM
这是什么语言啊?语法不错的样子,是nodejs吗?
Aug 25, 2015 10:58:37 PM
是 Python 啦……
Aug 25, 2015 11:10:58 PM
没注意看!#,抱歉
Aug 26, 2015 12:07:09 AM
GNU/Linux 是为数不多支持 TCP simultaneous open 的系统之一,因此这个功能虽然很有用,但使用的程序很少。
在注重安全的服务器上,这个内核特性往往被禁用来提升安全性,因为它一旦被攻击者利用,还是挺危险的。
The weakness allows an attacker to easily prevent
a client from connecting to a known server provided the source port for the connection is guessed correctly.
As the weakness could be used to prevent an antivirus or IPS from fetching updates, or prevent an SSL gateway from fetching a CRL, it should be eliminated by enabling this option. Though Linux is one of few operating systems supporting simultaneous connect, it has no legitimate use in practice and is rarely supported by firewalls.
Aug 26, 2015 09:40:38 AM
Play with yourself!(da fei ji)
233333333333
Sep 06, 2015 11:49:19 PM
写的真好,才知道simultaneous open这回事。PS.simultaneous open这个脚本写错了,会报错。
Sep 07, 2015 12:26:15 AM
咦?会报什么错呀?
Sep 07, 2015 12:32:27 AM
= =没事,刚刚发现了。datatime在python3.2里面是没有timestamp这个函数,3.5就有了。你的VPS上的Python好新~
Sep 07, 2015 12:38:43 AM
3.4 也有。是我自己编译安装的~
May 27, 2017 03:25:06 PM
会玩
Aug 28, 2020 01:38:22 PM
感谢楼主,昨天面试问了这个问题没答好,看了你的博客完全理解了