Python套接字点对点
我正在尝试用Python 2.7创建一个简单的点对点网络。问题是,我似乎无法在两台机器之间建立连接,让它们同时充当服务器和客户端。我可以做到一台是服务器,另一台是客户端,但两者都同时作为服务器和客户端时就不行。我需要创建两个套接字吗?另外,我是用TCP来连接的。
更新:
import socket, sys # Import socket module
s = socket.socket() # Create a socket object
host = socket.gethostname() # Get local machine name
port = 12345 # Reserve a port for your service.
s.bind((host, port)) # Bind to the port
if sys.argv[1] == "connect":
host = sys.argv[2]
s.connect((host, port))
s.close
else:
s.listen(5) # Now wait for client connection.
while True:
c, addr = s.accept() # Establish connection with client.
print 'Got connection from', addr
c.send('Thank you for connecting')
c.close()
代码写得不是很好,因为要让某台机器作为客户端连接,必须使用“connect”这个参数,后面跟上第二台机器的主机名或IP地址。我无法让两台机器同时连接并互相服务。
2 个回答
是的,你需要使用两个套接字,一个用来接受连接(服务器),另一个用来发起连接(客户端)。不过,你可以把这两个套接字绑定到同一个本地端口,使用这个端口号作为源端口和目标端口,这样就能确保每对连接之间只有一个连接。如果两个对等方同时尝试连接(比如因为它们同时发现了对方),其中一个客户端的连接尝试会失败(因为对方的服务器套接字接受了连接),你需要处理这个失败的情况(可以选择忽略它)。
要在同一个端口上绑定两个套接字,你需要在两个套接字上设置 SO_REUSEPORT/SO_REUSEADDR
标志。
下面是一个示例程序,演示了这种技术(使用优秀的 trio 库在 Python 3 中):
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
# the other client was faster than us
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)
这一行,它们会使用临时源端口,从而创建两个独立的连接,而不是只有一个。
是的,需要两个插座(socket)。一个是用来监听的插座,它应该在一个固定的端口上打开;另一个是客户端的插座,它应该在一个不同的(可能是动态的)端口上打开,通常这个端口号会比较高。举个例子:
服务器的插座在1500端口,客户端的插座在1501端口。
Peer1的地址是:192.168.1.101
Peer2的地址是:192.168.1.102
当Peer1连接到Peer2时,连接的样子是:192.168.1.101:1501 -> 192.168.1.102:1500。
当Peer2连接到Peer1时,连接的样子是:192.168.1.102:1501 -> 192.168.1.101:1500。
监听的TCP插座通常是在一个单独的线程中运行,因为它们会阻塞其他操作。