博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Python全栈之路系列----之-----网络编程(粘包与命令执行/数据传输)
阅读量:4645 次
发布时间:2019-06-09

本文共 8856 字,大约阅读时间需要 29 分钟。

粘包

什么是粘包

  1. 只有tcp有粘包现象,udp永远不会粘包
  2. 所谓粘包问题,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
  3. tcp协议会根据自身特性,将间隔时间短和数据量小的数据,合并成一个数据块,然后进行封包和算法优化后进行流式发送到客户端
  4. tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住

粘包产生的原因

  1. 发送端(服务端):发送数据时间间隔很短,数据量小,会合到一起,产生粘包,要等缓冲区满才发送出去
  2. 接收端(客户端):服务端发送一段数据,客户端只接收一小部分(限流,1024),客户端下次在接收的时候还是从缓存区拿上次遗留的数据,产生粘包

 

 

拆包的发生情况

 

当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。

 

补充问题一:为何tcp是可靠传输,udp是不可靠传输

 

基于tcp的数据传输请参考我的另一篇文章http://www.cnblogs.com/linhaifeng/articles/5937962.html,tcp在数据传输时,发送端先把数据发送到自己的缓存中,然后协议控制将缓存中的数据发往对端,对端返回一个ack=1,发送端则清理缓存中的数据,对端返回ack=0,则重新发送数据,所以tcp是可靠的

 

而udp发送数据,对端是不会返回确认信息的,因此不可靠

 

补充问题二:send(字节流)和recv(1024)及sendall

 

recv里指定的1024意思是从缓存里一次拿出1024个字节的数据

 

send的字节流是先放入己端缓存,然后由协议控制将缓存内容发往对端,如果待发送的字节流大小大于缓存剩余空间,那么数据丢失,用sendall就会循环调用send,数据不会丢失

 

解决粘包问题*

  • 为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据

  • struct模块:  该模块可以把一个类型,如数字,转成固定长度的bytes     >>>>struct.pack('i',1111111111111)

 

  • 问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据
  • 我们可以把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节(4个自己足够用了)
  1. 发送时:
  2. 先发报头长度
  3. 再编码报头内容然后发送最后发真实内容
  4. 接收时
  5. 先收报头长度,用struct取出来
  6. 根据取出的长度收取报头内容,然后解码,反序列化
  7. 从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容

 

基于通道的双向连接和标准输入,标准错误,来发送数据的描述信息(长度)来实现命令的执行和解决粘包

import socketimport struct#1、买手机phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#2、打电话phone.connect(('127.0.0.1',8091))#3、发收消息while True:    cmd=input('>>: ').strip()    if cmd == 'quit':break    if not cmd:continue    phone.send(cmd.encode('utf-8'))    #先收报头    header=phone.recv(4)    total_size=struct.unpack('i',header)[0]    #循环收:10241    total_data=b''    recv_size=0    while recv_size < total_size:        recv_data=phone.recv(1024)        total_data+=recv_data        recv_size+=len(recv_data)    print(total_data.decode('gbk'))#4、挂电话phone.close()
客户端1.0
import socketimport subprocessimport struct#1、买手机phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#2、绑定手机卡# phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加phone.bind(('127.0.0.1',8091))#3、开机phone.listen(5)#4、等电话连接print('starting...')while True: #连接循环    conn,addr=phone.accept()    print('IP:%s,PORT:%s' %(addr[0],addr[1]))    #5、收发消息    while True: #通信循环        try:            cmd=conn.recv(1024) #最大收1024            if not cmd:break #针对linux            #执行命令            obj=subprocess.Popen(cmd.decode('utf-8'),                             shell=True,                             stdout=subprocess.PIPE,                             stderr=subprocess.PIPE)            stdout=obj.stdout.read()            stderr=obj.stderr.read()            #发送数据的描述信息:长度            header=struct.pack('i',len(stdout)+len(stderr))            conn.send(header)            #发送真实数据            conn.send(stdout)            conn.send(stderr)        except Exception:            break    #6、挂电话    conn.close()#7、关机phone.close()
服务端1.0

 

基于自定义报头和struct模块来彻底解决粘包问题

import socketimport structimport json#1、买手机phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#2、打电话phone.connect(('127.0.0.1',8092))#3、发收消息while True:    cmd=input('>>: ').strip()    if cmd == 'quit':break    if not cmd:continue    phone.send(cmd.encode('utf-8'))    #先收报头长度    obj=phone.recv(4)    header_size=struct.unpack('i',obj)[0]    #再收报头    header_bytes=phone.recv(header_size)    header_json=header_bytes.decode('utf-8')    header_dic=json.loads(header_json)    print(header_dic)    #最后循环收真实的数据    total_size=header_dic['total_size']    filename=header_dic['filename']    total_data=b''    recv_size=0    with open(filename,'wb') as f:        while recv_size < total_size:            recv_data=phone.recv(1024)            total_data+=recv_data            recv_size+=len(recv_data)    print(total_data.decode('gbk'))#4、挂电话phone.close()
客户端2.0
import socketimport subprocessimport structimport json#1、买手机phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#2、绑定手机卡# phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加phone.bind(('127.0.0.1',8092))#3、开机phone.listen(5)#4、等电话连接print('starting...')while True: #连接循环    conn,addr=phone.accept()    print('IP:%s,PORT:%s' %(addr[0],addr[1]))    #5、收发消息    while True: #通信循环        try:            cmd=conn.recv(1024) #最大收1024            if not cmd:break #针对linux            #执行命令            obj=subprocess.Popen(cmd.decode('utf-8'),                             shell=True,                             stdout=subprocess.PIPE,                             stderr=subprocess.PIPE)            stdout=obj.stdout.read()            stderr=obj.stderr.read()            #制作报头            header_dic = {
'filename': 'a.txt', 'total_size': len(stdout)+len(stderr), 'md5': 'asdfa123xvc123'} header_json = json.dumps(header_dic) header_bytes = header_json.encode('utf-8') #先发报头的长度 conn.send(struct.pack('i',len(header_bytes))) #再发送报头 conn.send(header_bytes) #最后发送真实数据 conn.send(stdout) conn.send(stderr) except Exception: break #6、挂电话 conn.close()#7、关机
服务端2.0

 

文件上传

import socketimport os# 创建一个socket对象obj = socket.socket()# 服务端的IP和端口obj.connect(('127.0.0.1', 6542))# 用os模块获取要传送的文件总大小size = os.stat("old_file.txt").st_size# 把文件总大小发送给服务端obj.sendall(bytes(str(size), encoding="utf-8"))# 接受服务端返回的信息obj.recv(1024)# 以rb的模式打开一个要发送的文件dwith open("old_file.txt", "rb") as f:    # 循环文件的所有内容    for line in f:        # 发送给服务端        obj.sendall(line)# 关闭退出
客户端
import socket# 创建一个socket对象sk = socket.socket()# 允许连接的IP和端口sk.bind(('127.0.0.1', 6542))# 最大连接数sk.listen(5)while True:    # 会一直阻塞,等待接收客户端的请求,如果有客户端连接会获取两个值,conn=创建的连接,address=客户端的IP和端口    conn, address = sk.accept()    # 客户端发送过来的文件大小    file_size = str(conn.recv(1024),encoding="utf-8")    # 给客户端发送已经收到文件大小    conn.sendall(bytes("ack", encoding="utf-8"))    # 文件大小转换成int类型    total_size = int(file_size)    # 创建一个默认的值    has_recv = 0    # 打开一个新文件,以wb模式打开    f = open('new_file.txt', 'wb')    # 进入循环    while True:        # 如果传送过来的大小等于文件总大小,那么就退出        if total_size == has_recv:            break        # 接受客户端发送过来的内容        data = conn.recv(1024)        # 写入到文件当中        f.write(data)        # 现在的大小加上客户端发送过来的大小        has_recv += len(data)    # 关闭    f.close()
服务端

 

文件下载

 

from socket import *import structimport jsonimport hashlibdownload_dir=r'D:\\'client=socket(AF_INET,SOCK_STREAM)client.connect(('127.0.0.1',8000))while True:    cmd=input('>>: ').strip() #get a.txt    if not cmd:continue    client.send(cmd.encode('utf-8'))    #先收报头长度    obj=client.recv(4)    header_size=struct.unpack('i',obj)[0]    #再收报头    header_bytes=client.recv(header_size)    header_json=header_bytes.decode('utf-8')    header_dic=json.loads(header_json)    filename=header_dic['filename']    abs_path=r'%s\%s' %(download_dir,filename)    size=header_dic['size']    print(header_dic)    #再收真实数据    recv_size=0    with open(abs_path,'wb') as f:        m=hashlib.md5()        while recv_size < size:            line=client.recv(1024)            f.write(line)            m.update(line)            recv_size+=len(line)    client_md5=m.hexdigest()    #最后收md5值    server_md5=client.recv(1024).decode('utf-8')    if client_md5 !=  server_md5:        os.remove(abs_path)        print('文件已损坏,请重写下载')client.close()
客户端(md5)

 

from socket import *import osimport jsonimport structimport hashlibserver=socket(AF_INET,SOCK_STREAM)server.bind(('127.0.0.1',8000))server.listen(5)def get(filepath,conn):    #制作报头    header_dic={        'filename':os.path.basename(filepath),# C:\\\\1.png        'size':os.path.getsize(filepath),    }    header_json=json.dumps(header_dic)    header_bytes=header_json.encode('utf-8')    #先发送报头的长度    conn.send(struct.pack('i',len(header_bytes)))    #再发送报头    conn.send(header_bytes)    #再发送真实的数据    with open(filepath,'rb') as f:        m=hashlib.md5()        for line in f:            conn.send(line)            m.update(line)    #最后发送md5值    md5=m.hexdigest()    print(md5)    conn.send(md5.encode('utf-8'))while True:    conn,addr=server.accept()    while True:        try:            data=conn.recv(1024)            cmd,filepath=data.decode('utf-8').split() #get C:\\\\1.png            if cmd == 'get':                get(filepath,conn)            if not data:break        except Exception as e:            print(e)            break    conn.close()server.close()
服务端(md5)

 

UDP套接字

from socket import *client=socket(AF_INET,SOCK_DGRAM)while True:    client.sendto(b'hello',('127.0.0.1',8082))    data,server_addr=client.recvfrom(1024)    print(data.decode('utf-8'))
客户端
from socket import *import osprint(os.getpid())server=socket(AF_INET,SOCK_DGRAM)server.bind(('127.0.0.1',8080))while True:    data,client_addr=server.recvfrom(1024)    server.sendto(data.upper(),client_addr)server.close()
服务端

 

多道基础预习

http://www.cnblogs.com/zgd1234/p/7155523.html

 

转载于:https://www.cnblogs.com/zgd1234/p/7597735.html

你可能感兴趣的文章
儿子和女儿——解释器和编译器的区别与联系
查看>>
第一阶段冲刺3
查看>>
父类引用指向子类对象
查看>>
网页如何实现下载功能
查看>>
IT男专用表白程序
查看>>
读《大道至简》第六章感想
查看>>
ef linq 中判断实体中是否包含某集合
查看>>
章三 链表
查看>>
Solution for Concurrent number of AOS' for this application exceeds the licensed number
查看>>
CSE 3100 Systems Programming
查看>>
IntelliJ IDEA 的Project structure说明
查看>>
Java Security(JCE基本概念)
查看>>
Linux Supervisor的安装与使用入门
查看>>
创建 PSO
查看>>
JasperReport报表设计4
查看>>
项目活动定义 概述
查看>>
团队冲刺04
查看>>
我的Python分析成长之路8
查看>>
泛型在三层中的应用
查看>>
SharePoint2010 -- 管理配置文件同步
查看>>