今天(恰巧是今天)看到有人在 SegmentFault 上问「TCP server 为什么一个端口可以建立多个连接?」。提问者认为 client 端就不能使用相同的本地端口了。理论上来说,确定一条链路,只要五元组(源IP、源端口号、目标IP、目标端口号、协议)唯一就可以了,所以这不应该是技术限制。而实际上,Linux 3.9 之后确实可以让客户端使用相同的地址来连接不同的目标,只不过要提前跟内核说好而已。

当然,你不能使用同一个 socket,不然调用connect连接的时候会报错:

 
1
[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。

 
1
2
3
4
5
6
7
8
9
10
11
>>> 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 程序):

 
1
2
3
4
5
6
7
>>> 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

当然你要是连接相同的地址会报错的:

 
1
OSError: [Errno 99] Cannot assign requested address

那个五元组已经被占用啦。

同时创建连接:恰巧你也在这里

有时候,我们不能一个劲地等待。主动出击也是可以的,即便对方并没有在等待。

这个在 TCP 里叫「simultaneous open」,用于 TCP 打洞。但是比起 UDP 打洞难多了,因为那个「simultaneous」字眼:必须同时调用connect,双方的 SYN 包要交叉,早了或者晚了都是会被拒绝的。

所以手工就办不到啦,在本地测试也不容易办到。我本地的系统时间是使用 NTP 同步的,再用一个时钟也和 NTP 同步的 VPS 就可以啦,我这里延迟 80ms 左右,足够那两个 SYN 「在空中会面」了。以下是代码:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/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。在内网里包就不容易进来啦。

然后双方在约定的时间之前跑起来即可,结果是这样子的:

 
1
2
3
4
5
6
7
# 本地
>>> python3 t.py local
b'I love you.'
 
# VPS 上
>>> python3 t.py remote
b'I love you.'

一个人也可以建立 TCP 连接呢

如果你没有 VPS,或者没有公网 IP,也是有活动可以参与的哦。即使只有一个 socket,也可以自己连接到自己的:

 
1
2
3
4
5
6
7
8
>>> 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 输出:

 
1
2
>>> netstat -npt | grep 1314
tcp        0      0 127.0.0.1:1314          127.0.0.1:1314          ESTABLISHED 8050/python 

一个人也可以建立 TCP 连接呢的更多相关文章

  1. 为什么建立TCP连接需要三次握手,为什么断开TCP连接需要四次握手,TIME_WAIT状态的意义

    为什么建立TCP连接需要三次握手? 原因:为了应对网络中存在的延迟的重复数组的问题 例子: 假设client发起连接的连接请求报文段在网络中没有丢失,而是在某个网络节点长时间滞留了,导致延迟到达ser ...

  2. Linux 建立 TCP 连接的超时时间分析(解惑)

    Linux 系统默认的建立 TCP 连接的超时时间为 127 秒,对于许多客户端来说,这个时间都太长了, 特别是当这个客户端实际上是一个服务的时候,更希望能够尽早失败,以便能够选择其它的可用服务重新尝 ...

  3. 详解TCP三次握手(建立TCP连接过程)

    在讲述TCP三次握手,即建立TCP连接的过程之前,需要先介绍一下TCP协议的包结构. 这里只对涉及到三次握手过程的字段做解释 (1) 序号(Sequence number) 我们通过 TCP 协议将数 ...

  4. 通过UDP建立TCP连接

    解释 通过UDP广播查询服务器的IP地址,然后再建立TCP点对点连接. 应用场景 在服务器IP未知时,并且已知服务器与客户端明确在一个局域网或者允许组播的子网下. 通过UDP发现服务器地址然后再进行T ...

  5. 最简单的理解 建立TCP连接 三次握手协议

     最简单的理解一:建立TCP连接:三次握手协议    客户端:我要对你讲话,你能听到吗:服务端:我能听到:而且我也要对你讲话,你能听到吗:客户端:我也能听到.…….互相开始通话…….. 二:关闭TCP ...

  6. 图说使用socket建立TCP连接

    在网络应用如火如荼的今天,熟悉TCP/IP网络编程,那是最好不过.如果你并不非常熟悉,不妨花几分钟读一读. 为了帮助快速理解,先上个图,典型的使用socket建立和使用TCP/UDP连接过程为(截图来 ...

  7. 放弃等待,故障到来:少一个 await 引发的 tcp 连接泄漏故障

    更新:后来升级至 .NET Core 2.2 Preview 3 ,并将 System.Net.Http 升级至 4.3.4 之后没出现这个问题,问题与 https://github.com/dotn ...

  8. 建立TCP连接的三次握手

    请求端(通常称为客户)发送一个 SYN 报文段( SYN 为 1 )指明客户打算连接的服务器的端口,以及初始顺序号( ISN ).服务器发回包含服务器的初始顺序号( ISN )的 SYN 报文段( S ...

  9. 建立TCP连接过程

    1.服务器实例化一个ServerSocket 对象, 表示通过服务器上的端口通信. ServerSocket serverSocket = new ServerSocket(port); 2.服务器调 ...

随机推荐

  1. 让PHPCms内容页支持JavaScript_

    在PHPCms内容页中,出于完全考虑,默认是禁止JavaScript脚本的,所以我们在添加文章时,虽然加入了js代码,但实际上并没有起作用,而是以文本形式显示.如果要让内容页支持JavaScript, ...

  2. python基础——python解析yaml类型文件

    一.yaml介绍 yaml全称Yet Another Markup Language(另一种标记语言).采用yaml作为配置文件,文件看起来直观.简洁.方便理解.yaml文件可以解析字典.列表和一些基 ...

  3. Shell-help格式详解

    前言 linux shell命令通常可以通过-h或--help来打印帮助说明,或者通过man命令来查看帮助,有时候我们也会给自己的程序写简单的帮助说明,其实帮助说明格式是有规律可循的 帮助示例 下面是 ...

  4. 【Linux】Linux基本命令扫盲【转】

    转自:http://www.cnblogs.com/lcw/p/3762927.html [VI使用] 1.在命令行模式     :在vi编辑器中将光标放在函数上,shift + k 可直接man手册 ...

  5. HTTP Headers解析

    什么是HTTP Headers? 它包含了哪些内容? 利用requests.get()函数对豆瓣读书进行请求, 返回的r.headers如下所示: >>> import reques ...

  6. xgboost gbdt特征点分烈点

    lightGBM与XGBoost的区别:(来源于:http://baijiahao.baidu.com/s?id=1588002707760744935&wfr=spider&for= ...

  7. TObject、TPersisent 、TComponent、TControl、TGraphicControl、TWinControl 关系图

    VCL的类图结构               TObject                 |               TPersisent                 |         ...

  8. CF1030A 【In Search of an Easy Problem】

    题目巨简单,主要是给大家翻译一下 给n个数,其中存在1就输出HARD,否则输出EASY,不区分大小写 #include<iostream> #include<cstdio> u ...

  9. hdu 5943(素数间隔+二分图匹配)

    Kingdom of Obsession Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Oth ...

  10. Python线程和进程

    一.进程 程序并不能单独和运行只有将程序装载到内存中,系统为他分配资源才能运行,而这种执行的程序就称之为进程.程序和进程的区别在于:程序是指令的集合,它是进程的静态描述文本:进程是程序的一次执行活动, ...