import subprocess res = subprocess.Popen( ' dir ' ,shell=True,stdout=subprocess.PIPE,stderr= subprocess.PIPE) print ( ' Stdout: ' ,res.stdout.read().decode( ' gbk ' )) print ( ' Stderr: ' ,res.stderr.read().decode( ' gbk ' ))
PIPE把輸出的東西裝到一個'水管'里,如果在windows中的編碼格式是gbk,執行結果:
Stdout: 驅動器 C 中的卷是 系統 卷的序列號是 85C0 - 669A C:\Users\Administrator\PycharmProjects\Internet_program 的目錄 2019/09/16 13:48. 2019/09/16 13:48 .. 2019/09/16 13:47 .idea 2019/09/16 13:46 21 Client1.py 2019/09/16 13:42 0 Client2.py 2019/09/16 13:48 207 Sever1.py 2019/09/16 01:41 70 time_test.py 2019/09/14 23:51 venv 4 個文件 298 字節 4 個目錄 45,863,636,992 可用字節 Stderr:
在這里也可以使用os.popen()但是它會不管正確和錯誤的結果都放在一起,而用subprocess能夠分別拿到正確和錯誤的信息
?
基于TCP實現的黏包
Sever:
import socket sk = socket.socket() sk.bind(( ' 127.0.0.1 ' ,8092 )) sk.listen() conn,addr = sk.accept() while True: cmd = input( ' <<< ' ) conn.send(cmd.encode( ' gbk ' )) ret = conn.recv(1024).decode( ' gbk ' ) print (ret) conn.close() sk.close()
Client:
import socket import subprocess sk = socket.socket() sk.connect(( ' 127.0.0.1 ' ,8092 )) while True: cmd = sk.recv(1024).decode( ' gbk ' ) ret = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr= subprocess.PIPE) std_out = ' stdout: ' + (ret.stdout.read()).decode( ' gbk ' ) std_err = ' stderr: ' + (ret.stderr.read()).decode( ' gbk ' ) print (std_out) print (std_err) sk.send(std_out.encode( ' gbk ' )) sk.send(std_err.encode( ' gbk ' )) sk.close()
執行結果:
Sever:
<<< dir;ls stdout: 驅動器 C 中的卷是 系統 卷的序列號是 85C0 - 669A C:\Users\Administrator\PycharmProjects\Internet_program 的目錄 <<< ipconfig stderr:找不到文件 <<<
Client:
stdout: 驅動器 C 中的卷是 系統 卷的序列號是 85C0 - 669A C:\Users\Administrator\PycharmProjects\Internet_program 的目錄 stderr:找不到文件 stdout: Windows IP 配置 以太網適配器 Bluetooth 網絡連接 2 : 媒體狀態 . . . . . . . . . . . . : 媒體已斷開 連接特定的 DNS 后綴 . . . . . . . : 以太網適配器 本地連接: 媒體狀態 . . . . . . . . . . . . : 媒體已斷開 連接特定的 DNS 后綴 . . . . . . . : 無線局域網適配器 無線網絡連接: 連接特定的 DNS 后綴 . . . . . . . : 本地鏈接 IPv6 地址. . . . . . . . : fe80::8c6:36a9:6fa6: 8018%14 IPv4 地址 . . . . . . . . . . . . : 192.168.43.216 子網掩碼 . . . . . . . . . . . . : 255.255.255.0 默認網關. . . . . . . . . . . . . : 192.168.43.1 隧道適配器 本地連接 * 3 : 媒體狀態 . . . . . . . . . . . . : 媒體已斷開 連接特定的 DNS 后綴 . . . . . . . : stderr:
當我們在sever端輸入dir;ls命令時,只有stdout的結果跑出來,而當我們輸入ipconfig這個命令時,系統將上一次dir;ls未執行完的stderr的結果給跑出來。像這樣沒有接受完全或者接受多了的就是黏包現象。
TCP會有黏包現象但是它不丟包。
?
基于UDP實現的黏包
Sever:
import socket sk = socket.socket(type= socket.SOCK_DGRAM) sk.bind(( ' 127.0.0.1 ' ,8092 )) msg,addr = sk.recvfrom(10240 ) while 1 : cmd = input( ' <<< ' ) if cmd == ' q ' : break sk.sendto(cmd.encode( ' gbk ' ),addr) msg,addr = sk.recvfrom(10240 ) print (msg.decode( ' gbk ' )) sk.close()
Client:
import socket import subprocess sk = socket.socket(type= socket.SOCK_DGRAM) addr = ( ' 127.0.0.1 ' ,8092 ) sk.sendto( ' Start ' .encode( ' utf-8 ' ),addr) while 1 : cmd,addr = sk.recvfrom(10240 ) ret = subprocess.Popen(cmd.decode( ' gbk ' ),shell=True,stderr=subprocess.PIPE,stdout= subprocess.PIPE) std_out = ' Stdout: ' + (ret.stdout.read()).decode( ' gbk ' ) std_err = ' Stderr: ' + (ret.stderr.read()).decode( ' gbk ' ) print (std_out) print (std_err) sk.sendto(std_out.encode( ' gbk ' ),addr) sk.sendto(std_err.encode( ' gbk ' ),addr) sk.close()
執行結果:
Sever:
<<< dir;ls Stdout: 驅動器 C 中的卷是 系統 卷的序列號是 85C0 - 669A C:\Users\Administrator\PycharmProjects\Internet_program 的目錄 <<< dir Stderr:找不到文件 <<< ipconfig Stdout: 驅動器 C 中的卷是 系統 卷的序列號是 85C0 - 669A C:\Users\Administrator\PycharmProjects\Internet_program 的目錄 2019/09/16 14:43. 2019/09/16 14:43 .. 2019/09/16 14:37 .idea 2019/09/16 14:43 553 Client1.py 2019/09/16 13:42 0 Client2.py 2019/09/16 14:43 306 Sever1.py 2019/09/16 01:41 70 time_test.py 2019/09/14 23:51 venv 4 個文件 929 字節 4 個目錄 45,855,449,088 可用字節 <<< pwd Stderr: <<< ip Stdout: Windows IP 配置 以太網適配器 Bluetooth 網絡連接 2 : 媒體狀態 . . . . . . . . . . . . : 媒體已斷開 連接特定的 DNS 后綴 . . . . . . . : 以太網適配器 本地連接: 媒體狀態 . . . . . . . . . . . . : 媒體已斷開 連接特定的 DNS 后綴 . . . . . . . : 無線局域網適配器 無線網絡連接: 連接特定的 DNS 后綴 . . . . . . . : 本地鏈接 IPv6 地址. . . . . . . . : fe80::8c6:36a9:6fa6: 8018%14 IPv4 地址 . . . . . . . . . . . . : 192.168.43.216 子網掩碼 . . . . . . . . . . . . : 255.255.255.0 默認網關. . . . . . . . . . . . . : 192.168.43.1 隧道適配器 本地連接 * 3 : 媒體狀態 . . . . . . . . . . . . : 媒體已斷開 連接特定的 DNS 后綴 . . . . . . . : <<<
Client:
Stdout: 驅動器 C 中的卷是 系統 卷的序列號是 85C0 - 669A C:\Users\Administrator\PycharmProjects\Internet_program 的目錄 Stderr:找不到文件 Stdout: 驅動器 C 中的卷是 系統 卷的序列號是 85C0 - 669A C:\Users\Administrator\PycharmProjects\Internet_program 的目錄 2019/09/16 14:43. 2019/09/16 14:43 .. 2019/09/16 14:37 .idea 2019/09/16 14:43 553 Client1.py 2019/09/16 13:42 0 Client2.py 2019/09/16 14:43 306 Sever1.py 2019/09/16 01:41 70 time_test.py 2019/09/14 23:51 venv 4 個文件 929 字節 4 個目錄 45,855,449,088 可用字節 Stderr: Stdout: Windows IP 配置 以太網適配器 Bluetooth 網絡連接 2 : 媒體狀態 . . . . . . . . . . . . : 媒體已斷開 連接特定的 DNS 后綴 . . . . . . . : 以太網適配器 本地連接: 媒體狀態 . . . . . . . . . . . . : 媒體已斷開 連接特定的 DNS 后綴 . . . . . . . : 無線局域網適配器 無線網絡連接: 連接特定的 DNS 后綴 . . . . . . . : 本地鏈接 IPv6 地址. . . . . . . . : fe80::8c6:36a9:6fa6: 8018%14 IPv4 地址 . . . . . . . . . . . . : 192.168.43.216 子網掩碼 . . . . . . . . . . . . : 255.255.255.0 默認網關. . . . . . . . . . . . . : 192.168.43.1 隧道適配器 本地連接 * 3 : 媒體狀態 . . . . . . . . . . . . : 媒體已斷開 連接特定的 DNS 后綴 . . . . . . . : Stderr: Stdout: Stderr: ' pwd ' 不是內部或外部命令,也不是可運行的程序 或批處理文件。 Stdout: Stderr: ' ip ' 不是內部或外部命令,也不是可運行的程序 或批處理文件。
可以看出UDP不會有黏包現象,會產生丟包現象,沒發完就不發了,不完整也不可靠。
?
黏包成因
TCP協議的數據傳送
拆包機制
當發送端緩沖區的長度大于網卡的MTU時,TCP會將這次發送的數據拆成幾個數據包發送出去。MTU是Maximum Transmission Unit的縮寫,意思是網絡上傳送最大數據包,MTU是字節單位,大部分網絡設備的MTU都是1500. 如果本機的MTU比網關的MTU大,大的數據包就會被拆開來傳送,這樣會產生很多數據包碎片,增加丟包率,降低網絡速度。
在正常情況下它的拆包可理解為:
面向流的通信特點和Nagle算法
TCP(transport control protocol,傳輸控制協議),是面向連接的,面向流的,提供高可靠性的服務。收發兩端(客戶端和服務端)都要有一一成對的socket,因此發送端為了將多個發往接收端的包,更有效地發往對方,使用了優化算法(Nagle算法),將多次間隔較小且數據量小的數據,合并成一個大的數據塊,然后進行封包。這樣接收端就難于分辨出來了,必須提供科學的拆包機制。即面向流的通信是無消息保護邊界的。
對于空消息:TCP是基于數據流的,于是收發消息不能為空,這就需要在客戶端和服務端都添加空消息的處理機制,防止程序卡住,而UDP協議是基于數據報的,即便是你輸入的是空內容(直接回車),也可以被發送,UDP協議會幫你封裝上消息然后發出去。
可靠黏包的TCP協議:TCP協議數據不會丟,沒有收完包,就會下次接收,會繼續上次繼續接受。
基于tcp協議特點的黏包現象成因
當我們在socket服務端發送值1、2,然后根據優化算法,它會把1先放到這個緩存當中等一等,然后再把2一起封裝起來,然后再發出去,因此我們看到的就是黏包現象
這種現象的表面現象是兩個send太近且發送的消息太短
發送端可以使1K1K地發送數據,而接收端的應用程序可以兩K兩K地提走數據,當然也有可能一次提走3K或6K數據,或者一次只提走幾個字節的數據。也就是說,應用程序所看到的數據是一個整體,或者說是一個流(stream),一條消息有多少字節對應程序是不可見的,因此TCP協議是面向流的協議,這也是容易出現黏包問題的原因。而UDP協議是面向消息的協議,每個UDP段都是一條消息,應用程序必須以消息單位提取數據,不能一次提取任意字節的數據,這一點和TCP是很不同的。
?
解決黏包的方法
解決方案一:
Sever:
import socket sk = socket.socket() sk.bind(( ' 127.0.0.1 ' ,8080 )) sk.listen() conn,addr = sk.accept() while True: cmd = input( ' <<< ' ) if cmd == ' q ' : conn.send(b ' q ' ) break conn.send(cmd.encode( ' gbk ' )) num = conn.recv(1024).decode( ' utf-8 ' ) conn.send(b ' ok ' ) res = conn.recv(int(num)).decode( ' gbk ' ) print (res) conn.close() sk.close()
Client:
import socket import subprocess sk = socket.socket() sk.connect(( ' 127.0.0.1 ' ,8080 )) while True: cmd = sk.recv(1024).decode( ' gbk ' ) if cmd == ' q ' : break res = subprocess.Popen(cmd,shell= True, stdout = subprocess.PIPE, stderr = subprocess.PIPE) std_out = res.stdout.read() std_err = res.stderr.read() sk.send(str(len(std_out) + len(std_err)).encode( ' utf-8 ' )) sk.recv( 1024 ) print ( ' Stdout: ' + std_out.decode( ' gbk ' )) print ( ' Stderr: ' + std_err.decode( ' gbk ' )) sk.send(std_out) sk.send(std_err) sk.close()
執行結果:
Sever:
<<< dir 驅動器 C 中的卷是 系統 卷的序列號是 85C0 - 669A C:\Users\Administrator\PycharmProjects\Internet_program 的目錄 2019/09/17 15:33. 2019/09/17 15:33 .. 2019/09/17 15:31 .idea 2019/09/17 15:33 623 Client1.py 2019/09/16 13:42 0 Client2.py 2019/09/17 15:21 389 Sever1.py 2019/09/16 01:41 70 time_test.py 2019/09/14 23:51 venv 4 個文件 1,082 字節 4 個目錄 45,031,833,600 可用字節 <<< ls ' ls ' 不是內部或外部命令,也不是可運行的程序 或批處理文件。 <<< ipconfig Windows IP 配置 以太網適配器 Bluetooth 網絡連接 2 : 媒體狀態 . . . . . . . . . . . . : 媒體已斷開 連接特定的 DNS 后綴 . . . . . . . : 以太網適配器 本地連接: 媒體狀態 . . . . . . . . . . . . : 媒體已斷開 連接特定的 DNS 后綴 . . . . . . . : 無線局域網適配器 無線網絡連接: 連接特定的 DNS 后綴 . . . . . . . : 本地鏈接 IPv6 地址. . . . . . . . : fe80::8c6:36a9:6fa6: 8018%14 IPv4 地址 . . . . . . . . . . . . : 192.168.43.216 子網掩碼 . . . . . . . . . . . . : 255.255.255.0 默認網關. . . . . . . . . . . . . : 192.168.43.1 隧道適配器 本地連接 * 3 : 媒體狀態 . . . . . . . . . . . . : 媒體已斷開 連接特定的 DNS 后綴 . . . . . . . : <<<
Client:
Stdout: 驅動器 C 中的卷是 系統 卷的序列號是 85C0 - 669A C:\Users\Administrator\PycharmProjects\Internet_program 的目錄 2019/09/17 15:33. 2019/09/17 15:33 .. 2019/09/17 15:31 .idea 2019/09/17 15:33 623 Client1.py 2019/09/16 13:42 0 Client2.py 2019/09/17 15:21 389 Sever1.py 2019/09/16 01:41 70 time_test.py 2019/09/14 23:51 venv 4 個文件 1,082 字節 4 個目錄 45,031,833,600 可用字節 Stderr: Stdout: Stderr: ' ls ' 不是內部或外部命令,也不是可運行的程序 或批處理文件。 Stdout: Windows IP 配置 以太網適配器 Bluetooth 網絡連接 2 : 媒體狀態 . . . . . . . . . . . . : 媒體已斷開 連接特定的 DNS 后綴 . . . . . . . : 以太網適配器 本地連接: 媒體狀態 . . . . . . . . . . . . : 媒體已斷開 連接特定的 DNS 后綴 . . . . . . . : 無線局域網適配器 無線網絡連接: 連接特定的 DNS 后綴 . . . . . . . : 本地鏈接 IPv6 地址. . . . . . . . : fe80::8c6:36a9:6fa6: 8018%14 IPv4 地址 . . . . . . . . . . . . : 192.168.43.216 子網掩碼 . . . . . . . . . . . . : 255.255.255.0 默認網關. . . . . . . . . . . . . : 192.168.43.1 隧道適配器 本地連接 * 3 : 媒體狀態 . . . . . . . . . . . . : 媒體已斷開 連接特定的 DNS 后綴 . . . . . . . : Stderr:
這種寫法的好處就是能確定Sever到底要接受多少數據,不好的地方就是多了一次交互
解決方案二: 使用struct模塊
用struct模塊我們能把一個數據類型轉換成固定長度的bytes
這里以數字類型舉例,'i'代表int類型:
import struct print (struct.pack( ' i ' ,2048),len(struct.pack( ' i ' ,2048))) # b'\x00\x08\x00\x00' 4 print (struct.pack( ' i ' ,204800),len(struct.pack( ' i ' ,204800))) # b'\x00 \x03\x00' 4 print (struct.pack( ' i ' ,2048000),len(struct.pack( ' i ' ,2048000))) # b'\x00@\x1f\x00' 4
當后面的數值戳過一定范圍的時候程序就會報錯
Sever:
import socket import struct sk = socket.socket() sk.bind(( ' 127.0.0.1 ' ,8080 )) sk.listen() conn,addr = sk.accept() while True: cmd = input( ' <<< ' ) if cmd == ' q ' : conn.send(b ' q ' ) break conn.send(cmd.encode( ' gbk ' )) num = conn.recv(4 ) num = struct.unpack( ' i ' ,num)[0] res = conn.recv(int(num)).decode( ' gbk ' ) print (res) conn.close() sk.close()
Client:
import socket import subprocess import struct sk = socket.socket() sk.connect(( ' 127.0.0.1 ' ,8080 )) while True: cmd = sk.recv(1024).decode( ' gbk ' ) if cmd == ' q ' : break res = subprocess.Popen(cmd,shell= True, stdout = subprocess.PIPE, stderr = subprocess.PIPE) std_out = res.stdout.read() std_err = res.stderr.read() len_num = len(std_out) + len(std_err) num_by = struct.pack( ' i ' ,len_num) print ( ' Stdout: ' + std_out.decode( ' gbk ' )) print ( ' Stderr: ' + std_err.decode( ' gbk ' )) sk.send(num_by) sk.send(std_out) sk.send(std_err) sk.close()
執行結果:

<<< dir 驅動器 C 中的卷是 系統 卷的序列號是 85C0 - 669A C:\Users\Administrator\PycharmProjects\Internet_program 的目錄 2019/09/17 16:25. 2019/09/17 16:25 .. 2019/09/17 16:23 .idea 2019/09/17 16:25 659 Client1.py 2019/09/16 13:42 0 Client2.py 2019/09/17 16:22 400 Sever1.py 2019/09/17 16:08 288 time_test.py 2019/09/14 23:51 venv 4 個文件 1,347 字節 4 個目錄 45,025,951,744 可用字節 <<< configip ' configip ' 不是內部或外部命令,也不是可運行的程序 或批處理文件。 <<< ipconfig Windows IP 配置 以太網適配器 Bluetooth 網絡連接 2 : 媒體狀態 . . . . . . . . . . . . : 媒體已斷開 連接特定的 DNS 后綴 . . . . . . . : 以太網適配器 本地連接: 媒體狀態 . . . . . . . . . . . . : 媒體已斷開 連接特定的 DNS 后綴 . . . . . . . : 無線局域網適配器 無線網絡連接: 連接特定的 DNS 后綴 . . . . . . . : 本地鏈接 IPv6 地址. . . . . . . . : fe80::8c6:36a9:6fa6: 8018%14 IPv4 地址 . . . . . . . . . . . . : 192.168.43.216 子網掩碼 . . . . . . . . . . . . : 255.255.255.0 默認網關. . . . . . . . . . . . . : 192.168.43.1 隧道適配器 本地連接 * 3 : 媒體狀態 . . . . . . . . . . . . : 媒體已斷開 連接特定的 DNS 后綴 . . . . . . . : <<<

Stdout: 驅動器 C 中的卷是 系統 卷的序列號是 85C0 - 669A C:\Users\Administrator\PycharmProjects\Internet_program 的目錄 2019/09/17 16:25. 2019/09/17 16:25 .. 2019/09/17 16:23 .idea 2019/09/17 16:25 659 Client1.py 2019/09/16 13:42 0 Client2.py 2019/09/17 16:22 400 Sever1.py 2019/09/17 16:08 288 time_test.py 2019/09/14 23:51 venv 4 個文件 1,347 字節 4 個目錄 45,025,951,744 可用字節 Stderr: Stdout: Stderr: ' configip ' 不是內部或外部命令,也不是可運行的程序 或批處理文件。 Stdout: Windows IP 配置 以太網適配器 Bluetooth 網絡連接 2 : 媒體狀態 . . . . . . . . . . . . : 媒體已斷開 連接特定的 DNS 后綴 . . . . . . . : 以太網適配器 本地連接: 媒體狀態 . . . . . . . . . . . . : 媒體已斷開 連接特定的 DNS 后綴 . . . . . . . : 無線局域網適配器 無線網絡連接: 連接特定的 DNS 后綴 . . . . . . . : 本地鏈接 IPv6 地址. . . . . . . . : fe80::8c6:36a9:6fa6: 8018%14 IPv4 地址 . . . . . . . . . . . . : 192.168.43.216 子網掩碼 . . . . . . . . . . . . : 255.255.255.0 默認網關. . . . . . . . . . . . . : 192.168.43.1 隧道適配器 本地連接 * 3 : 媒體狀態 . . . . . . . . . . . . : 媒體已斷開 連接特定的 DNS 后綴 . . . . . . . : Stderr:
實現一個大文件的傳輸和下載
當我們在網絡上傳輸所有數據時,這些數據都叫數據包,數據包里的所有數據都叫報文,報文里不止有你的數據還有IP地址、MAC地址、端口號等,所有的報文都有報頭,這是由協議規定的。所有在網絡上傳播數據包的協議里都有一個報頭。什么時候需要自己定制報文?比如說復雜的應用上就會應用到、傳輸文件的時候(文件名、文件大小、文件類型、存儲路徑等)...
其實在網絡傳輸的過程當中處處有協議,協議就是一堆報頭和報文(都由字節組成)。
Sever:
import socket import struct import json buffer = 1024 sk = socket.socket() sk.bind(( ' 127.0.0.1 ' ,8080 )) sk.listen() conn,addr = sk.accept() head_len = conn.recv(4 ) struct.unpack( ' i ' ,head_len)[0] json_head = conn.recv(head_len).decode( ' utf-8 ' ) head = json.loads(json_head) file_size = head[ ' fileSize ' ] with open(r ' dir\%s ' %head[ ' fileName ' ], ' wb ' ) as f: while file_size: if file_size >= buffer: content = conn.recv(buffer) f.write(content) file_size -= buffer else : content = conn.recv(buffer) f.write(content) break conn.close() sk.close()
Client:
import socket import struct import os import json sk = socket.socket() sk.connect(( ' 127.0.0.1 ' ,8080 )) buffer = 1024 head = { ' filePath ' : r ' C:\Users\Administrator\Desktop\專題\海報資料夾\專題海報 ' , ' fileName ' : r ' 專題海報 ' , ' fileSize ' : None} file_path = os.path.join(head[ ' filePath ' ],head[ ' fileName ' ]) file_size = os.path.getsize(file_path) head[ ' fileSize ' ] = file_size json_head = json.dumps(head) # 字典轉成字典 bytes_head = json_head.encode( ' utf-8 ' ) # 字符串轉bytes head_len = len(bytes_head) # 報頭的長度 pack_len = struct.pack( ' i ' ,head_len) sk.send(pack_len) # 先發報頭的長度 sk.send(bytes_head) # 再發bytes類型的報頭 with open(r ' dir\%s ' %file_path, ' rb ' ) as f: while file_size: if file_size >= buffer: content = f.read(buffer) # 每次文件讀出的內容 sk.send(content) file_size -= buffer else : content = f.read(file_size) sk.send(content) break sk.close()
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
