socket基於TCP(粘包現象和處理)


6socket套接字

1565756321381

socket網絡套接字

什么是socket

  • 每個 socket 被創建后,都會分配兩個緩沖區,輸入緩沖區和輸出緩沖區。
  • ​ sock是處於應用層和傳輸層之間的抽象層,1他是一組操作起來非常簡單的接口(接收數據)此接口接收數據之后,交由操作系統
  • 為什么存在socket抽象層
  • 如果直接與操作系統數據交互非常麻煩,繁瑣,socket對這些繁瑣的操作高度的封裝和簡化
  • socket在python中就是一個模塊。

7基於TCP協議的socket簡單的網絡通信

import socket
server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

一對一

簡單的socket抽象層 定義一些簡單的接口 留下一個接口

1.socket處於應用層和傳輸層之間,提供了一些簡單的接口,避免與操作系統直接對接,省去了相當繁瑣復雜的過程

2.socket 在python中就是一個模塊

不考慮緩存區的大小 發送是沒有限制的雖然不限制但是不會一次性發送過多

有收必有發,send發--recv接收

AF_UNIX

基於套接字家族的名字:

unix一切皆文件,基於文件的套接字(dicheng1wen),運行同一機器,通過訪問同一個文件系統完成通信

AF_INET(應用最廣泛的一個)

(AF_INET6 ipv6)

encode轉碼 類字節 base()轉碼類字節

執行的時候端口被占有 長連接

如果進入需要等待

收發是成對的 有發必有收,

遇到一個recv 就阻塞 寫兩個就卡住

base類型:

​ ASCII字符:在字符串前面b' '

非ASCII字符:encode 轉化成 bytes類型

報錯類型

ConnectionRefusedError #服務端沒開 報錯
ConnectionResetError #服務端崩潰
ConnectionAbortedError#客戶端退出

單一

#server端
import socket#調用socket
sk=socket.socket()#使用socket里面的socket
#相當於買手機
sk.bind(('127.0.0.1',8080))#bind(里面放入一個元組('ip',端口號))綁定手機卡
sk.listen(5)#監聽 等着有人給我打電話#阻塞 5是允許5個人 鏈接我,剩下的鏈接也可以鏈接,等待 
#最大等待鏈接數5個
#允許5個進入半鏈接池
#()是和電腦性能有關 #

conn,addr=sk.accept()#接收到別人的電話 connection 連接管道 address地址 來電顯示 #阻塞
ret=conn.recv(1024)#收聽別人說話的長度#1024個字節#等消息
print(ret)
ret=conn.recv(1024)#等消息
print(ret.decode('utf-8'))#要解碼
conn.send(b'1')#和別人說話,必須傳一個bytes類型
conn.close()#掛電話#關閉鏈接
sk.close()#關手機#關閉socket對象

##重用ip地址
#client端
import socket
sk=socket.socket()#買手機
“”“
sk.setsockopt(socket.SQL_SOCKET,socket.SO_REUSEADDR,1)#允許socket重用避免服務重啟的時候 address already in use
”“”“
sk.connect(('127.0.0.1',8080))#撥別人的號

sk.send(b'hello')#發送消息
sk.send('是我的'.encode('utf-8'))#中文要轉碼成為bit
ret=sk.recv(1024)
print(ret)
conn.close()#掛電話
sk.close()#關手機

鏈接+循環通信

#client
import socket


phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#使用網絡傳輸 tcp
try:
    phone.connect(('127.0.0.1', 8848))
except ConnectionRefusedError:
    exit('服務端沒開')

while 1:
    try:
        data=input('強輸入')
        phone.send(data.encode('gbk'))
        if not data:
            print('不能為空')
        elif data.upper() == 'Q':
            print('您退出了')
            break
        else:
            from_server_data=phone.recv(1024)

            print(f'來自服務的消息:{from_server_data.decode("gbk")}')
    except ConnectionResetError:
        print('服務端崩潰了連不上')
        break
    except ConnectionAbortedError:
        pass



phone.close()
#server
import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

phone.bind(('127.0.0.1',8848))

#開機監聽
while 1:#循環通信
    phone.listen()
    print('開啟通信')

    conn,addr=phone.accept()#阻塞
    # s = input('請輸入')
    # conn.send(s.encode('utf-8'))
    print(conn,addr)

#from_client_data.upper()b

    while 1:
        try:
            from_client_data = conn.recv(1024)  # 至多接受1024個字節 阻塞
            if from_client_data.upper()==b'Q':
                print('客戶正常退出了')
            print(f'來自可無端的消息:{from_client_data.decode("utf-8")}')
            from_server_data=input('》》').strip().encode('utf-8')
            conn.send(from_server_data)
        except ConnectionResetError:
            print('客戶端強行斷開了')
            break


遠程命令

傳輸的就是cmd是 gbk類型的base

半連接池 listen

9.tcp 實例:遠程執行命令

subprocesss#遠程執行命令

# shell: 命令解釋器,相當於調用cmd 執行指定的命令。
# stdout:正確結果丟到管道中。
# stderr:錯了丟到另一個管道中。
# windows操作系統的默認編碼是gbk編碼。
#server端
import socket#引用socket模塊
import subprocess#引用遠程執行模塊
import struct
server_config=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#執行tcp默認不寫
server_config.bind(('127.0.0.1',8848))#寫一個地址
while 1:
    server_config.listen(5)
    print('開始通信')
    conn,addr=server_config.accept()
    while 1:
        try:
    		form_client_data=conn.recv(1024)
            if form_client_data.upper()==b'Q':
                #設置一個退出的按鈕
                print('客戶端退出了')
            else:
            	obj=subprocess.Popen(form_client_data.encode('utf-8'),
                                    shell=True,
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE,
                                    )
                result=obj.stdout.read()+obj.stderr.subprocess.read()
                #拼接到一起進行輸出 如果輸入的是錯的正確的命令為空,如果輸入的是正確的 錯誤的信息為空
                total_size=len(result)
                head_bytes=struct.pack('i',total_size)
                conn.send(head_bytes)
                conn.send(result)
          except ConnectionAbortedError:
            print(f'客戶端{addr}斷開')
            break
   conn.close()
server_config.close()
#client #客戶端
import socket
import struct
client_cofig=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#AF_INET#SOCK_STREAM
client_cofig.connect(('127.0.0.1',8848))
while 1:
    s=input('1212')
    client_cofig.send(s.encode('utf-8'))
    head.client=client_cogif.recv(4)
    total.size=struct.unpack('i',head.client)
    total.ppt=b''
    while len(total.ppt)<total.size:
        total.ppt+=client.recv(1024)
    print(total.ppt.decode('gbk'))
client_cofig.close()

10.粘包現象

11.操作系統的緩存區

1.為什么出現粘包

緩沖區

緩沖區優點:

1.暫時存儲一些數據

緩沖區

緩沖區存在 如果你的網絡波動,保證數據的收發穩定,勻速

緩沖區一般大小是8k

缺點:造成了粘包現象之一

防止數據丟失

不會一次性發很多 緩沖區滿了就先不發

沒收完的東西存儲在了緩沖區里 等下次取直接取 因為取的時間比發的時間快

send完直接recv從緩沖區取 取出剩余

12.什么情況下出現粘包

1.出現粘包的情況

粘包只會出現在tcp中

2.收發的本質

不一定要一收一發

​ 1.連續短暫的send多次(數據量很小),數據會統一發送出去(因為會等緩沖區滿了之后封包才發送出去s)

會有一個停留 nigle算法 連續send多次

#client
# import socket
#
# phone = socket.socket()
#
# phone.connect(('127.0.0.1',8848))
#
#
# phone.send(b'he')
# phone.send(b'll')
# phone.send(b'o')
#
#
# phone.close()
# Nigle算法

#server
# import socket
#
# phone = socket.socket()
#
# phone.bind(('127.0.0.1',8848))
#
# phone.listen(5)
#
#
# conn,addr = phone.accept()  # 等待客戶端鏈接我,阻塞狀態中
#
# from_client_data = conn.recv(1024)  # 最多接受1024字節
# print(f'來自客戶端{addr}消息:{from_client_data.decode("utf-8")}')
# conn.close()
# phone.close()


2.send數量過大 send超過限制對方recv接受的數量時 會暫存緩存區。

第二次recv時會將上一次沒有recv完的剩余數據

收發現象

發多次收一次

發一次收多次

13.low解決粘包現象

如何解決 服務端發一次數據 10000字節,

客戶端接受數據時,循環接受,1每次(至多)接收1024個字節直至將所有的字節全部接收完畢,將接收的數據拼接在一起,最后解碼

遇到的問題:recv次數無法確定

​ 你發送總數據之前,先給我發一個總數據的長度:len()

然后在發送總數據

客戶端:先接收一個長度接收字節因為有粘包,50000個字節

encode轉字符串 int轉出數字

struct模塊 加個類似tcp 固定head頭 是固定長度頭 之后解包

然后再循環recv 控制循環條件是反解出來的在服務端制作的長度


14.recv工作原理

'''
源碼解釋:
Receive up to buffersize bytes from the socket.
接收來自socket緩沖區的字節數據,
For the optional flags argument, see the Unix manual.
對於這些設置的參數,可以查看Unix手冊。
When no data is available, block untilat least one byte is available or until the remote end is closed.
當緩沖區沒有數據可取時,recv會一直處於阻塞狀態,直到緩沖區至少有一個字節數據可取,或者遠程端關閉。
When the remote end is closed and all data is read, return the empty string.
關閉遠程端並讀取所有數據后,返回空字符串。
'''
----------服務端------------:
# 1,驗證服務端緩沖區數據沒有取完,又執行了recv執行,recv會繼續取值。

import socket

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

phone.bind(('127.0.0.1',8080))

phone.listen(5)

conn, client_addr = phone.accept()
from_client_data1 = conn.recv(2)
print(from_client_data1)
from_client_data2 = conn.recv(2)
print(from_client_data2)
from_client_data3 = conn.recv(1)
print(from_client_data3)
conn.close()
phone.close()

# 2,驗證服務端緩沖區取完了,又執行了recv執行,此時客戶端20秒內不關閉的前提下,recv處於阻塞狀態。

import socket

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

phone.bind(('127.0.0.1',8080))

phone.listen(5)

conn, client_addr = phone.accept()
from_client_data = conn.recv(1024)
print(from_client_data)
print(111)
conn.recv(1024) # 此時程序阻塞20秒左右,因為緩沖區的數據取完了,並且20秒內,客戶端沒有關閉。
print(222)

conn.close()
phone.close()


# 3 驗證服務端緩沖區取完了,又執行了recv執行,此時客戶端處於關閉狀態,則recv會取到空字符串。

import socket

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

phone.bind(('127.0.0.1',8080))

phone.listen(5)

conn, client_addr = phone.accept()
from_client_data1 = conn.recv(1024)
print(from_client_data1)
from_client_data2 = conn.recv(1024)
print(from_client_data2)
from_client_data3 = conn.recv(1024)
print(from_client_data3)
conn.close()
phone.close()
------------客戶端------------
# 1,驗證服務端緩沖區數據沒有取完,又執行了recv執行,recv會繼續取值。
import socket
import time
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080))
phone.send('hello'.encode('utf-8'))
time.sleep(20)

phone.close()



# 2,驗證服務端緩沖區取完了,又執行了recv執行,此時客戶端20秒內不關閉的前提下,recv處於阻塞狀態。
import socket
import time
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080))
phone.send('hello'.encode('utf-8'))
time.sleep(20)

phone.close()

# 3,驗證服務端緩沖區取完了,又執行了recv執行,此時客戶端處於關閉狀態,則recv會取到空字符串。
import socket
import time
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080))
phone.send('hello'.encode('utf-8'))
phone.close()

15.高大上版 解決粘包方式(自定制包頭)

服務端

import socket
import subprocess
import json
import struct
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

phone.bind(('192.168.14.97',8848))

#開機監聽
while 1:
    phone.listen(2)
    print('開啟通信')

    conn,addr=phone.accept()#阻塞
    # s = input('請輸入')
    # conn.send(s.encode('utf-8'))
    print(conn,addr)

#from_client_data.upper()b

    while 1:
        try:
            from_client_data = conn.recv(1024)  # 至多接受1024個字節 阻塞
            if from_client_data.upper()==b'Q':
                print('客戶正常退出了')
                break
            else:
                # print(f'來自可無端的消息:{from_client_data.decode("utf-8")}')
                # from_server_data=input('》》').strip().encode('utf-8')
                #
                obj = subprocess.Popen(from_client_data.decode('utf-8'),
                                       shell=True,
                                       stdout=subprocess.PIPE,#正確命令
                                       stderr=subprocess.PIPE,# 錯誤命令

                                       )

                # s= # 正確命令
                # s2=obj.stderr.read().decode('gbk')  # 錯誤命令
                total_base=obj.stdout.read()+obj.stderr.read()#原本就是gbk格式的
                total_size=len(total_base)
                #1.制作自定義報頭
                head_dic={'file_name':'text1',
                'md5':54645898788,
                'total_size':total_size}
                #2.json形式的報頭
                head_dic_json=json.dumps(head_dic)
                #3.bytes形式包頭(把json形式字符串改成字節)
                head_dic_json_bytes = json.dumps(head_dic).encode('utf-8')
                #4獲取bytes的總字節數(int類型)
                len_head_dic_json_bytes=len(head_dic_json_bytes)
                #5將不固定的int總字節變成固定長度的4個字節
                four_head_bytes=struct.pack('i',len_head_dic_json_bytes)
                #6 發送固定的4個字節
                conn.send(four_head_bytes)
                #7發送包頭數據
                conn.send( head_dic_json_bytes)
                #8發送總數據
                conn.send(total_base)
        except ConnectionResetError:
            print('客戶端強行斷開了')
            break
    conn.close()
phone.close()

客戶端

import socket
import struct
import json
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
try:
    phone.connect(('192.168.14.97', 8848))
except ConnectionRefusedError:
    exit('服務端沒開')

while 1:
    try:
        data=input('強輸入')
        if not data:
            print('不能為空')
        elif data.upper() == 'Q':
            print('您退出了')
            break
        else:
            phone.send(data.encode('utf-8'))
            #獲取的包頭固定數據字節
            from_server_data = phone.recv(4)
            #轉換獲取的字節(變成int類型的長度)
            len_totals=struct.unpack('i',from_server_data)[0]
            #獲得bytes類型字典的總字節數
            len_totals_bytes=phone.recv(len_totals)
            #獲取betes的dic數據(變成json字符串類型)1
            len_totals_bytes_json=len_totals_bytes.decode('utf-8')
            #解開json字符串獲取原類型
            len_totals_json_dic=json.loads(len_totals_bytes_json)
            #通過自定義包頭的里面的長度獲取數據
            total_ppt=b''
            while len(total_ppt)< len_totals_json_dic['total_size']:
                total_ppt+=phone.recv(1024)
            print(f'來自服務的消息:{total_ppt.decode("gbk")}')
            print(f'來自服務的消息:{len(total_ppt)}')
    except ConnectionResetError:
        print('服務端崩潰了連不上')
        break
    except ConnectionAbortedError:
        pass



phone.close()


免責聲明!

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



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