socket模块、黏包现象、struct模块
一、socket模块(套接字)
socket上应用层与TCP/IP协议通信中间的软件抽象层,帮助我们编写基于网络进行数据交互的程序,否则意味着需要自己通过代码来控制OSI七层来进行数据传输。

1.socket简介
基于文件类型的套接字(单机): AF_UNIX
基于网络类型的套接字(联网): AF_INET
二、代码实现:TCP协议
1.初步实现
(1)服务端:
import socket from socket import SOL_SOCKET,SO_REUSEADDR """ 以后要养成查看源码编写代码的思路 """ # 1 产生一个socket对象并指定采用的通信协议TCP server = socket.socket() # 括号内不写参数,默认TCP协议 ,family = AF_INET基于网络的套接字, type = SOCK_STREAM流式协议即 # 2 绑定一个固定的地址(服务端必备的条件) server.bind(('127.0.0.1', 8080)) # 127.0.0.1为本地回环地址,只有自己的电脑可以访问 # 3 设立半连接池(预防洪水攻击) server.listen(5) # 4 等待客人连接,如果没有客人则程序会停在这里 sock, addr = server.accept() # accept方法返回sock, addr print(sock, addr) # sock就是双向通道,addr就是客户端地址 # 5 服务客人 data = sock.recv(1024) # 接收客户端发送过来的消息,1024字节 print(data.decode('utf8')) sock.send('你好好哈哈哈哈'.encode('utf8')) # 给客户端发消息,消息必须是bytes类型 # 6 关闭双向通道 sock.close() # 四次挥手 # 7 关闭服务器 server.close() # 关闭服务器
(2)客户端
"只要想用网络通信就要使用socket模块" import socket # 1 产生一个socket对象并指定采用的通信协议TCP client = socket.socket() # 2 通过服务端的地址连接服务端 client.connect(('127.0.0.1', 8080)) # 参数是元组,ip地址是服务端的地址 # 3 直接给服务端发送消息 client.send('吃了没'.encode('utf8')) # 4 接收服务端发送过来到的消息 data = client.recv(1024) print(data.decode('utf8')) # 5 断开与服务端的连接 client.close()
2.代码优化
(1)服务端
"代码优化" import socket from socket import SOL_SOCKET, SO_REUSEADDR # 1 产生一个socket对象并指定采用的通信协议TCP server = socket.socket() # 括号内不写参数,默认TCP协议 ,family = AF_INET基于网络的套接字, type = SOCK_STREAM流式协议即 server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) server.bind(('127.0.0.1', 8080)) # 127.0.0.1为本地回环地址,只有自己的电脑可以访问 server.listen(5) while True: # 添加循环,保证可以多客户端申请访问,在连接一个客户端的同时,其他客户端可以在半连接池中等待 sock, addr = server.accept() # accept方法返回sock, addr while True: # 添加循环,可以循环接收客户端发送的消息 try: # 添加异常处理2 windows系统: 当当前客户端不发消息了,就重新接收别的客人的消息,防止win系统报错 data = sock.recv(1024) # 接收客户端发送过来的消息,1024字节 if len(data) == 0: # 异常处理1 macos系统:当客户端发送空消息,则重新接收下一条消息 break print(f'来自客户端{addr}的字节>>>', data.decode('utf8')) msg = input('消息发送:').strip() sock.send(msg.encode('utf8')) # 给客户端发消息,消息必须是bytes类型 except BlockingIOError: # 异常处理 break
(2)客户端
client = socket.socket() client.connect(('127.0.0.1', 8080)) while True: # 添加循环:可以多次发消息 msg = input('发送给服务端>>>:').strip() if len(msg) == 0: print('不能发空消息') continue client.send(msg.encode('utf8')) data = client.recv(1024) print('来自客户端的消息>>>', data.decode('utf8'))
优化内容:
1.聊天内容自定义: 采用input语句来获取消息,可以自定义消息 2.重复发送消息: 添加循环,将聊天的部分用循环包起来 3.用户发送消息不能为空: 本质其实是两边不能都是recv或者send,一定是一方收一方发 4.服务端多次重启可能报错:macos系统 Address already in use 解决方式1:改端口号 解决方式2:博客里面代码拷贝即可 5.当客户端异常断开的情况下 如何让服务端继续服务其他客人 windows服务端会直接报错 mac服务端会有一段时间反复接收空消息延迟报错 异常处理、空消息判断
三、黏包现象
1.黏包现象简介
基于tcp协议传输数据的时候,如果接收端单次接收的数据长度小于发送的总长度,则会导致部分数据无法接收全,在第二次接收的时候会继续接收上次没接收完的数据,导致数据接收变混乱,这种现象就叫做黏包
或者连续发送小数据的时候,接收端一次接收所有收据,也导致数据接收变混乱,也叫黏包现象。
2.黏包现象产生的原因
1.接收数据时无法知道发送的数据的长短
2.TCP也称为流式协议:数据就像水流一样,没有间隔(TCP会针对数据量较小且发送间隔较短的多余数据一次性合并打包发送),并且在发送的数据传输的过程中还有缓存机制来避免数据丢失。
3.避免黏包现象的关键
如何确定即将接收的数据的大小,就是我们解决黏包现象的关键
解决的办法就是,客户端在发送数据之前先发送一条数据,告诉服务端这条数据长度是多少,服务器接收到这个数据之后反馈成功,客户端再发送真实数据。这样服务端就可以通过之前接收到数据长度来接收数据。
四、struct模块
1.解决黏包问题初级:
客户端:
1.将真实数据转成bytes类型并计算长度
2.利用struct模块将真实长度制作一个固定长度的报头
3.将固定长度的报头发送给服务端,服务端只需要在recv括号内填写固定长度的报头数据即可
4.然后在发送真实数据
服务端:
1.服务端线接收固定长度的报头
2.利用struct模块反向解析出真实数据长度
3.recv接收真实数据长度即可
但是struct模块对与打包数据的长度是有上限的
2.终极方案:
客户端:
1 制作真实数据的信息字典(数据长度、简介、名称)
2 利用struct模块制作字典的报头
3 发送固定长度的报头(解析出来的是字典的长度)
4 发送字典数据
5 发送真实数据
服务端:
1 接收固定长度的字典报头
2 解析出字典的长度并接收
3 通过字典获取到真实的数据的各项信息
4 接收真实数据的长度
3.发送图片防止黏包现象
客户端:
import json import os import socket import struct client = socket.socket() client.connect(('127.0.0.1', 9090)) # 1 获取真实文件的大小 file_size = os.path.getsize(r'/Users/duoduo/Pictures/球球.jpeg') print(file_size) # 242227 # 2 制作真实数据的信息字典 file_data_dict = { 'file_name': '球球小狗1.jpeg', 'file_size': file_size, 'file_desc': '白色小狗开心笑脸照片', 'file_info': '笑口常开好运自然来' } # 3 制作报头,也就是字典的信,利用json模块序列化,并转换二进制 data_head_to_bytes = json.dumps(file_data_dict).encode('utf8') # 利用struct模块压缩为固定长度 data_head_pack = struct.pack('i', len(data_head_to_bytes)) # 4 发送字典报头 client.send(data_head_pack) # 此时报头已经被压缩为长度为 4 bytes的二进制数据 # 5 发送字典 client.send(data_head_to_bytes) # 6 最后发送真实数据 with open(r'/Users/duoduo/Pictures/球球.jpeg', 'rb') as f: for line in f: client.send(line)
服务端:
import json import socket import struct server = socket.socket() server.bind(('127.0.0.1', 9090)) server.listen(8) sock, addr = server.accept() # 1 接收固定长度的报头 data_dict_head = sock.recv(4) # 2 根据报头解析出字典数据的长度,收到的是元组一定要索引 data_dict_len = struct.unpack('i', data_dict_head)[0] # 3 接收字典数据 data_dict_bytes = sock.recv(data_dict_len) data_dict = json.loads(data_dict_bytes) # 4 获取真实数据的各项信息,由于真实数据可能非常大,所以最好不要在recv的参数内直接填写真实数据的大小,而是可以分次接收,每次接收1024或其倍数 total_size = data_dict.get('file_size') recv_size = 0 with open(data_dict.get('file_name'), 'wb') as f: while recv_size < total_size: data = sock.recv(1024) f.write(data) recv_size += len(data)
五、UDP协议
客户端
import socket client = socket.socket(type=socket.SOCK_DGRAM) server_addr = ('127.0.0.1', 8080) while True: msg = input('>>>:').strip() client.sendto(msg.encode('utf8'), server_addr) data, addr = client.recvfrom(1024) print(data.decode('utf8'), addr)
服务端
import socket server = socket.socket(type=socket.SOCK_DGRAM) # 指定为UDP协议 server.bind(('127.0.0.1', 8080)) while True: data, addr = server.recvfrom(1024) print('客户端地址>>>:', addr) print('上述地址发送的消息>>>:', data.decode('utf8')) msg = input('>>>:').strip() server.sendto(msg.encode('utf8'), addr)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY