python-基於tcp協議的套接字(增強版)及粘包問題

1、基於tcp協議的套接字(通訊循環+連接循環)

服務端應該遵循:python

  1.綁定一個固定的ip和portlinux

  2.一直對外提供服務,穩定運行算法

  3.可以支持併發shell

 

基礎版套接字:json

from socket import *

server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(5)

conn, client_addr = server.accept()

# 通訊循環
while True:
    data = conn.recv(1024)
    conn.send(data.upper())

conn.close()
server.close()
server
from socket import *

client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8080))

# 通訊循環
while True:
    msg=input('>>: ').strip()
    client.send(msg.encode('utf-8'))
    data=client.recv(1024)
    print(data)

client.close()
client

以上的程序存在兩個bug數組

  1.當客戶端單方面終止程序時,服務端拋出異常(linux能夠用判斷是否爲空來處理)服務器

  2.recv收到空時,一直在等待。併發

解決方法:ssh

  1.異常捕獲socket

  2.再輸入時進行判斷

改進版:

from socket import *

server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8082))
server.listen(5)

conn, client_addr = server.accept()
print(client_addr)

# 通訊循環
while True:
    try:
        data = conn.recv(1024)
        if len(data) == 0:break # 針對linux系統
        print('-->收到客戶端的消息: ',data)
        conn.send(data.upper())
    except ConnectionResetError:
        break

conn.close()
server.close()
server
from socket import *

client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8081))

# 通訊循環
while True:
    msg=input('>>: ').strip() #msg=''
    if len(msg) == 0:continue
    client.send(msg.encode('utf-8')) #client.send(b'')
    # print('has send')
    data=client.recv(1024)
    # print('has recv')
    print(data)

client.close()
client

連接循環:服務器改進

from socket import *

server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8082))
server.listen(5)

conn, client_addr = server.accept()
print(client_addr)

# 通訊循環
while True:
    try:
        data = conn.recv(1024)
        if len(data) == 0:break # 針對linux系統
        print('-->收到客戶端的消息: ',data)
        conn.send(data.upper())
    except ConnectionResetError:
        break

conn.close()
server.close()
server

模擬ssh實現遠程執行命令

from socket import *
import subprocess

server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8081))
server.listen(5)

# 連接循環
while True:
    conn, client_addr = server.accept()
    print(client_addr)

    # 通訊循環
    while True:
        try:
            cmd = conn.recv(1024) #cmd=b'dir'
            if len(cmd) == 0: break  # 針對linux系統
            obj=subprocess.Popen(cmd.decode('utf-8'),
                             shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE
                             )
            stdout=obj.stdout.read()
            stderr=obj.stderr.read()
            print(len(stdout) + len(stderr))
            conn.send(stdout+stderr)
        except ConnectionResetError:
            break

    conn.close()

server.close()
server
from socket import *

client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8081))

# 通訊循環
while True:
    cmd=input('>>: ').strip()
    if len(cmd) == 0:continue
    client.send(cmd.encode('utf-8'))
    cmd_res=client.recv(1024000)
    print(cmd_res.decode('gbk'))

client.close()
client

recv實際上是和本地計算機要數據,因此解碼的時候應該用gbk格式

2、粘包

注:只有tcp存在粘包現象,udp永遠不存在粘包。

tcp是面向流的協議,發送端能夠1k,1k的發送數據,而接收端能夠2k,2k的提取數據,發送方每每收集到足夠多的數據後才生成一個tcp段,若連續幾回須要發送的數據都不多,一般tcp會根據(Nagle)優化算法,把這些數據合成一個TCP段後發出去,這樣接收方就收到了粘包數據。

總的來講,粘包問題就是由於接收方不知道消息之間的界限,不知道一次性提取多少字節形成的。

解決方法:(服務端)爲字節流加上一個報頭,將這個報頭(字典形式)json序列化,編碼。而後用struct發送報頭的長度,再發送報頭,最後發送真實數據

     (客戶端)先解出報頭的長度(struct),再接收報頭,將拿到的報頭解碼再反序列化,獲得報頭字典,最後接收真正的數據

# 服務端必須知足至少三點:
# 1. 綁定一個固定的ip和port
# 2. 一直對外提供服務,穩定運行
# 3. 可以支持併發
from socket import *
import subprocess
import struct
import json

server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8081))
server.listen(5)

# 連接循環
while True:
    conn, client_addr = server.accept()
    print(client_addr)

    # 通訊循環
    while True:
        try:
            cmd = conn.recv(1024)  # cmd=b'dir'
            if len(cmd) == 0: break  # 針對linux系統
            obj = subprocess.Popen(cmd.decode('utf-8'),
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE
                                   )
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()
            # 1. 先製做報頭
            header_dic = {
                'filename': 'a.txt',
                'md5': 'asdfasdf123123x1',
                'total_size': len(stdout) + len(stderr)
            }
            header_json = json.dumps(header_dic)
            header_bytes = header_json.encode('utf-8')

            # 2. 先發送4個bytes(包含報頭的長度)
            conn.send(struct.pack('i', len(header_bytes)))
            # 3  再發送報頭
            conn.send(header_bytes)

            # 4. 最後發送真實的數據
            conn.send(stdout)
            conn.send(stderr)
        except ConnectionResetError:
            break

    conn.close()

server.close()
server
from socket import *
import struct
import json

client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8081))

# 通訊循環
while True:
    cmd=input('>>: ').strip()
    if len(cmd) == 0:continue
    client.send(cmd.encode('utf-8'))
    #1. 先收4bytes,解出報頭的長度
    header_size=struct.unpack('i',client.recv(4))[0]

    #2. 再接收報頭,拿到header_dic
    header_bytes=client.recv(header_size)
    header_json=header_bytes.decode('utf-8')
    header_dic=json.loads(header_json)
    print(header_dic)
    total_size=header_dic['total_size']

    #3. 接收真正的數據
    cmd_res=b''
    recv_size=0
    while recv_size < total_size:
        data=client.recv(1024)
        recv_size+=len(data)
        cmd_res+=data

    print(cmd_res.decode('gbk'))

client.close()
client

補充:struct模塊

該模塊能夠幫一個類型,如數字,轉成固定長度的bytes

一、 struct.pack
struct.pack用於將Python的值根據格式符,轉換爲字符串(由於Python中沒有字節(Byte)類型,能夠把這裏的字符串理解爲字節流,或字節數組)。其函數原型爲:struct.pack(fmt, v1, v2, ...),參數fmt是格式字符串,關於格式字符串的相關信息在下面有所介紹。v1, v2, ...表示要轉換的python值。

二、 struct.unpack
struct.unpack作的工做恰好與struct.pack相反,用於將字節流轉換成python數據類型。它的函數原型爲:struct.unpack(fmt, string),該函數返回一個元組。 

import struct

obj1=struct.pack('i',13321111)
print(obj1,len(obj1))#b'\x97C\xcb\x00' 4

res1=struct.unpack('i',obj1)
print(res1[0])#13321111
View Code
相關文章
相關標籤/搜索