Python套接字/端口转发

时间:2017-07-14 07:59:12

标签: python linux bash sockets 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()

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中,一切都很好。但当我将我的客户端程序运行到另一台机器(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()

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

1 个答案:

答案 0 :(得分:6)

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

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

服务器

创建套接字的第一步,您必须指定将使用哪种协议进行这些进程之间的通信。在您的情况下,socket.AF_INET是使用IP协议的常量,socket.SOCK_STREAM是指定可靠的面向流的服务。可靠的面向流的服务意味着您希望确保每个发送的字节都将被传送到另一端,并且在通信期间不会丢失任何内容(底层操作系统将使用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端口),该怎么办?对于这些情况,您可以将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()创建另一个套接字,该套接字仅适用于此一个具体流,而旧套接字仍然接受尚未建立的新连接。

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

  • 数据包的流量完全匹配所有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()
    

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

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

    Python中的服务器

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

    客户端

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

    您可以在Beej's Guide to Network Programming找到有关袜子的更多信息。它是为了与C一起使用而编写的,但概念是相同的。