Python套接字/端口转发 [英] Python sockets/port forwarding

查看:56
本文介绍了Python套接字/端口转发的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Server.py

 导入套接字袜子= socket.socket(socket.AF_INET,socket.SOCK_STREAM)主机= socket.gethostname()端口= 5555sock.bind((主机,端口))sock.listen(1)conn,addr = sock.accept()数据=你好!"数据=字节(数据,'utf-8')conn.send(数据)sock.close() 

Linux上的Client.py

  import套接字袜子= socket.socket(socket.AF_INET,socket.SOCK_STREAM)主机= socket.gethostname()端口= 5555sock.connect((主机,端口))数据= sock.recv(2048)数据= str(数据,"utf-8")打印(数据)sock.close() 

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

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

Windows上的client.py

 导入套接字袜子= socket.socket(socket.AF_INET,socket.SOCK_STREAM)host =这是我的静态IP"端口= 5555sock.connect((主机,端口))数据= sock.recv(2048)数据= str(数据,"utf-8")打印(数据)sock.close() 

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

解决方案

您必须将服务器脚本中的 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端口),该怎么办.对于这些情况,您可以将 bind()中的IP地址保留为空,并且可以完全满足您的要求.

使用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()时,它将为套接字分配流.通话后,表格如下所示:

  + ===================================== + ======== +|流|插座|+ ============================================= +|(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()调用创建一个新的套接字并记录在表中.

  + ===================================== + ======== +|流|插座|+ ============================================= +|(TCP,1.1.1.1、10、10.0.0.1、55555)|2 |+ ------------------------------------- + -------- +|(TCP,*,*,*,55555)|1 |+ ------------------------------------- + -------- + 

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

如何处理多个连接

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

  1. 顺序处理

      try:l = prepare_socket()而True:l.listen()s,a = socket.accept()process_connection(s)#返回之前,您应该调用s.close()除了KeyboardInterrupt:l.close() 

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

  2. 并行处理

     导入线程线程= []尝试:l = prepare_socket()而True:l.listen()s,a = socket.accept()t = threading.Thread(target = process_connection,s)thread.append(t)t.start()除了KeyboardInterrupt:对于线程中的t:t.join()l.close() 

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

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

Python中的服务器

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

客户

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


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

I've written server and client programs with 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()

Client.py on linux

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

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

client.py on windows

  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.

解决方案

You have to change the socket.gethostname() in the server script to the empty string (or just directly call 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.

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

The second step is bind it to address. 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). Your PC has multiple IP address (well at least two). 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).

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.1 address.

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.

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.1 and that's why communication works only between processes on the same computer.

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.

That's what socket.accept() do. 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. 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).

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:

  • L4 protocol (usually TCP or UDP)
  • Source IP address
  • Source L4 port
  • Destination IP address
  • Destination L4 port

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.

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.

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

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:

+=====================================+========+
|                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.

+=====================================+========+
|                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 2 also match flow associated with socket 1 (on the contrary it does not apply). 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.

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.

  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

The Python also contains module called socketserver which contains shortcuts to create servers. 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). 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.


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.

这篇关于Python套接字/端口转发的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆