Python套接字点对点通信

14

我正在尝试在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地址。我无法使这两台机器同时连接和相互服务。


我更新了问题。谢谢! - user3566150
2个回答

9
是的,两个socket是必要的。监听socket应该在一个固定的端口上打开,而客户端端口应该在另一个(可能是动态的)端口上打开,通常在端口范围内较高的位置。例如:
服务器socket在端口1500上,客户端socket在端口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 sockets通常也在单独的线程上运行,因为它们是阻塞的。

3
如何从第一个节点获取第二个节点的IP地址?我们是否必须手动输入其IP地址? - Hzzkygcs

1

是的,您需要使用两个套接字,一个用于接受连接(服务器),另一个用于初始化连接(客户端)。但是,您可以将这两个套接字绑定到同一本地端口上,使用该端口号作为源和目标端口,从而确保您最终只会在每对对等体之间建立单个连接。如果两个对等方同时尝试连接(例如因为他们同时发现彼此),则其中一个客户端连接尝试将失败(其中对等方的服务器套接字已接受连接),您必须处理(忽略)它。

要在同一端口上绑定两个套接字,您需要在两个套接字上设置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
            # 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)那一行,它们将使用短暂的源端口,并创建两个单独的连接,而不仅仅是一个连接。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接