SOCKET開發!稱之為最實用的技巧!月薪上萬從這裡開始!

一、預備知識

SOCKET開發!稱之為最實用的技巧!月薪上萬從這裡開始!

SOCKET開發!稱之為最實用的技巧!月薪上萬從這裡開始!

發包:

C請求,S同意後並我也要挖隧道,C才可以挖隧道到S。(三次握手)

結束髮包:

C請求,S確認,S請求,C確認(四次揮手)

UDP協議:傳輸不可靠,但不需要建管道,直接按IP發過去

總結:①TCP傳輸可靠,但效率低

②UDP傳輸不可靠,但效率高

SOCKET開發!稱之為最實用的技巧!月薪上萬從這裡開始!

2.2 TCP

SOCKET開發!稱之為最實用的技巧!月薪上萬從這裡開始!

2.2.1 服務端

由上圖可知,服務端需要先建立SOCKET鏈接,首先需要導入socket模塊,並鏈接。

1 import socket2 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

之後就需要綁定(主機,端口號)到套接字,開始監聽。其中綁定時,IP號和端口號是元組,並且端口號是0-65535,但其中0-1024是給操作系統的,使用需要管理員權限。監聽,其中5代表最大鏈接數量。

s.bind(('127.0.0.1',8080))#0-65535:0-1024給操作系統使用s.listen(5)

緊接著,服務器通過一個永久循環來接收來自客戶端的連接,accept()會一直等待,知道客戶端發來信息(暫只考慮單線程情況)。

1 while True:#鏈接循環2 conn,client_addr=s.accept()

接下來就是收發消息了,並需要進行通信循環。

1 #收發消息2 while True:#通信循環3 try:4 data=conn.recv(1024) #1024表示接收數據的最大數,單位是bytes5 print('客戶端的數據',data)6 conn.send(data.upper())7 except ConnectionResetError:8 break9 conn.close()

接下來就是關閉套接字。

1 s.close()

2.2.2 客戶端

首先和服務端一樣,需要先建立SOCKET鏈接,首先需要導入socket模塊,並鏈接。

1 import socket2 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

之後通過(主機IP號,端口號)到套接字連接。

1 s.connect(('127.0.0.1',8080))

之後發收消息,同樣有著通信循環,和服務端相比,由於沒有等待連接,因此少個鏈接循環。

1 #發收消息2 while True:#通信循環3 msg=input('>>').strip()4 phone.send(msg.encode('utf-8'))5 data=phone.recv(1024)6 print(data.decode('utf-8'))

接下來就是關閉套接字。

1 s.close()

2.3 UDP協議

相比TCP協議,UDP是面向無連接的協議,因此使用UDP協議時,不需要建立連接,只需要知道對方的IP地址和端口號,就可以發送數據包,其不管是否發送到達。

和TCP協議類似,也是服務端和客戶端。

2.3.1 服務端

服務端需要先建立SOCKET鏈接,首先需要導入socket模塊,並綁定端口。

1 import socket2 server=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)3 server.bind(('127.0.0.1',8080))

其不需要監聽和連接,即不需要listen()和accept(),而是直接接收來自客戶端的數據。

1 while True:2 data,cliend_addr=server.recvfrom(1024)3 print(data)4 server.sendto(data.upper(),cliend_addr)

最後關閉套接字。

1 server.close()

2.3.2 客戶端

同樣,也需要先建立SOCKET鏈接,首先需要導入socket模塊。

1 import socket 2 client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

但不需要調用connect(),直接通過sendto()給服務端發數據。

1 while True:2 msg=input('>>:').strip()3 data=client.sendto(msg.encode('utf-8'),('127.0.0.1',8080))4 data,server_addr=client.recvfrom(1024)5 print(data,server_addr)

最後關閉套接字。

1 server.close()

2.4 粘包現象及解決方案

2.4.1 粘包現象

何為粘包,在上文中,我們一直使用s.recv(1024)來接收數據,但如果需要接收的數據比1024長,那麼剩餘的數據會在發送端的IO緩衝區暫存下來,等下次接收端來接收數據時,先將緩衝區的數據發送出去,再接收下次的數據。當然,我們可以將1024改為8192,但數據比這個還大呢,我們接收的額定值就不能變大了,還是會發生這樣的事件。因此,這樣的事件我們稱之為粘包現象。當然,粘包現象僅存在於TCP協議中,UDP協議中不存在。

2.4.2 解決方案

粘包問題的根源在於,接收端不知道發送端將要傳送的字節流的長度,所以解決粘包的方法就是圍繞,如何讓發送端在發送數據前,把自己將要發送的字節流總大小讓接收端知曉,然後接收端來一個死循環接收完所有數據。此處,我們就需要藉助於第三方模塊struct。用法為:

 1 import json,struct 2 #為避免粘包,必須製作固定長度的報頭 3 header_dic={'file_size':1073741824,'file_name':'a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #1G文件大小,文件名和md5值 4  5 #為了該報頭能傳送,需要序列化並且轉為bytes,用於傳輸 6 header_json = json.dumps(header_dic) # 轉成字符串類型 7 header_bytes = header_json.encode('utf-8') 8  9 #為了讓客戶端知道報頭的長度,用struck將報頭長度這個數字轉成固定長度:4個字節10 head_len_bytes=struct.pack('i',len(head_bytes)) #這4個字節裡只包含了一個數字,該數字是報頭的長度11 12 #客戶端開始發送報文長度13 conn.send(head_len_bytes) #先發報頭的長度,4個bytes14 #再發報頭的字節格式15 conn.send(head_bytes) 16 #然後發真實內容的字節格式17 conn.sendall(文件內容) 18 19 #服務端開始接收20 head_len_bytes=s.recv(4) #先收報頭4個bytes,得到報頭長度的字節格式21 x=struct.unpack('i',head_len_bytes)[0] #提取報頭的長度22 23 header_bytes=s.recv(x) #按照報頭長度x,收取報頭的bytes格式24 header_str=header_bytes.decode('utf-8')25 header_dic=json.loads(header_str) #提取報頭26 27 #最後根據報頭的內容提取真實的數據,比如數據的長度28 real_data_len=s.recv(header_dic['file_size'])29 s.recv(real_data_len)

因此對於一個文件傳輸:

服務端:

 1 import socket 2 import os 3 import struct 4 import json 5 share_dir=r'C:\Users\。。。\Desktop\python\oldboypython\day6\10文件傳輸\服務端\share' 6  7 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 8 # phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 9 phone.bind(('127.0.0.1',9901)) #0-65535:0-1024給操作系統使用10 phone.listen(5)11 print('starting...')12 while True: # 鏈接循環13 conn,client_addr=phone.accept()14 print(client_addr)15 while True: #通信循環16 try:17 #1、收命令18 res=conn.recv(8096)#b'get a.txt'19 if not res:break #適用於linux操作系統20 #2、解析命令,提取相應的命令參數21 cmds=res.decode('utf-8').split()#['get','a.txt']22 filename=cmds[1]23 24 #3、以讀的方式打開文件,讀取文件內容發送給客戶端25 #3.1 製作固定長度的報頭26 header_dic={27 'filename':filename,28 'md5':'xxdxxx',29 'file_size':os.path.getsize('%s/%s'%(share_dir,filename))30 }31 header_json=json.dumps(header_dic)#轉成字符串類型32 header_bytes=header_json.encode('utf-8')33 34 #3.2 先發送報頭的長度35 conn.send(struct.pack('i',len(header_bytes)))36 37 #3.3 再發報頭38 conn.send(header_bytes)39 40 #3.4 發真實的數據41 # conn.send(stdout+stderr) #+是一個可以優化的點42 with open('%s/%s'%(share_dir,filename),'rb') as f:43 # conn.send(f.read())44 for line in f:45 conn.send(line)46 except ConnectionResetError: #適用於windows操作系統47 break48 conn.close()49 50 phone.close()文件傳輸服務端

客戶端:

 1 import socket 2 import struct 3 import json 4  5 download_dir=r'C:\Users\。。。\Desktop\python\oldboypython\day6\10文件傳輸\客戶端\download' 6 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 7 phone.connect(('127.0.0.1',9901)) 8 while True: 9 #1、發命令10 cmd=input('>>: ').strip() #get a.txt11 if not cmd:continue12 phone.send(cmd.encode('utf-8'))13 #2、接收文件的內容,以寫的方式打開新文件,接收服務端發來的文件的內容寫入客戶端的新文件14 #2.1 先收報頭的長度15 obj=phone.recv(4)16 header_size=struct.unpack('i',obj)[0]17 #2.2 在收報頭18 header_bytes=phone.recv(header_size)19 #2.3 從包頭中解析出對真實數據的描述的信息20 header_json=header_bytes.decode('utf-8')21 header_dic=json.loads(header_json)22 print(header_dic)23 total_size=header_dic['file_size']24 file_name=header_dic['filename']25 #2.4 接收數據26 with open('%s/%s'%(download_dir,file_name),'wb') as f:27 recv_size=028 while recv_size 
<29 line="phone.recv(1024)30" f.write="" recv_size="" print="" phone.close=""/>


分享到:


相關文章: