socket補充:通信循環、鏈接循環、遠程操作及黏包現象
socket通信循環
server端:
import socketphone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)phone.bind(('127.0.0.1',8080))phone.listen(5)conn, client_addr = phone.accept()
print(conn, client_addr, sep='\n')while 1: # 循環收發消息try:from_client_data = conn.recv(1024)print(from_client_data.decode('utf-8'))conn.send(from_client_data + b'SB')except ConnectionResetError:breakconn.close()
phone.close()
client端:
import socketphone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 買電話phone.connect(('127.0.0.1',8080)) # 與客戶端建立連接, 撥號while 1: # 循環收發消息client_data = input('>>>')phone.send(client_data.encode('utf-8'))from_server_data = phone.recv(1024)print(from_server_data.decode('utf-8'))phone.close() # 掛電話
socket通信鏈接循環
server端:
import socketphone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)phone.bind(('127.0.0.1',8080))phone.listen(5)while 1 : # 循環連接客戶端conn, client_addr = phone.accept()print(client_addr)while 1:try:from_client_data = conn.recv(1024)print(from_client_data.decode('utf-8'))conn.send(from_client_data + b'SB')except ConnectionResetError:breakconn.close()
phone.close()
服務端:
import socketphone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 買電話phone.connect(('127.0.0.1',8080)) # 與客戶端建立連接, 撥號while 1:client_data = input('>>>')phone.send(client_data.encode('utf-8'))from_server_data = phone.recv(1024)print(from_server_data.decode('utf-8'))phone.close() # 掛電話
socket遠程操作
import subprocessobj = subprocess.Popen('dir1',shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,)print(obj.stdout.read().decode('gbk')) # 正確命令
print(obj.stderr.read().decode('gbk')) # 錯誤命令
服務端:
import socket
import subprocess
phone = socket.socket()phone.bind(('127.0.0.1',8848))phone.listen(2)
# listen: 2 允許有兩個客戶端加到半鏈接池,超過兩個則會報錯while 1:conn,addr = phone.accept() # 等待客戶端鏈接我,阻塞狀態中print(f'鏈接來了: {conn,addr}')while 1:try:from_client_data = conn.recv(1024) # 最多接受1024字節if from_client_data.upper() == b'Q':print('客戶端正常退出聊天了')breakobj = subprocess.Popen(from_client_data.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,)result = obj.stdout.read() + obj.stderr.read()conn.send(result)except ConnectionResetError:print('客戶端鏈接中斷了')breakconn.close()
phone.close()# shell: 命令解釋器,相當于調用cmd 執行指定的命令。
# stdout:正確結果丟到管道中。
# stderr:錯了丟到另一個管道中。
# windows操作系統的默認編碼是gbk編碼。
客戶端:
import socketphone = socket.socket()phone.connect(('127.0.0.1',8848))
while 1:to_server_data = input('>>>輸入q或者Q退出').strip().encode('utf-8')if not to_server_data:# 服務端如果接受到了空的內容,服務端就會一直阻塞中,所以無論哪一端發送內容時,都不能為空發送print('發送內容不能為空')continuephone.send(to_server_data)if to_server_data.upper() == b'Q':breakfrom_server_data = phone.recv(1024) # 最多接受1024字節print(f'{from_server_data.decode("gbk")}')phone.close()
什么叫做黏包現象?為什么會出現黏包現象?
socket收發消息的原理
應用程序所看到的數據是一個整體,或說是一個流(stream),一條消息有多少字節對應用程序是不可見的,因此TCP協議是面向流的協議,這也是容易出現粘包問題的原因。
而UDP是面向消息的協議,每個UDP段都是一條消息,應用程序必須以消息為單位提取數據,不能一次提取任意字節的數據,這一點和TCP是很不同的。怎樣定義消息呢?
可以認為對方一次性write/send的數據為一個消息,需要明白的是當對方send一條信息的時候,無論底層怎樣分段分片,TCP協議層會把構成整條消息的數據段排序完成后才呈現在內核緩沖區。
設置緩沖區的兩個好處:
- 暫時存儲一些數據.
- 緩沖區存在如果你的網絡波動,保證數據的收發穩定,勻速.
所謂粘包問題主要還是因為接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所造成的。
只有TCP有粘包現象,UDP永遠不會粘包
黏包的兩種情況:
1,接收方沒有及時接收緩沖區的包,造成多個包接收(客戶端發送了一段數據,服務端只收了一小部分,服務端下次再收的時候還是從緩沖區拿上次遺留的數據,產生粘包)
server端:
import socket
import subprocessphone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)phone.bind(('127.0.0.1', 8080))phone.listen(5)while 1: # 循環連接客戶端conn, client_addr = phone.accept()print(client_addr)while 1:try:cmd = conn.recv(1024)ret = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)correct_msg = ret.stdout.read()error_msg = ret.stderr.read()conn.send(correct_msg + error_msg)except ConnectionResetError:breakconn.close()
phone.close()
client端:
import socketphone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 買電話phone.connect(('127.0.0.1',8080)) # 與客戶端建立連接, 撥號while 1:cmd = input('>>>')phone.send(cmd.encode('utf-8'))from_server_data = phone.recv(1024)print(from_server_data.decode('gbk'))phone.close()
2、發送端需要等緩沖區滿才發送出去,造成粘包(發送數據時間間隔很短,數據也很小,會合到一起,產生粘包)
server端:
import socketphone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)phone.bind(('127.0.0.1', 8080))phone.listen(5)conn, client_addr = phone.accept()frist_data = conn.recv(1024)
print('1:',frist_data.decode('utf-8')) # 1: helloworld
second_data = conn.recv(1024)
print('2:',second_data.decode('utf-8'))conn.close()
phone.close()
客戶端:
import socketphone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) phone.connect(('127.0.0.1', 8080)) phone.send(b'hello')
phone.send(b'world')phone.close() # 兩次返送信息時間間隔太短,數據小,造成服務端一次收取
如何解決黏包現象?
struct模塊
該模塊可以把一個類型,如數字,轉成固定長度的bytes
import struct
# 將一個數字轉化成等長度的bytes類型。
ret = struct.pack('i', 183346)
print(ret, type(ret), len(ret))# 通過unpack反解回來
ret1 = struct.unpack('i',ret)[0]
print(ret1, type(ret1), len(ret1))# 但是通過struct 處理不能處理太大ret = struct.pack('l', 4323241232132324)
print(ret, type(ret), len(ret)) # 報錯
方案一:low版。
問題的根源在于,接收端不知道發送端將要傳送的字節流的長度,所以解決粘包的方法就是圍繞,如何讓發送端在發送數據前,把自己將要發送的字節流總數按照固定字節發送給接收端后面跟上總數據,然后接收端先接收固定字節的總字節流,再來一個死循環接收完所有數據。
server端:
import socket
import subprocess
import struct
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)phone.bind(('127.0.0.1', 8080))phone.listen(5)while 1:conn, client_addr = phone.accept()print(client_addr)while 1:try:cmd = conn.recv(1024)ret = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)correct_msg = ret.stdout.read()error_msg = ret.stderr.read()# 1 制作固定報頭total_size = len(correct_msg) + len(error_msg)header = struct.pack('i', total_size)# 2 發送報頭conn.send(header)# 發送真實數據:conn.send(correct_msg)conn.send(error_msg)except ConnectionResetError:breakconn.close()
phone.close()
client端:
import socket
import struct
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)phone.connect(('127.0.0.1',8080))while 1:cmd = input('>>>').strip()if not cmd: continuephone.send(cmd.encode('utf-8'))# 1,接收固定報頭header = phone.recv(4)# 2,解析報頭total_size = struct.unpack('i', header)[0]# 3,根據報頭信息,接收真實數據recv_size = 0res = b''while recv_size < total_size:recv_data = phone.recv(1024)res += recv_datarecv_size += len(recv_data)print(res.decode('gbk'))phone.close()