上一篇随笔:“socket 接收大数据”,在win系统上能够运行,并且解决了大数据量的数据传输出现的问题,但是运行在linux系统上就会出现如下图所示的情况:

就是服务端两次发送给客户端的数据(第一次发送是时准备发送数据的字节大小,第二次是数据内容)粘在一起了,这是socket中的粘包:

查看服务端代码就能知道发生粘包的原因:

  1. import socket,os
  2.  
  3. server = socket.socket()
  4. server.bind(('localhost',2222))
  5.  
  6. server.listen()
  7.  
  8. while True:
  9. conn,addr = server.accept()
  10. print("一个新的连接:",addr)
  11. while True:
  12. print("等待新指令")
  13. data = conn.recv(500)
  14. if not data:
  15. print("客户端已经断开")
  16. break
  17. print("执行指令:",data.decode())
  18. cmd_res = os.popen(data.decode()).read()
  19. print("发送文件大小", len(cmd_res))
  20. print("send before")
  21. if len(cmd_res) == 0:
  22. cmd_res = "cmd has no output......"
  23. #合成下面两次手动send,将数据大小和数据内容合一次发送给客户端,所以导致数据粘在一起了
  24. conn.send(str(len(cmd_res)).encode())
  25. conn.send(cmd_res.encode())
  26. print("send done")
  27. server.close()  

解决大数据传输过程中的粘包:

1、sleep

sleep一下这个样子就可以使缓冲区超时,就不在等下一次的了,这样就可以和下一条命令隔离开(不过这样会降低代码性能,不建议使用)

  1. import socket,os
  2.  
  3. server = socket.socket()
  4. server.bind(('localhost',2222))
  5.  
  6. server.listen()
  7.  
  8. while True:
  9. conn,addr = server.accept()
  10. print("一个新的连接:",addr)
  11. while True:
  12. print("等待新指令")
  13. data = conn.recv(500)
  14. if not data:
  15. print("客户端已经断开")
  16. break
  17. print("执行指令:",data.decode())
  18. cmd_res = os.popen(data.decode()).read()
  19. print("发送文件大小", len(cmd_res))
  20. print("send before")
  21. if len(cmd_res) == 0:
  22. cmd_res = "cmd has no output......"
  23. conn.send(str(len(cmd_res)).encode())
  24. #在两次发送之间sleep一下
  25. sleep(0.5)
  26. conn.send(cmd_res.encode())
  27. print("send done")
  28. server.close()  

2、客户端、服务端之间插入交互解决粘包问题

在服务端来一个等待客户端确认,就ok了,这个确认不需要用户输入,而是客户端自动的给你来这个响应,就是说,客户端自动的写好代码,自动的给服务器一个响应,只要收到服务端的数据大小,我就立刻给服务器一个响应,就是在第一次send和第二次send之前插入一个交互,就能把数据分开了

服务端:

  1. import socket,os
  2.  
  3. server = socket.socket()
  4. server.bind(('localhost',2222))
  5.  
  6. server.listen()
  7.  
  8. while True:
  9. conn,addr = server.accept()
  10. print("一个新的连接:",addr)
  11. while True:
  12. print("等待新指令")
  13. data = conn.recv(500)
  14. if not data:
  15. print("客户端已经断开")
  16. break
  17. print("执行指令:",data.decode())
  18. cmd_res = os.popen(data.decode()).read()
  19. print("发送文件大小", len(cmd_res))
  20. print("send before")
  21. if len(cmd_res) == 0:
  22. cmd_res = "cmd has no output......"
  23. conn.send(str(len(cmd_res)).encode()) # 数据大小
  24. #等待客户端确认
  25. client_acknowledge = server.recv(1024)
  26. conn.send(cmd_res.encode()) # 数据内容
  27. print("send done")
  28. server.close()

客户端:

  1. import socket
  2.  
  3. client = socket.socket()
  4.  
  5. client.connect(("localhost", 2222))
  6.  
  7. while True:
  8. cmd = input(">>:").strip()
  9. if len(cmd) == 0:continue
  10. client.send(cmd.encode("utf-8"))
  11. cmd_res_size = client.recv(500)
  12. # 发个响应给服务端,告诉服务端,客户端已经准备好了
  13. print("即将接收数据大小:", cmd_res_size.decode())
  14. client.send("客户端准备好接收数据内容了".encode())
  15. recevied_size = 0
  16. recevied_data = b""
  17. while recevied_size < int(cmd_res_size.decode()):
  18. cmd_res = client.recv(500)
  19. recevied_size += len(cmd_res)
  20. recevied_data += cmd_res
  21. else:
  22. print(recevied_data.decode())
  23. print("cmd res receive done ....", recevied_size)
  24. client.close()

 

socket 文件下载

1、简单FTP:

上传下载是FTP最基本的功能,现在来模拟一下FTP的文件下载功能

服务端:

获取命令和文件名->判断文件是否存在->打开文件->获取文件大小->发送文件大小给客户端->等待客户端确认->边读边发

  1. import socket,os
  2.  
  3. server = socket.socket()
  4. server.bind(('localhost',2222))
  5.  
  6. server.listen()
  7.  
  8. while True:
  9. conn,addr = server.accept()
  10. print("一个新的连接:",addr)
  11. while True:
  12. print("等待新指令")
  13. data = conn.recv(1024)
  14. if not data:
  15. print("客户端已经断开")
  16. break
  17. cmd, file_name = data.decode().split() # 接收客户端发过来的命令和文件名
  18. print("执行指令:%s, 文件名:%s" % (cmd, file_name))
  19. if os.path.isfile(file_name):
  20. with open(file_name, "rb") as f:
  21. file_size = os.stat(file_name).st_size # 获取一个文件的大小:os.stat(文件名).st_size
  22. conn.send(str(file_size).encode())
  23. conn.recv(1024) # 等待客户端确认,防止发生粘包
  24. for line in f:
  25. conn.send(line)
  26. print("send done")
  27. server.close()

客户端:

判断是否是下载命令(get) ->发送下载命令和文件名 ->获取文件大小->发送确认信息->判断时候已经全部接收

  1. import socket
  2.  
  3. client = socket.socket()
  4.  
  5. client.connect(("localhost", 2222))
  6.  
  7. while True:
  8. cmd = input(">>:").strip()
  9. if len(cmd) == 0:continue
  10. print(cmd)
  11. if cmd.startswith("get"):
  12. client.send(cmd.encode("utf-8")) # 发送下载命令和文件名
  13. cmd_res_size = client.recv(1024) # 接收文件大小
  14. print("即将接收数据大小:", cmd_res_size.decode())
  15. client.send("客户端准备好接收数据内容了".encode())
  16. recevied_size = 0
  17. recevied_data = b""
  18. file_name = cmd.split()[1] # 文件名
  19. with open(file_name + "_new", "wb",) as f:
  20. while recevied_size < int(cmd_res_size.decode()):
  21. cmd_res = client.recv(500)
  22. recevied_size += len(cmd_res)
  23. f.write(cmd_res)
  24. else:
  25. print(recevied_data.decode())
  26. print("cmd res receive done ....", recevied_size)
  27. client.close()

  

2、MD5值校验:

上面代码实现了FTP文件的下载的功能,可是就一定能够保证客户端下载的文件跟服务端发送的文件一致?为了解决这个问题可以用到MD5值进行校验,从而判断客户端和服务端的一致性!

 服务端:

  1. import socket, os, hashlib
  2.  
  3. server = socket.socket()
  4. server.bind(('localhost',2222))
  5.  
  6. server.listen()
  7.  
  8. while True:
  9. conn,addr = server.accept()
  10. print("一个新的连接:",addr)
  11. while True:
  12. print("等待新指令")
  13. data = conn.recv(1024)
  14. if not data:
  15. print("客户端已经断开")
  16. break
  17. cmd, file_name = data.decode().split() # 接收客户端发过来的命令和文件名
  18. print("执行指令:%s, 文件名:%s" % (cmd, file_name))
  19. if os.path.isfile(file_name):
  20. m = hashlib.md5() # 生成MD5对象
  21. with open(file_name, "rb") as f:
  22. file_size = os.stat(file_name).st_size # 获取一个文件的大小:os.stat(文件名).st_size
  23. conn.send(str(file_size).encode())
  24. conn.recv(1024) # 等待客户端确认,防止发生粘包
  25. for line in f:
  26. m.update(line) # 不断更新计算MD5值
  27. conn.send(line)
  28. print("md5值", m.hexdigest())
  29. conn.recv(1024) # 等待客户端确认,防止发生粘包,准备发送MD5值
  30. conn.send(m.hexdigest().encode()) # 发送MD5值给客户端
  31. print("send done")
  32. server.close()

 客户端:

  1. import socket, hashlib
  2.  
  3. client = socket.socket()
  4.  
  5. client.connect(("localhost", 2222))
  6.  
  7. while True:
  8. cmd = input(">>:").strip()
  9. if len(cmd) == 0:continue
  10. print(cmd)
  11. if cmd.startswith("get"):
  12. client.send(cmd.encode("utf-8")) # 发送下载命令和文件名
  13. file_size = client.recv(1024) # 接收文件大小
  14. print("即将接收数据大小:", file_size.decode())
  15. client.send("客户端准备好接收数据内容了".encode())
  16. revived_size = 0
  17. file_name = cmd.split()[1] # 文件名
  18. m = hashlib.md5() # 生成MD5对象
  19. with open(file_name + "_new", "wb",) as f:
  20. while revived_size < int(file_size.decode()):
  21. cmd_res = client.recv(500)
  22. revived_size += len(cmd_res)
  23. m.update(cmd_res) # 不断更新计算接收数据的文件值
  24. f.write(cmd_res)
  25. else:
  26. print(file_size, revived_size)
  27. client_md5_value = m.hexdigest() # 生成接收数据的MD5值16进制形式
  28. client.send("ready to revived file md5 value".encode())
  29. server_md5_value = client.recv(1024) # 接收服务端的MD5值
  30. print("client接收文件MD5值:%s,server发送文件的MD5值:%s" % (client_md5_value,server_md5_value))
  31. if client_md5_value == server_md5_value.decode(): # 客户端和服务端的MD5值做比较
  32. print("file revived done")
  33. else:
  34. print(client_md5_value, server_md5_value.decode())
  35. client.close()

3、解决粘包方式改进:

上面的代码用MD5来校验还是用的之前解决粘包的方法,就是客户端发送一个请求,等待服务端的确认的这样的方式。下面用另外一种方法:就是客户端已经知道可接收多少数据了,既然客户端已经知道接收多少数据了,那么客户端在接收数据的时候,正好接收已经知道的数据,多余的数据就不接收了,就是说我循环接收了已知数据大小的文件。比如:服务端要发6100字节的数据,客户端正好收到6000字节的数据,然后就不往下再收了(因为在接收,就有可能跟MD5值黏在一块了,如果客户端正好接收6000字节的话,剩下的100字节就不收了,客户端把6000字节的文件保存后,再来revived一下,下面revived的正好是100字节,这100字节就是MD5值)

 服务端:

  1. import socket, os, hashlib
  2.  
  3. server = socket.socket()
  4. server.bind(('localhost',2222))
  5.  
  6. server.listen()
  7.  
  8. while True:
  9. conn,addr = server.accept()
  10. print("一个新的连接:",addr)
  11. while True:
  12. print("等待新指令")
  13. data = conn.recv(1024)
  14. if not data:
  15. print("客户端已经断开")
  16. break
  17. cmd, file_name = data.decode().split() # 接收客户端发过来的命令和文件名
  18. print("执行指令:%s, 文件名:%s" % (cmd, file_name))
  19. if os.path.isfile(file_name):
  20. m = hashlib.md5() # 生成MD5对象
  21. with open(file_name, "rb") as f:
  22. file_size = os.stat(file_name).st_size # 获取一个文件的大小:os.stat(文件名).st_size
  23. conn.send(str(file_size).encode()) # 发送文件大小
  24. conn.recv(1024) # 等待客户端确认,防止发生粘包
  25. for line in f:
  26. m.update(line) # 不断更新计算MD5值
  27. conn.send(line)
  28. print("md5值", m.hexdigest())
  29. conn.send(m.hexdigest().encode()) # 发送MD5值给客户端
  30. print("send done")
  31. server.close()

客户端:

  1. import socket, hashlib
  2.  
  3. client = socket.socket()
  4.  
  5. client.connect(("localhost", 2222))
  6.  
  7. while True:
  8. cmd = input(">>:").strip()
  9. if len(cmd) == 0:continue
  10. print(cmd)
  11. if cmd.startswith("get"):
  12. client.send(cmd.encode("utf-8")) # 发送下载命令和文件名
  13. file_size = client.recv(1024) # 接收文件大小
  14. print("即将接收数据大小:", file_size.decode())
  15. client.send("客户端准备好接收数据内容了".encode())
  16. revived_size = 0
  17. file_name = cmd.split()[1] # 文件名
  18. m = hashlib.md5() # 生成MD5对象
  19. with open(file_name + "_new", "wb",) as f:
  20. while revived_size < int(file_size.decode()):
  21. if int(file_size.decode()) - revived_size > 1024: # 只要剩余文件字节大于1024字节,就默认最大值接收
  22. size = 1024
  23. else:
  24. size = int(file_size.decode()) - revived_size # 最后一次,剩多少收多少
  25. print("last receive:", size)
  26. file_data = client.recv(size)
  27. revived_size += len(file_data)
  28. m.update(file_data) # 不断更新计算接收数据的文件值
  29. f.write(file_data)
  30. else:
  31. print(file_size, revived_size)
  32. client_md5_value = m.hexdigest() # 生成接收数据的MD5值16进制形式
  33. server_md5_value = client.recv(1024) # 接收服务端的MD5值
  34. print("client接收文件MD5值:%s,server发送文件的MD5值:%s" % (client_md5_value, server_md5_value))
  35.  
  36. client.close()

  

【python】-- Socket粘包问题 ,解决粘包的几种方法、socket文件下载,md5值检验的更多相关文章

  1. tcp粘包、解决粘包问题

    目录 subproess模块 TCP粘包问题 粘包两种情况 解决粘包问题 struct模块的使用 使用struct模块解决粘包 优化解决粘包问题 上传大文件 服务端 客户端 UDP协议 upd套接字 ...

  2. python全栈开发day28-网络编程之粘包、解决粘包,上传和下载的作业

    一.昨日内容回顾 1. tcp和udp编码 2. 自定义mysocket解决编码问题 二.今日内容总结 1.粘包 1)产生粘包原因: (1).接收方不知道消息之间的边界,不知道一次性要取多少字节的数据 ...

  3. 8-2udp和tcp网络编程以及粘包和解决粘包的方法

    一  tcp网络编程 server 端 import socket sk=socket.socket() #实例化一个对象 sk.setsockopt(socket.SOL_SOCKET,socket ...

  4. vue开发环境和生产环境里面解决跨域的几种方法

    什么是跨域   跨域指浏览器不允许当前页面的所在的源去请求另一个源的数据.源指协议,端口,域名.只要这个3个中有一个不同就是跨域. 这里列举一个经典的列子: #协议跨域 http://a.baidu. ...

  5. python多进程编程中常常能用到的几种方法

    python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU资源,在python中大部分情况需要使用多进程.python提供了非常好用的多进程包Multiprocessing,只需要定义 ...

  6. 【机器学习算法-python实现】协同过滤(cf)的三种方法实现

    (转载请注明出处:http://blog.csdn.net/buptgshengod) 1.背景       协同过滤(collaborative filtering)是推荐系统经常使用的一种方法.c ...

  7. js常用代码示例及解决跨域的几种方法

    1.阻止默认行为 // 原生js document.getElementById('btn').addEventListener('click', function (event) { event = ...

  8. java war包 路径--解决war包中文件路径问题

    https://blog.csdn.net/u013409283/article/details/51480948 转自:http://free-chenwei.iteye.com/blog/1507 ...

  9. Appium Android 获取包名appPackage和appActivity的几种方法

    情况1: 安装包未安装到手机 准备前提条件: 1 Android SDK管理工具目录 2 PC端有apk包 使用方法: 1 打开终端,当前路径移动到sdk管理工具目录tools或build-tools ...

随机推荐

  1. 比特币Bitcoin-qt客户端加密前后如何导入导出私钥?

    一.Bitcoin-qt客户端加密后 如需要导出某一地址对应的私钥,需要先调用 walletpassphrase 密码 解锁持续时间(秒), 如:walletpassphrase h123456789 ...

  2. C#与数据结构--图的遍历

    http://www.cnblogs.com/abatei/archive/2008/06/06/1215114.html 8.2 图的存储结构 图的存储结构除了要存储图中各个顶点的本身的信息外,同时 ...

  3. vscode - 使用Settings进行同步扩展以及配置信息等

    1. 创建token(记住要复制上面的token): https://github.com/settings/tokens. 2. 创建gist id https://gist.github.com/ ...

  4. Android SDK安装时出错“android Failed to rename directory”的解决方法

    Android SDK安装时出错"android Failed to rename directory"的解决的方法     安装Android SDK时遇到Failed to r ...

  5. 解决log4j.xml问题http//jakarta.apache.org/log4j/ uri is not registered

    在Eclipse中,配置log4j.xml出现"http //jakarta.apache.org/log4j/ uri is not registered"的错误信息. 原始的l ...

  6. Spring IOC源代码具体解释之容器初始化

    Spring IOC源代码具体解释之容器初始化 上篇介绍了Spring IOC的大致体系类图,先来看一段简短的代码,使用IOC比較典型的代码 ClassPathResource res = new C ...

  7. iOS项目开发之Socket编程

    有一段时间没有认真总结和写博客了 前段时间找工作.进入工作阶段.比较少静下来认真总结,现在静下心来总结一下最近的一些心得 前言 AsyncSocket介绍 AsyncSocket详解 AsyncSoc ...

  8. GB28181出内网

    最近关注GB28181的朋友很多,昨天有位朋友问到GB28181出内网的问题,希望我花5分钟的时间 讲讲如何通过GB28181协议将内网的摄像机视频推送到公网.要说清楚这个问题,5分钟的时间应该不 够 ...

  9. ASP.NET CORE RAZOR :向 Razor 页面应用添加模型

    本文来自:https://docs.microsoft.com/zh-cn/aspnet/core/tutorials/razor-pages/model 在本部分中将添加用于管理数据库中的电影的类. ...

  10. [DevExpress]TreeListLookUpEdit带checkbox之经典运用

    上代码: public partial class TreeListLookUpEdit : DevExpress.XtraEditors.XtraForm { private string _Key ...