1. 程式人生 > 程式設計 >Python 建立TCP伺服器的方法

Python 建立TCP伺服器的方法

問題

你想實現一個伺服器,通過TCP協議和客戶端通訊。

解決方案

建立一個TCP伺服器的一個簡單方法是使用 socketserver 庫。例如,下面是一個簡單的應答伺服器:

from socketserver import BaseRequestHandler,TCPServer

class EchoHandler(BaseRequestHandler):
  def handle(self):
    print('Got connection from',self.client_address)
    while True:

      msg = self.request.recv(8192)
      if not msg:
        break
      self.request.send(msg)

if __name__ == '__main__':
  serv = TCPServer(('',20000),EchoHandler)
  serv.serve_forever()

在這段程式碼中,你定義了一個特殊的處理類,實現了一個 handle() 方法,用來為客戶端連線服務。 request 屬性是客戶端socket,client_address 有客戶端地址。 為了測試這個伺服器,執行它並開啟另外一個Python程序連線這個伺服器:

>>> from socket import socket,AF_INET,SOCK_STREAM
>>> s = socket(AF_INET,SOCK_STREAM)
>>> s.connect(('localhost',20000))
>>> s.send(b'Hello')
5
>>> s.recv(8192)
b'Hello'
>>>

很多時候,可以很容易的定義一個不同的處理器。下面是一個使用 StreamRequestHandler 基類將一個類檔案介面放置在底層socket上的例子:

from socketserver import StreamRequestHandler,TCPServer

class EchoHandler(StreamRequestHandler):
  def handle(self):
    print('Got connection from',self.client_address)
    # self.rfile is a file-like object for reading
    for line in self.rfile:
      # self.wfile is a file-like object for writing
      self.wfile.write(line)

if __name__ == '__main__':
  serv = TCPServer(('',EchoHandler)
  serv.serve_forever()

討論

socketserver 可以讓我們很容易的建立簡單的TCP伺服器。 但是,你需要注意的是,預設情況下這種伺服器是單執行緒的,一次只能為一個客戶端連線服務。 如果你想處理多個客戶端,可以初始化一個 ForkingTCPServer 或者是 ThreadingTCPServer 物件。例如:

from socketserver import ThreadingTCPServer


if __name__ == '__main__':
  serv = ThreadingTCPServer(('',EchoHandler)
  serv.serve_forever()

使用fork或執行緒伺服器有個潛在問題就是它們會為每個客戶端連線建立一個新的程序或執行緒。 由於客戶端連線數是沒有限制的,因此一個惡意的黑客可以同時傳送大量的連線讓你的伺服器奔潰。

如果你擔心這個問題,你可以建立一個預先分配大小的工作執行緒池或程序池。 你先建立一個普通的非執行緒伺服器,然後在一個執行緒池中使用 serve_forever() 方法來啟動它們。

if __name__ == '__main__':
  from threading import Thread
  NWORKERS = 16
  serv = TCPServer(('',EchoHandler)
  for n in range(NWORKERS):
    t = Thread(target=serv.serve_forever)
    t.daemon = True
    t.start()
  serv.serve_forever()

一般來講,一個 TCPServer 在例項化的時候會繫結並激活相應的 socket 。 不過,有時候你想通過設定某些選項去調整底下的 socket` ,可以設定引數 bind_and_activate=False 。如下:

if __name__ == '__main__':
  serv = TCPServer(('',EchoHandler,bind_and_activate=False)
  # Set up various socket options
  serv.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)
  # Bind and activate
  serv.server_bind()
  serv.server_activate()
  serv.serve_forever()

上面的 socket 選項是一個非常普遍的配置項,它允許伺服器重新繫結一個之前使用過的埠號。 由於要被經常使用到,它被放置到類變數中,可以直接在 TCPServer 上面設定。 在例項化伺服器的時候去設定它的值,如下所示:

if __name__ == '__main__':
  TCPServer.allow_reuse_address = True
  serv = TCPServer(('',EchoHandler)
  serv.serve_forever()

在上面示例中,我們演示了兩種不同的處理器基類( BaseRequestHandler 和 StreamRequestHandler )。 StreamRequestHandler 更加靈活點,能通過設定其他的類變數來支援一些新的特性。比如:

import socket

class EchoHandler(StreamRequestHandler):
  # Optional settings (defaults shown)
  timeout = 5           # Timeout on all socket operations
  rbufsize = -1          # Read buffer size
  wbufsize = 0           # Write buffer size
  disable_nagle_algorithm = False # Sets TCP_NODELAY socket option
  def handle(self):
    print('Got connection from',self.client_address)
    try:
      for line in self.rfile:
        # self.wfile is a file-like object for writing
        self.wfile.write(line)
    except socket.timeout:
      print('Timed out!')

最後,還需要注意的是絕大部分Python的高層網路模組(比如HTTP、XML-RPC等)都是建立在 socketserver 功能之上。 也就是說,直接使用 socket 庫來實現伺服器也並不是很難。 下面是一個使用 socket 直接程式設計實現的一個伺服器簡單例子:

from socket import socket,SOCK_STREAM

def echo_handler(address,client_sock):
  print('Got connection from {}'.format(address))
  while True:
    msg = client_sock.recv(8192)
    if not msg:
      break
    client_sock.sendall(msg)
  client_sock.close()

def echo_server(address,backlog=5):
  sock = socket(AF_INET,SOCK_STREAM)
  sock.bind(address)
  sock.listen(backlog)
  while True:
    client_sock,client_addr = sock.accept()
    echo_handler(client_addr,client_sock)

if __name__ == '__main__':
  echo_server(('',20000))

以上就是Python 建立TCP伺服器的方法的詳細內容,更多關於Python 建立TCP伺服器的資料請關注我們其它相關文章!