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

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

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

基于TCP协议实现的黏包

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# tcp_server_cmd.py import socket
import subprocess ip_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': break res = 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.py import socket ip_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.py import socket
import subprocess ip_port = ('127.0.0.1', 8080)
BUFFERSIZE = 2048 udp_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':break res = 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.py import socket ip_port = ('127.0.0.1', 8080)
BUFFERSIZE = 2048 udp_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: continue
elif cmd == 'quit':
udp_client_socket.sendto(cmd.encode('utf-8'), ip_port)
break udp_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 subprocess ip_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 = True while 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 #防止死循环,在多个客户端连接时,可以去掉
break res = subprocess.Popen(cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE) #执行客户端输入命令
#以下标准输出信息都只能读取一次
std_err = res.stderr.read() #获取输出到标准输出设备的错误信息
if std_err: #判断返回信息的类型
ret = std_err
else:
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.py import socket ip_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 = 0
while 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 json ip_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 = True while 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 #防止死循环,在多个客户端连接时,可以去掉
break res = subprocess.Popen(cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE) #执行客户端输入命令
#以下标准输出信息都只能读取一次
std_err = res.stderr.read() #获取输出到标准输出设备的错误信息
if std_err: #判断返回信息的类型
back_info = std_err
else:
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.py import socket
import struct
import json ip_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 = 0
while 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模块方法

Python Socket通信黏包问题分析及解决方法的更多相关文章

  1. TCP通信粘包问题分析和解决(全)(转)

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

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

    一.TCP协议 粘包现象 和解决方案 黏包现象让我们基于tcp先制作一个远程执行命令的程序(命令ls -l ; lllllll ; pwd)执行远程命令的模块 需要用到模块subprocess sub ...

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

    转载至https://www.cnblogs.com/kex1n/p/6502002.html 在socket网络程序中,TCP和UDP分别是面向连接和非面向连接的.因此TCP的socket编程,收发 ...

  4. python 文字转语音包pyttsx安装出错解决方法

    pyttsx的python的文字转语音的包,但是pyttsx的官方网站上资源只更新2012年,所以在py3中使用pip install pyttsx或者下载安装包进行安装时,虽然可以安装成功,但是im ...

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

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

  6. 【转载】TCP粘包问题分析和解决(全)

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

  7. python - socket通信笔记

    参考: 通过编写聊天程序来熟悉python中多线程和socket的用法:https://www.cnblogs.com/mingjiatang/p/4905395.html python socket ...

  8. Python Socket通信原理

    [Python之旅]第五篇(一):Python Socket通信原理   python Socket 通信理论 socket例子 摘要:  只要和网络服务涉及的,就离不开Socket以及Socket编 ...

  9. python socket发送魔法包网络唤醒开机.py

    python socket发送魔法包网络唤醒开机.py 现在的电脑应该都普遍支持有线网络的WOL了,支持无线网络唤醒的电脑,可能比较少. """ python socke ...

随机推荐

  1. 7.11js常用对象

    <!DOCTYPE html> <html> <head> <title>js常用对象</title> <script type=&q ...

  2. 第39级台阶|2013年蓝桥杯B组题解析第三题-fishers

    第39级台阶 小明刚刚看完电影<第39级台阶>,离开电影院的时候,他数了数礼堂前的台阶数,恰好是39级! 站在台阶前,他突然又想着一个问题: 如果我每一步只能迈上1个或2个台阶.先迈左脚, ...

  3. 编译openssl失败(SLES11.3), undefined reference to `OPENSSL_cpuid_setup'

    https://stackoverflow.com/questions/11381514/undefined-reference-when-compiling-openssl I ran into t ...

  4. 洛谷P1040 加分二叉树【记忆化搜索】

    题目链接:https://www.luogu.org/problemnew/show/P1040 题意: 某一个二叉树的中序遍历是1~n,每个节点有一个分数(正整数). 二叉树的分数是左子树分数乘右子 ...

  5. 蚂蚁金服研发的金融级分布式中间件SOFA背后的故事

    导读:GIAC大会期间,蚂蚁金服杨冰,黄挺等讲师面向华南技术社区做了<数字金融时代的云原生架构转型路径>和<从传统服务化走向Service Mesh>等演讲,就此机会,高可用架 ...

  6. 蓝桥杯 入门训练 Fibonacci数列 解析

    问题描述 Fibonacci数列的递推公式为:Fn=Fn-1+Fn-2,其中F1=F2=1. 当n比较大时,Fn也非常大,现在我们想知道,Fn除以10007的余数是多少. 输入格式 输入包含一个整数n ...

  7. c语言笔记 数组2

    15. c99以前一直使用 gets 和 puts来输入输出字符串,但是gets因为无法获知内存大小,容易出现内存溢出(对此c99对gets,采取保留态势,c11直接废除,但是某些编译器仍然默认可以使 ...

  8. HDU 2072 - 单词数 - [(有点小坑的)字典树模板题]

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2072 Problem Descriptionlily的好朋友xiaoou333最近很空,他想了一件没有 ...

  9. FutureTask的用法及两种常用的使用场景

    FutureTask可用于异步获取执行结果或取消执行任务的场景.通过传入Runnable或者Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后可以在外部通过Fu ...

  10. ES6常用对象操作

    ES6常用对象操作 一. const 简单类型数据常量 // const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动.对于简单类型的数据(数值.字符串.布尔值),值就保存在 ...