python3網路編程之socket,python3網路編程
文章內容:
- socket介紹
- socket參數介紹
- 流程描述
- socket對象內建方法
- 基本socket執行個體
- 通過socket實現簡單ssh並實現接收大資料
socket介紹
socket又稱”通訊端“,應用程式通常通過”通訊端“向網路發出請求或者應答網路請求,使主機間或者一台電腦上的進程可以通訊。
socket起源於Unix,在Unix一切皆檔案的哲學的思想下,socket是一種"開啟—讀/寫—關閉"模式的實現,伺服器和用戶端各自維護一個"檔案",在建立串連開啟後,可以向自己檔案寫入內容供對方讀取或者讀取對方內容,通訊結束時關閉檔案。socket的英文原義是“插槽”或“插座”,就像我們家裡有線電話一樣,如果沒有網線的那個插口,電話是無法通訊的。Socket是實現TCP,UDP協議的介面,便於使用TCP,UDP。
socket參數介紹
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
參數一:地址簇:
1. socket.AF_INET IPv4(預設)
2. socket.AF_INET6 IPv6
3. socket.AF_UNIX 只能夠用於單一的Unix系統處理序間通訊
參數二:Socket類型:
1. 流式socket(SOCK_STREAM)用於TCP通訊(預設)
流式通訊端提供可靠的,連線導向的通訊流;它使用TCP協議,從而保證了資料轉送的正確性和順序性
2. 資料報socket(SOCK_DGRAM)用於UDP通訊
資料通訊端定義了一種不需連線的服務,資料通過相互獨立的報文進行傳輸,是無序的,並且不保證是 可靠的,無差錯的、它使用資料報協議UDP
3. 原始socket(SOCK_RAW)用於新的網路通訊協定實現的測試等原始通訊端,普通的通訊端無法處理ICMP、IGMP等網路報文,而SOCK_RAW可以, 其次,SOCK_RAW也可以處理特殊的IPv4報文;
此外,利用原始通訊端,可以通過IP_HDRINCL通訊端選項由使用者構造IP頭
參數三:協議
0 (預設)與特定的地址家族相關的協議,如果是0,則系統就會根據地址形式格式和通訊端類別,自動選擇一個合適的協議
流程描述
1.伺服器根據地址類型(Ipv4,Ipv6)、socket類型、協議建立socket2.伺服器為socket綁定IP地址和連接埠號碼3.伺服器socket監聽連接埠號碼請求,隨時準備接收用戶端發來的串連,這時候伺服器的socket並沒有被開啟4.客服端建立socket5.客服端開啟socket,根據伺服器IP地址和連接埠號碼試圖串連伺服器socket6.伺服器socket接收到客服端socket請求,被動開啟,開始接收用戶端請求,直到用戶端返回串連資訊。 這時候socket進入阻塞狀態。(所謂阻塞即accept()方法一直等到用戶端返回串連資訊後才返回,開始接收下一個用戶端串連請求)7.用戶端串連成功,向伺服器發送串連狀態資訊8.伺服器accept方法返回,串連成功9.用戶端向socket寫入資訊(或服務端向socket寫入資訊)10.伺服器讀取資訊(用戶端讀取資訊)11.用戶端關閉12.伺服器端關閉
socket對象內建方法
伺服器端:
s.bind() # 綁定地址(host,port)到通訊端, 在AF_INET下,以元組(host,port)的形式表示地址。s.listen() # 開始TCP監聽。backlog指定在拒絕串連之前,作業系統可以掛起的最大串連數量。該值至少為1,大部分應用程式設為5就可以了。s.accept() # 被動接受TCP用戶端串連,(阻塞式)等待串連的到來
用戶端:
s.connect() # 主動初始化TCP伺服器串連,。一般address的格式為元組(hostname,port),如果串連出錯,返回socket.error錯誤。s.connect_ex() # connect()函數的擴充版本,出錯時返回出錯碼,而不是拋出異常
公用用途函數(常用):
s.recv() # 接收TCP資料,資料以字串形式返回,bufsize指定要接收的最大資料量。flag提供有關訊息的其他資訊,通常可以忽略。s.send() # 發送TCP資料,將string中的資料發送到串連的通訊端。傳回值是要發送的位元組數量,該數量可能小於string的位元組大小。s.sendall() # 完整發送TCP資料,完整發送TCP資料。將string中的資料發送到串連的通訊端,但在返回之前會嘗試發送所有資料。# 成功返回None,失敗則拋出異常。s.close()# 關閉通訊端
公用用途函數(不常用):
s.recvform() # 接收UDP資料,與recv()類似,但傳回值是(data,address)。# 其中data是包含接收資料的字串,address是發送資料的通訊端地址。s.sendto() # 發送UDP資料,將資料發送到通訊端,address是形式為(ipaddr,port)的元組,指定遠程地址。# 傳回值是發送的位元組數。s.getpeername() # 返回串連通訊端的遠程地址。傳回值通常是元組(ipaddr,port)。s.getsockname() # 返回通訊端自己的地址。通常是一個元組(ipaddr,port)s.setsockopt(level,optname,value) # 設定給定通訊端選項的值。s.getsockopt(level,optname[.buflen]) # 返回通訊端選項的值。s.settimeout(timeout) # 設定通訊端操作的逾時期,timeout是一個浮點數,單位是秒。值為None表示沒有逾時期。# 一般,逾時期應該在剛建立通訊端時設定,因為它們可能用於串連的操作(如connect())s.gettimeout() # 返回當前逾時期的值,單位是秒,如果沒有設定逾時期,則返回None。s.fileno() # 返回通訊端的檔案描述符。s.setblocking(flag) # 如果flag為0,則將通訊端設為非阻塞模式,否則將通訊端設為阻塞模式(預設值)。# 非阻塞模式下,如果調用recv()沒有發現任何資料,或send()調用無法立即發送資料,那麼將引起socket.error異常。s.makefile() # 建立一個與該通訊端相關連的檔案
基本socket執行個體
伺服器端(SocketServer.py):
import socket# 建立socketserver = socket.socket()# 為socket綁定IP和連接埠號碼server.bind(('localhost', 9999))# 監聽設定連接埠等待用戶端的請求server.listen()print('Waiting for client connect.')# 接受並建立與用戶端的串連,程式在此處開始阻塞,只到有用戶端串連進來...conn, addr = server.accept()print("New connect:", addr)data = conn.recv(1024)print("Accept news:", data)server.close()
用戶端(SocketClinet.py):
import socket# 建立socketclient = socket.socket()# 根據伺服器IP地址和連接埠號碼試圖串連伺服器socketclient.connect(('localhost', 9999))client.send(b'123')client.close()
以上代碼只是實現了伺服器端和用戶端一次互動,那要怎麼實現多次互動呢?
伺服器端支援多次互動(SocketServer.py):
import socket# 建立socketserver = socket.socket()# 為socket綁定IP和連接埠號碼server.bind(('localhost', 9999))# 監聽設定連接埠等待用戶端的請求server.listen()print('Waiting for client connect.')# 接受並建立與用戶端的串連,程式在此處開始阻塞,只到有用戶端串連進來...conn, addr = server.accept()print("New connect:", addr)while True: data = conn.recv(1024) # 判斷伺服器接到的資料是否為空白(避免用戶端一斷開,伺服器端進入死迴圈) if not data: print("Client disconnect.") break print("Accept news:", data) conn.send(data.upper())server.close()
客服端支援多次互動(SocketClient.py):
import socket# 建立socketclient = socket.socket()# 根據伺服器IP地址和連接埠號碼試圖串連伺服器socketclient.connect(('localhost', 9999))while True: msg = input(">>>:").strip() if len(msg) == 0:continue client.send(msg.encode('utf-8')) data = client.recv(1024) print("from server:",data)client.close()
以上代碼雖然實現了伺服器端和用戶端的多次互動,但是用戶端一旦斷開了,伺服器端也會跟著立刻斷開,因為伺服器只有一個while 迴圈,用戶端一斷開,服務端收不到資料 ,就會直接break跳出迴圈,然後程式就退出了,這顯然不是我們想要的結果 ,我們想要的是,用戶端如果斷開了,我們這個服務端還可以為下一個用戶端服務。
Socket實現多串連處理:
import socket# 建立socketserver = socket.socket()# 為socket綁定IP和連接埠號碼server.bind(('localhost', 9999))# 監聽設定連接埠等待用戶端的請求server.listen()while True: print('Waiting for client connect.') # 接受並建立與用戶端的串連,程式在此處開始阻塞,只到有用戶端串連進來... conn, addr = server.accept() print("New connect:", addr) while True: data = conn.recv(1024) # 判斷伺服器接到的資料是否為空白(避免用戶端一斷開,伺服器端進入死迴圈) if not data: print("Client disconnect.") break print("Accept news:", data) conn.send(data.upper())server.close()
View Code
PS:此時伺服器端依然只能同時為一個客戶服務,其客戶來了,得排隊(串連掛起)。
通過socket實現簡單ssh並實現接收大資料
其實在接收大資料的時候會引入一個重要的概念”粘包“,即伺服器端你調用send兩次,當你send調用時,資料其實並沒有立刻被發送給用戶端,而是放到了系統的socket發送緩衝區裡,等緩衝區滿了,或者資料等待逾時了,資料才會被send到用戶端,這樣就把好幾次的小資料拼成一個大資料,統一發送到用戶端了,這麼做的目地是為了提高io利用效率,一次性發送總比連發好幾次效率高嘛。 但也帶來一個問題,就是“粘包”,即2次或多次的資料粘在了一起統一發送了。
我們在這裡必須要想辦法把粘包分開, 因為不分開,你就沒辦法取出來伺服器端返回的命令執行結果的大小呀。so ,那怎麼分開呢?首先你是沒辦法讓緩衝區強制重新整理把資料發給用戶端的。 你能做的,只有一個。就是,讓緩衝區逾時,逾時了,系統就不會等緩衝區滿了,會直接把資料發走,因為不能一個勁的等後面的資料呀,等太久,會造成資料延遲了,那可是極不好的。so如果讓緩衝區逾時呢?
解決方案如下:
伺服器端每發送一個資料給用戶端,就立刻等待用戶端進行回應,即調用 conn.recv(1024), 由於recv在接收不到資料時是阻塞的,這樣就會造成,伺服器端接收不到用戶端的響應,就不會執行後面的conn.sendall(命令結果)的指令,收到用戶端響應後,再發送命令結果時,緩衝區就已經被清空了,因為上一次的資料已經被強制發到用戶端了。
伺服器端:
import socket,os,timeserver = socket.socket()server.bind(('localhost', 9999))server.listen()while True: print('Waiting for client connect.') conn, addr = server.accept() print("new conn:", addr) while True: print("等待新指令") data = conn.recv(1024) if not data: print("用戶端已斷開") break print("執行命令:", data) cmd_res = os.popen(data.decode()).read() # 接受字串,執行結果也是字串 print("before send", len(cmd_res)) if len(cmd_res) == 0: cmd_res = "cmd has no output..." conn.send( str(len(cmd_res.encode())).encode("utf-8") ) # 先發大小給用戶端 # 為了防止粘包 # time.sleep(0.5) client_ack = conn.recv(1024) # wait client to confirm print("ack from client:", client_ack) conn.send(cmd_res.encode("utf-8")) print("send done")server.close()
用戶端:
import socketclient = socket.socket()client.connect(('localhost', 9999))while True: cmd = input(">>>:").strip() if len(cmd) == 0: continue client.send(cmd.encode("utf-8")) cmd_res_size = client.recv(1024) # 接受命令結果長度 print("命令結果大小:", cmd_res_size) client.send("準備好接受了,loser可以發了".encode("utf-8")) received_size = 0 received_data = b'' while received_size < int(cmd_res_size.decode()): data = client.recv(1024) received_size += len(data) # 每次收到的有可能小於1024,所以必須用len判斷 # print(data.decode()) received_data += data else: print("cmd res receive done...", received_size) print(received_data.decode())client.close()