~~網絡編程(五):粘包現象~~


進擊のpython

*****

網絡編程——粘包現象


前面我們提到了套接字的使用方法,以及相關bug的排除

還記得我們提到過一個1024嗎?

我們現在要針對這個來研究一下一個陷阱

在研究這個陷阱之前我要先教你幾條語句

這是windows的命令啊

ipfonfig 查看本地網卡的ip地址

dir 查看某一個文件夾下的子文件名和子文件夾名

tasklist 查看運行的進程

那我這三條命令怎么執行呢??直接敲??

好像沒什么用,所以說我需要打開我的cmd窗口來鍵入這些命令

而cmd也就是一個能把特殊的字母組合執行出來的一個程序而已

當我在cmd里鍵入dir的時候得到的就是這些東西

那我想在編譯器里搞這個東西呢?

哦!第一反應就是os模塊

import os
os.system("dir")

就執行起來了吧


那我這算是拿到結果了嗎?

我覺得不算,為什么?

咱們想要達到的效果是我在客戶端輸入一個dir發送給服務端,服務端給我返回這一堆東西才叫拿到結果了是吧

import os

res = os.system("dir")
print(f"返回的結果是:{res}")

那結果我打印的是什么呢??是0!那為什么是這個呢?

這個0是代表這個命令是不是成功

如果返回的是0,就是成功了,如果是非零,就是失敗了!

所以說他返回的是一個是否成功執行語句的狀態,而不是執行語句的返回結果

那os模塊就被pass掉了,因為他無法返回我們需要的東西

那除了os.還有什么嗎?subprocess

他下面有一個方法

import subprocess
subprocess.Popen()

里面接收兩個參數,第一個參數是字符串的命令

第二個是shell=True,作用是在終端也就是cmd下運行

那我這么寫就沒問題了

import subprocess

subprocess.Popen("dir", shell=True)

但是我不要把這個結果給終端,我要把這個結果給客戶端

那我是不是就要把結果傳進管道然后進行傳輸呢?

好,那這個方法就可以傳遞第三個屬性stdout = subprocess.PIPE

這個管道是用來接收正確的結果的

那錯誤的結果傳在哪呢?第四個屬性! stderr = subprocess.PIPE

好,當我把所有的參數都填進去之后我們再看,是不是在控制台就沒有輸出結果了啊

輸出結果去哪了呢?放到管道里去了!

import subprocess

obj = subprocess.Popen("dir", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

那我想把管道里的東西取出來怎么弄呢?

我們先來看看我打印obj是個啥?很明顯是個對象是吧

那既然是對象就能調用方法

print(obj.stdout)
<_io.BufferedReader name=3>

看到IO第一反應就是文件,用read()方法讀一下

你發現你打印的時候什么???這不就是我們想要的字節類型的數據嘛

然后我們再來看

import subprocess

obj = subprocess.Popen("dir", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print(obj.stdout.read().decode("gbk"))
print(obj.stdout.read().decode("gbk"))

打印了幾次?只有一次!為什么?

因為我把數據放到管道之后,第一次打印就把結果拿出來了,第二次再拿就啥也拿不到了

反而錯誤管道里就有信息了

那現在就可以把代碼寫進去吧

# 服務端
import socket
import subprocess

# 買手機

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 綁定手機卡
phone.bind(("127.0.0.1", 8080))
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 開機
phone.listen(5)

# 等電話
connet, client_addr = phone.accept()

# 收發消息
while 1:
    try:
        k = connet.recv(1024)
        obj = subprocess.Popen(k.decode("gbk"), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout = obj.stdout.read()
        stderr = obj.stderr.read()
        connet.send(stdout + stderr)
    except ConnectionResetError:
        break

# 掛電話
connet.close()

# 關機
phone.close()
# 客戶端
import socket

# 買手機
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 撥號
phone.connect(("127.0.0.1", 8080))

# 發收信息
while 1:
    msg = input(">>>")
    phone.send(msg.encode("gbk"))
    k = phone.recv(1024)
    k = k.decode("gbk")
    print(f"從服務端接收的消息:{k}")

connet.close()
# 關閉
phone.close()

其實到現在,我們其實完成了一個模擬ssh遠程執行命令的操作,就是客戶端獲取主機的信息的操作

但是我們會發現問題,我要是傳的超過1024個字節的數據,怎么辦呢???

還有一個就是 stdout + stderr 這個加號就相當於新開個內存空間

而不是直接在他本身上加,所以這個效率應該是可以優化的


比如說我傳一個超過1024的,會發生什么現象?

我先打印一下D盤的文件

我發現信息沒有完整的顯示出來

然后我再打印一下IP信息

我會發現,打印結果的上面,還有上一次打印沒打印出來的信息!

說明溢出的數據沒有丟,而是等待下一次的調用然后傳過去

其實也很好理解

我們這不是流式傳輸嘛,那水流還能 嘎巴 一下就斷了?

不能吧,當我拿到一點水后關閉通道,本該進來的水就因為容器太小的原因

留在了外面,等到下次調用的時候,在外面的這一部分就先進來,然后再進其他的

這個現象,就叫粘包!

指的是多個包的返回值黏在一起了

那應該怎么解決呢?


我是不是可以在發消息之前,先告訴服務端我要傳多大的文件

然后服務端就對這個信息做出相應的操作

有一種方法是把1024改一個更大的數,但其實

還是治標不治本,因為你不知道返回值有多大,就有可能被超越

那還有一種方法,那我沒接完我就繼續接唄

那我就應該把數據長度發給客戶端,然后再發送數據

然后再說說+的問題,因為他是流數據,那我按順序發,他是不是就自己拼上了啊

connet.send(stdout)
connet.send(stderr)

而服務端首先應該接收到數據長度然后再接收數據是吧

那我客戶端大概應該這么寫

msg = input(">>>")
    phone.send(msg.encode("gbk"))
    re_len = 1025  # 數據長度
    re_size = 0
    r = b""  # 我傳過來的是字節模式
    if re_size < re_len:
        k = phone.recv(1024)
        r += k
        re_size += len(k)
    print(f"從服務端接收的消息:{k}")

那這個數據長度,就應該是服務端傳過來的對吧

所以服務端大概應該這么寫

stdout = obj.stdout.read()
stderr = obj.stderr.read()
        
r_size = len(stdout)+len(stderr)
connet.send(str(r_size).encode("gbk")) # 數字模式不能傳,只能傳字符串
connet.send(stdout)
connet.send(stderr)

但是問題就出現了!我這三個發送信息也是粘包,那我怎么能讓客戶端進行分辨?

還記得我們在說傳輸數據的時候提到了報頭的概念嘛?

所以其實我們是在寫報頭,而報頭是固定長度的

所以我現在就要學會如何發報頭對吧!


那我們現在開始自定義報頭吧

這時候我們就需要學習一個新的模塊struct

struck.pack()

相當於打包這里面傳的是兩個參數,第一個是數據類型,第二個的是數據

res = struct.pack("i", 1234)
print(res, type(res), len(res))

打印的是:

b'\xd2\x04\x00\x00' <class 'bytes'> 4

所以,我這就算是拿到了報頭的長度

那我這打包怎么解包???

res = struct.unpack("i",res)
(1234,)

我拿到的是元組,所以[0]是不是就拿到了1234了

那長度是不是也就拿到了

那服務端就可以寫了

r_size = len(stdout) + len(stderr)
res = struct.pack("i", r_size)

那客戶端就知道怎么做了

res = phone.recv(4)
re_len = struct.unpack("i", res)[0]

那總的來說,代碼就如下:

# 客戶端
import socket

# 買手機
import struct

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 撥號
phone.connect(("127.0.0.1", 8080))

# 發收信息
while 1:
    msg = input(">>>")
    phone.send(msg.encode("gbk"))
    res = phone.recv(4)
    re_len = struct.unpack("i", res)[0]

    re_size = 0
    r = b""  # 我傳過來的是字節模式
    while re_size < re_len:
        k = phone.recv(1024)
        r += k
        re_size += len(k)
        print(re_size, re_len)
    print(f'從服務端接收的消息:{r.decode("gbk")}')

connet.close()
# 關閉
phone.close()
# 服務端
import socket
import struct
import subprocess

# 買手機

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 綁定手機卡
phone.bind(("127.0.0.1", 8080))
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 開機
phone.listen(5)

# 等電話
connet, client_addr = phone.accept()

# 收發消息
while 1:
    try:
        k = connet.recv(1024)
        obj = subprocess.Popen(k.decode("gbk"), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout = obj.stdout.read()
        stderr = obj.stderr.read()

        r_size = len(stdout) + len(stderr)
        res = struct.pack("i", r_size)
        connet.send(res)

        connet.send(stdout)
        connet.send(stderr)
    except ConnectionResetError:
        break

# 掛電話
connet.close()

# 關機
phone.close()

*這是有問題的*
*你要繼續看吖*


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM