一、基于tcp協定的套接字(通信循環+連結循環)
服務端應該遵循:
1.綁定一個固定的ip和port
2.一直對外提供服務,穩定運作
3.能夠支援并發
基礎版套接字:

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收到空時,一直在等待。
解決方法:
1.異常捕獲
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()

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()
連結循環:伺服器改進

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()
模拟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()

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()
recv其實是和本地計算機要資料,是以解碼的時候應該用gbk格式
二、粘包
注:隻有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()

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()
補充:struct子產品
該子產品可以幫一個類型,如數字,轉成固定長度的bytes
1、 struct.pack
struct.pack用于将Python的值根據格式符,轉換為字元串(因為Python中沒有位元組(Byte)類型,可以把這裡的字元串了解為位元組流,或位元組數組)。其函數原型為:struct.pack(fmt, v1, v2, ...),參數fmt是格式字元串,關于格式字元串的相關資訊在下面有所介紹。v1, v2, ...表示要轉換的python值。
2、 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
焚膏油以繼晷,恒兀兀以窮年。