TCP/IP网络通讯粘包问题

  案例:模拟执行shell命令,服务器返回相应的类容。发送指令的客户端容错率暂无考虑,按照正确的指令发送即可。

  服务端代码

 # -*- coding: utf- -*-

 # 声明字符编码
# coding:utf- import socket
import subprocess def server_Tcp():
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, )
server.bind(('', ))
server.listen() while True:
client_socket, client_info = server.accept()
print("%s 已连接" % str(client_info))
try: while True:
client_msg = client_socket.recv()
if not client_msg: break
print("%s 收到指令<<%s" % (client_info, client_msg.decode('utf-8')))
# 处理执行的命令
res = subprocess.Popen(client_msg.decode('utf-8'), shell=True, stderr=subprocess.PIPE,
stdout=subprocess.PIPE, stdin=subprocess.PIPE)
err = res.stderr.read()
if err:
cmd_err = err
else:
cmd_err = res.stdout.read() # print(cmd_err.decode("gbk"))
client_socket.send(cmd_err)
except Exception as e:
print("%s 已断开" % str(client_info))
print(e)
continue
client_socket.close() server.close() if __name__ == "__main__":
server_Tcp()

  客户端代码

 # -*- coding: utf- -*-

 # 声明字符编码
# coding:utf- import socket def client_Tcp():
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(('127.0.0.1', )) while True:
msg = input('>>').strip()
if not msg: continue
if msg == 'quit': break
client.send(msg.encode('utf-8'))
result = client.recv()
print(result)
print("<<%s" % (result.decode('utf-8'))) client.close() if __name__ == "__main__":
client_Tcp()

说明:python中执行命令的指令模块是

  import subprocess
  # 处理执行的命令
  res = subprocess.Popen(client_msg.decode('utf-8'), shell=True, stderr=subprocess.PIPE,stdout=subprocess.PIPE, stdin=subprocess.PIPE)

执行演示

  首先运行服务端,然后运行客户端输入ipconfig指令。

服务端

客户端

我们在windows下的cmd命令下看看,或者是在shell客户端查看Linux系统的一样。

以上客户端接收的结果为什么不是完整的简要说明

  首先我们的客户端和服务端在电脑中其实就是两个程序。也就是应用程序,应用程序是无法和硬件接触的,应用程序去调用socket层去处理收发数据,而socket后面是去和操作系统对接,最后我们的收发数据是由操作系统去和硬件对接完成的。我们的数据都是在内存中读取的。如下图

当服务器一次性发送了10M数据,然而客户端每次接收2M这时候客户端就没有接收到完整的数据,下次接收还是在接收上次服务器发送的数据,也就是说,服务端可以发送任意大小的数据(配置条件下),而客户端不知道这次接收多大的数据就产生了黏包。

还有一种情况是服务端连续多次send如果这几次send相隔很近,TCP内部机制会将这个几次send组包为一次send发送内容。

在上述案例中我输入第二个指令dir 结果接收的还是第一次的内容

解决黏包方法之一

  思路是在我们每次发送的信息中封装一个头部信息,告诉对方这次发送数据的大小这样客户端就知道要接收多少数据量了。

服务端

 # -*- coding: utf- -*-

 # 声明字符编码
# coding:utf- import socket
import subprocess
import struct BUFFER_SIZE = def server_Tcp():
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, )
server.bind(('', ))
server.listen() while True:
client_socket, client_info = server.accept()
print("%s 已连接" % str(client_info))
try: while True:
client_msg = client_socket.recv(BUFFER_SIZE)
if not client_msg: break
print("%s 收到指令<<%s" % (client_info, client_msg.decode('utf-8')))
# 处理执行的命令
res = subprocess.Popen(client_msg.decode('utf-8'), shell=True, stderr=subprocess.PIPE,
stdout=subprocess.PIPE, stdin=subprocess.PIPE)
err = res.stderr.read()
if err:
cmd_err = err
else:
cmd_err = res.stdout.read() # # 第一种方式:解决粘包问题
# msg_len = len(cmd_err)
# print("数据长度为:", msg_len)
# client_socket.send(str(msg_len).encode('utf-8'))
# # 马上等待回复
# is_ok = client_socket.recv(BUFFER_SIZE)
# if is_ok == b"OK":
# client_socket.send(cmd_err) # 第二种方式:解决粘包问题
msg_len = len(cmd_err)
msg_len = struct.pack('i', msg_len)
# 下面两次发送,在客户端会当成一次接收
client_socket.send(msg_len)
client_socket.send(cmd_err) except Exception as e:
print("%s 已断开" % str(client_info))
print(e)
continue
client_socket.close() server.close() if __name__ == "__main__":
server_Tcp()

客户端

 # -*- coding: utf- -*-

 # 声明字符编码
# coding:utf- import socket
import struct BUFFER_SIZE = def client_Tcp():
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(('127.0.0.1', )) while True:
msg = input('>>').strip()
if not msg: continue
if msg == 'quit': break client.send(msg.encode('utf-8')) # # 第一种方式:解决粘包问题
# content_length = client.recv(BUFFER_SIZE)
# # 规定好第一次发来的内容是内容大小,给服务器回复个OK
# client.send(b"OK")
# content_length = int(content_length.decode('utf-8'))
# 第二种方式:解决粘包问题
# 先接收四个字节
length_data = client.recv()
content_length = struct.unpack('i', length_data)[] print("准备接收%d大小的数据")
recv_size =
recv_msg = b''
# 循环获取数据
while recv_size < content_length:
recv_msg += client.recv(BUFFER_SIZE)
recv_size = len(recv_msg) print("<<%s" % (recv_msg.decode('gbk'))) client.close() if __name__ == "__main__":
client_Tcp()

当我们再次运行后输入ipconfig命令,这时候客户端收到的就是完整的内容了。截图略....

总结:

  TCP是面向连接的,传输是基于数据流的,收发两端进行连接,数据传输,断开连接都是要经过来回的确认。TCP协议数据传输是不会丢失的,一次没有接收完,下次依然会接收,直到一端接收到ack时才知道数据接收完成。

  UDP是无连接的。面向消息的,不管发送的是什么内容,内部都会为当前消息组装一个报文头。

  UDP不会产生粘包问题,因为UDP只收一次,每次都会有个报文头+内容

 socketserver ------多线程服务

  用法很简单,在服务端代码中,创建一个类,让这个类继承BaseRequestHandler 然后在类中实现父类方法handle ,客户端代码不需做改变。详见代码

服务端

 # -*- coding: utf- -*-

 # 声明字符编码
# coding:utf- import socketserver BUFFER_SIZE =
ADDRESS_IP = ('172.16.6.5', ) class Communication(socketserver.BaseRequestHandler): def handle(self):
print(self.request) # ------->
print(self.client_address)
while True:
try:
client_msg = self.request.recv(BUFFER_SIZE)
if not client_msg:
break print("客户端【%s】>>%s" % (self.client_address, client_msg))
self.request.sendall(client_msg.upper())
except Exception as e:
print(e)
break if __name__ == "__main__":
server = socketserver.ThreadingTCPServer(ADDRESS_IP, Communication)
server.serve_forever()

客户端代码

 # -*- coding: utf- -*-

 # 声明字符编码
# coding:utf- import socket BUFFER_SIZE =
ADDRESS_IP = ('172.16.6.5', ) def client_Tcp():
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(ADDRESS_IP) while True:
msg = input('>>:')
client.sendall(msg.encode('utf-8')) server_msg = client.recv(BUFFER_SIZE)
print("<<%s" % (server_msg.decode('utf-8'))) client.close() if __name__ == "__main__":
client_Tcp()

这样就可以实现服务端响应多个客户端了。

验证合法性 

  案例:写一个服务端,客户端。服务端验证客户端是否是合法的,验证通过的客户端才能正常进行通讯。

  思路:和解决粘包思路一样,在头部再次封装一个验证的信息,当客户端连接服务成功后,服务端发送一个验证信息,客户端接收后通过key值加密后发回给服务端,服务端接收客户发来的密文和自己的进行比较,相同则是合法的。

服务端

 # -*- coding: utf- -*-

 # 声明字符编码
# coding:utf- import socketserver
import struct
import os
import hmac BUFFER_SIZE =
BIND_IP_PORT = ('172.16.6.5', )
AUTO_KEY = b"bc892ed2-8b2c-11e8-a328-f40669b72fef" class Communication(socketserver.BaseRequestHandler):
""" 通讯类型 """ def handle(self):
# print(self.client_address)
# print(self.request)
cl, pl = self.client_address
print("[%s:%s]客户端已连接" % (cl, pl))
# 合法验证
if conn_auth(self.request) == True:
# 循环接收客户端信息
while True:
try:
# 等待客户端消息
# 数据粘包处理
# 数据的前四位存储的是数据的大小
# 先接收四个字节
length_data = self.request.recv()
if not length_data: break content_length = struct.unpack('i', length_data)[] print("接收客户端[%s][%d]大小的数据" % (self.client_address, content_length))
recv_size =
recv_msg = b''
# 循环获取数据
while recv_size < content_length:
recv_msg += self.request.recv(BUFFER_SIZE)
recv_size = len(recv_msg)
print("[%s]>>%s" % (self.client_address, recv_msg.decode('utf-8')))
# 服务器回复客户端
server_msg = input("<<") server_send_length = struct.pack('i', len(server_msg)) self.request.sendall(server_send_length)
self.request.sendall(server_msg.encode("utf-8"))
except Exception as e:
print("[%s]客户端异常关闭" % self.client_address)
print(e)
else:
print("客户端【%s:%s】非法接入" % (cl, pl)) def conn_auth(conn):
# 合法性验证
msg = os.urandom()
conn.sendall(msg)
h = hmac.new(AUTO_KEY, msg)
digest = h.digest()
respone = conn.recv(len(digest))
return hmac.compare_digest(respone, digest) if __name__ == "__main__":
server = socketserver.ThreadingTCPServer(BIND_IP_PORT, Communication)
server.serve_forever()

客户端

 # -*- coding: utf- -*-

 # 声明字符编码
# coding:utf- import socket
import struct
import os, hmac BUFFER_SIZE =
BIND_IP_PORT = ('172.16.6.5', )
AUTO_KEY = b"bc892ed2-8b2c-11e8-a328-f40669b72fef" def client_Tcp():
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(BIND_IP_PORT) # 合法性验证
conn_auth(client)
while True: msg = input('>>').strip()
if not msg: continue
if msg == 'quit': break server_send_length = struct.pack('i', len(msg))
client.send(server_send_length)
client.send(msg.encode('utf-8'))
# # 第一种方式:解决粘包问题
# content_length = client.recv(BUFFER_SIZE)
# # 规定好第一次发来的内容是内容大小,给服务器回复个OK
# client.send(b"OK")
# content_length = int(content_length.decode('utf-8'))
# 第二种方式:解决粘包问题
# 先接收四个字节
length_data = client.recv()
content_length = struct.unpack('i', length_data)[] print("准备接收服务端[%d]大小的数据" % content_length)
recv_size =
recv_msg = b''
# 循环获取数据
while recv_size < content_length:
recv_msg += client.recv(BUFFER_SIZE)
recv_size = len(recv_msg) print("<< %s" % (recv_msg.decode('utf-8'))) client.close() def conn_auth(conn):
msg = conn.recv()
h = hmac.new(AUTO_KEY, msg)
digest = h.digest()
conn.sendall(digest) if __name__ == "__main__":
client_Tcp()

程序跑起来..................

客户端发送一个内容

服务端接收

当我把客户端的key值改一下试试..................

客户端连接服务端后,服务端进行验证不通过。

Python学习笔记【第十四篇】:Python网络编程二黏包问题、socketserver、验证合法性的更多相关文章

  1. Python 学习笔记(十四)Python类(三)

    完善类的内容 示例: #! /usr/bin/env python # coding =utf-8 #通常类名首字母大写 class Person(object): """ ...

  2. Python 学习笔记(十四)Python类(二)

    创建简单的类 新式类和经典类(旧式类) Python 2.x中默认都是经典类,只有显式继承了object才是新式类 Python 3.x中默认都是新式类,经典类被移除,不必显式的继承object 新式 ...

  3. Python 学习笔记(十四)Python类(一)

    基本概念 问题空间:问题空间是问题解决者对一个问题所达到的全部认识状态,它是由问题解决者利用问题所包含的信息和已贮存的信息主动的地构成的. 初始状态:一开始时的不完全的信息或令人不满意的状况: 目标状 ...

  4. Python学习笔记(十四)

    Python学习笔记(十四): Json and Pickle模块 shelve模块 1. Json and Pickle模块 之前我们学习过用eval内置方法可以将一个字符串转成python对象,不 ...

  5. Python学习笔记(十四):模块高级

    以Mark Lutz著的<Python学习手册>为教程,每天花1个小时左右时间学习,争取两周完成. --- 写在前面的话 2013-7-23 21:30 学习笔记 1,包导入是把计算机上的 ...

  6. Python基础笔记系列十四:python无缝调用c程序

    本系列教程供个人学习笔记使用,如果您要浏览可能需要其它编程语言基础(如C语言),why?因为我写得烂啊,只有我自己看得懂!! python语言可以对c程序代码进行调用,以弥补python语言低性能的缺 ...

  7. Python学习笔记【第四篇】:基本数据类型

    变量:处理数据的状态 变量名 = 状态值 类型 python中有以下基本数据类型: 1:整形 2:字符串类型 3:Bool类型 4:列表 5:元祖(不可变) 6:字典(无序) 7:集合 (无序.不重复 ...

  8. python学习笔记-(十四)I/O多路复用 阻塞、非阻塞、同步、异步

    1. 概念说明 1.1 用户空间与内核空间 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方).操作系统的核心是内核,独立于普通的应用程序,可 ...

  9. python学习笔记-(十四)进程&协程

    一. 进程 1. 多进程multiprocessing multiprocessing包是Python中的多进程管理包,是一个跨平台版本的多进程模块.与threading.Thread类似,它可以利用 ...

随机推荐

  1. UNIX 系统下退出 git commit 编辑器

    如果是 Emacs 编辑器,输入 Ctrl X + Ctrl S(保存),再输入Ctrl X + Ctrl C(退出) 如果是VIM编辑器,输入 ESC + :wq UNIX 系统默认打开的是 Ema ...

  2. PCB设计基本流程

    [PCB设计基本流程]1.准备原理图和网络表2.电路板规划3.参数设置4.导入网标5.布局6.布线7.规则检查与手工调整8.输出文件 [具体步骤]1.在原理图环境下:Tool——>Footpri ...

  3. windows service 2008 R2 升级 sp1遇到的问题

    因为我的程序是以vs2015开发的,所以在在布署windows service 2008 R2 项目的时候报出 红框里的错,说明要安装.net framework4.6. 感觉so easy,下载一个 ...

  4. 一键脚本清理DEBIAN系统无用组件 减少系统资源

    虽然如今我们选择服务器资源都比较多,以前我们看到很多128MB内存.甚至32MB内存的建站网站,感觉特别羡慕.其实这些也不是难事,相比之下,DEBIAN系统比CENTOS系统占用资源少,然后我们需要进 ...

  5. 旅游类的APP原型模板分享——Priceline

    Priceline是一款旅游服务的APP应用.功能有查找预订酒店.车票.机票等服务. 本原型由国产Mockplus(原型工具)和iDoc(智能标注,一键切图工具)提供. 先简单看看动图: 点击这里,可 ...

  6. 老赵点滴地址:http://blog.zhaojie.me/2009/05/a-simple-actor-model-implementation.html

    老赵点滴地址:http://blog.zhaojie.me/2009/05/a-simple-actor-model-implementation.html

  7. c语言基础课第一次作业

    1)大学和高中最大的不同是没有人天天看着你,请看大学理想的师生关系是?有何感想? 通过阅读邹欣老师的博客,了解到了老师心中理想的师生关系是(健身教练与健身学员).在初中,高中我们一直都是填鸭式教育,像 ...

  8. C# Winform Soket 网络编程 多个客户端连接服务器并返回客户端操作请求

    2017.8.2 服务器: #region 参数与集合 /// <summary> /// 客户端IP /// </summary> string clientIP; /// ...

  9. mongoDb造数据

    package mongoUtil; import com.mongodb.MongoClient; import com.mongodb.client.MongoCollection; import ...

  10. 磨人的Fragment的转换

    磨人的Fragment的转换 本次任务是 程序运行之后将第一个Fragment加载出来 然后点击"SHOW NEXT PAGE"切换到第二个Fragment 当再次点击按钮时下方出 ...