python必坑知識點(網絡編程)
1 基礎
1.1 作業題1
# 1. 簡述 二層交換機 & 路由器 & 三層交換機 的作用。
"""
二層交換機:構建局域網并實現局域網內數據的轉發。
路由器:實現跨局域網進行通信。
三層交換機:具備二層交換機和路由的功能。
"""
# 2. 簡述常見詞:IP、子網掩碼、DHCP、公網IP、端口、域名的作用。
"""
IP,本質上是一個32位的二進制,通過 . 等分為了4組8位二進制。
子網掩碼,用于指定IP的網絡地址和主機地址。
DHCP,網絡設備中的一個服務,用于給接入當前網絡的電腦自動設置 IP、子網掩碼、網關。(動態分配IP、子網掩碼、網關)
公網IP,一般企業拉專線時會給固定的公網IP,只有具備公網IP才可以被互聯網上的其他電腦訪問。
端口,IP用于表示某臺電腦,端口則表示此電腦上的具體程序。0-65535
域名,與IP構造對應關系,方便用戶記憶。
"""
2 TCP和UDP
2.1 基礎
# IP可復用
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 非阻塞
server.setblocking(False)
# TCP和UDP
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
2.1.1 TCP實例
-
server
import os import json import socket import struct def recv_data(conn, chunk_size=1024): # 獲取頭部信息:數據長度 has_data_len = 0 bytes_data = b'' while has_data_len < 4: chunk = conn.recv(4 - has_data_len) has_data_len += len(chunk) bytes_data += chunk data_length = struct.unpack('i', bytes_data)[0] # 獲取數據 data_list = [] has_read_data_size = 0 while has_read_data_size < data_length: size = chunk_size if (data_length - has_read_data_size) > chunk_size else data_length - has_read_data_size chunk = conn.recv(size) data_list.append(chunk) has_read_data_size += len(chunk) data = b"".join(data_list) return data def recv_file(conn, save_file_name, chunk_size=1024): save_file_path = os.path.join('files', save_file_name) # 獲取頭部信息:數據長度 has_read_size = 0 bytes_list = [] while has_read_size < 4: chunk = conn.recv(4 - has_read_size) bytes_list.append(chunk) has_read_size += len(chunk) header = b"".join(bytes_list) data_length = struct.unpack('i', header)[0] # 獲取數據 file_object = open(save_file_path, mode='wb') has_read_data_size = 0 while has_read_data_size < data_length: size = chunk_size if (data_length - has_read_data_size) > chunk_size else data_length - has_read_data_size chunk = conn.recv(size) file_object.write(chunk) file_object.flush() has_read_data_size += len(chunk) file_object.close() def run(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # IP可復用 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(('127.0.0.1', 8001)) sock.listen(5) while True: conn, addr = sock.accept() while True: # 獲取消息類型 message_type = recv_data(conn).decode('utf-8') if message_type == 'close': # 四次揮手,空內容。 print("關閉連接") break # 文件:{'msg_type':'file', 'file_name':"xxxx.xx" } # 消息:{'msg_type':'msg'} message_type_info = json.loads(message_type) if message_type_info['msg_type'] == 'msg': data = recv_data(conn) print("接收到消息:", data.decode('utf-8')) else: file_name = message_type_info['file_name'] print("接收到文件,要保存到:", file_name) recv_file(conn, file_name) conn.close() sock.close() if __name__ == '__main__': run() -
client
import os import json import socket import struct def send_data(conn, content): data = content.encode('utf-8') header = struct.pack('i', len(data)) conn.sendall(header) conn.sendall(data) def send_file(conn, file_path): file_size = os.stat(file_path).st_size header = struct.pack('i', file_size) conn.sendall(header) has_send_size = 0 file_object = open(file_path, mode='rb') while has_send_size < file_size: chunk = file_object.read(2048) conn.sendall(chunk) has_send_size += len(chunk) file_object.close() def run(): client = socket.socket() client.connect(('127.0.0.1', 8001)) while True: """ 請發送消息,格式為: - 消息:msg|你好呀 - 文件:file|xxxx.png """ content = input(">>>") # msg or file if content.upper() == 'Q': send_data(client, "close") break input_text_list = content.split('|') if len(input_text_list) != 2: print("格式錯誤,請重新輸入") continue message_type, info = input_text_list # 發消息 if message_type == 'msg': # 發消息類型 send_data(client, json.dumps({"msg_type": "msg"})) # 發內容 send_data(client, info) # 發文件 else: file_name = info.rsplit(os.sep, maxsplit=1)[-1] # 發消息類型 send_data(client, json.dumps({"msg_type": "file", 'file_name': file_name})) # 發內容 send_file(client, info) client.close() if __name__ == '__main__': run()
2.1.2 UDP實例
-
server
import socket server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) server.bind(('127.0.0.1', 8002)) while True: data, (host, port) = server.recvfrom(1024) # 阻塞 print(data, host, port) server.sendto("好的".encode('utf-8'), (host, port)) -
client
import socket client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) while True: text = input("請輸入要發送的內容:") if text.upper() == 'Q': break client.sendto(text.encode('utf-8'), ('127.0.0.1', 8002)) data, (host, port) = client.recvfrom(1024) print(data.decode('utf-8')) client.close()
2.2 三次握手,四次揮手
三次握手情景:
1、客戶端主機說:“我可以給你發送數據嗎?”
2、服務器說:“可以的,不過我可能也會給你發送數據。”
3、客戶端:“好,那我開始互相發送數據吧。”
四次揮手的情景大致是這樣的:
1、客戶端主機說:“我沒有數據了,斷開連接吧。 ”
2、服務器說:“好,但是我還有數據(不斷給客戶端發送數據,此時客戶端已經不能給服務端發送數據了,但是必須要就收服務端發來的數據)。”
3、(當服務端給客戶端發完數據后)服務端說:“我發完了,斷開連接吧。”
4、客戶端說:“好,斷開連接吧。”
2.3 OSI七層模型

2.4 對比
| UDP | TCP | |
|---|---|---|
| 是否連接 | 無連接 | 面向連接 |
| 是否可靠 | 不可靠傳輸,不使用流量控制和擁塞控制 | 可靠傳輸,使用流量控制和擁塞控制 |
| 連接對象個數 | 支持一對一,一對多,多對一和多對多交互通信 | 只能是一對一通信 |
| 傳輸方式 | 面向報文 | 面向字節流 |
| 首部開銷 | 首部開銷小,僅8字節 | 首部最小20字節,最大60字節 |
| 適用場景 | 適用于實時應用(IP電話、視頻會議、直播等) | 適用于要求可靠傳輸的應用,例如文件傳輸 |
3 粘包
當客戶端向服務端放松數據時,服務端由于網絡等原因,無法迅速的接受到數據,這時如果客戶端連續的發送多條數據,服務端接收時無法區分客戶端發來幾條數據,而是直接接收全部(數據量如果小的話),這時客戶端發送的幾條消息就會被服務端當成一條數據接收。
處理:
客戶端每次發送數據時,先把數據的長度按固定的字節數發送到服務端,服務端在接收時,先按固定的字節數把客戶端要發送的數據的長度獲取一下,之后再根據數據長度獲取客戶端發來的數據。
4 阻塞和非阻塞
server.setblocking(False) # 加上就變為了非阻塞
如果代碼變成了非阻塞,程序運行時一旦遇到 `accept`、`recv`、`connect` 就會拋出 BlockingIOError 的異常。
5 IO多路復用
IO多路復用 + 非阻塞,可以實現讓TCP的服務端同時處理多個客戶端的請求
"""
優點:
1. 干點那其他的事。
2. 讓服務端支持多個客戶端同時來連接。
"""
# ################### socket服務端 ###################
import select
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setblocking(False) # 加上就變為了非阻塞
server.bind(('127.0.0.1', 8001))
server.listen(5)
inputs = [server, ] # socket對象列表 -> [server, 第一個客戶端連接conn ]
while True:
# 當 參數1 序列中的socket對象發生可讀時(accetp和read),則獲取發生變化的對象并添加到 r列表中。
# r = []
# r = [server,]
# r = [第一個客戶端連接conn,]
# r = [server,]
# r = [第一個客戶端連接conn,第二個客戶端連接conn]
# r = [第二個客戶端連接conn,]
r, w, e = select.select(inputs, [], [], 0.05)
for sock in r:
# server
if sock == server:
conn, addr = sock.accept() # 接收新連接。
print("有新連接")
# conn.sendall()
# conn.recv("xx")
inputs.append(conn)
else:
data = sock.recv(1024)
if data:
print("收到消息:", data)
else:
print("關閉連接")
inputs.remove(sock)
# 干點其他事 20s
# ################### socket客戶端 ###################
import socket
client = socket.socket()
# 阻塞
client.connect(('127.0.0.1', 8001))
while True:
content = input(">>>")
if content.upper() == 'Q':
break
client.sendall(content.encode('utf-8'))
client.close()

浙公網安備 33010602011771號