繁体   English   中英

Python 套接字/端口转发

[英]Python sockets/port forwarding

我用 Python 编写了服务器和客户端程序。

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

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

当我在本地机器(Linux Mint)上运行服务器和客户端时,它可以正常工作。 我在 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:

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

我必须说我在我的路由器设置中对端口 5555 进行了端口转发。早些时候,我对端口 80 做了同样的事情,我自己的站点工作正常,但现在它不适用于 5555 与 Python sockets? 为什么:我不明白,还有一件事。 我试图在我的服务器和客户端文件中将端口更改为 80,但它也不起作用。 请帮忙。

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

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

服务器

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

第二步是bindbind到地址。 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端口),该怎么办? 对于这些场景,您可以将bind()的IP地址保留为空,并且它完全符合您的要求。

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

socket.bind()您已准备好使用套接字,但它仍处于“非活动状态”。 socket.listen()激活套接字并等待有人想要连接。 当socket接收到新的连接请求时,它将进入队列并等待处理。

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

老套接字发生了什么? 好吧它还活着,你可以再次调用socket.listen()来获得另一个流(连接)。

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

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

  • 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()建立连接时(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 |
+-------------------------------------+--------+

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

现在你有1个端口的2个插座。 与套接字2相关联的流匹配的每个接收分组也匹配与套接字1相关联的流(相反,它不适用)。 这不是问题,因为套接字2具有精确匹配(不使用* ),因此具有该流的任何数据将被传递到套接字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() 

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

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

请注意那些只是示例代码而且它们不完整! 例如,它不包含用于在意外异常上正常退出的代码。

Python中的服务器

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

客户

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


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

如果您需要端口转发的替代方案。 我找到了一种摆脱端口转发的方法,您可以使用Ngrock以防您像我一样忘记路由器密码,甚至它使连接更加安全,因为它不需要您的公共 IP。 如果您还在为如何设置而苦恼,那么设置非常容易,请观看此视频

暂无
暂无

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

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