標籤:輸出 自訂 迴圈 stderr status ddr des 目錄 .so
基於UDP的socket
面向不需連線的不可靠資料轉送,可以沒有伺服器端,只不過沒有伺服器端,發送的資料會被直接丟棄,並不能到達伺服器端
1 #用戶端2 import socket3 ip_port=(‘127.0.0.1‘,8080)4 BUFSIZE=10245 sock_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #SOCK_DGRAM就是UDP6 while True:7 msg=input(‘>>‘).strip()8 if not msg:continue9 sock_client.sendto(msg.encode(‘utf-8‘),ip_port) #UDP用的是sendto發送資料
UDP服務端+用戶端
1 #服務端 2 import socket 3 ip_port=(‘127.0.0.1‘,8080) 4 BUFSIZE=1024 5 sock_server=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 6 sock_server.bind(ip_port) 7 #對比TCP,缺少listen偵聽地址,缺少accept等待串連的代碼 8 while True: 9 msg,addr=sock_server.recvfrom(BUFSIZE) #UDP接收資料使用recvfrom接收10 print(‘recv:‘,msg,addr)11 sock_server.sendto(msg.upper(),addr)12 13 #用戶端14 import socket15 ip_port=(‘127.0.0.1‘,8080)16 BUFSIZE=102417 sock_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)18 while True:19 msg=input(‘>>‘).strip()20 if not msg:continue21 sock_client.sendto(msg.encode(‘utf-8‘),ip_port)22 # back_msg,addr=sock_client.recvfrom(BUFSIZE) #一般UDP用於廣播,不會接收資料,如果沒有服務端,啟用該行代碼會出錯23 # print(back_msg.decode(‘utf-8‘),addr)
由於UDP是面向不需連線的(實際上有連結,不然通過什麼去傳資料去取資料),可以使用多個用戶端串連服務端,但這並不是並發訪問。
注意:
1. 發訊息,都是將資料發送到己端的發送緩衝中,收訊息都是從己端的緩衝區中收
tcp:send發訊息,recv收訊息
udp:sendto發訊息,recvfrom收訊息
2. tcp是基於資料流的,而udp是基於資料報的:
send(bytes_data):發送資料流,資料流bytes_data若為空白,自己這段的緩衝區也為空白,作業系統不會控制tcp協議發空包
sendinto(bytes_data,ip_port):發送資料報,bytes_data為空白,還有ip_port,所有即便是發送空的bytes_data,資料報其實也不是空的,自己這端的緩衝區收到內容,作業系統就會控制udp協議發包。
3.1 tcp協議
(1)如果收訊息緩衝區裡的資料為空白,那麼recv就會阻塞(阻塞很簡單,就是一直在等著收)
(2)只不過tcp協議的用戶端send一個空資料就是真的空資料,用戶端即使有無窮個send空,也跟沒有一個樣。
(3)tcp基於連結通訊
- 基於連結,則需要listen(backlog),指定半串連池的大小
- 基於連結,必須先啟動並執行服務端,然後用戶端發起連結請求
- 對於mac系統:如果一端斷開了連結,那另外一端的連結也跟著完蛋recv將不會阻塞,收到的是空(解決方案是:服務端在收訊息後加上if判斷,空訊息就break掉通訊迴圈)
- 對於windows/linux系統:如果一端斷開了連結,那另外一端的連結也跟著完蛋recv將不會阻塞,收到的是空(解決方案是:服務端通訊迴圈內加異常處理,捕捉到異常後就break掉通訊迴圈)
3.2 udp協議
(1)如果如果收訊息緩衝區裡的資料為“空”,recvfrom也會阻塞
(2)只不過udp協議的用戶端sendinto一個空資料並不是真的空資料(包含:空資料+地址資訊,得到的報仍然不會為空白),所以用戶端只要有一個sendinto(不管是否發送空資料,都不是真的空資料),服務端就可以recvfrom到資料。
(3)udp無連結
- 無連結,因而無需listen(backlog),更加沒有什麼串連池之說了
- 無連結,udp的sendinto不用管是否有一個正在啟動並執行服務端,可以己端一個勁的發訊息,只不過資料丟失
- recvfrom收的資料小於sendinto發送的資料時,在mac和linux系統上資料直接丟失,在windows系統上發送的比接收的大直接報錯
- 只有sendinto發送資料沒有recvfrom收資料,資料丟失
粘包
對昨天ssh的用戶端代碼做點手腳
服務端不動代碼
1 #用戶端動手腳 2 import socket 3 ssh_client=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 4 ssh_client.connect((‘127.0.0.1‘,8080)) 5 while True: #通訊迴圈 6 cmd=input(‘>>: ‘).strip() 7 if not cmd:continue 8 ssh_client.send(cmd.encode(‘utf-8‘)) 9 cmd_res = ssh_client.recv(100) #動手腳位置,將一次接收的資料大小改為100位元組10 print(cmd_res.decode(‘gbk‘)) #windows11 # print(cmd_res.decode(‘utf-8‘)) #linux12 ssh_client.close()
運行服務端後,執行用戶端測試:
1 >>: dir 2 磁碟機 C 中的卷沒有標籤。 3 卷的序號是 5E42-F448 4 5 C:\Users\Mr.chai\Desktop\PythonProject\筆記 6 >>: pwd 7 2017.7.10\通訊端_test 的目錄 8 9 2017/07/11 16:58 <DIR> .10 2017/07/11 16:58 <DIR> 11 >>: pwd12 ..13 2017/07/10 11:04 0 __init__.py14 2017/07/11 16:58 711 客戶15 >>: pwd16 端.py17 2017/07/11 16:03 1,992 服務端.py18 3 個檔案 2,703 位元組19 20 >>: pwd21 2 個目錄 42,335,735,808 可用位元組22 ‘pwd‘ 不是內部或外部命令,也不是可啟動並執行程式23 或批處24 >>:
對比沒動手腳之前:
1 >>: dir 2 磁碟機 C 中的卷沒有標籤。 3 卷的序號是 5E42-F448 4 5 C:\Users\Mr.chai\Desktop\PythonProject\筆記\2017.7.10\通訊端_test 的目錄 6 7 2017/07/11 17:02 <DIR> . 8 2017/07/11 17:02 <DIR> .. 9 2017/07/10 11:04 0 __init__.py10 2017/07/11 17:02 712 用戶端.py11 2017/07/11 16:03 1,992 服務端.py12 3 個檔案 2,704 位元組13 2 個目錄 42,335,076,352 可用位元組14 15 >>: pwd16 ‘pwd‘ 不是內部或外部命令,也不是可啟動並執行程式17 或批次檔。18 19 >>:
What happened?
發生了什麼事?原因是這個樣子。。
首先是socket資料傳送和資料接收的原理:
原理
粘包只會出現在TCP裡
UDP在windows下會提示:
OSError: [WinError 10040] 一個在資料通訊端上發送的訊息大於內部訊息緩衝區或其他一些網路限制,或該使用者用於接收資料報的緩衝區比資料報小
而在linux下會出現丟資料的情況:
>>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaAAAAAAAAAA (‘192.168.1.10‘, 8080)>>
問題出現在接收方,這是因為接收方不知道返回的訊息之間的界限,不知道一次性提取多少位元組的資料所造成的,第一次dir返回的訊息遠遠大於100個位元組,而懂了手腳後變成了一次只能從緩衝中取100位元組,再次取的時候會繼續取緩衝中沒取完的資料
出現粘包的情況:
發送端需要等緩衝區滿才發送出去,造成粘包(發送資料時間間隔很短,資料很小,會合到一起,產生粘包)
接收方不及時接收緩衝區的包,造成多個包接收(用戶端發送了一段資料,服務端只收了一小部分,服務端下次再收的時候還是從緩衝區拿上次遺留的資料,產生粘包)
send(位元組流)和recv(1024)及sendall
解決粘包的lowB方法
粘包的根源是接收端不知道發送端將要傳送的位元組流的長度,那麼接收端提前把自己要發送的位元組流總大小讓接收端知曉,然後接收端來一個死迴圈接收完所有資料
服務端
1 import socket 2 import subprocess 3 ip_addr=(‘127.0.0.1‘,8088) 4 BUFSIZE=1024 5 s_server=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 6 s_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 7 s_server.bind(ip_addr) 8 s_server.listen(5) 9 print(‘run server...‘)10 11 while True:12 conn,addr=s_server.accept()13 print(‘用戶端地址:‘,addr)14 while True:15 try:16 client_res=conn.recv(BUFSIZE)17 if len(client_res.decode(‘utf-8‘)) == 0:continue18 res=subprocess.Popen(client_res.decode(‘utf-8‘),19 shell=True,20 stdout=subprocess.PIPE,21 stderr=subprocess.PIPE)22 stdout=res.stdout.read()23 stderr=res.stderr.read()24 std_bytes=stdout+stderr #標準輸出和標準錯誤組合25 std_size=len(std_bytes) #計算總長度26 conn.send(str(std_size).encode(‘utf-8‘)) #將總長度發給用戶端,用戶端收到該訊息返回一個狀態27 status=conn.recv(BUFSIZE).decode(‘utf-8‘) #將返回來的狀態賦值28 if status: #如果該狀態成立,那麼開始發送所有資料29 conn.send(std_bytes)30 except Exception:31 break32 conn.close()33 s_server.close()
用戶端
1 import socket 2 ip_addr=(‘127.0.0.1‘,8088) 3 BUFSIZE=100 4 s_client=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 5 s_client.connect(ip_addr) 6 while True: 7 cmd=input(‘>>‘).strip() 8 if not cmd:continue 9 s_client.send(cmd.encode(‘utf-8‘))10 11 std_size=int(s_client.recv(BUFSIZE).decode(‘utf-8‘)) #將接收的資料總長度轉換成數字12 s_client.send(‘True‘.encode(‘utf-8‘)) #返回給伺服器端一個狀態True13 res=b‘‘14 get_size=015 while get_size < std_size:16 if (std_size-get_size) < 100: #如果總長度比下載的長度小於定義的100,那麼就取資料的最小值,否則按100取值17 res+=s_client.recv(std_size-get_size)18 else:19 res+=s_client.recv(BUFSIZE)20 get_size+=BUFSIZE #每取一次值加100,最後一次的值肯定大於總長度21 print(res.decode(‘gbk‘))22 s_client.close()
解決粘包的高大上方法-自訂資料頭
該方法是基於上面方法的改良,即在傳輸資料之前,在伺服器端定一個固定長度資料頭部,該資料頭部封裝了一系列關於該資料的資訊,如資料的總長度,或者傳輸檔案資料的使用者資訊、時間資訊等等,用戶端取得整個資料的時候,先取固定長度的資料頭部讀取資訊,按照頭部資訊接收資料
Python開發基礎----socket通訊端基礎2