参考:http://www.cnblogs.com/Eva-J/articles/8244551.html#_label5

1.黏包的表现(以客户端远程操作服务端命令为例)

注:只有在TCP协议通信的情况下,才会产生黏包问题

基于TCP协议实现的黏包

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# tcp_server_cmd.pyimport socket
import subprocessip_port    = ('127.0.0.1', 8080) #服务端地址及端口
BUFFERSIZE = 1024 #设置缓冲区大小

tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #设置为通过TCP协议通信(默认)
tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR, 1)#用于socket关闭后,重用socket
tcp_server_socket.bind(ip_port) #绑定ip和端口
tcp_server_socket.listen() #开始监听客户端连接while True:conn, addr = tcp_server_socket.accept() #与客户端建立连接print('客户端地址:', addr)while True:cmd = conn.recv(BUFFERSIZE).decode('utf-8') #接收客户端输入print('cmd:', cmd)if len(cmd)<1 or cmd == 'quit': breakres = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,stderr=subprocess.PIPE) #执行客户端输入命令#以下标准输出信息都只能读取一次std_out = res.stdout.read() #获取输出到标准输出设备的成功信息std_err = res.stderr.read() #获取输出到标准输出设备的错误信息print("stdout:",std_out.decode('gbk'))print("stderr:",std_err.decode('gbk'))conn.send(std_out)conn.send(std_err)conn.close() #关闭连接

tcp_server_socket.close() #关闭socket

tcp-server-package

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#tcp_client_cmd.pyimport socketip_port = ('127.0.0.1', 8080)  #服务端地址及端口
BUFFERSIZE = 1024   #设置缓冲区大小
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #获取socket对象
tcp_client_socket.connect(ip_port) #与服务端建立连接while True:cmd = input("Please input cmd<<< ").strip() #输入命令if len(cmd) < 1: continue     #跳过本次循环,开始下一次循环elif cmd == 'quit': tcp_client_socket.send(cmd.encode('utf-8')) #发送中断请求给服务端break     #中断循环
tcp_client_socket.send(cmd.encode('utf-8'))ret = tcp_client_socket.recv(BUFFERSIZE)print(ret.decode('gbk'))tcp_client_socket.close()

tcp-client-package

基于UDP协议实现(无黏包现象)

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# udp_server_cmd.pyimport socket
import subprocessip_port    = ('127.0.0.1', 8080)
BUFFERSIZE = 2048udp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #设置为通过UDP协议通信
udp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
udp_server_socket.bind(ip_port)while True:cmd, addr = udp_server_socket.recvfrom(BUFFERSIZE)print('client ip:',addr)cmd = cmd.decode('utf-8')print('cmd:',cmd)if len(cmd)<1 or cmd == 'quit':breakres = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,stderr=subprocess.PIPE)std_out = res.stdout.read()std_err = res.stderr.read()print('stdout:', std_out.decode('gbk'))print('stderr:', std_err.decode('gbk'))udp_server_socket.sendto(std_out, addr)udp_server_socket.sendto(std_err, addr)udp_server_socket.close()

udp-server-package

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# udp_client_cmd.pyimport socketip_port    = ('127.0.0.1', 8080)
BUFFERSIZE = 2048udp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_client_socket.connect(ip_port)while True:cmd = input("Please input cmd<<< ").strip()if len(cmd)<1: continueelif cmd == 'quit': udp_client_socket.sendto(cmd.encode('utf-8'), ip_port)breakudp_client_socket.sendto(cmd.encode('utf-8'), ip_port)ret, addr = udp_client_socket.recvfrom(BUFFERSIZE)print(ret.decode('gbk'))udp_client_socket.close()

udp-client-cmd

2.黏包的成因(基于TCP协议传输)

  • tcp协议的拆包机制
  • tcp面向流的通信是无消息保护边界的
  • tcp的Nagle优化算法:若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据
  • 接收方和发送方的缓存机制

3.导致黏包的根本因素

  • 接收方不知道消息之间的界限,不知道一次性提取多少字节的数据

4.黏包的解决方法

由于导致黏包的根本原因是接收端不知道发送端将要传送的字节流的长度,故有如下两种解决方案

方案一:在发送消息前,将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# tcp_server_cmd.py"""
实现客户端远程操作服务端命令
"""
import socket
import subprocessip_port    = ('127.0.0.1', 8080) #服务端地址及端口
BUFFERSIZE = 1024 #设置缓冲区大小

tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #设置为通过TCP协议通信(默认)
tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR, 1)#用于socket关闭后,重用socket
tcp_server_socket.bind(ip_port) #绑定ip和端口
tcp_server_socket.listen() #开始监听客户端连接

flag = Truewhile flag:conn, addr = tcp_server_socket.accept() #与客户端建立连接print('client ip addr:', addr)while True:cmd = conn.recv(BUFFERSIZE).decode('utf-8') #接收客户端输入if len(cmd)<1 or cmd == 'quit': flag = False #防止死循环,在多个客户端连接时,可以去掉breakres = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,stderr=subprocess.PIPE) #执行客户端输入命令#以下标准输出信息都只能读取一次std_err = res.stderr.read() #获取输出到标准输出设备的错误信息if std_err:  #判断返回信息的类型ret = std_errelse:ret = res.stdout.read() #获取输出到标准输出设备的成功信息"""以下是方案一的核心部分"""conn.send(str(len(ret)).encode('utf-8')) #发送要发送信息的长度print("ret:",ret.decode('gbk'))data = conn.recv(BUFFERSIZE).decode('utf-8') #接收客户端准备确认信息if data == 'recv_ready': conn.sendall(ret) #发送所有信息
conn.close() #关闭连接

tcp_server_socket.close() #关闭socket

tcp_server_package

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#client_tcp_cmd.pyimport socketip_port = ('127.0.0.1', 8080)  #服务端地址及端口
BUFFERSIZE = 1024   #设置缓冲区大小
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #获取socket对象
tcp_client_socket.connect(ip_port) #与服务端建立连接while True:cmd = input("Please input cmd<<< ").strip() #输入命令if len(cmd) < 1: continue     #跳过本次循环,开始下一次循环elif cmd == 'quit': tcp_client_socket.send(cmd.encode('utf-8')) #发送中断请求给服务端break     #中断循环
tcp_client_socket.send(cmd.encode('utf-8')) #发送要执行的命令"""以下是方案一的核心部分"""info_len = tcp_client_socket.recv(BUFFERSIZE).decode('utf-8') #接收要接收的信息长度
tcp_client_socket.send(b'recv_ready') #给服务端发送已经准备好接收信息
data     = b''ret_size = 0while ret_size < int(info_len): #判断信息是否已接收完data += tcp_client_socket.recv(BUFFERSIZE) #接收指定大小的信息ret_size += len(data)  #将已经接收的信息长度累加print(data.decode('gbk'))tcp_client_socket.close()      #关闭socket

tcp_client_package

存在的问题:
程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这种方式会放大网络延迟带来的性能损耗

方案二:针对方案一的问题,引入struct模块,struct模块可以将发送的数据长度转换成固定长度的字节

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# tcp_server_cmd.py"""
实现客户端远程操作服务端命令
"""
import socket
import subprocess
import struct
import jsonip_port    = ('127.0.0.1', 8080) #服务端地址及端口
BUFFERSIZE = 1024 #设置缓冲区大小

tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #设置为通过TCP协议通信(默认)
tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR, 1)#用于socket关闭后,重用socket
tcp_server_socket.bind(ip_port) #绑定ip和端口
tcp_server_socket.listen() #开始监听客户端连接

flag = Truewhile flag:conn, addr = tcp_server_socket.accept() #与客户端建立连接print('client ip addr:', addr)while True:cmd = conn.recv(BUFFERSIZE).decode('utf-8') #接收客户端输入if len(cmd)<1 or cmd == 'quit': flag = False #防止死循环,在多个客户端连接时,可以去掉breakres = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,stderr=subprocess.PIPE) #执行客户端输入命令#以下标准输出信息都只能读取一次std_err = res.stderr.read() #获取输出到标准输出设备的错误信息if std_err:  #判断返回信息的类型back_info = std_errelse:back_info = res.stdout.read() #获取输出到标准输出设备的成功信息"""以下是方案二的核心部分(定制化报头)"""head        = {'data_size':len(back_info)}head_json   = json.dumps(head) #将python对象转化为json字符串head_bytes  = bytes(head_json, encoding='utf-8') #将json字符串转化为bytes字节码对象head_struct_len = struct.pack('i', len(head_bytes)) #使用struct将定制化的报头打包为4个字节的长度conn.send(head_struct_len)  #发送定制报头的长度,4个字节conn.send(head_bytes) #发送定制报头信息print("back_info:",back_info.decode('gbk'))conn.sendall(back_info) #发送所有的真实信息
conn.close() #关闭连接

tcp_server_socket.close() #关闭socket

tcp_server_package

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#client_tcp_cmd.pyimport socket
import struct
import jsonip_port = ('127.0.0.1', 8080)  #服务端地址及端口
BUFFERSIZE = 1024   #设置缓冲区大小
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #获取socket对象
tcp_client_socket.connect(ip_port) #与服务端建立连接while True:cmd = input("Please input cmd<<< ").strip() #输入命令if len(cmd) < 1: continue     #跳过本次循环,开始下一次循环elif cmd == 'quit': tcp_client_socket.send(cmd.encode('utf-8')) #发送中断请求给服务端break     #中断循环
tcp_client_socket.send(cmd.encode('utf-8')) #发送要执行的命令"""以下是方案二的核心部分(定制化报头)"""head_struct = tcp_client_socket.recv(4) #接收4字节的定制报头head_json_len = struct.unpack('i', head_struct)[0] #struct解包定制报头后是一个tuple,如(1024,)head_json = tcp_client_socket.recv(head_json_len).decode('utf-8') #将接收的bytes字节码报头解码为json字符串head = json.loads(head_json) #将json字符串转化为python对象print('head:',head)data     = b''ret_size = 0while ret_size < head['data_size']: #判断信息是否已接收完data += tcp_client_socket.recv(BUFFERSIZE) #接收指定缓冲大小的信息ret_size += len(data)  #将已经接收的信息长度累加print(data.decode('gbk'))  #windows默认编码是gbk

tcp_client_socket.close()      #关闭socket

tcp_client_package

5.TCP和UDP协议的简介

待补充。。。

6.补充

1.[WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试

原因:端口被占用导致

解决:

Windows下
C:\> netstat -ano|findstr 8080             #查找8080端口占用进程号
TCP    127.0.0.1:8080         0.0.0.0:0              LISTENING       17496
C:\> tasklist |findstr 17496               #查找17496进程号对应的程序
python.exe                   17496 Console                    1     10,664 K
C:\> taskkill /pid 17496 /F                #杀掉17496进程
成功: 已终止 PID 为 17496 的进程。Linux下
[root@localhost]# netstat -nltup | grep 80 #查找80端口上的程序
tcp        0      0 0.0.0.0:80                  0.0.0.0:*                   LISTEN      1479/nginx
[root@localhost]# ps -ef | grep nginx      #查找nginx对应进程号
root      1479     1  0 Jul23 ?        00:00:00 nginx: master process ./nginx
[root@localhost]# kill -9  1479            #杀掉1479进程

2.struct模块可打包和解包的数据类型

3.socket模块方法说明

服务端套接字函数
s.bind()    绑定(主机,端口号)到套接字
s.listen()  开始TCP监听
s.accept()  被动接受TCP客户的连接,(阻塞式)等待连接的到来客户端套接字函数
s.connect()     主动初始化TCP服务器连接
s.connect_ex()  connect()函数的扩展版本,出错时返回出错码,而不是抛出异常公共用途的套接字函数
s.recv()            接收TCP数据
s.send()            发送TCP数据
s.sendall()         发送TCP数据
s.recvfrom()        接收UDP数据
s.sendto()          发送UDP数据
s.getpeername()     连接到当前套接字的远端的地址
s.getsockname()     当前套接字的地址
s.getsockopt()      返回指定套接字的参数
s.setsockopt()      设置指定套接字的参数
s.close()           关闭套接字面向锁的套接字方法
s.setblocking()     设置套接字的阻塞与非阻塞模式
s.settimeout()      设置阻塞套接字操作的超时时间
s.gettimeout()      得到阻塞套接字操作的超时时间面向文件的套接字的函数
s.fileno()          套接字的文件描述符
s.makefile()        创建一个与该套接字相关的文件

socket模块方法

转载于:https://www.cnblogs.com/yueyun00/p/10002730.html

Python Socket通信黏包问题分析及解决方法相关推荐

  1. TCP通信粘包问题分析和解决

    在socket网络程序中,TCP和UDP分别是面向连接和非面向连接的.因此TCP的socket编程,收发两端(客户端和服务器端)都要有成对的socket,因此,发送端为了将多个发往接收端的包,更有效的 ...

  2. python tcp黏包和struct模块解决方法,大文件传输方法及MD5校验

    https://www.cnblogs.com/zaizai1573/p/10230973.html 一.TCP协议 粘包现象 和解决方案 黏包现象 让我们基于tcp先制作一个远程执行命令的程序(命令 ...

  3. TCP粘包问题分析和解决(全)

    TCP通信粘包问题分析和解决(全) 在socket网络程序中,TCP和UDP分别是面向连接和非面向连接的.因此TCP的socket编程,收发两端(客户端和服务器端)都要有成对的socket,因此,发送 ...

  4. python通信原理实验报告_【Python之旅】第五篇(一):Python Socket通信原理-阿里云开发者社区...

    只要和网络服务涉及的,就离不开Socket以及Socket编程,下面就说说Python Socket通信的基本原理. 1.Socket socket也称作"套接字",用于描述IP地 ...

  5. TCP、UDP、socket、黏包

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 TCP协议和UDP协议 概念 什么是socket? socket TCP基本语法 TCP循环发消息 UDP基本语法 UDP循 ...

  6. Python中报错:IndentationError: unexpected indent的原因分析和解决方法

    Python中报错:"IndentationError: unexpected indent"的原因分析和解决方法 "IndentationError: unexpect ...

  7. python安装numpy模块-python的numpy模块安装不成功简单解决方法总结

    为了画个图,被numpy这个模块的安装真的折腾疯了!!!一直装不上,花了几个小时,看了网上的很多教程.方法发现总结得不是很全,这里总结一下,防止大家再出现这个问题没有解决方法. Python的魅力之一 ...

  8. python怎么安装pyecharts_基于Python安装pyecharts所遇的问题及解决方法

    最近学习到数据可视化内容,老师推荐安装pyecharts,于是pip install 了一下,结果...掉坑了,下面是我的跳坑经验,如果你有类似问题,希望对你有所帮助. 第一个坑: 这个不难理解,缺少 ...

  9. python安装pyecharts清华_基于Python安装pyecharts所遇的问题及解决方法

    最近学习到数据可视化内容,老师推荐安装pyecharts,于是pip install 了一下,结果...掉坑了,下面是我的跳坑经验,如果你有类似问题,希望对你有所帮助. 第一个坑: 这个不难理解,缺少 ...

最新文章

  1. c语言求阶乘和的流程图_C/C++编程笔记:C语言 rand() 随机函数,深入解析程序随机数!...
  2. 苏勇老师写的CCIE详解
  3. PHP内核探索之变量(2)-理解引用
  4. 入职阿里5年,他如何破解“技术债”?
  5. 修复cocos2dx的Label,WP8下不能换行的问题
  6. [Javascript_库编写]创建自己的“JavaScript库”
  7. 暴雪帝国辉煌能否延续
  8. Android画布和图形绘制---Canvas and Drawables(一)
  9. ubuntu linux 使用常见问题
  10. Android进阶--android自动化测试python+uiautomator
  11. Visual C++——定时器(计时器)SetTimer函数
  12. xshell5产品秘钥
  13. 2008最火爆的十大网络流行语
  14. 【大学物理】第一章:质点运动学
  15. 巨蟒django之CRM1 需求分析表结构设计注册登录验证
  16. 代码源每日一题-宝箱(贪心/思维)
  17. aliases节点分析
  18. 视频信号指标与测试方法
  19. SPI接口的MISO和MOSI连接时注意
  20. Linux 内存映射之文件映射

热门文章

  1. qt做的接收串口数据并显示曲线_QT无人机地面站设计与制作
  2. 为此计算机所有用户安装加载项,安装Office 2013后,无法在计算机上安装Outlook加载项...
  3. iOS核心动画之CALayer-layer的创建
  4. caffe 提取特征并可视化(已测试可执行)及在线可视化
  5. 亚伦•斯沃茨:提升时间的品质
  6. (数据科学学习手札30)朴素贝叶斯分类器的原理详解Python与R实现
  7. LinCode落单的数
  8. iOSPush自动隐藏tabbar
  9. 架构师是大忽悠吗?阿里技术大牛告诉你真相!
  10. httpClient实现微信公众号消息群发