Python3 Tcp未發送/接收完數據即被RST處理辦法

1、背景說明

昨天一個同事讓幫忙寫個服務,用於接收並返回他那邊提交過來的數據,以便其查看提交的數據及格式是否正確。python

開始想用django寫個接口,但寫接口接口名稱就得是定死的,他那邊只能向這接口提交數據;接收一下就返回這種事情不如直接寫個socket監聽而後返回去。django

之前也沒怎麼寫正經的socket編程,基本是能收發點數據應差很少了,這次收發的數據一多就出了問題。編程

一是沒接收完客戶端要發送的數據就給客戶端回RST,二是沒發送完要給客戶端發送的數據就又直接給客戶端發送RST。服務器

 

2、問題處理

2.1 tcp收發處理

使用s.recv()接收數據時,s.recv()只管從操做系統緩衝區中讀取數據,阻塞模式下只要讀到數據、非阻塞模式下無論讀到讀不到或者讀到多少,函數都算執行完了程序會繼續日後執行。所以接收大量數據時咱們須要不斷使用s.recv()進行讀取而後設定一個終止標誌。網絡

使用s.send()發送數據時,s.send()只管通知操做系統發送數據,操做系統每次只是盡力發送數據而後把本次發送的數據做爲返回值並不保證數據發送完成。所以在發送大量數據時咱們須要不斷使用s.send()發送而後設定一個終止標誌。(不過若是單純說python那可使用snedall()函數來實現一次發送完,sendall()的實現方法和咱們這裏說的意思同樣)socket

另外注意若是測試本身使用requests等做爲客戶端時,服務端的返回要加上http的響應頭部,否則數據原樣返回requests等進行解析會因不是一個正確的http響應而出錯。tcp

 

2.2 有問題程序

import socket

# 獲取本地主機名
host = socket.gethostname()
port = 9999

# 建立socket對象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 綁定端口號
server_socket.bind((host, port))
# 設置最大鏈接數,超事後排隊
server_socket.listen(5)

# 這裏實現的是接收客戶端發來的數據、打印、而後再原樣返回給客戶端
while True:
    client_socket, addr = server_socket.accept()
    print(f"鏈接地址: {str(addr)}")

    # 錯誤2、當發來的數據很長時tcp不會等接收完成再執行下一條語句,這裏沒處理這個問題
    result = client_socket.recv(1024 * 1024)
    # 問題1、decode默認使用utf-8編碼,但當發來的數據有utf-8不可解碼內容時會報異常,這裏沒捕獲異常
    print(f"{result.decode()}")

    # 錯誤3、發送時tcp不會等發送完再執行下一條語句,這裏沒處理這個問題
    client_socket.send(result)
    # 注意4、若是客戶端中的接收代碼是和上邊錯誤二同樣的,那麼沒發完也會被客戶端reset
    client_socket.close()

 

2.3 修正後程序

import socket

# 獲取本地主機名
host = socket.gethostname()
port = 9999

# 建立socket對象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 綁定端口號
server_socket.bind((host, port))
# 設置最大鏈接數,超事後排隊
server_socket.listen(5)
print("服務器程序已啓動")

while True:
    # 創建客戶端鏈接
    client_socket, addr = server_socket.accept()
    # 阻塞模式,設置接收超時時長爲1秒,1秒內沒有新數據視爲數據已傳送完成
    client_socket.settimeout(1)
    # client_socket.setblocking(0)
    print(f"鏈接地址: {str(addr)}")
    # 這裏result不能賦值爲None,不然下邊的result += tmp_result會因類型不一致報錯
    # 這裏result不能賦值爲字符串"",不然下邊的result += tmp_result會因類型不一致報錯
    result = b""
    tmp_result = ""

    # 錯誤二修正:此處while循環用於確保接收完全部數據再執行後續指令
    while True:
        # 每次最多讀取2048字節
        try:
            tmp_result = client_socket.recv(2048)
        # 1秒內無數據,觸發超時異常,此時咱們斷定爲數據已接收完成break退出
        # 不能使用獲取數據爲空做爲退出標誌,由於阻塞模式除非是已創建的網絡鏈接被拆除否則讀不到數據是不會返回的
        except socket.timeout as e:
            print(f"{e}")
            break
        # 將本次讀取到的內容拼接到result中
        result += tmp_result


    # 問題一修正:對解碼異常進行捕獲,直接以byte形式輸出
    try:
        print(f"{result.decode()}")
    except:
        print(f"{result}")

    total_lenght = result.__len__()
    print(f"\r\ntotal_length :{total_lenght}")
    flag = 0
    # 錯誤三修正:確保數據發送完才執行後續代碼
    # python其實可使用sendall()來完成,但sendall自己也是相似如下形式,爲了通用性咱們這裏暫時不用
    while True:
        # 每次從已發送數據位置發送
        # 每次返回的是本次發送數據長度
        tmp_flag = client_socket.send(result[flag:])
        flag += tmp_flag
        # 若是已發送完則退出
        if flag == total_lenght:
            break

    # 至於問題四,客戶端未完成接收即返回reset,那就只能由客戶端去處理了
    client_socket.close()

 

參考:函數

https://stackoverflow.com/questions/34252273/what-is-the-difference-between-socket-send-and-socket-sendall測試

相關文章
相關標籤/搜索