bash Python 套接字/端口转发

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/45097727/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-18 16:16:55  来源:igfitidea点击:

Python sockets/port forwarding

pythonlinuxbashsocketsnetworking

提问by Alexander Mueller

I've written server and client programs with python

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

Server.py

服务器.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()

Client.py on linux

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()

When I run server and then client on local machine(Linux Mint) - It works correctly. I got "Hello!" in bash and everything is fine. BUT when I got my client program to another machine (Windows 8) and ran it (previously I ran server on Linux, of course, and change ip address in client to my static linux mint's IP) it says

当我在本地机器(Linux Mint)上运行服务器然后客户端时 - 它工作正常。我得到了“你好!” 在 bash 中,一切都很好。但是当我将我的客户端程序放到另一台机器(Windows 8)并运行它时(当然,以前我在 Linux 上运行服务器,并将客户端中的 IP 地址更改为我的静态 linux mint 的 IP)它说

ConnectionRefusedError: [WinError 10061] No connection could be made because the target machine actively refused it

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()

I must say that I had done port forwarding in my router settings on port 5555. Earlier I had done same thing to port 80 and my own site worked correctly, but now it doesn't work to 5555 with python sockets! Why? I can't get it! And one more thing: I tried to change port to 80 in server and client files but it didn't work too. PLease, help.

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

回答by Qeek

You have to change the socket.gethostname()in the server script to the empty string (or just directly call socket.bind(('', port))).

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

Your problem is not in the python but in the usage of sockets generally. When you create socket you just prepare your process to receive/send some data from/to another process.

您的问题不在于python,而在于通常使用套接字。创建套接字时,您只需准备进程从/向另一个进程接收/发送一些数据。

Server

服务器

The first step for creating a socket you have to specify what kind of protocol will be used for communication between those processes. In your case it is the socket.AF_INETwhich is constant for use of IP protocol and the socket.SOCK_STREAMis specify reliable stream-oriented service. The reliable stream-oriented service means that you want to be sure that every single sent byte will be delivered to the other side and nothing can be lost during the communication (the underlying OS will use TCP protocol for that). From this point we are using IPv4 protocol (because we set the socket.AF_INET)

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

The second step is bindit to address. The bindprocess assign address where you expected that client will join (with your socket's settings it's a IP address and the TCP port). Your PC has multiple IP address (well at least two). It's always has 127.0.0.1which is called callback and it works only when your applications communicate on the same PC (that is you Linux - Linux scenario in the question) and then you have IP which is used for communication with others computers (let's pretend it is 10.0.0.1).

第二步是bind寻址。该bind过程分配您期望客户端加入的地址(使用您的套接字设置,它是一个 IP 地址和 TCP 端口)。您的 PC 有多个 IP 地址(至少有两个)。它始终具有127.0.0.1称为回调的功能,并且仅当您的应用程序在同一台 PC 上进行通信时才起作用(即问题中的 Linux - Linux 场景),然后您拥有用于与其他计算机通信的 IP(假设它是10.0.0.1) .

When you call socket.bind(('127.0.0.1', 5555))you're setting the socket to listen only for communication from the same PC. If you call socket.bind(('10.0.0.1', 5555))then the socket setting is ready to receive data targeted to the 10.0.0.1address.

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

But what if you have 10 IPs or more and you want to receive everything (with right TCP port). For those scenarios you can leave the IP address in bind()empty and it does exactly what you want.

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

With Python's version of bind()you can enter also "computer name" instead of the concrete IP. The socket.gethostname()call return your computer's name. The problem is in the translation of "computer name" to the IP which Python makes behind your backs. The translation has some rules but generally your "computer name" can be translated into any IP address which you have set on your computer. In your case the your computer's name is converted into 127.0.0.1and that's why communication works only between processes on the same computer.

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

After socket.bind()you have the socket ready to use but it is still "inactive". The socket.listen()activate the socket and wait while someone want to connect. When socket receives new connection request it will put into queue and wait for processing.

socket.bind()您准备好使用套接字后,它仍处于“非活动状态”。的socket.listen()同时,有人想连接激活插槽和等待。当socket收到新的连接请求时,它会放入队列等待处理。

That's what socket.accept()do. It pulls the connection request from queue, accept it and establish the stream (remember the socket.SOCK_STREAMwhile you set up the socket) between the server and the client. The new stream is actually new socket but ready to communicate with other side.

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

What did happen with the old socket? Well it's still alive and you can call socket.listen()again to get another stream (connection).

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

How is possible to have multiple sockets on the same port

如何在同一个端口上有多个套接字

Every connection within computer's network is defined by flow which is 5-tuple of:

计算机网络中的每个连接都由流定义,流是以下 5 元组:

  • L4 protocol (usually TCP or UDP)
  • Source IP address
  • Source L4 port
  • Destination IP address
  • Destination L4 port
  • L4 协议(通常是 TCP 或 UDP)
  • 源IP地址
  • 源 L4 端口
  • 目标 IP 地址
  • 目标 L4 端口

When you create new connection from client the flow can look like this (TCP, 192.168.0.1, 12345, 10.0.0.1, 55555). Just for clarification the server's response flow is (TCP, 10.0.0.1, 55555, 192.168.0.1, 12345)but it isn't important for us. If you create another connection from client that it will differ at source TCP port (if you do it from another computer that it will differ also at the source IP). Only from this information you can distinguish every connection created to your computer.

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

When you create a server socket in your code and call socket.listen()it listen for any flow with this pattern (TCP, *, *, *, 55555)(the * means match everything). So when you get connection with (TCP, 192.168.0.1, 12345, 10.0.0.1, 55555)then socket.accept()create another socket which works only with this one concrete flow while the old socket still accepting new connections which wasn't established.

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

When operating system receives a packet it looks in the packet and check the flow. From this point it can happen a several scenarios:

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

  • The packet's flow match all 5 items exactly (without usage of *). Then the packet's content is delivered to the queue associated with that socket (you're reading the queue when you call socket.recv()).
  • The packet's flow matched socket with associated flow contains *then it is considered as new connection and you can call scoket.accept().
  • The operating system doesn't contain open socket which would match the flow. In that case the OS refuse connection (or just ignore the packet it depends on firewall settings).
  • 数据包的流完全匹配所有 5 个项目(不使用*)。然后将数据包的内容传送到与该套接字关联的队列(您在调用 时正在读取队列socket.recv())。
  • 数据包的流与相关流包含的套接字匹配,*然后它被视为新连接,您可以调用scoket.accept().
  • 操作系统不包含与流匹配的开放套接字。在这种情况下,操作系统拒绝连接(或者只是忽略它取决于防火墙设置的数据包)。

Probably some example can clarify those scenarios. The operating system has something like table where it map flows to sockets. When you call socket.bind()it will assign flow to the socket. After the call the table can look like this:

可能一些例子可以澄清这些场景。操作系统有类似表的东西,它可以将流映射到套接字。当您调用socket.bind()它时,它会将流量分配给套接字。调用后,表格可能如下所示:

+=====================================+========+
|                Flow                 | Socket |
+=====================================+========+
| (TCP, *, *, *, 55555)               |      1 |
+-------------------------------------+--------+

When it receive packet with flow (TCP, 1.1.1.1, 10, 10.0.0.1, 10)then it won't match any flow (last port won't match). So the connection is refused. If it receives a packet with flow (TCP, 1.1.1.1, 10, 10.0.0.1, 55555)then the packet is delivered to the socket 1(because there is a match). The socket.accept()call creates a new socket and record in the table.

当它收到带有流的数据包时(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 |
+-------------------------------------+--------+

Now you got 2 sockets for 1 port. Every received packet which match the flow associated with the socket 2also match flow associated with socket 1(on the contrary it does not apply). It's not a problem because the socket 2has preciser match (is doesn't use the *) so any data with that flow will be delivered to socket 2.

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

How to server multiple connections

如何处理多个连接

When you want to do a "real" server than you're application should be able process multiple connection (without restarting). There are 2 basic approaches:

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

  1. sequential processing

    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()
    

    In this case you can process only one client while others clients have to wait for accept. If the process_connection()takes too long then others clients will timeout.

  2. parallel processing

    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()
    

    Now when you receive new connection it will create new thread so every connection is processed in parallel. The main disadvantage of this solution is that you have to solve common troubles with threading (like access to shared memory, deadlocks etc.).

  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()
    

    在这种情况下,您只能处理一个客户端,而其他客户端必须等待接受。如果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()
    

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

Beware those are only example codes and they are not complete! For example it doesn't contain code for graceful exit on unexpected exceptions.

请注意,这些只是示例代码,并不完整!例如,它不包含在意外异常时正常退出的代码。

Servers in the Python

Python 中的服务器

The Python also contains module called socketserverwhich contains shortcuts to create servers. Hereyou can find example how to use it.

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

Client

客户

With the client it's much more simpler than with the server. You just have to create socket with some settings (same as server side) and then tell it where is the server is (what is its IP and TCP port). This is accomplished through socket.connect()call. As bonus it also establish the stream between your client and server so from this point you can communicate.

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



You can find more information about socktes at the Beej's Guide to Network Programming. It's written for usage with C but the concepts are the same.

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