Python之socket編程進階版

1.socket之簡單的ssh功能html

2.socket之簡單的ftp服務器python

3.socketserver的用法linux

4.socketserver的多併發的實現windows

1.socket實現ssh服務緩存

 1.1咱們如今Windows環境下試一下ssh的功能服務器

 1 import socket,os
 2 server = socket.socket()
 3 server.bind(('localhost',6969))
 4 server.listen()
 5 conn, addr = server.accept()
 6 while True:
 7     data = conn.recv(1024)
 8     if not data:
 9         print("client lost!")
10         break
11     print("cmd:",data)
12     res = os.popen(data.decode()).read()
13     if len(res) == 0:
14         res = 'cmd has no output'
15     print(res)
16     conn.send(res.encode('utf-8'))
17 server.close()
server端
 1 import socket
 2 client = socket.socket()
 3 client.connect(('localhost',6969))
 4 while True:
 5     msg = input(">>>").strip()
 6     if len(msg) == 0:
 7         continue
 8     client.send(msg.encode("utf-8"))
 9     data = client.recv(1024)
10     print(data.decode())
11 client.close()
client端

運行一下dir,沒問題,運行一下ipconfig,貌似和跟cmd下運行的結果不同併發

注意看結尾的部分,是否是少了一點,從新運行一下dirssh

我擦,怎麼全亂了???想想,咱們在client端收的數據大小是1024,也就是說每次只能接受1024的數據。超過的部分會在一個相似buff的緩存內被阻塞等待下一次的接收。這種狀況叫作「半包」那怎麼改一改?socket

思路:。ide

        1.服務端在發送數據前先得到數據大小,併發送給客戶端。

   2.客戶端先接受數據大小,再對根據數據大小接收數據。

更改後的代碼

 1 import socket,os
 2 server = socket.socket()
 3 server.bind(('localhost',6969))
 4 server.listen()
 5 conn, addr = server.accept()
 6 while True:
 7     data = conn.recv(1024)
 8     if not data:
 9         print("client lost!")
10         break
11     print("cmd:",data)
12     res = os.popen(data.decode()).read()
13     data_size = len(res)
14     if data_size == 0:
15         res = 'cmd has no output'
16     conn.send(str(data_size).encode())
17     conn.send(res.encode('GBK'))   #這裏在調試時先用的時UTF-8,但出現unicodeDecodeError,
18                                     # 改爲GBK就行了,問題緣由未解               
19 server.close()
server端
 1 import socket
 2 client = socket.socket()
 3 client.connect(('localhost',6969))
 4 while True:
 5     msg = input(">>>").strip()
 6     if len(msg) == 0:
 7         continue
 8     client.send(msg.encode("utf-8"))
 9     data_size = int(client.recv(1024).decode())#先得到傳輸的數據大小
10     received_size = 0
11     data = ''
12     while received_size <data_size:
13         data += client.recv(1024).decode('GBK')
14         received_size = len(data)
15     print(data)
16 client.close()
client端

這裏有個問題還待解:原先數據在傳輸的時候用的轉碼格式時UTF-8,但在超出1024的時候會出現UnicodeDecodeError,改用GBK就行了,不知道爲何。

如今咱們把他們放在linux裏試一下,報錯了!

看一下服務器端,在接受數據時候891後面跟着ifconfig的內容,891是ifconfig獲得的數據的size。

(這種狀況在linux環境下很容易復現,3.0以上版本在windows下不太容易出現,2.7是很容易出現的。因此我把它放在linux中演示一下。)

這裏就須要瞭解一個概念:粘包

咱們看一下服務端的代碼

#服務端
conn.send(str(data_size).encode())
conn.send(res.encode('GBK'))

 在數據的傳輸中,若是有兩個數據包是前後緊挨着發送時,並非按順序一次次的接收,頗有可能在接收端第一次接受時把兩個包粘在一塊兒接收了。這就是「粘包」。

在碼代碼的時候,必定要防止出現這種狀況。 

最簡單粗暴的方法,就是在兩個send過程當中加一個time.sleep,讓緩衝區超時,在這個時間裏客戶端會先把第一個包取出來,第二個包再過來等着客戶端的讀取。但是這種思路也太low了,在對時間要求比較高的環境中也是個找罵的方法(作個交易軟件,你要是敢sleep個0.5秒那但是幾千萬上下啊!!!)

因此改一下思路,咱們讓服務器端發送第一個包的時候等客戶端返回個響應,再發數據包。只要兩個包不前後一塊兒發送就OK!再發個最終的代碼:

 1 import socket,os
 2 server = socket.socket()
 3 server.bind(('localhost',6969))
 4 server.listen()
 5 conn, addr = server.accept()
 6 while True:
 7     data = conn.recv(1024)
 8     if not data:
 9         print("client lost!")
10         break
11     print("cmd:",data)
12     res = os.popen(data.decode()).read()
13     data_size = len(res)
14     if data_size == 0:
15         res = 'cmd has no output'
16     print(str(data_size))
17     conn.send(str(data_size).encode())
18     client_ack = conn.recv(1024)   #等待服務器響應,服務器無響應的話就阻塞在這裏
19     conn.send(res.encode('GBK'))   #這裏在調試時先用的時UTF-8,但出現unicodeDecodeError,
20                                     # 改爲GBK就行了,問題緣由未解
21 server.close()
server端
 1 import socket
 2 client = socket.socket()
 3 client.connect(('localhost',6969))
 4 while True:
 5     msg = input(">>>").strip()
 6     if len(msg) == 0:
 7         continue
 8     client.send(msg.encode("utf-8"))
 9     data_size = int(client.recv(1024).decode())#先得到傳輸的數據大小
10     client.send('準備接收數據'.encode())#在這裏給服務器發一個狀態,服務器接收後開始發數據
11     received_size = 0
12     data = ''
13     while received_size <data_size:
14         data += client.recv(1024).decode('GBK')
15         received_size = len(data)
16     print(data)
17 client.close()
client端

服務器在發送第一條指令後,用一個接收指令的代碼讓他阻塞住,客戶端在接收到第一條指令後給服務器發送一條信息,服務器接收後再發送後面的數據。


2.下面,咱們把ssh服務的代碼稍微改一下,就能夠作一個ftp的服務器了!!

       首先,在用ftp傳輸文件時,有個功能要了解一下:md5,就是一個文件的數字簽名,咱們在傳輸前得到文件的md5編碼,在傳輸後再獲取編碼,把兩個編碼對比一下可得到文件的一致性。做爲文件校驗的功能使用。因此這裏咱們要回顧一下hashlib模塊裏md5的用法

 1 import hashlib
 2 data1 = 'test1'
 3 data2 = 'test2'
 4 data ='test1test2'
 5 m1 = hashlib.md5()
 6 m2 = hashlib.md5()
 7 m1.update(data1.encode())#先對data1進行編碼,得到第一個字符串的md5文件
 8 m1.update(data2.encode())#在第一個文件的MD5上直接對第二個字符串編碼
 9 m2.update(data.encode())#直接字符串進行編碼
10 print('m1:',m1.hexdigest())
獲取MD5值
m1: beff3fcba56f29677c5d52b843df365e
m2: beff3fcba56f29677c5d52b843df365e
運行結果

咱們在對文件進行MD5編碼時候,直接把文件用readlines讀出來是不大現實的(數據是先讀在內存中,小文件沒問題,超過幾個G的文件就把內存撐爆了)因此須要用readline把數據一行行讀出來,而後不停用update把MD5值更新出來。

  其次,咱們來捋一下ftp server端的工做順序

  1.讀取文件名

  2.檢測文件是否存在

  3.打開文件

  4.檢測文件大小併發送給客戶端

  5.發送文件

  6.等待客戶端確認

  7.發送md5給客戶端。

客戶端的工做流程

  1.輸入指令

  2.對指令解耦併發送給服務器

  3.接收文件大小

  4.創建新的空白文件

  5.逐行接收文件並生成相應的MD5

  6.接收原文件的MD5,和新文件進行校驗

  7.關閉文件

下面是代碼

 1 import socket
 2 client = socket.socket()
 3 client.connect(('localhost',6969))
 4 while True:
 5     msg = input(">>>").strip()
 6     if len(msg) == 0:
 7         continue
 8     client.send(msg.encode("utf-8"))
 9     data_size = int(client.recv(1024).decode())#先得到傳輸的數據大小
10     received_size = 0
11     data = ''
12     while received_size <data_size:
13         data += client.recv(1024).decode('GBK')
14         received_size = len(data)
15     print(data)
16 client.close()
ftp_server端
 1 #Author__Aaron
 2 import socket,hashlib
 3 client = socket.socket()
 4 client.connect(("localhost",9696))
 5 while True:
 6     cmd = input('cmd:')
 7     if len(cmd) == 0:
 8         continue
 9     if cmd.startswith("get"):
10         m = hashlib.md5()
11         client.send(cmd.encode())
12         totle_size = client.recv(1024).decode()
13         totle_size = int(totle_size)
14         print("文件大小:",totle_size)
15         client.send(b"ready to recv file")
16         reveived_size = 0
17         cmd = cmd.split()[1]
18         filename = cmd.split(".")[0]+"_new."+cmd.split(".")[1]#新文件名是原文件名+‘new’
19         f = open(filename,"wb")
20         while reveived_size < totle_size:
21             if totle_size - reveived_size > 1024:
22                 get_size = 1024             #防止粘包
23             else:
24                 get_size = totle_size-reveived_size
25             data = client.recv(get_size)
26             f.write(data)
27             m.update(data)
28             reveived_size += len(data)
29         else:
30             print("received done")
31             f.close()
32         file_md5 = client.recv(1024).decode()
33         new_file_md5 = m.hexdigest()
34         if file_md5 == new_file_md5:
35             print("文件校驗正確,md5=%s,文件接受完畢!"%file_md5)
36         else:
37             print("文件校驗失敗")
38             print("源文件校驗碼:",file_md5)
39             print("新文件校驗碼:",new_file_md5)
ftp_client

客戶端在接收數據防止粘包的地方作了些小改進:接收數據量用變量來處理,最後一個循環直接剩下的數據量,前期變量值都是1024。

這裏還存在一些問題待改進:

1.在服務器端只斷定了有沒有文件的存在,若是有能正常工做,若是沒有的話就會卡死。

2.客戶端接收MD5後只進行校驗,若是錯誤只提示錯誤並沒有相應的處理。

3.只作了數據的單項傳輸(get),其實還應該有從客戶端上傳數據給服務器(put)。

留在後面解決把!


3.socketserver模塊的用法

https://docs.python.org/2/library/socketserver.html#SocketServer.BaseServer.handle_request

socketserver模塊有四個類

1.TCPServer

class socketserver.TCPServer(server_address,RequestHanddlerClass,bind_and_active=True)

2UDPServer

class socketserver.UDPServer(server_address,RequestHanddlerClass,bind_and_active=True)

3. UnixStreamServer,相似於TCPServer提供面向數據流的套接字鏈接,可是旨在UNIX平臺上可用;

4. UnixDatagramServer,相似於UDPServer提供面向數據報的套接字鏈接,可是旨在UNIX平臺上可用;

其中經常使用的是前兩個。

+------------+
| BaseServer | +------------+ | v +-----------+ +------------------+ | TCPServer |------->| UnixStreamServer | +-----------+ +------------------+ | v +-----------+ +--------------------+ | UDPServer |------->| UnixDatagramServer | +-----------+ +--------------------+
這裏引用一下python官網上對SocketServer使用方法的描述
Creating a server requires several steps.
Frist,you must creat a request handler class by subclassing the BaseRequestHandler class and overriding its Handle()
method;this method will process incoming requests.
Second,you must instantiate one of the server classes,passing it the server's address and the request handler class.
Then call the handle_request() or serv_forever() method of the server object to process ond or many requests.
Finally,call server_close() to close the socket
首先,必須建立一個請求處理類,而且這個類要繼承BaseRequestHandler,而且還要重構父類裏的handle()
handle裏處理全部和客戶端的交互
其次,要實例化一個server class(四個中的一個,舉例TCPServer),而且把server的ip和第一步建立的請求處理類看成參數傳遞給TCPServer。
接下來能夠調用server.handle_request()(只處理一個請求)或server.server_forever()(處理多個請求,永遠執行)
最終,調用server_close()關閉。

如今咱們寫一個最簡單的socket server
 1 import socketserver
 2 class MyTCPHandler(socketserver.BaseRequestHandler):#繼承BaseRequestHandler類。
 3     def handle(self):  #重構父類裏的handle()
 4         self.data = self.request.recv(1024).strip()
 5         print('{}wrote:'.format(self.client_address[0]))
 6         print(self.data)
 7         self.request.send(self.data.upper()) #把接收的數據upper後返送
 8 
 9 if __name__ == '__main__':
10     HOST,PORT = 'localhost',9999   #定義IP和端口
11     server = socketserver.TCPServer((HOST,PORT),MyTCPHandler)   #對創建的類實例化,並傳遞IP和請求的類
12     server.serve_forever()
socket_server服務器端

客戶端用最基礎的就能夠

 1 import socket
 2 client = socket.socket()
 3 client.connect(('localhost',9999))
 4 while True:
 5     msg = input('>>>')
 6     if len(msg) == 0:
 7         continue
 8     client.send(msg.encode())
 9     data = client.recv(1024).decode()
10     print(data)
socket_server客戶端

執行,發現第一次發送,正常,第二次就報錯了

緣由就是handle裏處理完一次就結束了,客戶端就斷開了。因此要在handle里加while。

 1 import socketserver
 2 class MyTCPHandler(socketserver.BaseRequestHandler):#繼承BaseRequestHandler類。
 3     def handle(self):  #重構父類裏的handle()
 4         while True:
 5             try:
 6                 self.data = self.request.recv(1024).strip()
 7                 print('{}wrote:'.format(self.client_address[0]))
 8                 print(self.data)
 9                 self.request.send(self.data.upper()) #把接收的數據upper後返送
10             except ConnectionResetError as e:#在這裏處理了客戶端掉線
11                 print('err',e)
12                 break
13 
14 if __name__ == '__main__':
15     HOST,PORT = 'localhost',9999   #定義IP和端口
16     server = socketserver.TCPServer((HOST,PORT),MyTCPHandler)   #對創建的類實例化,並傳遞IP和請求的類
17     server.serve_forever()
socket_server服務器端改進版

最後,咱們試一下socketserver的多併發

 1 import socketserver
 2 class MyTCPHandler(socketserver.BaseRequestHandler):#繼承BaseRequestHandler類。
 3     def handle(self):  #重構父類裏的handle()
 4         while True:
 5             try:
 6                 self.data = self.request.recv(1024).strip()
 7                 print('{}wrote:'.format(self.client_address[0]))
 8                 print(self.data)
 9                 self.request.send(self.data.upper()) #把接收的數據upper後返送
10             except ConnectionResetError as e:#在這裏處理了客戶端掉線
11                 print('err',e)
12                 break
13 
14 if __name__ == '__main__':
15     HOST,PORT = 'localhost',9999   #定義IP和端口
16     server = socketserver.ThreadingTCPServer((HOST,PORT),MyTCPHandler)   #對創建的類實例化,並傳遞IP和請求的類
17     server.serve_forever()
多併發版

把TCPServer改爲ThreadingTCPServer,就實現了多併發。能夠多開幾個客戶端試一下

能夠看到服務器端能夠同時接收多個客戶端的信息。

相關文章
相關標籤/搜索