是的,您需要使用两个套接字,一个用于接受连接(服务器),另一个用于初始化连接(客户端)。但是,您可以将这两个套接字绑定到同一本地端口上,使用该端口号作为源和目标端口,从而确保您最终只会在每对对等体之间建立单个连接。如果两个对等方同时尝试连接(例如因为他们同时发现彼此),则其中一个客户端连接尝试将失败(其中对等方的服务器套接字已接受连接),您必须处理(忽略)它。
要在同一端口上绑定两个套接字,您需要在两个套接字上设置SO_REUSEPORT
/SO_REUSEADDR
标志。
以下是一个演示此技术的示例程序(使用 Python 3 上的优秀 trio):
from errno import EADDRNOTAVAIL
from functools import partial
from itertools import count
import trio
import socket
async def peer(SRC, DEST):
counter = count(start=1)
async def sender(stream, n):
print(f"sender{n}@{SRC}: started!")
while True:
data = bytes(f"Hello from {n}@{SRC}", "utf8")
print(f"sender{n}@{SRC}: sending {data!r}")
await stream.send_all(data)
await trio.sleep(1)
async def receiver(stream, n):
print(f"receiver{n}@{SRC}: started!")
async for data in stream:
print(f"receiver{n}@{SRC}: got data {data!r}")
print(f"receiver{n}@{SRC}: connection closed")
async with trio.open_nursery() as nursery:
async def run(connection: trio.SocketStream):
count = next(counter)
print(f"peer@{SRC} got connection{count} from {method}() with {connection.socket.getpeername()}")
async with connection:
async with trio.open_nursery() as nursery:
print(f"peer@{SRC}: spawning sender...")
nursery.start_soon(sender, connection, count)
print(f"peer@{SRC}: spawning receiver...")
nursery.start_soon(receiver, connection, count)
print(f"peer: listening at {SRC}")
servers = await trio.open_tcp_listeners(SRC[1], host=SRC[0])
servers[0].socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
servers[0].socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
await nursery.start(trio.serve_listeners, partial(run, "listen"), servers)
print(f"peer: connecting from {SRC} to {DEST}")
client = trio.socket.socket()
client.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
client.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
await client.bind(address=SRC)
try:
await client.connect(address=DEST)
except OSError as err:
if err.errno != EADDRNOTAVAIL:
raise
print(f"peer@{SRC}: {err.strerror}")
else:
await run('connect', trio.SocketStream(client))
async def main():
async with trio.open_nursery() as nursery:
a = ("127.0.0.1", 12345)
b = ("127.0.0.1", 54321)
nursery.start_soon(peer, a, b)
nursery.start_soon(peer, b, a)
trio.run(main)
在这个小演示中,两个端点在同一主机上的不同端口内运行相同的程序,但是如果使用了相同的端口但不同的主机,同样也可以工作。请注意,如果注释掉
client.bind(address=SRC)
那一行,它们将使用短暂的源端口,并创建两个单独的连接,而不仅仅是一个连接。