Python Socket通信黏包问题分析及解决方法

参考: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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/252944.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2440内存管理

title: 2440内存管理 tags: ARM date: 2018-10-17 19:08:49 --- 2440内存管理 特性 大/小端&#xff08;通过软件选择&#xff09;地址空间&#xff1a;每个 Bank 有 128M 字节(总共 1G/8 个 Bank)除了 BANK0&#xff08;16/32 位&#xff09;之外【引导ROM&#xff0c;其总线宽…

C#设计模式之十二代理模式(Proxy Pattern)【结构型】

一、引言 今天我们要讲【结构型】设计模式的第七个模式&#xff0c;也是“结构型”设计模式中的最后一个模式&#xff0c;该模式是【代理模式】&#xff0c;英文名称是&#xff1a;Proxy Pattern。还是老套路&#xff0c;先从名字上来看看。“代理”可以理解为“代替”&#…

CentOS7.5 使用二进制程序部署Kubernetes1.12.2(三)

一、安装方式介绍 1、yum 安装 目前CentOS官方已经把Kubernetes源放入到自己的默认 extras 仓库里面&#xff0c;使用 yum 安装&#xff0c;好处是简单&#xff0c;坏处也很明显&#xff0c;需要官方更新 yum 源才能获得最新版本的软件&#xff0c;而所有软件的依赖又不能自己指…

马来西亚热情拥抱阿里巴巴 马云倡议的eWTP首次落地海外

摘要&#xff1a;3月22日&#xff0c;马来西亚总理纳吉布与阿里巴巴集团董事局主席马云一同出现在吉隆坡一场盛大启动仪式上&#xff0c;他们将共同见证马云的eWTP理念落地马来西亚。 3月22日&#xff0c;在邀请阿里巴巴集团董事局主席马云、阿里巴巴集团CEO张勇、蚂蚁金服集团…

随便玩玩之PostgreSQL(第一章)PostgreSQL简介

随便玩玩之PostgreSQL 未经授权不得转载 第1章PostgreSQL简介 1.1什么是PostgreSQLPostgresql是数据库&#xff08;软件&#xff09;。The worlds most advanced open source database.世界上最先进的开源数据库。 1.2PostgreSQL的优势随便用、不要钱 比MySQL好&#xff0c;媲美…

生产环境中Oracle常用函数总结

1>to_char,将日期转换为字符&#xff1b;add_months,在第一个参数的日期上加或者减第二个参数的值&#xff1b;select dkzh,jkhtbh,yhkrq,dkffrq,shqs,dqyqcs,to_char(add_months(dkffrq,shqsdqyqcs1),yyyymm) from grdk_dk_zz a where a.dkzt in(02,03) and jgbm like 01||…

Dockerfile构建容器镜像 - 运维笔记

在Docker的运用中&#xff0c;从下载镜像&#xff0c;启动容器&#xff0c;在容器中输入命令来运行程序&#xff0c;这些命令都是手工一条条往里输入的&#xff0c;无法重复利用&#xff0c;而且效率很低。所以就需要一 种文件或脚本&#xff0c;我们把想执行的操作以命令的方式…

201421123042 《Java程序设计》第8周学习总结

1. 本周学习总结 以你喜欢的方式&#xff08;思维导图或其他&#xff09;归纳总结集合相关内容。 2. 书面作业 1. ArrayList代码分析 1.1 解释ArrayList的contains源代码 源代码&#xff1a; 答&#xff1a;查找对象是否再数组中&#xff0c;并且返回在数组中的下标。如果不在数…

Linux学习-11月12日(Apache安装)

2019独角兽企业重金招聘Python工程师标准>>> 11.6 MariaDB安装 11.7/11.8/11.9 Apache安装 扩展 apache dso https://yq.aliyun.com/articles/6298 apache apxs https://wizardforcel.gitbooks.io/apache-doc/content/51.html apache工作模式 https://blog.csdn.…

Linux C 读取文件夹下所有文件(包括子文件夹)的文件名

本文&#xff1a;http://www.cnblogs.com/xudong-bupt/p/3504442.html Linux C 下面读取文件夹要用到结构体struct dirent&#xff0c;在头#include <dirent.h>中&#xff0c;如下&#xff1a; #include <dirent.h> struct dirent {long d_ino; /* inode number 索…

报表工具实现单据套打

【摘要】 单据套打再也不用手动测量&#xff0c;反复调试了&#xff0c;报表工具实现单据套打&#xff0c;去乾学院看个究竟&#xff1a;报表工具实现单据套打!实际项目开发中&#xff0c;很多情况会涉及到单据的打印。即在一张印刷好的空白单据上&#xff0c;准确无误地打印上…

session机制详解以及session的相关应用

session是web开发里一个重要的概念&#xff0c;在大多数web应用里session都是被当做现成的东西&#xff0c;拿来就直接用&#xff0c;但是一些复杂的web应用里能拿来用的session已经满足不了实际的需求&#xff0c;当碰到这样的情况时候我们需要更加深入的理解session的机制&am…

(转)Shell中获取字符串长度的七种方法

Shell中获取字符串长度的七种方法 原文&#xff1a;http://blog.csdn.net/jerry_1126/article/details/51835119 求字符串操作在shell脚本中很常用&#xff0c;下面归纳、汇总了求字符串的几种可能方法: 【方法一】:利用${#str}来获取字符串的长度 【方法二】:利用awk的length方…

linux下用core和gdb查询出现段错误的地方

有些时候我们在一段C代码的时候&#xff0c;由于对一个非法内存进行了操作&#xff0c;在程序运行的过程中&#xff0c;出现了"段错误"。呵呵&#xff0c;这种问题我想很多人会经常遇到。遇到这种问题是非常无语的&#xff0c;只是提示了"段错误"&#xff…

什么是js的严格模式

设立严格模式的原因&#xff1a; - 消除Javascript语法的一些不合理、不严谨之处&#xff0c;减少一些怪异行为; - 消除代码运行的一些不安全之处&#xff0c;保证代码运行的安全&#xff1b; - 提高编译器效率&#xff0c;增加运行速度&#xff1b; - 为未来新版本的Javascrip…

代码解说Android Scroller、VelocityTracker

在编写自己定义滑动控件时经常会用到Android触摸机制和Scroller及VelocityTracker。Android Touch系统简单介绍&#xff08;二&#xff09;:实例具体解释onInterceptTouchEvent与onTouchEvent的调用过程对Android触摸机制须要用到的函数进行了具体的解释。本文主要介绍两个重要…

支付宝支付

1 申请商户平台 2 申请开放平台 3 申请APP支付 4 创建应用 (名称&#xff0c;logo) 5 生成RSA秘钥&#xff08;公钥&#xff0c;私钥&#xff09; 6 在应用中配置公钥 7 配置其他内容&#xff0c;包括iOS bundle ID。配置安卓包名&#xff0c;和签名。 获取appid&#xff0c;公…

不可错过的CMS学习笔记

引子 带着问题去学习一个东西&#xff0c;才会有目标感&#xff0c;我先把一直以来自己对CMS的一些疑惑罗列了下&#xff0c;希望这篇学习笔记能解决掉这些疑惑&#xff0c;希望也能对你有所帮助。 CMS出现的初衷、背景和目的&#xff1f; CMS的适用场景&#xff1f; CMS的tr…

相机工作原理

轻轻一按&#xff0c;你的相机就把光子转换为了比特。于是一张相片就保存到了你的 iPhone 里。 让我们假设一下你身处室外&#xff0c;环顾四周。三亿里之外&#xff0c;太阳无时无刻不在发射光子。它们需要花上 8 分钟之久才能到达我们舒适的星球。有一些光子撞击到你周围的物…

CentOS用户和用户组的操作

2019独角兽企业重金招聘Python工程师标准>>> CentOS用户和用户组的操作 長得太帥忚四種檌 关注 2018.05.12 16:40* 字数 312 阅读 115评论 0喜欢 0 用户组的操作 1.添加用户组&#xff1a; groupadd 组名2.修改组名 groupmod -n 新组名 原组名删除用户组groupdel 组…