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协议的黏包问题

黏包和拆包的原因

  1. 发送方黏包
    • 如果发送方发送的消息非常小,TCP 在传输过程中可能会将多个消息合并在一起,发送为一个大的数据包。这是因为 TCP 会尽量减少包头的开销,以提高效率。
  2. 接收方拆包
    • 接收方可能会在一个接收操作中收到多个包,或者在多个接收操作中收到一个包。这是因为 TCP 并不关心数据包的边界,它只关心数据流的顺序和完整性

如何处理黏包问题?

1.固定长度消息

2.分隔符协议

3.长度前缀协议

解决黏包问题

使用struct模块解决黏包问题

使用 struct 模块来解决 TCP 通信中的黏包问题,通常是通过在发送数据时为每个消息添加一个固定长度的“消息长度字段”,然后接收端根据该长度字段来正确地解析消息。这种方法通常称为“长度前缀协议”,是解决黏包和拆包问题的常见做法。

原理

  1. 发送方:在发送数据前,先将数据的长度(通常是 4 字节)放在消息的前面。这样,接收方就可以先读取长度字段,确定消息的完整长度,再去读取数据。
  2. 接收方:接收时,首先读取长度字段(确定消息的长度),然后根据这个长度读取完整的消息。

 代码演示

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

 

posted @ 2024-12-21 17:39  邵杠杠  阅读(1)  评论(0编辑  收藏  举报