Python之網(wǎng)絡編程(socket模塊)
-
什么是socket帽衙?
- Socket是應用層與TCP / IP協(xié)議族通信的中間軟件抽象層,它是一組接口贞绵。在設計模式中厉萝,Socket其實就是一個“門面模式”,它把復雜的TCP / IP協(xié)議族隱藏在Socket接口后面榨崩,對用戶來說谴垫,一組簡單的接口就是全部,讓Socket去組織數(shù)據(jù)母蛛,以符合指定的協(xié)議翩剪。
- 基于文件類型的套接字家族:
- 套接字家族的名字:AF_UNIX
Unix一切皆文件,基于文件的套接字調用的就是底層的文件系統(tǒng)來取數(shù)據(jù)彩郊,兩個套接字進程運行在同一機器前弯,可以通過訪問同一個文件系統(tǒng)間接完成通信。
- 套接字家族的名字:AF_UNIX
- 基于網(wǎng)絡類型的套接字家族
- 套接字家族的名字:AF_INET
還有AF_INET6被用于ipv6秫逝,還有一些其他的地址家族恕出,不過,他們要么是只用于某個平臺违帆,要么就是已經(jīng)被廢棄浙巫,或者是很少被使用,或者是根本沒有實現(xiàn)刷后,所有地址家族中的畴,AF_INET是使用最廣泛的一個,Python支持很多種地址家族尝胆,但是由于我們只關心網(wǎng)絡編程丧裁,所以大部分時候我么只使用AF_INET。
- 套接字家族的名字:AF_INET
-
為什么要用socket班巩?
- 使用socket渣慕,我們無需深入理解TCP/UDP協(xié)議,socket已經(jīng)為我們封裝好了抱慌,我們只需要遵循socket的規(guī)則去編程逊桦,寫出的程序自然就是遵循TCP/UDP標準的。
-
基于TCP協(xié)議的socket(無并發(fā))
-
服務端
# tcp是基于可靠鏈接的抑进,必須先啟動服務端强经,然后再啟動客戶端去鏈接服務端 import socket # 由于 socket 模塊中有太多的屬性。因此可以使用'from module import *'語句寺渗。也就是 'from socket import *',這樣我們就把 socket 模塊里的所有屬性都帶到我們的命名空間里了,這樣能大幅減短代碼匿情。 # from socket import * # socket_server = socket(AF_INET,SOCK_STREAM) socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 創(chuàng)建服務器套接字,SOCK_STREAM流式協(xié)議信殊,指的是TCP協(xié)議炬称;SCOK_DGRAM數(shù)據(jù)報協(xié)議,指的是UDP協(xié)議。 socket_server.bind(('127.0.0.1', 8080)) # 把地址綁定到套接字涡拘,只有服務端需要綁定玲躯,IP地址填寫服務器IP,端口是數(shù)字類型鳄乏,1025-65530任選跷车,端口0-1024系統(tǒng)占用 socket_server.listen(5) # 監(jiān)聽鏈接,backlog = 5 表示同一時間能接受5個請求橱野,并不是最大連接數(shù) # 等待連接 conn, client_address = socket_server.accept() # 程序阻塞朽缴,等待連接,有兩個參數(shù),一個連接對象conn水援,一個客戶端地址client_address(包含IP和端口),對象conn是tcp三次握手的產(chǎn)物裹唆,用來收發(fā)消息誓斥,而socket_server對象是專門用來建立連接的 # 收發(fā)消息 msg = conn.recv(1024) # 收消息许帐,有個返回值給msg劳坑,1024是一個最大的限制,表示最多能收 1024 bytes conn.send('Hello!!!'.encode('utf-8')) # 發(fā)消息成畦,網(wǎng)絡中只能傳輸bytes類型 conn.close() # 斷開(關閉)客戶端套接字距芬,回收系統(tǒng)資源。完成TCP四次揮手循帐。 socket_server.close() # 斷開(關閉)服務器套接字框仔,回收系統(tǒng)資源
-
客戶端
import socket socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 創(chuàng)建客戶端套接字,SOCK_STREAM流式協(xié)議拄养,指的是TCP協(xié)議离斩;SCOK_DGRAM數(shù)據(jù)報協(xié)議,指的是UDP協(xié)議银舱。 socket_client.connect(('127.0.0.1', 8080)) # 與服務器建立連接,地址為服務器的IP地址和端口號 socket_client.send('Hello!'.encode('utf-8')) # 發(fā)消息跛梗,注意字符串不能直接直接發(fā)寻馏,需要轉換成二進制 msg = socket_client.recv(1024) # 收消息 socket_client.close()
-
-
加上通信循環(huán)和連接循環(huán)的socket(無并發(fā))(解決服務端不可以循環(huán)接收客戶端信息的問題)
-
服務端
import socket socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 如果什么參數(shù)都不傳,默認就是這個 socket_server.bind(('127.0.0.1', 8080)) socket_server.listen(5) while True: # 加連接循環(huán) conn, client_address = socket_server.accept() while True: # 加通信循環(huán) try: msg = conn.recv(1024) if not msg: break # 針對Linux操作系統(tǒng) print('客戶端:', client_address) conn.send(msg + b'_SB') except ConnectionResetError: break conn.close() socket_server.close()
-
客戶端
import socket socke_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) socke_client.connect(('127.0.0.1', 8080)) while True: msg = input('>>>:') socke_client.send(msg.encode('utf-8')) msg = socke_client.recv(1024) print(msg) socke_client.close()
-
-
遠程執(zhí)行命令的小程序(無并發(fā))
-
服務端
from socket import * import subprocess import struct # Python提供了一個struct模塊來解決str和其他二進制數(shù)據(jù)類型的轉換核偿。struct的pack函數(shù)把任意數(shù)據(jù)類型變成字符串 socket_server = socket(AF_INET, SOCK_STREAM) socket_server.bind(('127.0.0.1', 8080)) socket_server.listen(5) while True: conn, client_address = socket_server.accept() print('正在監(jiān)聽……') while True: try: cmd = conn.recv(1024) if not cmd: break print('開始接收文件……') obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) res_stdout = obj.stdout.read() res_stderr = obj.stderr.read() except ConnectionResetError: break # -*- 解決TCP粘包問題 -*-# # 制作固定長度的報頭 total_size = len(res_stdout) + len(res_stderr) header = struct.pack('i', total_size) # 制作固定長度的報頭 # 發(fā)送報頭 conn.send(header) # conn.send(res_stdout+res_stderr) # 由于TCP的優(yōu)化诚欠,使用了Nagle算法,將多次間隔較小且數(shù)據(jù)量小的數(shù)據(jù)漾岳,合并成一個大的數(shù)據(jù)塊轰绵,然后進行封包,實際上以下兩條命令會合并到一起發(fā)送尼荆。 conn.send(res_stdout) conn.send(res_stderr) conn.close() socket_server.close()
-
客戶端
from socket import * import struct socket_client = socket(AF_INET, SOCK_STREAM) socket_client.connect(('127.0.0.1', 8080)) while True: cmd = input('>>>:') if not cmd: continue # 判斷cmd不為空左腔,if x is not None:continue 是最好的寫法 socket_client.send(cmd.encode('utf-8')) # -*- 解決TCP粘包問題 -*-# # 先收固定長度的報頭 header = socket_client.recv(4) # 解析報頭 total_size = struct.unpack('i', header)[0] # 根據(jù)報頭,收取數(shù)據(jù)捅儒,為防止數(shù)據(jù)過大翔悠,撐爆內存,以小單位循環(huán)收取 recv_size = 0 res = b'' while recv_size < total_size: recv_date = socket_client.recv(1024) res += recv_date recv_size += len(recv_date) # info = socket_client.recv(1024) print(res.decode('gbk')) socket_client.close()
-
-
遠程執(zhí)行命令的小程序(自定義報頭野芒,無并發(fā))
-
服務端
from socket import * import subprocess import struct # Python提供了一個struct模塊來解決str和其他二進制數(shù)據(jù)類型的轉換蓄愁。struct的pack函數(shù)把任意數(shù)據(jù)類型變成字符串 import json socket_server = socket(AF_INET, SOCK_STREAM) socket_server.bind(('127.0.0.1', 8081)) socket_server.listen(5) while True: conn, client_address = socket_server.accept() print('正在監(jiān)聽……') while True: try: cmd = conn.recv(1024) if not cmd: break print('開始接收文件……') obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) res_stdout = obj.stdout.read() res_stderr = obj.stderr.read() except ConnectionResetError: break # 制作報頭 header_dic = {'total_size': len(res_stdout) + len(res_stderr), 'md5': 'xxxxxxxxxxxx', 'filename': 'xxx.py'} header_json = json.dumps(header_dic) # 將字典轉換成字符串 header_bytes = header_json.encode('utf-8') # 將字符串轉換成 # 獲取報頭長度 header_size = len(header_bytes) # 發(fā)送報頭 conn.send(struct.pack('i', header_size)) # conn.send(res_stdout+res_stderr) # 由于TCP的優(yōu)化,使用了Nagle算法狞悲,將多次間隔較小且數(shù)據(jù)量小的數(shù)據(jù)撮抓,合并成一個大的數(shù)據(jù)塊,然后進行封包摇锋,實際上以下兩條命令會合并到一起發(fā)送丹拯。 # 發(fā)送數(shù)據(jù) conn.send(res_stdout) conn.send(res_stderr) conn.close() socket_server.close()
-
客戶端
from socket import * import struct import json socket_client = socket(AF_INET, SOCK_STREAM) socket_client.connect(('127.0.0.1', 8081)) while True: cmd = input('>>>:') if not cmd: continue # 判斷cmd不為空,if x is not None:continue 是最好的寫法 socket_client.send(cmd.encode('utf-8')) # 先收報頭的長度 header_size = struct.unpack('i', socket_client.recv(4))[0] # 接收報頭 header_bytes = socke_client.recv(header_size) # 解析報頭 header_json = header_bytes.decode('utf-8') header_dic = json.loads(header_json) total_size = header_dic['total_size'] # 根據(jù)報頭荸恕,收取數(shù)據(jù)乖酬,為防止數(shù)據(jù)過大,撐爆內存融求,以小單位循環(huán)收取 recv_size = 0 res = b'' while recv_size < total_size: recv_date = socke_client.recv(1024) res += recv_date recv_size += len(recv_date) # info=socket_client.recv(1024) print(res.decode('gbk')) socket_client.close()
-
-
基于UDP的socket
數(shù)據(jù)報協(xié)議(UDP)沒有粘包問題咬像,UDP協(xié)議面向無連接,發(fā)送數(shù)據(jù)生宛,無需對方確認县昂,發(fā)送效率高,但UDP協(xié)議有效傳輸數(shù)據(jù)大小為 512 bytes陷舅,超過這個大小就非常容易丟包倒彰,DNS服務使用UDP協(xié)議,由于這個限制莱睁,導致全球根服務器數(shù)量限制在13臺待讳。
-
服務端
import socket socket_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) socket_server.bind(('127.0.0.1', 8080)) while True: client_msg, client_address = socket_server.recvfrom(1024) socket_server.sendto(client_msg.upper(), client_address)
-
客戶端
import socket socket_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) while True: msg = input('>>>:').strip() socket_client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080)) server_msg, server_address = socket_client.recvfrom(1024) print(server_msg.decode('utf-8'))