1. 程式人生 > 其它 >學習python,從入門到放棄(32)

學習python,從入門到放棄(32)

學習python,從入門到放棄(32)

socket套接字

當我們需要編寫實現資料互動的程式時,需要編寫程式碼操作OSI七層,相當的複雜。但是由於操作OSI七層是所有cs架構的程式都需要經歷的過程,所以有固定的模組。

socket 模組提供了快捷方式,不需要自己處理每一層。

socket模組

cs架構的軟體無論是在編寫還是執行,都應該先考慮服務端,因為服務端是直接影響客戶端的各種功能的,沒有服務端客戶端就是一個空殼。

服務端程式碼

import socket

server = socket.socket()  # 產生一個socket物件
"""
通過檢視原始碼得知 
括號內不寫引數預設就是基於網路的遵循TCP協議的套接字
"""
server.bind(('127.0.0.1', 8080))  # 設定服務端地址方便客戶端進行連線
"""
服務端應該具備的特徵
    固定的地址
    ...  
127.0.0.1是計算機的本地迴環地址 只有當前計算機本身可以訪問
"""
server.listen(5)  # 建立半連線池
sock, addr = server.accept()  # 等待客戶端進行連線,否則程式將在此阻斷
"""
listen和accept對應TCP三次握手服務端的兩個狀態
"""
print(addr)  # 輸出客戶端的地址
data = sock.recv(1024)  # 接收客戶端傳送的內容
print(data.decode('utf8'))  # 轉化為utf8格式輸出
sock.send('你好啊'.encode('utf8'))  # 向客戶端傳送內容
"""
recv和send接收和傳送的都是bytes型別的資料,所以要進行轉化
"""
sock.close()  # 關閉連線
server.close()  # 關閉服務端

客戶端程式碼

import socket

client = socket.socket()  # 產生一個socket物件
client.connect(('127.0.0.1', 8080))  # 根據服務端的地址連結

client.send(b'hello sweet heart!!!')  # 給服務端傳送訊息
data = client.recv(1024)  # 接收服務端回覆的訊息
print(data.decode('utf8'))

client.close()  # 關閉客戶端

服務端與客戶端首次互動,一邊是 recv 那麼另一邊必須是 send ,否則無法連線。

通訊迴圈

  1. 利用 input 獲取使用者輸入用以訊息固定
  2. 將雙方用於資料互動的程式碼迴圈起來解決通訊迴圈的問題

客戶端程式碼:

import socket

server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
sock, addr = server.accept()
while True:
    data = sock.recv(1024)
    print(data.decode('utf8'))
    msg = input('請回復訊息>>>:').strip()
    if msg == 'q':  # 退出通訊
        break
    sock.send(msg.encode('utf8'))
sock.close()
server.close()

服務端程式碼:

import socket

client = socket.socket()  # 產生一個socket物件
client.connect(('127.0.0.1', 8080))  # 根據服務端的地址連結

while True:
    msg = input('請輸入你需要傳送的訊息>>>:').strip()
    if msg == 'q':  # 退出通訊
        break
    client.send(msg.encode('utf8'))  # 給服務端傳送訊息
    data = client.recv(1024)  # 接收服務端回覆的訊息
    print(data.decode('utf8'))

client.close()  # 關閉客戶端

程式碼優化及連結迴圈

  1. 統計長度並判斷,解決傳送訊息不能為空。

  2. 反覆重啟服務端可能會報錯:address in use,使用模組

    from socket import SOL_SOCKET,SO_REUSEADDR
    

    並在 bind 前加

    server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    
  3. 客戶端如果異常斷開,服務端程式碼應該重新回到accept等待新的客人

服務端程式碼:

import socket

server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
    sock, addr = server.accept()
    print(addr)  # 客戶端的地址
    while True:
        try:
            data = sock.recv(1024)
            if len(data) == 0:
                break
            print(data.decode('utf8'))
            msg = input('請回復訊息>>>:').strip()
            if len(msg) == 0:
                msg = '太忙 暫無訊息'
            sock.send(msg.encode('utf8'))
        except Exception:
            break

客戶端程式碼:

import socket

client = socket.socket()  # 產生一個socket物件
client.connect(('127.0.0.1', 8080))  # 根據服務端的地址連結

while True:
    msg = input('請輸入你需要傳送的訊息>>>:').strip()
    if len(msg) == 0:  # 使用者如果什麼都不輸 直接回車 那麼不該往下走
        continue
    if msg == 'q':
        break
    client.send(msg.encode('utf8'))  # 給服務端傳送訊息
    data = client.recv(1024)  # 接收服務端回覆的訊息
    print(data.decode('utf8'))

client.close()  # 關閉客戶端

黏包問題

服務端程式碼:

import socket

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

conn, addr = server.accept()
data1 = conn.recv(5)
print(data1)
data2 = conn.recv(5)
print(data2)
data3 = conn.recv(5)
print(data3)

客戶端程式碼

import socket

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

client.send(b'aaaaaasssssssssssd')
client.send(b'hello')
client.send(b'world')

輸出結果:

'''
b'aaaaa'
b'assss'
b'sssss'
'''

黏包指資料與資料之間沒有明確的分界線,導致不能正確的讀取資料。

這個問題的核心原因是因為TCP協議的特點,會將資料量比較小並且時間間隔比較短的資料整合到一起傳送,並且還會受制於 recv 括號內的數字大小。

問題產生的原因其實是因為 recv 括號內我們不知道即將要接收的資料到底多大,如果每次接收的資料我們都能夠精確的知道它的大小,那麼肯定不會出現黏包。

import struct

data1 = 'hello world!'
print(len(data1))  # 12
res1 = struct.pack('i', len(data1))  # 第一個引數是格式 寫i就可以了
print(len(res1))  # 4
ret1 = struct.unpack('i', res1)
print(ret1)  # (12,)

pack 可以將任意長度的數字打包成固定長度。
unpack 可以將固定長度的數字解包成打包之前資料真實的長度。