仍是繼續延續篇五中前三節的例子,經過對代碼的修修補補,把它改爲一個能夠在鏈接後就能在Client端執行Server端命令的程序,因此就有點相似於SSH鏈接程序了。
python
至於仍是用前面的例子來改嘛,是由於上課也一直這麼幹,並且老師也講得很是不錯,本身吸取後也做爲一個學習的記錄吧,由於確實是很是不錯的!
shell
之因此能對前面的例子如這樣的修改,應當有這樣的思想:前面的例子中,Server端可以返回Client端輸入的字符串,那麼若是Client端輸入的是Linux的shell命令,Server端是否能夠執行這些命令而後返回執行的結果?併發
因此是基於這樣的思想來進行對前面例子的改進的,達到這樣的效果,須要其它一些模塊的支持,固然也須要在細節上作一些改進,下面給出的代碼中都會有說比較詳細的說明。
ssh
就看看這個簡版的SSH程序是個什麼樣的東東吧。
socket
Server端程序代碼:ide
import SocketServer import commands #使用其中的getstatusoutput()函數,讓Server端能夠識別Client端發送過來的命令並執行 import time #主要使用其中的time.sleep()函數,用來解決Server端發送數據的「連塊」問題 class MySockServer(SocketServer.BaseRequestHandler): def handle(self): print 'Got a new connection from', self.client_address while True: cmd = self.request.recv(1024) if not cmd: print 'Last connection with:',self.client_address break cmd_result = commands.getstatusoutput(cmd) #獲取Client端的指令並執行,返回結果是一個存儲兩個元素的元組,第一個元素爲0表示成功執行,第二個元素則是命令的執行結果 self.request.send(str(len(cmd_result[1]))) #發送命令執行結果的大小長度,Client端要想接收任意大小的執行結果,就須要根據命令執行結果的大小來選擇策略,這裏須要注意的是,發送的數據是字符串,因此須要做類型轉換 time.sleep(0.2) #睡眠0.2s,是爲了解決「連塊」的問題 self.request.sendall(cmd_result[1]) #發送命令執行結果 if __name__ == '__main__': HOST = '' PORT = 50007 s = SocketServer.ThreadingTCPServer((HOST, PORT), MySockServer) s.serve_forever()
Client端程序代碼:函數
import socket HOST = '192.168.1.13' PORT = 50007 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) def data_all(obj, lenth_size): data = '' #用來存儲每一次循環時socket接收的數據,解決socket大概兩萬多字節的緩衝瓶頸限制 while lenth_size != 0: #若是接收的數據長度不爲0,開始執行接收數據處理策略 if lenth_size <= 4096: #這裏以4096爲單位塊,做爲每次的數據處理量大小 data_recv = obj.recv(lenth_size) #經過recv()接收數據 lenth_size = 0 #經過這一步的處理,數據所有接收完畢,置lenth_size爲0,結束循環,完成數據的接收處理工做 else: data_recv = obj.recv(4096) #以4096爲單位塊,一次接收4096的數據量大小 lenth_size -= 4096 #處理完一次4096字節的數據後,將lenth_size減去4096 data += data_recv #判斷外層,用本地的data來存儲接收到的數據,由於本地的data大小沒有限制,因此不存在data飽和沒法繼續存儲數據的問題,但前面socket的recv()函數一次最多隻能接收的數據量大小是有限制的,這取決於socket的緩衝區大小,所以data_all函數的做用就是經過使用屢次recv()函數,而且每次接收必定量的數據後就進行本地存儲,直到把全部的數據都接收完畢 return data while True: user_input = raw_input('cmd to send:').strip() if len(user_input) == 0:continue s.sendall(user_input) data_size = int(s.recv(1024)) #獲得命令執行結果的大小長度,由於發送過來的數據是字符串,因此這裏要做類型轉換 print '\033[32;1mdata size:\033[0m',data_size #打印命令執行結果的大小 result = data_all(s, data_size) #經過data_all函數來執行相應的數據接收處理策略 print result #打印命令執行結果 s.close() #關閉套接字
演示:學習
步驟1:Server端運行服務端程序spa
xpleaf@xpleaf-machine:/mnt/hgfs/Python/day5$ python ssh_server5.py ===>光標在此到處於等待狀態
步驟2:Client端運行客戶端程序並觀察返回結果線程
xpleaf@xpleaf-machine:/mnt/hgfs/Python/day5$ python ssh_client5.py cmd to send:df data size: 502 ===>命令執行結果的大小 df: "/var/lib/lightdm/.gvfs": 權限不夠 ===>命令的執行結果 文件系統 1K-塊 已用 可用 已用% 掛載點 /dev/sda3 8781832 3458300 4877428 42% / udev 493784 4 493780 1% /dev tmpfs 201040 784 200256 1% /run none 5120 0 5120 0% /run/lock none 502592 144 502448 1% /run/shm /dev/sda1 93207 30139 58256 35% /boot .host:/ 162256468 152391980 9864488 94% /mnt/hgfs cmd to send:pwd ===>執行pwd命令 data size: 21 /mnt/hgfs/Python/day5 cmd to send:ls data size: 357 [1]sec_4_ver1(單線程,非交互式) [2]sec_4_ver2(單線程,交互式,阻塞模式通常演示) [3]sec_4_ver3(單線程,交互式,阻塞模式進階演示) [4]sec_4_ver3(單線程,交互式,多併發) client4.py duotiao_jian_biao3.py jian_biao2.py my_conn1.py server4.py ssh_client5.py ssh_server5.py Thread_socket_server4.py cmd to send:top -bn 3 ===>注意該命令的執行結果(大小)已經超出了socket的緩衝區大小,所以上面Client端中的程序代碼主要就是爲了解決該問題,這也是Client端的關鍵所在 data size: 34378 ===>該命令的執行結果大小爲三萬多字節,超出了socket緩衝區,socket的recv()函數是沒法一次接收那麼多數據的 …… 省略輸出結果 cmd to send:ls ===>繼續執行命令,返回結果正常 data size: 357 [1]sec_4_ver1(單線程,非交互式) [2]sec_4_ver2(單線程,交互式,阻塞模式通常演示) [3]sec_4_ver3(單線程,交互式,阻塞模式進階演示) [4]sec_4_ver3(單線程,交互式,多併發) client4.py duotiao_jian_biao3.py jian_biao2.py my_conn1.py server4.py ssh_client5.py ssh_server5.py Thread_socket_server4.py
能夠看到上面兩個程序已經比較好的實現了命令執行的功能了,問題主要是集中在:
1.Server端發送數據的「連塊」問題,即發送兩次數據時,若是發送間隔比較短,socket會把兩次發送的數據放在一塊兒來發送,這裏經過time.sleep()函數來解決。
2.socket的緩衝區大小問題,即當執行top -bn 3這樣執行結果長度大的命令時,socket緩衝區一次是沒法存儲這麼多數據的,因此只能分屢次來接收數據,這樣就會在Client端帶來必定的問題,好比命令執行的不一樣步等,解決的方法是經過用循環接收的方法來進行本地存儲Server端發送的數據。
固然,若是要執行man等查詢方面的命令,上面的程序也是沒法作到的,因此這裏說,這只是一個簡版的SSH程序,做爲對Python socket的學習就行了,真要用SSH的話,那還不如直接下個ssh鏈接軟件。