在以前博客C/S架構的網絡編程中,IO多路複用是將多個IO操做複用到1個服務端進程中進行處理,即不管有多少個客戶端進行鏈接請求,服務端始終只有1個進程對客戶端進行響應,這樣的好處是節省了系統開銷(select不適合單個客戶端長會話操做,這樣其它客戶端鏈接請求就會一直等待,poll/epoll對select進行了改進)。下面介紹結合了IO多路複用和多進程(多線程)的SocketServer模塊。html
SocketServer內部使用IO多路複用以及「多線程」和「多進程」 ,從而實現併發處理多個客戶端請求的Socket服務端。即:每一個客戶端請求鏈接到服務器時,Socket服務端都會建立一個「線程」或者「進程」專門負責處理當前客戶端的全部請求。python
SocketServer與select/poll/epoll的本質區別:客戶端第1次鏈接時,服務端就爲該客戶端建立一個線程或進程,此後服務端就利用此線程或進程與客戶端進行通訊,後續的數據傳輸幾乎不要server端的直接參與。若是服務端建立的是進程,那麼client1和client2同時向server端傳輸數據時是互不影響的;若是服務端建立的是線程(python中多線程,在同一時間只有一個線程在運行,底層會自動進行上下文切換,即python中不存在真正的多線程),那麼client1和client2交替上傳數據。 react 知識回顧: python中的多線程,有一個GIL(全局解釋器鎖)限制在同一時刻只有1個線程在運行,底層自動進行上下文切換,保證多個線程輪流運行(cpu切片),也就是python中不存在真正的多線程問題,僞多線程,實際多個線程不能真正實現併發處理。linux |
中間處理過程如圖所示程序員
1.1 ThreadingTCPServerweb
ThreadingTCPServer實現的Soket服務器內部會爲每一個client建立一個 「線程」,該線程用來和客戶端進行交互。數據庫
一、ThreadingTCPServer基礎編程
使用ThreadingTCPServer要求:ubuntu
(1)建立一個繼承自SocketServer.BaseRequestHandler的類
windows
(2)類中必須定義一個名稱爲 handle 的方法
(3)啓動ThreadingTCPServer
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import SocketServer 5 6 class MyServer(SocketServer.BaseRequestHandler): 7 8 def handle(self): 9 # print self.request,self.client_address,self.server 10 conn = self.request 11 conn.sendall('歡迎致電 10086,請輸入1xxx,0轉人工服務.') 12 Flag = True 13 while Flag: 14 data = conn.recv(1024) 15 if data == 'exit': 16 Flag = False 17 elif data == '0': 18 conn.sendall('經過可能會被錄音.balabala一大推') 19 else: 20 conn.sendall('請從新輸入.') 21 22 23 if __name__ == '__main__': 24 server = SocketServer.ThreadingTCPServer(('127.0.0.1',8009),MyServer) 25 server.serve_forever()
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import socket 5 6 ip_port = ('127.0.0.1',8009) 7 sk = socket.socket() 8 sk.connect(ip_port) 9 sk.settimeout(5) 10 11 while True: 12 data = sk.recv(1024) 13 print 'receive:',data 14 inp = raw_input('please input:') 15 sk.sendall(inp) 16 if inp == 'exit': 17 break 18 19 sk.close()
二、ThreadingTCPServer源碼剖析
ThreadingTCPServer的類圖關係以下:
注:實際上在類的繼承時,子類會繼承父類的方法,因此咱們在分析類的繼承關係時,直接把父類的方法放到子類中,這樣就直觀些,在python中還有1點要注意的是子類究竟是繼承哪一個父類的方法,由於python中存在多繼承。
內部調用流程爲
啓動服務端程序 一、執行TCPServer.__init__ 方法,建立服務端Socket對象並綁定IP和端口 二、執行BaseServer.__init__ 方法,將自定義的繼承自SocketServer.BaseRequestHandler的類MyRequestHandle賦值給self.RequestHandlerClass 三、執行BaseServer.server_forever方法,While 循環一直監聽是否有客戶端請求到達 ... 當客戶端鏈接到達服務器 四、執行ThreadingMixIn.process_request方法,建立一個「線程」用來處理請求 五、執行ThreadingMixIn.process_request_thread方法 六、執行BaseServer.finish_request方法,執行self.RequestHandlerClass(),即執行自定義MyRequestHandler的構造方法 (自動調用基類BaseRequestHandler的構造方法,在該構造方法中又會調用MyRequestHandler的handle方法) |
ThreadingTCPServer相關源碼
1 class BaseServer: 2 3 """Base class for server classes. 4 5 Methods for the caller: 6 7 - __init__(server_address, RequestHandlerClass) 8 - serve_forever(poll_interval=0.5) 9 - shutdown() 10 - handle_request() # if you do not use serve_forever() 11 - fileno() -> int # for select() 12 13 Methods that may be overridden: 14 15 - server_bind() 16 - server_activate() 17 - get_request() -> request, client_address 18 - handle_timeout() 19 - verify_request(request, client_address) 20 - server_close() 21 - process_request(request, client_address) 22 - shutdown_request(request) 23 - close_request(request) 24 - handle_error() 25 26 Methods for derived classes: 27 28 - finish_request(request, client_address) 29 30 Class variables that may be overridden by derived classes or 31 instances: 32 33 - timeout 34 - address_family 35 - socket_type 36 - allow_reuse_address 37 38 Instance variables: 39 40 - RequestHandlerClass 41 - socket 42 43 """ 44 45 timeout = None 46 47 def __init__(self, server_address, RequestHandlerClass): 48 """Constructor. May be extended, do not override.""" 49 self.server_address = server_address 50 self.RequestHandlerClass = RequestHandlerClass 51 self.__is_shut_down = threading.Event() 52 self.__shutdown_request = False 53 54 def server_activate(self): 55 """Called by constructor to activate the server. 56 57 May be overridden. 58 59 """ 60 pass 61 62 def serve_forever(self, poll_interval=0.5): 63 """Handle one request at a time until shutdown. 64 65 Polls for shutdown every poll_interval seconds. Ignores 66 self.timeout. If you need to do periodic tasks, do them in 67 another thread. 68 """ 69 self.__is_shut_down.clear() 70 try: 71 while not self.__shutdown_request: 72 # XXX: Consider using another file descriptor or 73 # connecting to the socket to wake this up instead of 74 # polling. Polling reduces our responsiveness to a 75 # shutdown request and wastes cpu at all other times. 76 r, w, e = _eintr_retry(select.select, [self], [], [], 77 poll_interval) 78 if self in r: 79 self._handle_request_noblock() 80 finally: 81 self.__shutdown_request = False 82 self.__is_shut_down.set() 83 84 def shutdown(self): 85 """Stops the serve_forever loop. 86 87 Blocks until the loop has finished. This must be called while 88 serve_forever() is running in another thread, or it will 89 deadlock. 90 """ 91 self.__shutdown_request = True 92 self.__is_shut_down.wait() 93 94 # The distinction between handling, getting, processing and 95 # finishing a request is fairly arbitrary. Remember: 96 # 97 # - handle_request() is the top-level call. It calls 98 # select, get_request(), verify_request() and process_request() 99 # - get_request() is different for stream or datagram sockets 100 # - process_request() is the place that may fork a new process 101 # or create a new thread to finish the request 102 # - finish_request() instantiates the request handler class; 103 # this constructor will handle the request all by itself 104 105 def handle_request(self): 106 """Handle one request, possibly blocking. 107 108 Respects self.timeout. 109 """ 110 # Support people who used socket.settimeout() to escape 111 # handle_request before self.timeout was available. 112 timeout = self.socket.gettimeout() 113 if timeout is None: 114 timeout = self.timeout 115 elif self.timeout is not None: 116 timeout = min(timeout, self.timeout) 117 fd_sets = _eintr_retry(select.select, [self], [], [], timeout) 118 if not fd_sets[0]: 119 self.handle_timeout() 120 return 121 self._handle_request_noblock() 122 123 def _handle_request_noblock(self): 124 """Handle one request, without blocking. 125 126 I assume that select.select has returned that the socket is 127 readable before this function was called, so there should be 128 no risk of blocking in get_request(). 129 """ 130 try: 131 request, client_address = self.get_request() 132 except socket.error: 133 return 134 if self.verify_request(request, client_address): 135 try: 136 self.process_request(request, client_address) 137 except: 138 self.handle_error(request, client_address) 139 self.shutdown_request(request) 140 141 def handle_timeout(self): 142 """Called if no new request arrives within self.timeout. 143 144 Overridden by ForkingMixIn. 145 """ 146 pass 147 148 def verify_request(self, request, client_address): 149 """Verify the request. May be overridden. 150 151 Return True if we should proceed with this request. 152 153 """ 154 return True 155 156 def process_request(self, request, client_address): 157 """Call finish_request. 158 159 Overridden by ForkingMixIn and ThreadingMixIn. 160 161 """ 162 self.finish_request(request, client_address) 163 self.shutdown_request(request) 164 165 def server_close(self): 166 """Called to clean-up the server. 167 168 May be overridden. 169 170 """ 171 pass 172 173 def finish_request(self, request, client_address): 174 """Finish one request by instantiating RequestHandlerClass.""" 175 self.RequestHandlerClass(request, client_address, self) 176 177 def shutdown_request(self, request): 178 """Called to shutdown and close an individual request.""" 179 self.close_request(request) 180 181 def close_request(self, request): 182 """Called to clean up an individual request.""" 183 pass 184 185 def handle_error(self, request, client_address): 186 """Handle an error gracefully. May be overridden. 187 188 The default is to print a traceback and continue. 189 190 """ 191 print '-'*40 192 print 'Exception happened during processing of request from', 193 print client_address 194 import traceback 195 traceback.print_exc() # XXX But this goes to stderr! 196 print '-'*40
1 class TCPServer(BaseServer): 2 3 """Base class for various socket-based server classes. 4 5 Defaults to synchronous IP stream (i.e., TCP). 6 7 Methods for the caller: 8 9 - __init__(server_address, RequestHandlerClass, bind_and_activate=True) 10 - serve_forever(poll_interval=0.5) 11 - shutdown() 12 - handle_request() # if you don't use serve_forever() 13 - fileno() -> int # for select() 14 15 Methods that may be overridden: 16 17 - server_bind() 18 - server_activate() 19 - get_request() -> request, client_address 20 - handle_timeout() 21 - verify_request(request, client_address) 22 - process_request(request, client_address) 23 - shutdown_request(request) 24 - close_request(request) 25 - handle_error() 26 27 Methods for derived classes: 28 29 - finish_request(request, client_address) 30 31 Class variables that may be overridden by derived classes or 32 instances: 33 34 - timeout 35 - address_family 36 - socket_type 37 - request_queue_size (only for stream sockets) 38 - allow_reuse_address 39 40 Instance variables: 41 42 - server_address 43 - RequestHandlerClass 44 - socket 45 46 """ 47 48 address_family = socket.AF_INET 49 50 socket_type = socket.SOCK_STREAM 51 52 request_queue_size = 5 53 54 allow_reuse_address = False 55 56 def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True): 57 """Constructor. May be extended, do not override.""" 58 BaseServer.__init__(self, server_address, RequestHandlerClass) 59 self.socket = socket.socket(self.address_family, 60 self.socket_type) 61 if bind_and_activate: 62 try: 63 self.server_bind() 64 self.server_activate() 65 except: 66 self.server_close() 67 raise 68 69 def server_bind(self): 70 """Called by constructor to bind the socket. 71 72 May be overridden. 73 74 """ 75 if self.allow_reuse_address: 76 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 77 self.socket.bind(self.server_address) 78 self.server_address = self.socket.getsockname() 79 80 def server_activate(self): 81 """Called by constructor to activate the server. 82 83 May be overridden. 84 85 """ 86 self.socket.listen(self.request_queue_size) 87 88 def server_close(self): 89 """Called to clean-up the server. 90 91 May be overridden. 92 93 """ 94 self.socket.close() 95 96 def fileno(self): 97 """Return socket file number. 98 99 Interface required by select(). 100 101 """ 102 return self.socket.fileno() 103 104 def get_request(self): 105 """Get the request and client address from the socket. 106 107 May be overridden. 108 109 """ 110 return self.socket.accept() 111 112 def shutdown_request(self, request): 113 """Called to shutdown and close an individual request.""" 114 try: 115 #explicitly shutdown. socket.close() merely releases 116 #the socket and waits for GC to perform the actual close. 117 request.shutdown(socket.SHUT_WR) 118 except socket.error: 119 pass #some platforms may raise ENOTCONN here 120 self.close_request(request) 121 122 def close_request(self, request): 123 """Called to clean up an individual request.""" 124 request.close()
1 class ThreadingMixIn: 2 """Mix-in class to handle each request in a new thread.""" 3 4 # Decides how threads will act upon termination of the 5 # main process 6 daemon_threads = False 7 8 def process_request_thread(self, request, client_address): 9 """Same as in BaseServer but as a thread. 10 11 In addition, exception handling is done here. 12 13 """ 14 try: 15 self.finish_request(request, client_address) 16 self.shutdown_request(request) 17 except: 18 self.handle_error(request, client_address) 19 self.shutdown_request(request) 20 21 def process_request(self, request, client_address): 22 """Start a new thread to process the request.""" 23 t = threading.Thread(target = self.process_request_thread, 24 args = (request, client_address)) 25 t.daemon = self.daemon_threads 26 t.start() 27 28 複製代碼
1 class ThreadingTCPServer(ThreadingMixIn, TCPServer): 2 pass
RequestHandler相關源碼
1 class BaseRequestHandler: 2 3 """Base class for request handler classes. 4 5 This class is instantiated for each request to be handled. The 6 constructor sets the instance variables request, client_address 7 and server, and then calls the handle() method. To implement a 8 specific service, all you need to do is to derive a class which 9 defines a handle() method. 10 11 The handle() method can find the request as self.request, the 12 client address as self.client_address, and the server (in case it 13 needs access to per-server information) as self.server. Since a 14 separate instance is created for each request, the handle() method 15 can define arbitrary other instance variariables. 16 17 """ 18 19 def __init__(self, request, client_address, server): 20 self.request = request 21 self.client_address = client_address 22 self.server = server 23 self.setup() 24 try: 25 self.handle() 26 finally: 27 self.finish() 28 29 def setup(self): 30 pass 31 32 def handle(self): 33 pass 34 35 def finish(self): 36 pass
源碼精簡
1 import socket 2 import threading 3 import select 4 5 def process(request, client_address): 6 print request,client_address 7 conn = request 8 conn.sendall('歡迎致電 10086,請輸入1xxx,0轉人工服務.') 9 flag = True 10 while flag: 11 data = conn.recv(1024) 12 if data == 'exit': 13 flag = False 14 elif data == '0': 15 conn.sendall('經過可能會被錄音.balabala一大推') 16 else: 17 conn.sendall('請從新輸入.') 18 19 sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 20 sk.bind(('127.0.0.1',8002)) 21 sk.listen(5) 22 23 while True: 24 r, w, e = select.select([sk,],[],[],1) 25 print 'looping' 26 if sk in r: 27 print 'get request' 28 request, client_address = sk.accept() 29 t = threading.Thread(target=process, args=(request, client_address)) 30 t.daemon = False 31 t.start() 32 33 sk.close()
從精簡代碼能夠看出,SocketServer的ThreadingTCPServer之因此能夠同時處理請求得益於select和Threading兩個東西,其實本質上就是在服務器端爲每個客戶端建立一個線程,用於後續的數據處理,當前線程用來處理對應客戶端的請求,因此能夠支持同時n個客戶端連接(長鏈接)。
1.2 ForkingTCPServer
ForkingTCPServer與ThreadingTCPServer的使用和執行流程基本一致,只不過在內部分別爲請求者創建「進程」和「線程」。
基本使用:
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import SocketServer 5 6 class MyServer(SocketServer.BaseRequestHandler): 7 8 def handle(self): 9 # print self.request,self.client_address,self.server 10 conn = self.request 11 conn.sendall('歡迎致電 10086,請輸入1xxx,0轉人工服務.') 12 Flag = True 13 while Flag: 14 data = conn.recv(1024) 15 if data == 'exit': 16 Flag = False 17 elif data == '0': 18 conn.sendall('經過可能會被錄音.balabala一大推') 19 else: 20 conn.sendall('請從新輸入.') 21 22 23 if __name__ == '__main__': 24 server = SocketServer.ForkingTCPServer(('127.0.0.1',8009),MyServer) 25 server.serve_forever()
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import socket 5 6 ip_port = ('127.0.0.1',8009) 7 sk = socket.socket() 8 sk.connect(ip_port) 9 sk.settimeout(5) 10 11 while True: 12 data = sk.recv(1024) 13 print 'receive:',data 14 inp = raw_input('please input:') 15 sk.sendall(inp) 16 if inp == 'exit': 17 break 18 19 sk.close()
以上ForkingTCPServer只是將 ThreadingTCPServer 實例中的代碼:
server = SocketServer.ThreadingTCPServer(( '127.0.0.1' , 8009 ),MyRequestHandler)
變動爲:
server = SocketServer.ForkingTCPServer(( '127.0.0.1' , 8009 ),MyRequestHandler)
|
SocketServer的ThreadingTCPServer之因此能夠同時處理請求得益於select和os.fork兩個東西,其實本質上就是在服務器端爲每個客戶端建立一個進程,用於後續數據處理,當前新建立的進程用來處理對應客戶端的請求,因此,能夠支持同時n個客戶端連接(長鏈接)。
源碼剖析參考 ThreadingTCPServer
使用傳統的BIO(Blocking IO/阻塞IO)進行網絡編程時,進行網絡IO讀寫時都會阻塞當前線程,若是實現一個TCP服務器,都須要對每一個客戶端鏈接開啓一個線程,而不少線程可能都在傻傻的阻塞住等待讀寫數據,系統資源消耗大。
Twisted是用Python實現的基於事件驅動的網絡引擎框架,功能很是豐富,基本包括了經常使用的網絡組件,例如:網絡協議、線程、數據庫管理、網絡操做、電子郵件等。
編程框架,即別人預先定義好的一個框架(一個項目),如.net某個web框架有25個class,從BeginRequest依次執行類裏的process方法。程序員自定義一個類添加到框架裏,應用程序則從上到下運行,就會執行自定義代碼。框架只知道這個類的列表,不關心具體內容,從上到下執行,相似於一個執行鏈,C#裏叫委託鏈。也就是把代碼類放到這個列表中,委託這個框架替你執行。
事件驅動(not event),把自定義代碼註冊到框架中,框架代替你執行。或者框架提供幾個接口,讓你插入數據(python無接口概念,只有事件驅動)。委託不能爲空,事件能夠爲空。通俗來說,所謂事件驅動,就是說程序就像是一個報警器(reactor),時刻等待着外部事件(event),諸若有人入侵等,一旦有事件發生,程序就會觸發一些特定的操做(callback),注入撥打報警電話等。
簡而言之,事件驅動分爲二個部分:第一,註冊事件;第二,觸發事件。
自定義事件驅動框架以下:
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 #event_drive.py 5 6 event_list = [] 7 8 def run(): 9 for event in event_list: 10 obj = event() 11 obj.execute() 12 13 class BaseHandler(object): 14 """ 15 用戶必須繼承該類,從而規範全部類的方法(相似於接口的功能) 16 """ 17 def execute(self): 18 raise Exception('you must overwrite execute')
程序員使用該自定義事件驅動框架
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 from source import event_drive 5 6 class MyHandler(event_drive.BaseHandler): 7 8 def execute(self): 9 print 'event-drive execute MyHandler' 10 11 event_drive.event_list.append(MyHandler) 12 event_drive.run()
1 導入自定義框架event_drive 2 自定義類MyClass,這個類必須繼承event_drive中的BaseHandler類 3 MyClass類中重載execute方法 4 註冊事件到框架的委託鏈,把自定義的類MyClass加入到事件列表event_list中(下面的Twisted框架是建立對象後改一個字段爲類名也是一樣的目的) 5 執行run方法 事件驅動只不過是框架規定了執行順序,程序員在使用框架時,能夠向原執行順序中註冊「事件」,從而在框架執行時能夠出發已註冊 的「事件」。 |
基於事件驅動Twisted模塊的Socket
(1)因爲twisted是第三方模塊,默認沒有安裝,須要先安裝
cd Twisted-15.5.0 python setup.py build #編譯 python setup.py install #安裝 上述安裝方法適用於windows和linux的命令行安裝,實際上也能夠直接執行python setup.py install 注意:twisted依賴於zope和win32api模塊,須要先安裝依賴。 |
(2)實例
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 from twisted.internet import protocol 5 from twisted.internet import reactor 6 7 class Echo(protocol.Protocol): #繼承protocol.py中的Protocol類 8 def dataReceived(self, data): 9 self.transport.write(data) #將收到的內容直接發送回去 10 11 def main(): 12 factory = protocol.ServerFactory() #實例化 13 factory.protocol = Echo #將自定義類傳給對象 14 15 reactor.listenTCP(8000,factory) #將端口和實例化對象做爲參數傳給reactor 16 reactor.run() 17 18 if __name__ == '__main__': 19 main()
1 import socket 2 3 ip_port=('127.0.0.1',8000) 4 sk=socket.socket() 5 sk.connect(ip_port) 6 sk.settimeout(5) 7 8 while True: 9 inp=raw_input("please input:") 10 sk.sendall(inp) 11 print sk.recv(1024) 12 13 sk.close()
源碼類圖關係
實例執行流程
運行服務端程序 建立Protocol的派生類Echo 建立ServerFactory對象,並將Echo類封裝到其protocol字段中 執行reactor的listenTCP方法,內部使用tcp.Port建立socket server對象,並將該對象添加到了reactor的set類型的字段_read 中 執行reactor的run方法,內部執行while循環,並經過select來監視_read中文件描述符是否有變化,循環中... 客戶端請求到達 執行reactor的_doReadOrWrite方法,其內部經過反射調用tcp.Port類的doRead方法,內部accept客戶端鏈接並建立Server對象實例(用於封裝客戶端socket信息)和建立Echo對象實例(用於處理請求),而後調用Echo對象實例的makeConnection方法,建立鏈接。 執行tcp.Server類的doRead方法,讀取數據, 執行tcp.Server類的_dataReceived方法,若是讀取數據內容爲空(關閉連接),不然,出發Echo的dataReceived方法 執行Echo的dataReceived方法 |
從源碼能夠看出,上述實例本質上使用了事件驅動的方法 和 IO多路複用的機制來進行Socket的處理。
Pycharm debug模式調試調用關係
Twisted優勢:
一、使用基於事件驅動的編程模型,而不是多線程模型。
二、跨平臺:爲主流操做系統平臺暴露出的事件通知系統提供統一的接口。
三、「內置電池」的能力:提供流行的應用層協議實現,所以Twisted立刻就可爲開發人員所用。
四、符合RFC規範,已經經過健壯的測試套件證實了其一致性。
五、能很容易的配合多個網絡協議一塊兒使用。
六、可擴展。
更多Twisted內容,請參考:
http://www.cnblogs.com/c9com/archive/2013/01/05/2845552.html(Twisted reactor解剖)
http://www.cnblogs.com/zhangjing0502/archive/2012/07/11/2586666.html(Twisted的網絡通訊模型)
http://www.cnblogs.com/zhangjing0502/archive/2012/07/11/2586575.html(Python中reactor,factory,protocol)
http://www.cnblogs.com/zhangjing0502/archive/2012/05/16/2504415.html(Twisted異步編程--Deferred)
http://www.cnblogs.com/zhangjing0502/archive/2012/05/30/2526552.html([Python-Twisted] Twisted入門之端口轉發服務器)
http://www.cnblogs.com/Rex7/p/4752581.html(跟蹤 twisted 裏deferred 的Callback)
linux運維中都須要對服務器進行配置,若是服務器數量較多,那麼能夠進行遠程自動化批量操做。在python中的paramiko模塊就是實現遠程執行命令的模塊。使用paramiko模塊僅須要在本地安裝相應的模塊(pycrypto以及paramiko模塊),對遠程服務器沒有配置要求,paramiko模塊基於ssh協議,實現對遠程服務器的相關操做,對於鏈接多臺服務器,進行復雜的鏈接操做特別有幫助。
3.1 paramiko安裝
1 windows下的安裝paramiko (1)解壓pycrypto-2.6.tar.gz源碼到路徑C:\Python27\Lib\site-packages (2)在windows控制檯下進入目錄pycrypto-2.6,依次執行python setup.py build和python setup.py install window是若是沒有安裝編譯器,那麼會報錯,解決辦法是安裝VCForPython27.msi(Microsoft Visual C++ Compiler for Python 2.7) 編譯過程當中會出現「Unable to find vcvarsall.bat」的錯誤,解決方法參考http://blog.csdn.net/donger_soft/article/details/44838109 測試是否安裝成功:在python命令行下輸入:import pycrypto,檢查是否報錯 (3)解壓paramiko-1.10.1.tar.gz源碼到路徑C:\Python27\Lib\site-packages (4)在windows控制檯下進入目錄paramiko-1.10.1,依次執行python setup.py build和python setup.py install 測試是否安裝成功:在python命令行下輸入:import paramiko,檢查是否報錯 2 ubuntu下的安裝paramiko (1)先安裝python-devel(前提是要安裝編譯器gcc) (2)解壓pycrypto-2.6.tar.gz源碼,進入目錄pycrypto-2.6,執行python setup.py build && python setup.py install 測試是否安裝成功:在python命令行下輸入:import pycrypto,檢查是否報錯 (3)解壓paramiko-1.10.1.tar.gz源碼,進入目錄paramiko-1.10.1,執行python setup.py build && python setup.py install 測試是否安裝成功:在python命令行下輸入:import paramiko,檢查是否報錯 |
3.2 paramiko使用
SSHClient方法
1 #!/usr/bin/env python 2 #-*- coding:utf-8 -*- 3 4 import paramiko 5 6 ssh = paramiko.SSHClient() #建立ssh對象 7 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) #容許鏈接不在know_hosts文件中的主機 8 ssh.connect(hostname='192.168.1.100',port=22,username='root',password='111111') #hostname='主機名'或'ip地址' 9 stdin,stdout,stderror = ssh.exec_command('df') #在遠程服務器執行命令df 10 11 print stdout.read() #獲取命令結果 12 print stderror.read() #若是命令執行錯誤,則返回標準錯誤輸出 13 ssh.close() #關閉鏈接
1 #!/usr/bin/env python 2 #-*- coding:utf-8 -*- 3 4 import paramiko 5 6 transport = paramiko.Transport(('192.168.7.100',22)) #建立transport對象 7 transport.connect(username='root',password='nihao123!')#調用鏈接方法connect 8 9 ssh = paramiko.SSHClient() #建立ssh對象 10 ssh._transport = transport #把上面建立的transport對象賦值給ssh對象中的_transport字段 11 12 stdin,stdout,stderr = ssh.exec_command('ifconfig') #執行命令ifconfig 13 14 print stdout.read() 15 print stderr.read() 16 17 transport.close()
在上述兩個實例中,其實實例1中connect內部封裝了Transport,即:
ssh = paramiko.SSHClient()
t = self._transport = Transport(sock, gss_kex=gss_kex, gss_deleg_creds=gss_deleg_creds)
注意:在操做文件時只能用實例2的方法
SFTPClient方法
1 #!/usr/bin/env python 2 #-*- coding:utf-8 -*- 3 4 import paramiko 5 6 private_key = paramiko.RSAKey.from_private_key_file('/root/.ssh/id_rsa') 7 8 ssh = paramiko.SSHClient() #建立ssh對象 9 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) #容許鏈接不在know_host文件中的的主機 10 ssh.connect(hostname='192.168.1.100',port=22,username='root',pkey=private_key) #鏈接服務器 11 stdin,stdout,stderr = ssh.exec_command('ifconfig') #執行命令 12 print stdout.read() #獲取命令執行結果 13 ssh.close() 14 15 ''' 16 若是是運維人員這裏不須要看 17 一、首先建立一個公鑰和私鑰 18 ssh-keygen 19 二、複製id_rsa.pub至要被遠程執行命令的機器,並把id_rsa.pub裏的內容增長至authorized_keys文件中 20 若是authorized_keys文件不存在建立便可 21 '''
1 #!/usr/bin/env python 2 #-*- coding:utf-8 -*- 3 4 import paramiko 5 6 private_key = paramiko.RSAKey.from_private_key_file('/root/.ssh/id_rsa') 7 8 transport = paramiko.Transport(('192.168.1.100',22)) 9 transport.connect(username='root',pkey=private_key) #鏈接 10 11 sftp = paramiko.SFTPClient.from_transport(transport)#建立SFTPClient對象 12 13 sftp.put('test.zip','/tmp/test.zip') #將test.zip上傳到目標機器的/tmp/目錄下,並命名爲test.zip 14 sftp.get('/tmp/messages.log','test.log') #下載目標服務器/tmp/messages.log 到本地,並命名爲test.log 15 16 transport.close()
在遠程服務器執行命令時,其實時間主要消耗在創建鏈接上了。
自定義類的,在鏈接後進行相應的上傳下載操做,這樣就能夠在一次鏈接中進行其它操做,避免頻繁的建立鏈接,關閉鏈接,減小資源消耗
1 #!/usr/bin/env python 2 #-*- coding:utf-8 -*- 3 4 import paramiko 5 import uuid 6 7 class DownUpLoad(object): 8 def __init__(self,ip,port,user,passwd): 9 self.hostname = ip 10 self.port = port 11 self.username = user 12 self.password = passwd 13 14 def create_file(self): 15 file_name = str(uuid.uuid4()) #uuid.uuid4()會生成一個文件UUID,看成文件名 16 with open(file_name,'wb') as f: 17 f.write('This is test file!') 18 return file_name 19 20 def run(self): 21 self.connect() 22 self.upload() 23 self.rename() 24 self.close() 25 26 def connect(self): #鏈接方法 27 transport = paramiko.Transport((self.hostname, self.port)) #建立一個鏈接對象 28 transport.connect(username=self.username, password=self.password)#調用transport對象中的鏈接方法 29 self.__transport = transport #把transport賦值給__transport 30 31 def close(self): #關閉鏈接 32 self.__transport.close() 33 34 def upload(self): #上傳文件方法 35 file_name = self.create_file() #建立文件 36 sftp = paramiko.SFTPClient.from_transport(self.__transport) #建立基於transport鏈接的SFTPClient 37 sftp.put(file_name,'/tmp/test.txt') #上傳文件 38 39 def rename(self): #執行命名方法 40 ssh = paramiko.SSHClient() #創建ssh對象 41 ssh._transport = self.__transport #替換ssh_transport字段爲self.__transport 42 stdin,stdout,stderr = ssh.exec_command('mv /tmp/test1 /tmp/test2') #執行命令 43 print stdout.read() #讀取執行命令 44 45 if __name__ == '__main__': 46 ha = DownUpLoad() 47 ha.run()
參考資料:
http://www.cnblogs.com/wupeiqi/articles/5095821.html
http://www.cnblogs.com/wupeiqi/articles/5040823.html
http://www.cnblogs.com/luotianshuai/p/5111587.html
http://www.cnblogs.com/luotianshuai/p/5131053.html