30.Python基础篇-socket模块
介绍
socket
模块是用于实现网络通信的模块。它提供了底层网络操作的接口,使得用户可以通过网络实现客户端和服务器之间的数据传输。通过 socket
模块,程序可以通过网络进行数据传输、连接和通信。
使用socket模块创建一个TCP服务
server端代码
# server端代码 import socket sk = socket.socket() # 创建一个TCP协议的套接字 sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # sk.bind(('127.0.0.1', 8080)) # 作为服务端需要绑定一个IP和端口 # 监听传入的连接请求。 sk.listen() # 只有TCP协议需要监听,UDP不需要 # accept() 方法接收客户端的连接。 conn, addr = sk.accept() # 只有TCP协议需要 # 接收客户端发送的数据,并解码 ret = conn.recv(1024).decode("UTF-8") # 1024指接收1024个字节的数据 # 向客户端发送数据,发送的数据必须是bytes类型 conn.send(bytes('hello client', encoding='UTF-8')) # 最后关闭连接和socket服务 conn.close() sk.close()
client端代码
# client端代码 import socket sk = socket.socket() # 相比server端,client端不需要绑定IP和端口,但是需要向指定IP和端口与服务端进行连接 sk.connect(('127.0.0.1', 8080)) # 与server端连接 # 至此。不需要做其他操作,可以直接发送或接收数据 # 向服务端发送数据,与server同样的,只能发送bytes类型 sk.send(bytes('hello server', encoding="UTF-8")) # 接收服务端数据,并解码 ret = sk.recv(1024).decode("UTF-8") # 关闭socket。 sk.close()
使用socket模块创建一个UDP服务
server端代码
# UDP 协议 server端 import socket # 创建一个UDP协议的套接字 sk = socket.socket(type=socket.SOCK_DGRAM) # type=socket.SOCK_DGRAM表示创建一个UDP的套接字 # 作为服务端需要绑定IP和端口 sk.bind(('127.0.0.1', 8080)) # UDP协议直接接收数据即可,不用监听等其他操作。但是UDP协议的server端,一上来必须是接受数据,不能是发送数据。 # recvfrom返回一个元组。第一个元素是客户端发送的内容,第二个元素是客户端的地址 msg, addr = sk.recvfrom(1024) print(msg) # b'hello server' print(addr) # ('127.0.0.1', 64098) # 使用sendto方法,向客户端发送数据。第一个参数为bytes类型的内容,第二个参数为客户端的地址 sk.sendto(bytes('hello client', encoding='UTF-8'), addr) # 最后关闭server sk.close()
client端代码
# UDP 协议 client端 import socket # 创建一个UDP协议的套接字 sk = socket.socket(type=socket.SOCK_DGRAM) # type=socket.SOCK_DGRAM表示创建一个UDP的套接字 ip_port = ('127.0.0.1', 8080) # 普通的变量 # 使用sendto方法,向服务端发送数据。第一个参数为bytes类型的内容,第二个参数为目标服务端的地址 # 作为UDP协议客户端,一上来必须是发送数据的一方,不能是接收数据的一方 sk.sendto(b'hello server', ip_port) # 使用recvfrom接受数据,1024表示接收数据的最大字节数 # recvfrom返回一个元组。第一个元素是服务端发送的内容,第二个元素是服务端的地址 server_msg, addr = sk.recvfrom(1024) print(server_msg) # b'hello client' print(addr) # ('127.0.0.1', 8080) # 最后关闭server sk.close()
TCP协议的黏包问题
黏包和拆包的原因
- 发送方黏包:
- 如果发送方发送的消息非常小,TCP 在传输过程中可能会将多个消息合并在一起,发送为一个大的数据包。这是因为 TCP 会尽量减少包头的开销,以提高效率。
- 接收方拆包:
- 接收方可能会在一个接收操作中收到多个包,或者在多个接收操作中收到一个包。这是因为 TCP 并不关心数据包的边界,它只关心数据流的顺序和完整性
如何处理黏包问题?
1.固定长度消息
2.分隔符协议
3.长度前缀协议
解决黏包问题
使用struct模块解决黏包问题
使用 struct
模块来解决 TCP 通信中的黏包问题,通常是通过在发送数据时为每个消息添加一个固定长度的“消息长度字段”,然后接收端根据该长度字段来正确地解析消息。这种方法通常称为“长度前缀协议”,是解决黏包和拆包问题的常见做法。
原理
- 发送方:在发送数据前,先将数据的长度(通常是 4 字节)放在消息的前面。这样,接收方就可以先读取长度字段,确定消息的完整长度,再去读取数据。
- 接收方:接收时,首先读取长度字段(确定消息的长度),然后根据这个长度读取完整的消息。
代码演示
server端代码
import socket import struct # 创建TCP套接字 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.bind(('localhost', 8080)) server_socket.listen(1) # 等待客户端连接 print("Server listening...") client_socket, client_address = server_socket.accept() print(f"Connected by {client_address}") # 接收消息的长度字段(4 字节) length_data = client_socket.recv(4) if length_data: # 使用struct解包出消息长度 message_length = struct.unpack('!I', length_data)[0] print(f"Expected message length: {message_length}") # 根据长度字段接收实际的消息数据 message_data = client_socket.recv(message_length) print(f"Received message: {message_data.decode('utf-8')}") # 关闭连接 client_socket.close() server_socket.close()
client端代码
# client端 import socket import struct # 创建TCP套接字 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_socket.connect(('localhost', 8080)) # 要发送的消息 message = "Hello, this is a test message." # 计算消息的长度 message_length = len(message) # 使用struct将消息的长度和消息内容一起打包 # '!I' 表示网络字节顺序(大端)和一个无符号整数(长度字段) # 'message.encode('utf-8')' 将消息内容转换为字节 packed_message = struct.pack('!I', message_length) + message.encode('utf-8') # 发送打包好的数据 client_socket.sendall(packed_message) # 关闭连接 client_socket.close()