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()
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()
運行一下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()
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()
這裏有個問題還待解:原先數據在傳輸的時候用的轉碼格式時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()
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()
服務器在發送第一條指令後,用一個接收指令的代碼讓他阻塞住,客戶端在接收到第一條指令後給服務器發送一條信息,服務器接收後再發送後面的數據。
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())
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()
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)
客戶端在接收數據防止粘包的地方作了些小改進:接收數據量用變量來處理,最後一個循環直接剩下的數據量,前期變量值都是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()
客戶端用最基礎的就能夠
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)
執行,發現第一次發送,正常,第二次就報錯了
緣由就是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()
最後,咱們試一下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,就實現了多併發。能夠多開幾個客戶端試一下
能夠看到服務器端能夠同時接收多個客戶端的信息。