python網絡編程——SocketServer/Twisted/paramiko模塊

      在以前博客C/S架構的網絡編程中,IO多路複用是將多個IO操做複用到1個服務端進程中進行處理,即不管有多少個客戶端進行鏈接請求,服務端始終只有1個進程對客戶端進行響應,這樣的好處是節省了系統開銷(select不適合單個客戶端長會話操做,這樣其它客戶端鏈接請求就會一直等待,poll/epoll對select進行了改進)。下面介紹結合了IO多路複用和多進程(多線程)的SocketServer模塊。html

1 SocketServer模塊

    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()
SocketServer實現的服務端
 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
BaseServer
  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()
TCPServer
 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 複製代碼
ThreadingMixIn
1 class ThreadingTCPServer(ThreadingMixIn, TCPServer): 
2         pass
ThreadingTCPServer

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
SocketServer.BaseRequestHandler

源碼精簡

 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代碼

    從精簡代碼能夠看出,SocketServer的ThreadingTCPServer之因此能夠同時處理請求得益於selectThreading兩個東西,其實本質上就是在服務器端爲每個客戶端建立一個線程,用於後續的數據處理,當前線程用來處理對應客戶端的請求,因此能夠支持同時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之因此能夠同時處理請求得益於selectos.fork兩個東西,其實本質上就是在服務器端爲每個客戶端建立一個進程,用於後續數據處理,當前新建立的進程用來處理對應客戶端的請求,因此,能夠支持同時n個客戶端連接(長鏈接)。

源碼剖析參考 ThreadingTCPServer

2 Twisted模塊

    使用傳統的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)

3 paramiko模塊

   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:在遠程服務器執行命令
 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()
實例2:在遠程服務器執行命令

  在上述兩個實例中,其實實例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 '''
實例5:基於用戶名密碼的上傳下載
 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()
實例6:基於用戶名密鑰對的上傳下載

   在遠程服務器執行命令時,其實時間主要消耗在創建鏈接上了。

自定義類的,在鏈接後進行相應的上傳下載操做,這樣就能夠在一次鏈接中進行其它操做,避免頻繁的建立鏈接,關閉鏈接,減小資源消耗

 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

相關文章
相關標籤/搜索