Python套接字/端口转发

Ale*_*ler 2 python sockets linux bash networking

我已经用python编写了服务器和客户端程序

Server.py

 import socket

sock = socket.socket (socket.AF_INET, socket.SOCK_STREAM)

host = socket.gethostname()
port = 5555

sock.bind((host, port))

sock.listen(1)

conn, addr = sock.accept()

data = "Hello!"
data = bytes(data, 'utf-8')

conn.send(data)

sock.close()
Run Code Online (Sandbox Code Playgroud)

Linux上的Client.py

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

host = socket.gethostname()
port = 5555

sock.connect((host, port))

data = sock.recv(2048)

data = str(data, "utf-8")

print(data)

sock.close()
Run Code Online (Sandbox Code Playgroud)

当我在本地计算机(Linux Mint)上运行服务器和客户端时,它可以正常工作。我收到“你好!” 在bash中,一切都很好。但是,当我将客户端程序转移到另一台计算机(Windows 8)并运行它时(当然,以前我在Linux上运行服务器,并且将客户端中的IP地址更改为我的静态Linux Mint的IP)

ConnectionRefusedError:[WinError 10061]无法建立连接,因为目标计算机主动拒绝了它

Windows上的client.py

  import socket

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    host = "here is my static ip"
    port = 5555

    sock.connect((host, port))

    data = sock.recv(2048)

    data = str(data, "utf-8")

    print(data)

    sock.close()
Run Code Online (Sandbox Code Playgroud)

我必须说我已经在端口5555的路由器设置中完成了端口转发。之前我对端口80做过同样的事情,并且我自己的站点正常工作,但是现在在使用python套接字的5555中它不起作用!为什么?我不明白!还有一件事:我试图将服务器和客户端文件中的端口更改为80,但是它也无法正常工作。请帮忙。

Qee*_*eek 7

您必须socket.gethostname()将服务器脚本中的更改为空字符串(或直接调用socket.bind(('', port)))。

您的问题不在python中,而是在套接字的使用方面。创建套接字时,您只需准备过程即可从另一个进程接收/向另一个进程发送一些数据。

服务器

创建套接字的第一步,您必须指定在这些进程之间进行通信时将使用哪种协议。在您的情况下,socket.AF_INET对于IP协议的使用,它是恒定不变的,并且它socket.SOCK_STREAM是指定可靠的面向流的服务。可靠的面向流的服务意味着您希望确保每个发送的字节都将传递到另一侧,并且在通信过程中不会丢失任何内容(底层OS会为此使用TCP协议)。从这一点来看,我们正在使用IPv4协议(因为我们设置了socket.AF_INET

第二步是bind解决。在bind这里,你预计客户将加入过程中分配的地址(与插座的设置,它是一个IP地址和TCP端口)。您的PC有多个IP地址(至少两个)。它始终具有127.0.0.1称为回调的功能,并且仅当您的应用程序在同一台PC上进行通信(即您的Linux-Linux场景)并且您具有用于与其他计算机进行通信的IP时才起作用(假设它是10.0.0.1) 。

通话时,socket.bind(('127.0.0.1', 5555))您将套接字设置为仅侦听来自同一台PC的通信。如果调用,socket.bind(('10.0.0.1', 5555))则套接字设置已准备就绪,可以接收针对该10.0.0.1地址的数据。

但是,如果您有10个IP或更多,并且想要接收所有内容(使用正确的TCP端口),该怎么办。对于这些情况,您可以将IP地址保留为bind()空,并且它确实可以满足您的要求。

使用Python版本时,bind()您还可以输入“计算机名称”而不是具体的IP。该socket.gethostname()呼叫将返回您计算机的名称。问题在于将“计算机名称”转换为Python支持的IP。转换有一些规则,但是通常您的“计算机名”可以转换为您在计算机上设置的任何IP地址。在您的情况下,计算机的名称将转换127.0.0.1为该名称,这就是为什么通信只能在同一计算机上的进程之间进行的原因。

socket.bind()您准备好使用套接字之后,它仍然是“不活动的”。的socket.listen()同时,有人想连接激活插槽和等待。当套接字收到新的连接请求时,它将进入队列并等待处理。

那是socket.accept()做什么的。它从队列中拉出连接请求,接受连接请求,并socket.SOCK_STREAM在服务器和客户端之间建立流(记住在设置套接字时)。新流实际上是新套接字,但准备与另一端进行通信。

旧插座发生了什么?好了,它仍然存在,您可以socket.listen()再次调用以获取另一个流(连接)。

同一端口上如何有多个插座

计算机网络内的每个连接均由以下流的五元组定义:

  • L4协议(通常为TCP或UDP)
  • 源IP地址
  • 源L4端口
  • 目的IP地址
  • 目的L4端口

当您从客户端创建新连接时,流程如下所示(TCP, 192.168.0.1, 12345, 10.0.0.1, 55555)。只是为了澄清服务器的响应流程,(TCP, 10.0.0.1, 55555, 192.168.0.1, 12345)但这对我们并不重要。如果您从客户端创建另一个连接,则它在源TCP端口上将有所不同(如果从另一台计算机进行连接,则它在源IP上也将有所不同)。仅从此信息中,您就可以区分与计算机建立的每个连接。

当您在代码中创建服务器套接字并调用socket.listen()它时,将侦听具有此模式的任何流(TCP, *, *, *, 55555)(*表示匹配所有内容)。因此,当你有连接(TCP, 192.168.0.1, 12345, 10.0.0.1, 55555),然后socket.accept()建立一个只用这一个具体流程的工作而旧插座仍然接受这是不成立的新连接另一个套接字。

当操作系统收到数据包时,它将查找该数据包并检查流。从这一点来看,它可能会发生以下几种情况:

  • 数据包的流完全匹配所有5个项目(不使用*)。然后,数据包的内容将传递到与该套接字关联的队列(调用时,您正在读取队列socket.recv())。
  • 数据包的流匹配的套接字以及关联的流包含,*则将其视为新连接,您可以调用scoket.accept()
  • 操作系统不包含与流程匹配的开放套接字。在这种情况下,操作系统会拒绝连接(或者只是忽略数据包,这取决于防火墙设置)。

可能有一些例子可以阐明这些情况。操作系统具有类似于表的内容,它将流映射到套接字。调用时socket.bind(),它将为套接字分配流。调用后,该表如下所示:

+=====================================+========+
|                Flow                 | Socket |
+=====================================+========+
| (TCP, *, *, *, 55555)               |      1 |
+-------------------------------------+--------+
Run Code Online (Sandbox Code Playgroud)

当它接收到具有流的数据包时(TCP, 1.1.1.1, 10, 10.0.0.1, 10),它将不匹配任何流(最后一个端口将不匹配)。因此,连接被拒绝。如果它接收到具有流(TCP, 1.1.1.1, 10, 10.0.0.1, 55555)的数据包,则该数据包将传递到套接字1(因为存在匹配项)。该socket.accept()调用将创建一个新的套接字并记录在表中。

+=====================================+========+
|                Flow                 | Socket |
+=====================================+========+
| (TCP, 1.1.1.1, 10, 10.0.0.1, 55555) |      2 |
+-------------------------------------+--------+
| (TCP, *, *, *, 55555)               |      1 |
+-------------------------------------+--------+
Run Code Online (Sandbox Code Playgroud)

现在,您有2个1端口的插座。与套接字相关的流匹配的每个接收到的数据包也与套接字相关的流2匹配1(相反,它不适用)。这不是问题,因为套接字2具有更精确的匹配(不使用*),因此具有该流的任何数据都将传递到socket 2

如何处理多个连接

当您想做一个“真正的”服务器时,您的应用程序应该能够处理多个连接(无需重新启动)。有两种基本方法:

  1. 顺序处理

    try:
        l = prepare_socket()
        while True:
            l.listen()
            s, a = socket.accept()
            process_connection(s) # before return you should call s.close()
    except KeyboardInterrupt:
        l.close()
    
    Run Code Online (Sandbox Code Playgroud)

    在这种情况下,您只能处理一个客户端,而其他客户端则必须等待接受。如果process_connection()花费的时间太长,则其他客户端将超时。

  2. 并行处理

    import threading
    threads = []
    
    try:
        l = prepare_socket()
        while True:
            l.listen()
            s, a = socket.accept()
            t = threading.Thread(target=process_connection, s)
            threads.append(t)
            t.start()
    except KeyboardInterrupt:
        for t in threads:
            t.join()
        l.close()
    
    Run Code Online (Sandbox Code Playgroud)

    现在,当您接收到新的连接时,它将创建新的线程,因此将并行处理每个连接。该解决方案的主要缺点是您必须解决线程常见的问题(例如访问共享内存,死锁等)。

当心这些仅仅是示例代码,它们还不完整!例如,它不包含用于在意外异常时正常退出的代码。

Python中的服务器

Python还包含名为的模块socketserver,其中包含创建服务器的快捷方式。在这里,您可以找到示例如何使用它。

客户

使用客户端比使用服务器要简单得多。您只需要创建一些设置(与服务器端相同)的套接字,然后告诉它服务器在哪里(其IP和TCP端口是什么)。这是通过socket.connect()调用完成的。作为奖励,它还可以在客户端和服务器之间建立流,因此从这一点上您可以进行通信。


您可以在Beej的《网络编程指南》中找到有关袜子的更多信息。它是为使用C而编写的,但是概念是相同的。