简体   繁体   English

Python 套接字/端口转发

[英]Python sockets/port forwarding

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 the server and then the client on my local machine (a Linux Mint), it works correctly.当我在本地机器(Linux Mint)上运行服务器和客户端时,它可以正常工作。 I got "Hello," in bash.我在 bash 中得到“你好”。 and everything is fine, BUT when I ran my client program on another machine (a 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: and everything is fine, BUT when I ran my client program on another machine (a 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:

ConnectionRefusedError: [WinError 10061] No connection could be made because the target machine actively refused it ConnectionRefusedError: [WinError 10061] 由于目标机器主动拒绝,无法建立连接

client.py on Windows 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?我必须说我在我的路由器设置中对端口 5555 进行了端口转发。早些时候,我对端口 80 做了同样的事情,我自己的站点工作正常,但现在它不适用于 5555 与 Python sockets? Why: I can't get it, And one more thing.为什么:我不明白,还有一件事。 I tried to change the port to 80 in my server and client files, but it didn't work too.我试图在我的服务器和客户端文件中将端口更改为 80,但它也不起作用。 PLease, help.请帮忙。

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. 您的问题不在python中,而是在套接字的使用中。 When you create socket you just prepare your process to receive/send some data from/to another process. 创建套接字时,您只需准备好进程以从/向另一个进程接收/发送一些数据。

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_INET which is constant for use of IP protocol and the socket.SOCK_STREAM is specify reliable stream-oriented service. 在你的情况下,它是socket.AF_INET这是使用IP协议的常数和socket.SOCK_STREAM是指定可靠的面向流的服务。 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). 可靠的面向流的服务意味着您希望确保每个发送的字节都将被传送到另一端,并且在通信期间不会丢失任何内容(底层操作系统将使用TCP协议)。 From this point we are using IPv4 protocol (because we set the socket.AF_INET ) 从这一点开始我们使用IPv4协议(因为我们设置了socket.AF_INET

The second step is bind it to address. 第二步是bindbind到地址。 The bind process assign address where you expected that client will join (with your socket's settings it's a IP address and the TCP port). bind进程分配您希望客户端将加入的地址(使用套接字的设置,它是IP地址和TCP端口)。 Your PC has multiple IP address (well at least two). 您的PC有多个IP地址(至少两个)。 It's always has 127.0.0.1 which 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 ). 它总是有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. 当你调用socket.bind(('127.0.0.1', 5555))你要设置套接字只监听来自同一台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.1 address. 如果调用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). 但是,如果你有10个或更多的IP并且想要接收所有内容(使用正确的TCP端口),该怎么办? For those scenarios you can leave the IP address in bind() empty and it does exactly what you want. 对于这些场景,您可以将bind()的IP地址保留为空,并且它完全符合您的要求。

With Python's version of bind() you can enter also "computer name" instead of the concrete IP. 使用Python的bind()版本,您还可以输入“计算机名称”而不是具体的IP。 The socket.gethostname() call return your computer's name. socket.gethostname()调用返回计算机的名称。 The problem is in the translation of "computer name" to the IP which Python makes behind your backs. 问题在于将“计算机名称”转换为Python背后的IP。 The translation has some rules but generally your "computer name" can be translated into any IP address which you have set on your computer. 翻译有一些规则,但通常您的“计算机名称”可以转换为您在计算机上设置的任何IP地址。 In your case the your computer's name is converted into 127.0.0.1 and that's why communication works only between processes on the same computer. 在您的情况下,您的计算机名称将转换为127.0.0.1 ,这就是为什么通信仅在同一台计算机上的进程之间工作的原因。

After socket.bind() you have the socket ready to use but it is still "inactive". socket.bind()您已准备好使用套接字,但它仍处于“非活动状态”。 The socket.listen() activate the socket and wait while someone want to connect. socket.listen()激活套接字并等待有人想要连接。 When socket receives new connection request it will put into queue and wait for processing. 当socket接收到新的连接请求时,它将进入队列并等待处理。

That's what socket.accept() do. 这就是socket.accept()所做的。 It pulls the connection request from queue, accept it and establish the stream (remember the socket.SOCK_STREAM while you set up the socket) between the server and the client. 它从队列中提取连接请求,接受它并在服务器和客户端之间建立流(在设置套接字时记住socket.SOCK_STREAM )。 The new stream is actually new socket but ready to communicate with other side. 新流实际上是新的套接字,但准备与另一方通信。

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) L4协议(通常是TCP或UDP)
  • Source IP address 源IP地址
  • Source L4 port 源L4端口
  • Destination IP address 目标IP地址
  • Destination L4 port 目的地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) . 从客户端创建新连接时,流可能如下所示(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. 只是为了澄清服务器的响应流程是(TCP, 10.0.0.1, 55555, 192.168.0.1, 12345)但对我们来说并不重要。 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). 如果您从客户端创建另一个连接,它将在源TCP端口上有所不同(如果您从另一台计算机执行此操作,它将在源IP上也不同)。 Only from this information you can distinguish every connection created to your computer. 只有通过此信息,您才能区分创建到计算机的每个连接。

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). 当您在代码中创建服务器套接字并调用socket.listen()它会侦听具有此模式的任何流(TCP, *, *, *, 55555) (*表示匹配所有内容)。 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. 因此,当您与(TCP, 192.168.0.1, 12345, 10.0.0.1, 55555) socket.accept()建立连接时(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 * ). 数据包的流量完全匹配所有5个项目(不使用* )。 Then the packet's content is delivered to the queue associated with that socket (you're reading the queue when you call socket.recv() ). 然后将数据包的内容传递到与该套接字关联的队列(当您调用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() . 数据包的流匹配套接字与关联的流包含*然后它被视为新连接,您可以调用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). 在这种情况下,操作系统拒绝连接(或者只是忽略它取决于防火墙设置的数据包)。

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. 当你调用socket.bind()它会将流分配给套接字。 After the call the table can look like this: 调用后,表格如下所示:

+=====================================+========+
|                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). 当它接收带有流的数据包(TCP, 1.1.1.1, 10, 10.0.0.1, 10) ,它将不匹配任何流(最后一个端口将不匹配)。 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). 如果它收到带有流的数据包(TCP, 1.1.1.1, 10, 10.0.0.1, 55555)则数据包将被传送到套接字1 (因为存在匹配)。 The socket.accept() call creates a new socket and record in the table. 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. 现在你有1个端口的2个插座。 Every received packet which match the flow associated with the socket 2 also match flow associated with socket 1 (on the contrary it does not apply). 与套接字2相关联的流匹配的每个接收分组也匹配与套接字1相关联的流(相反,它不适用)。 It's not a problem because the socket 2 has preciser match (is doesn't use the * ) so any data with that flow will be delivered to socket 2 . 这不是问题,因为套接字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: 有两种基本方法:

  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. 如果process_connection()花费的时间太长,那么其他客户端将会超时。

  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.). 此解决方案的主要缺点是您必须通过线程解决常见问题(例如访问共享内存,死锁等)。

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 socketserver which contains shortcuts to create servers. Python还包含名为socketserver模块,其中包含用于创建服务器的快捷方式。 Here you can find example how to use it. 在这里您可以找到如何使用它的示例。

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). 您只需要创建带有一些设置的套接字(与服务器端相同),然后告诉它服务器在哪里(它的IP和TCP端口是什么)。 This is accomplished through socket.connect() call. 这是通过socket.connect()调用完成的。 As bonus it also establish the stream between your client and server so from this point you can communicate. 作为奖励,它还在您的客户端和服务器之间建立流,因此从这一点开始您可以进行通信。


You can find more information about socktes at the Beej's Guide to Network Programming . 您可以在Beej的网络编程指南中找到有关袜子的更多信息。 It's written for usage with C but the concepts are the same. 它是为了与C一起使用而编写的,但概念是相同的。

If you need an alternative to port forwarding.如果您需要端口转发的替代方案。 I found a way out of port forwarding you can use Ngrock in case you forget your router password like me and even its makes the connection even more secure as it doesnt require your public IP.我找到了一种摆脱端口转发的方法,您可以使用Ngrock以防您像我一样忘记路由器密码,甚至它使连接更加安全,因为它不需要您的公共 IP。 It's Very easy to setup if you are still strugling how to setup see this video如果您还在为如何设置而苦恼,那么设置非常容易,请观看此视频

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM