1.可能用得到的库
import socket import threading import binascii import time
socket
: 这是Python的一个内置库,用于网络通信。它提供了基于TCP和UDP的套接字接口。threading
: 这也是Python的一个内置库,用于多线程编程。它提供了高级接口来处理线程,包括启动、停止、同步和锁定等功能。binascii
: 这个库提供了一些方法来转换二进制数据和ASCII表示之间的转换。例如,它可以用于将二进制数据转换为十六进制字符串。time
: 这是Python的一个内置库,用于处理时间。它提供了获取当前时间、延迟执行、格式化时间等功能。
2.确定使用哪个端口进行模拟
local_ip01 = "127.0.0.1" local_port01 = 2345 local_ip02 = "127.0.0.1" local_port02 = 2346 remote_ip01 = "127.0.0.1" remote_port01 = 2347 remote_ip02 = "127.0.0.1" remote_port02 = 2348
用自己电脑本机模拟,IP地址就定了是localhost或者127.0.0.1了
当你在本地机器上模拟客户端和服务器时,通常使用localhost
或127.0.0.1
作为IP地址。这两者都指向你的本地机器。
对于端口号,其范围是从0到65535。然而,并非所有的端口都可以自由使用:
0到1023号端口被称为“知名端口”,这些端口通常被系统进程或者重要的服务使用。例如,HTTP服务默认使用80端口,HTTPS服务默认使用443端口。
1024到49151号端口被称为“注册端口”,这些端口主要用于软件应用程序。你的应用程序也可以使用这些端口,但最好避免使用已经被其他应用程序注册的端口。
49152到65535号端口被称为“动态”或“私有”端口,这些端口通常由动态端口选择的系统或应用程序使用。
因此,如果你在编写脚本并需要选择一个端口,建议选择1024以上的端口,以避免与系统服务冲突。如果你的应用程序需要与其他人共享,那么最好在1024到49151之间选择一个未被使用的端口。
3.保险起见,看看这些端口是否确定可用
感觉可能没有必要,但是不测测感觉不舒服,hhh
def is_port_available(ip, port): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: try: s.bind((ip, port)) return True except socket.error: return False # 测试端口是否可用 for port in [2345, 2346, 2347, 2348]: print(f"Port {port} is available: {is_port_available('127.0.0.1', port)}")
3.创建套接字和它的行为
def udp_communication01(): # 创建一个UDP套接字 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 绑定到本地IP和端口 sock.bind((local_ip01, local_port01)) # 设置超时 sock.settimeout(0.5) # 0.5 seconds timeout while True: time.sleep(0.5) try: # 尝试接收另外一端的数据 data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes hex_data = binascii.hexlify(data).decode() # 转换为十六进制字符串 print("port 21 received message:", hex_data, "from:", addr) except ConnectionResetError: # 尝试发送数据到另外一端 message = "想要发送什么填写什么" # 换为字节或者其他格式的数据(看项目需求) message_bytes = binascii.unhexlify(message) # 向远程端口发送消息 sock.sendto(message_bytes, (remote_ip01, remote_port01)) except socket.timeout: # 尝试发送数据到另外一端 message = "想要发送什么填写什么" # 换为字节或者其他格式的数据(看项目需求) message_bytes = binascii.unhexlify(message) # 向远程端口发送消息 sock.sendto(message_bytes, (remote_ip01, remote_port01))
他在干嘛?
这个函数的主要目的是创建一个UDP套接字,绑定到本地的IP和端口,并在一个无限循环中接收和发送数据。
首先,函数创建一个UDP套接字,并绑定到本地的IP和端口。
然后,它设置套接字的超时时间为0.5秒。这意味着如果
sock.recvfrom
方法在0.5秒内没有接收到任何数据,它将引发一个socket.timeout
异常。函数进入一个无限循环,在每次迭代中,它首先暂停0.5秒,然后尝试从套接字接收数据。
如果接收到数据,它将数据转换为十六进制字符串,并打印出接收到的消息和发送者的地址。
如果在尝试接收数据时发生
ConnectionResetError
异常,函数将构造一个消息,并将其发送到远程的IP和端口。这个消息是一个字符串,你可以根据你的需求修改它。如果在尝试接收数据时发生
socket.timeout
异常,函数将做同样的事情:构造一个消息,并将其发送到远程的IP和端口。
需要注意的是,local_ip01
,local_port01
,remote_ip01
,和remote_port01
这四个变量在这个函数中没有被定义,你需要在调用这个函数之前定义这些变量,或者将它们作为参数传递给这个函数。
4.套接字线程 启动 !
# 创建一个子线程 thread01 = threading.Thread(target=udp_communication01) # 启动子线程 thread01.start()
创建一个新的线程来运行udp_communication01
函数可以使得这个函数在后台运行,而主线程可以继续执行其他任务。
Because
程序有一些可能会阻塞的操作时。例如,udp_communication01
函数中的sock.recvfrom
方法会阻塞,直到它接收到数据。如果这个函数在主线程中运行,那么整个程序将会被阻塞。但是,如果这个函数在一个单独的线程中运行,那么即使它被阻塞,主线程也可以继续执行其他任务。
通过创建一个新的线程来运行udp_communication01
函数,你的程序可以在等待数据的同时,继续执行其他任务。
敲黑板,划重点
threading.Thread(target=udp_communication01)
这种写法是将udp_communication01
函数作为目标函数传递给Thread
类的构造函数,但并不立即调用它。当你调用thread01.start()
时,新的线程会开始运行,并调用udp_communication01
函数。
threading.Thread(target=udp_communication01())
这种写法会立即调用udp_communication01
函数,并将其返回值作为目标函数传递给Thread
类的构造函数。这通常不是你想要的,除非udp_communication01
函数返回的是另一个函数,你想在新的线程中调用这个返回的函数。
在大多数情况下,你应该使用第一种写法,因为你通常希望在新的线程中调用目标函数,而不是在创建Thread
对象时立即调用它。