1.tcp黏包问题:(数据传输过多,一次性接受不玩的问题)
server端的程序:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from conf.config import * import socket sk=socket.socket() sk.bind(ip_port) sk.listen() conn,addr=sk.accept() ret=conn.recv(5).decode('utf-8')#接受5个字节的数据 ret1=conn.recv(12).decode('utf-8') print(ret) print(ret1)
client端的程序:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from conf.config import * import socket sk=socket.socket() sk.connect(ip_port) sk.send(b'jfkdjfjfjdkjfdj')#每一个英文字符占用一个字节 sk.close()
结果为
通过结果我们可以发现我们只传了一次数据但是服务器第一次只接受了一部分,服务器在进行第二次接受数据时,把原来第一次没有接受完的数据进行接受
2.产生这一个的原因如下:
当server端发送数据给client时,server首先将数据发送到操作系统上,操作系统通过ip发送给对面机器上的操作系统上,对面的操作系统根据端口号,确定程序,来查看程序所接受
的数据量,当client接受的数据量小于操作系统种接受的数据量时,多余的数据量将会保存在操作系统的缓存区域内,等待下一次接受时使用,如果client没有发送信息,之后server进行
接受,那么server只接受buffer中剩余的数据量,如果client有数据发送,就是client显示的数据量就是buffer剩余的数据量+(client接受的数据量综合-剩余的数据量)[这个时新接收的数据量】
3.多次发送小数据量的数据包被合并的问题:
server端程序:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from conf.config import * import socket sk=socket.socket() sk.bind(ip_port) sk.listen() conn,addr=sk.accept() ret=conn.recv(1024).decode('utf-8')#接受5个字节的数据 ret1=conn.recv(1024).decode('utf-8') print(ret) print(ret1)
client端程序;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from conf.config import * import socket sk=socket.socket() sk.connect(ip_port) sk.send(b'hello')#每一个英文字符占用一个字节 sk.send(b'egg') sk.close()
我们看到的结果:
4.面对多个小数据包发送合并问题我们可以采用sleep方法进行解决:
server端程序:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from conf.config import * import socket sk=socket.socket() sk.bind(ip_port) sk.listen() conn,addr=sk.accept() ret=conn.recv(1024).decode('utf-8')#接受5个字节的数据 ret1=conn.recv(1024).decode('utf-8') print(ret) print(ret1)
client端程序:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from conf.config import * import time import socket sk=socket.socket() sk.connect(ip_port) sk.send(b'hello')#每一个英文字符占用一个字节 time.sleep(0.01) sk.send(b'egg') sk.close()
结果为
5.解决黏包中不知数据量大小的问题可以采用以下方法:
5.1我们首先知道我们发送的数据量有多大,然后按照这个数据量进行接受。
知识科普:subprocess中获取到的管道数据只能够读取一次,和它相似的还有就是队列queue
server端程序:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from conf.config import * import socket sk=socket.socket() sk.bind(ip_port) sk.listen() conn,addr=sk.accept() while True: info=input('请输入操作指令:') if info=='q': conn.send(b'q') break conn.send(info.encode('utf-8')) num=conn.recv(1024).decode('utf-8') print(num) conn.send(b'ok') ret=conn.recv(int(num)).decode('gbk') print(ret) conn.close() sk.close()
client端程序:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from conf.config import * import subprocess import time import socket sk=socket.socket() sk.connect(ip_port) while True: cmd=sk.recv(1024).decode('gbk') if cmd=='q': break ret=subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) std_out=ret.stdout.read() std_err=ret.stderr.read() std_len=str(len(std_out+std_err)).encode('utf-8') sk.send(std_len) sk.recv(1024) sk.send(std_out) sk.send(std_err) sk.close()
5.2使用上述程序是可以解决黏包的问题,但是多了一次交互,增加了一次堵塞的时间,为了解决这个问题我们引进了struct模块:struct模块是将固定长度的数据转换成bytes
tip:1.在网络上进行文件传输时,文件是可以一行一行进行读取的,但是图片和视频也是可以进行一行一行读取的,但是在读取过程中每次读取的数据量是不一致的,所以我们在
进行文件读取时次啊用字节读取的方式,每次读取固定的字节。并且记住读的速度要比写的速度快。而且一边在文件传输过程中设置buffer的大小不超过4096
tips2:一般使用struct都是使用整数类型。i代表int就是将一个数字转换成固定长度的bytes(4)
程序如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
ret=struct.pack('i',1024) print(ret) ret1=struct.unpack('i',ret) print(ret1)#得到的是一个元组类型 print(ret1[0]) 结果为 b'x00x04x00x00' (1024,) 1024
5.3将上述程序通过struct进行修改如下:
server端程序;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from conf.config import * import struct import socket sk=socket.socket() sk.bind(ip_port) sk.listen() conn,addr=sk.accept() while True: info=input('请输入操作指令:') if info=='q': conn.send(b'q') break conn.send(info.encode('utf-8')) num=conn.recv(1024) #先接收4 num=struct.unpack('i',num) #将传入过来的bytes整型进行转换,得到文件大小 print(num) ret=conn.recv(num[0]).decode('gbk')#只提取元组中数据量的大小 print(ret) conn.close() sk.close()
client程序;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from conf.config import * import struct import subprocess import time import socket sk=socket.socket() sk.connect(ip_port) while True: cmd=sk.recv(1024).decode('gbk') if cmd=='q': break ret=subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) std_out=ret.stdout.read() std_err=ret.stderr.read() std_len=len(std_out+std_err) std_num=struct.pack('i',std_len)#将数字转化成bytes类型 sk.send(std_num) #发送文件的大小4+数据大小 sk.send(std_out) #发送数据 sk.send(std_err)#发送数据 sk.close()
6.struct的执行过程:借助struct模块,我们知道长度数字是可以转换成标准大小的4字节数字,因此可以利用这个特点预先发送数据长度
发送时 | 接收时 |
先发送struct转化好的数据长度4字节 | 先接受4个字节使用struct抓换成的数字来获取要接受的数据长度 |
在发送数据 | 再按照长度进行数据的接受 |
7.我们在网络上传输的数据都叫做数据包,数据包里的内容称之为报文‘报文里不止有我们发送的数据,还有ip地址、mac地址、端口号。
8.所有的报文都有报头,协议、报头接收多少个字节。
9。我们可以自己指定报文:比如说复杂的应用上我们就可以应用上设置报文。传输文件时就可以用到:我们可以设置文件的名字,文件的大小’、文件的类型、存储路径。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
head={'filename':'test','filesize':409600,'filetype':'txt','filepath':r'D:/usr/bin'} #报头的长度 #先接收4个字节 #send(head) #根据这4个字节获取报头 #send(file) #从报头中获取filesize,然后根据filesize接受文件
10.一般我们在写文件上传和下载时要把ip和端口号放在配置文件里:
server端:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from conf.config import * import socket import json import struct buffer=1024 sk=socket.socket() sk.bind(ip_port) sk.listen() conn,addr=sk.accept() head=conn.recv(4) str_head=struct.unpack('i',head)[0] json_head=conn.recv(str_head).decode('utf-8') dic_head=json.loads(json_head) filesize=dic_head['filesize'] filename=dic_head['filename'] with open(filename,'wb')as f: while filesize: if filesize>=buffer: content=conn.recv(buffer) f.write(content) filesize-=buffer else: content=conn.recv(filesize) f.write(content) filesize=0 conn.close() sk.close()
client端程序:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from conf.config import * import socket import os import json import struct buffer=1024 sk=socket.socket() sk.connect(ip_port) head={'filename':r'豆瓣top250.docx','filepath':r'C:UsersOYMKDesktop', 'filesize':None } file1=os.path.join(head['filepath'],head['filename']) #文件的绝对路径 filesize=os.path.getsize(file1)#获取文件的大小 head['filesize']=filesize #放入字典中 json_head=json.dumps(head) #将字典转换成字符串 byte_head=json_head.encode('utf-8')#将字符串转换成bytes len_head=len(byte_head)#计算长度 str_head=struct.pack('i',len_head) sk.send(str_head)#将报头大小发送过去 sk.send(byte_head)#在发送bytes类型的报头 with open(file1,'rb') as f: while filesize: if filesize>=buffer: content=f.read(buffer) sk.send(content) filesize-=buffer else: content=f.read(filesize) sk.send(content) filesize=0 sk.close()
流程图