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.pswp.cn/news/252944.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/252944.shtml
英文地址,請注明出處:http://en.pswp.cn/news/252944.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Django 路由

定義&#xff1a; URL配置(URLconf)就像Django 所支撐網站的目錄。它的本質是URL與要為該URL調用的視圖函數之間的映射表&#xff1b;你就是以這種方式告訴Django&#xff0c;對于這個URL調用這段代碼&#xff0c;對于那個URL調用那段代碼。 URL配置格式&#xff1a; urlpatter…

Ubuntu默認不進入圖形界面

修改 /etc/X11/default-display-manager如果值為/usr/sbin/gdm&#xff0c;(ubuntu12.04 為/usr/sbin/lightdm)則進入圖形界面 如果值為false&#xff0c;則進入控制臺&#xff08;命令行方式&#xff09;。如果想從控制臺進入圖形界面&#xff0c;可以在控制臺上輸入命令 sudo…

讀《構建之法》的心得體會

前段時間&#xff0c;我看了《構建之法》的一些內容&#xff0c;有了一些心得體會。 軟件工程所討論的是代碼量巨大、涉及人數眾多、項目需求多變時所要解決的問題。而在校學生根本就沒有這樣的環境。而鄒欣老師的《構建之法》是我讀過的書中最淺顯易懂的軟件工程書。 在緒論中…

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;先從名字上來看看。“代理”可以理解為“代替”&#…

IPv6檢測

1&#xff09;判斷服務器是否支持IPv6 &#xff1a; http://ipv6-test.com/validate.php 2&#xff09;檢測當前設備打開網站的連接方式是IPv4還是IPv6&#xff1a; http://ipv6.sjtu.edu.cn/ 轉載于:https://www.cnblogs.com/superbobo/p/6687605.html

百度首席科學家吳恩達宣布將從百度離職

海外網3月22日電 據媒體消息&#xff0c;百度首席科學家吳恩達&#xff08;Andrew Ng&#xff09;在英文自媒體平臺Medium及微博、Twitter等個人社交平臺發布公開信&#xff0c;宣布自己將從百度離職&#xff0c;開啟自己在人工智能領域的新篇章。 吳恩達是人工智能和機器學習…

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

一、安裝方式介紹 1、yum 安裝 目前CentOS官方已經把Kubernetes源放入到自己的默認 extras 倉庫里面&#xff0c;使用 yum 安裝&#xff0c;好處是簡單&#xff0c;壞處也很明顯&#xff0c;需要官方更新 yum 源才能獲得最新版本的軟件&#xff0c;而所有軟件的依賴又不能自己指…

Oracle存儲過程--案例

限額控制 CREATE OR REPLACE PACKAGE BODY NP_PCKG_MERCHANT_LIMIT ASPROCEDURE CHECK_LIMIT (in_iplCode IN VARCHAR2, --行業編號in_iplState IN VARCHAR2, --卡類型in_posNo IN VARCHAR2, --商戶號in_tranAmt IN …

SpringMVC—對Ajax的處理(含 JSON 類型)(2)

這里編寫了一個通用的類型轉換器&#xff1a;用來轉換形如&#xff1a; firstNamejack&lastNamelily&gender1&foodsSteak&foodsPizza&quoteEnteryourfavoritequote!&educationJr.High&tOfDDay 到 Student 對象。/*** author solverpeng* create 20…

馬來西亞熱情擁抱阿里巴巴 馬云倡議的eWTP首次落地海外

摘要&#xff1a;3月22日&#xff0c;馬來西亞總理納吉布與阿里巴巴集團董事局主席馬云一同出現在吉隆坡一場盛大啟動儀式上&#xff0c;他們將共同見證馬云的eWTP理念落地馬來西亞。 3月22日&#xff0c;在邀請阿里巴巴集團董事局主席馬云、阿里巴巴集團CEO張勇、螞蟻金服集團…

征名公布|Qtum量子鏈企業版—Unita 中文名征集圓滿落幕

Qtum量子鏈基金會為感謝社區與為了充分調動社區積極性&#xff0c;調動社區力量&#xff0c;在Qtum企業版完整公布之前將中文名留給社區成員們集思廣益&#xff0c;其中截止2018年11月26日&#xff0c;我們征集到數百份來自社區的優秀名稱&#xff0c;在經過基金會層層嚴肅認真…

隨便玩玩之PostgreSQL(第一章)PostgreSQL簡介

隨便玩玩之PostgreSQL 未經授權不得轉載 第1章PostgreSQL簡介 1.1什么是PostgreSQLPostgresql是數據庫&#xff08;軟件&#xff09;。The worlds most advanced open source database.世界上最先進的開源數據庫。 1.2PostgreSQL的優勢隨便用、不要錢 比MySQL好&#xff0c;媲美…

bootstrap 利用jquery 添加disabled屬性

添加&#xff1a; $("#id").attr("disabled","disabled"); 去除&#xff1a; $("#id").removeattr("disabled");轉載于:https://www.cnblogs.com/duyunchao-2261/p/6692141.html

生產環境中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||…

國內VR內容分發平臺探討:未來充滿變數,一切才剛開始

移動VR搞內容分發平臺的思維源自于移動互聯網時代&#xff0c;App Store成就了iPhone和蘋果;安卓端谷歌應用商店稱霸全球&#xff0c;唯獨進不了中國&#xff0c;于是國內涌現了一大批移動分發平臺&#xff0c;91無線、豌豆莢、安卓應用商店、機鋒、安智、小米商店……最后大部…

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驅動靜態編譯和動態編譯方法詳解

內核源碼樹的目錄下都有兩個文檔Kconfig和Makefile。分布到各目錄的Kconfig構成了一個分布式的內核配置數據庫&#xff0c;每個Kconfig分別描述了所屬目錄源文檔相關的內核配置菜單。在內核配置make menuconfig時&#xff0c;從Kconfig中讀出菜單&#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.…