今日单词
socket 插座 套接字 少开的phone 手机server 服务器 少我bind 捆绑client客户 可来嗯accept 接受 阿婆晒的addr connconnect 接受recv 接收send 发送decode解码encode编码total 全部的 偷偷size 大小 塞子result 结果 内稍ciipconfig 微软操作系统中的一个系统命令,用于查看本机的IP信息ipconfig/all查看当前电脑网卡的ip信息、DNS信息、DHCP服务器信息等import subprocess 子过程可以执行远端命令import struct结构 是不的 将该模块可以把一个类型,如数字,转成固定长度的bytespack包装 怕可unpack 打开包裹
粘包现象
须知:只有TCP有粘包现象,UDP永远不会粘包粘包不一定会发生如果发生了:1.可能是在客户端已经粘了 2.客户端没有粘,可能是在服务端粘了
socket收发消息的原理
所谓粘包问题主要还是因为接收方不知道消息之间的界限 还有系统缓存区的问题 时间差的原因,不知道一次性提取多少字节的数据所造成的。
为什么要有缓存区
输入输出缓冲区的默认大小一般都是 8K,可以通过 getsockopt() 函数获取:1024字节=1k
缓冲区的作用?
存储少量数据
如果你的网络出现短暂的异常或者波动,接收数据就会出现短暂的中断,影响你的下载或者上传的效率.
但是 凡是都是双刃剑,缓冲区解决了上传下载的传输效率的问题,带来了黏包问题.
6.收发的本质:
不一定是一收一发
为什么出现粘包?
1,接收方没有及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包) recv会产生黏包(如果recv接受的数据量(1024)小于发送的数据量,第一次只能接收规定的数据量1024,第二次接收剩余的数据量)
2,发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据也很小,会合到一起,产生粘包)send 也可能发生粘包现象.(连续send少量的数据发到输出缓冲区,由于缓冲区的机制,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络)
总结精简版
第一种.连续短暂的send多次(数据量很少),你的数据会统一发送出去,(不可控)第二种: send的数据过大,大于对方recv的上限时,对方第次recv时,会接收上一次没有recv完的剩余的
如何解决粘包现象
错误实例:
1. 可以扩大recv的上限. recv(10240000000000000000000000000) 不是解决这个问题的根本原因, 8g, 10G,这些都会直接放在内存中.2. 故意延长recv的时间. sleep 这样会非常影响效率.
low版解决粘包现象
server
# 原因# 1. 粘包第一种: send的数据过大,大于对方recv的上限时,对方第二次recv时,会接收上一次没有recv完的剩余的数据。# 解决方式 服务端 发送时记录字节长度 在将不固定第字节长度 利用模块转化为固定4个长度bytes# 先发送4个字节过去 在发送原内容# 客户端 先接受固定4个字节 在利用固定的4个字节bytes利用模块转回原来的字节个数(int)# 利用while循环接收# 利用的模块# import struct# # 将一个数字转化成等长度的bytes类型。# ret = struct.pack('i', 180000000)# # print(ret, type(ret), len(ret))## # 通过unpack反解回来# ret1 = struct.unpack('i',ret)[0]# # print(ret1)# print(ret1, type(ret1))# import struct# import subprocess# obj = subprocess.Popen('dir1',# shell=True,# stdout=subprocess.PIPE,# stderr=subprocess.PIPE,# )# print(obj.stdout.read().decode('gbk')) # 正确命令# print(obj.stderr.read().decode('gbk')) # 错误命令import structimport subprocessimport socketphone=socket.socket()phone.bind(('127.0.0.1',6666))phone.listen(5)while 1: coon, addr = phone.accept() while 1: try: from_client_data=coon.recv(1024) print(f'来自{addr}消息{from_client_data.decode("utf-8")}') obj = subprocess.Popen(from_client_data.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) to_client_data=(obj.stderr.read().decode('gbk')+obj.stdout.read().decode('gbk')).encode('utf-8')#总字节数 total_size=len(to_client_data)#总字节数长度 # print(total_size) # 将一个数字转化成等长度的bytes类型。 size = struct.pack('i', total_size) coon.send(size)#发送固定4个字节 coon.send(to_client_data)#发送总数据 except Exception: break coon.close()phone.close()
client
import socketimport structphone=socket.socket()phone.connect(('127.0.0.1',6666))while 1: to_cerver_data=input('????') if not to_cerver_data: # 服务端如果接受到了空的内容,服务端就会一直阻塞中,所以无论哪一端发送内容时,都不能为空发送 print('发送内容不能为空') continue phone.send(to_cerver_data.encode('utf-8')) from_server_size=phone.recv(4)#接收固定4个字节 from_server_total= struct.unpack('i',from_server_size)[0] # 反解报头 tatal=b'' while len(tatal)
旗舰版
优点
1.高大上版:自定制报头
2.高大上版:可以解决文件过大的问题.server端
import socketimport structimport json# 1. 创建socket对象(买手机) 左边是 右边是# phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)phone = socket.socket() # 可以默认不写# 连接服务器ip地址与端口 左边服务器IP地址 右边端口phone.connect(('127.0.0.1', 9999))# 发消息while 1: try: to_server = input('>>>').strip() if to_server.upper() == 'Q': phone.send('q'.encode('utf-8'))#发消息 break phone.send(to_server.encode('utf-8'))#send 发送 # 第1步 先读前4个 此时读的是字典长度 head_4 = phone.recv(4) #第2步 将固定4个byres 转化为字典byes原长度数字 此时是数字类型 ret1 = struct.unpack('i',head_4)[0] ##第3步 读取 字典长度byres head_dic_len = phone.recv(ret1) #第4步将字典 byres解码 转化为 特殊的字符串 head_dic_json=head_dic_len.decode('utf-8') #第5步将 json转化为字典 head_dic=json.loads(head_dic_json) #第6步获取 字典有用的信息 比如文件byres 长度 place_len=head_dic['file_size'] #第7步读取 原文件 place = phone.recv(place_len) print(place.decode('utf-8')) except ConnectionResetError: print('对方服务器崩了。。。。') break# 关机phone.close()#关闭手机#ipconfig
client端
import socket#建立通讯import subpr1111ocess#远程返回import struct#将该模块可以把一个类型,如数字,转成固定长度的bytesimport jsonphone = socket.socket()#买手机phone.bind(('127.0.0.1', 9999))#插卡phone.listen(5)#开放监听# 4. 接收连接print('start')conn, addr = phone.accept()while 1: try: cmd = conn.recv(1024) # 接收发送端的消息 obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE,#正确命令 stderr=subprocess.PIPE,#错误命令 ) #返回数据 由于有黏包现象 客户端接收不了这么多 result = obj.stdout.read() + obj.stderr.read()#gbk 格式 result = result.decode('gbk').encode('utf-8')#解码成utf-8 #第一步制作报头 head 头 head_dict={'file_size':len(result)} #第2步将字典转为json字符串 head_dict_json = json.dumps(head_dict) #第3步 将json字符串 转为 byes类型 head_dict_byres=head_dict_json.encode('utf-8') #第4步将json的byres 长度转为固定4个 byres total_size_byres=struct.pack('i',len(head_dict_byres)) #第5步 发送 固定4个byres total_size_byres conn.send(total_size_byres) #第6步 发送字典byres 给客服端 conn.send(head_dict_byres) # 第7步 发送原内容 给客服端 conn.send(result) except ConnectionResetError: breakconn.close()phone.close()