Python入門教程:socketserver編程 熱文推薦:

上文中,我們自己使用socket和threading模塊實現了一個簡單的多線程服務器。在非正式環境,隨便用用還是可以的,但是如果要在生產環境中使用,那是萬萬不夠的。

Python考慮得很周到,爲了滿足我們對多線程網絡服務器的需求,提供了socketserver模塊。socketserver在內部使用IO多路複用以及多線程/進程機制,實現了併發處理多個客戶端請求的socket服務端。每個客戶端請求連接到服務器時,socketserver服務端都會創建一個“線程”或者“進程” 專門負責處理當前客戶端的所有請求。

讓我們來看看socketserver模塊的Python源碼(what!我連基本的都還沒明白,你就讓我看源代碼......):

import socket
import selectors
import os
import errno
import sys
try:
    import threading
except ImportError:
    import dummy_threading as threading
from io import BufferedIOBase
from time import monotonic as time

__all__ = ["BaseServer", "TCPServer", "UDPServer",
           "ThreadingUDPServer", "ThreadingTCPServer",
           "BaseRequestHandler", "StreamRequestHandler",
           "DatagramRequestHandler", "ThreadingMixIn"]
if hasattr(os, "fork"):
    __all__.extend(["ForkingUDPServer","ForkingTCPServer", "ForkingMixIn"])
if hasattr(socket, "AF_UNIX"):
    __all__.extend(["UnixStreamServer","UnixDatagramServer",
                    "ThreadingUnixStreamServer",
                    "ThreadingUnixDatagramServer"])

# poll/select have the advantage of not requiring any extra file descriptor,
# contrarily to epoll/kqueue (also, they require a single syscall).
if hasattr(selectors, 'PollSelector'):
    _ServerSelector = selectors.PollSelector
else:
    _ServerSelector = selectors.SelectSelector

class BaseServer:
    pass

class TCPServer(BaseServer):
    pass

class UDPServer(TCPServer):
    pass

if hasattr(os, "fork"):
    pass

class ThreadingMixIn:
    pass

if hasattr(os, "fork"):
    class ForkingUDPServer(ForkingMixIn, UDPServer): pass
    class ForkingTCPServer(ForkingMixIn, TCPServer): pass

class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass

if hasattr(socket, 'AF_UNIX'):
    pass

class StreamRequestHandler(BaseRequestHandler):
    pass

class _SocketWriter(BufferedIOBase):
    pass

class DatagramRequestHandler(BaseRequestHandler):
   pass

我把每個類的具體代碼給省略後,就剩下這麼些內容。

在socketserver模塊的開頭,導入了socket和threading等一些Python內置模塊,和我們一樣樣的!然後,定義了一個all魔法變量,表示我們默認只能使用該模塊裏的這些類。

接下來,if hasattr(os, "fork"):判斷語句,用於測試當前操作系統是否支持fork操作,如果支持,好吧,你可以你解鎖更多功能。

if hasattr(socket, "AF_UNIX"):是針對UNIX系統的功能,同樣基於操作系統的支持。

if hasattr(selectors, 'PollSelector'):就是最關鍵的IO複用機制的選擇了,如果操作系統支持Poll,則啓用PollSelector,否則使用默認的SelectSelector。

拋開那些複雜的類名及其作用,從邏輯流程來講,其實socketserver也很簡單。

閱讀源代碼有助於我們理解模塊的運行機制,提高自己的代碼水平。但具體使用模塊,還要落到實處。對於socketserver模塊,我們最常使用的是ThreadingTCPServer類。其定義如下:

class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass

這裏的pass不是我省略了,而是它真的就只有pass,-。它本身一句代碼都沒有,一切的功能都從兩個父類裏繼承,ThreadingMixIn爲它提供了多線程能力,TCPServer爲它提供基本的socket通信能力。其繼承關係,如下圖所示:

ThreadingTCPServer實現的Soket服務器內部會爲每個客戶端創建一個線程,該線程用來和客戶端進行交互。服務器相當於一個總管,在接收連接並創建新的線程後,就撒手不管了,後面的通信就是線程和客戶端之間的連接了,一定要理解這一點!

使用ThreadingTCPServer的要點:

  • 創建一個繼承自socketserver.BaseRequestHandler的類;
  • 這個類中必須定義一個名字爲handle的方法,不能是別的名字!
  • 將這個類,連同服務器的ip和端口,作爲參數傳遞給ThreadingTCPServer()構造器
  • 手動啓動ThreadingTCPServer。
    下面是一個ThreadingTCPServer使用的例子:

服務器端:

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socketserver

class MyServer(socketserver.BaseRequestHandler):
    """
    必須繼承socketserver.BaseRequestHandler類
    """
    def handle(self):
        """
        必須實現這個方法!
        :return:
        """
        conn = self.request         # request裏封裝了所有請求的數據
        conn.sendall('歡迎訪問socketserver服務器!'.encode())
        while True:
            data = conn.recv(1024).decode()
            if data == "exit":
                print("斷開與%s的連接!" % (self.client_address,))
                break
            print("來自%s的客戶端向你發來信息:%s" % (self.client_address, data))
            conn.sendall(('已收到你的消息<%s>' % data).encode())

if __name__ == '__main__':
    # 創建一個多線程TCP服務器
    server = socketserver.ThreadingTCPServer(('127.0.0.1', 9999), MyServer)
    print("啓動socketserver服務器!")
    # 啓動服務器,服務器將一直保持運行狀態
    server.serve_forever()

客戶端:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
客戶端依然使用socket模塊就可以了,不需要導入socketserver模塊
"""

import socket

ip_port = ('127.0.0.1', 9999)
sk = socket.socket()
sk.connect(ip_port)
sk.settimeout(5)
data = sk.recv(1024).decode()
print('服務器:', data)
while True:
    inp = input('你:').strip()
    if not inp:
        continue

    sk.sendall(inp.encode())

    if inp == 'exit':
        print("謝謝使用,再見!")
        break
    data = sk.recv(1024).decode()
    print('服務器:', data)
sk.close()

客戶端的代碼很好理解,和前面一樣樣的,關鍵是服務器端。

分析一下服務器端的代碼,核心要點有這些:

  • 連接數據封裝在self.request中!調用send()和recv()方法都是通過self.request對象。
  • handle()方法是整個通信的處理核心,一旦它運行結束,當前連接也就斷開了(但其他的線程和客戶端還正常),因此一般在此設置一個無限循環。
  • 注意server = socketServer.ThreadingTCPServer((‘127.0.0.1’,8009),MyServer)中參數傳遞的方法。
  • server.serve_forever()表示該服務器在正常情況下將永遠運行。

socketserver模塊還提供了ThreadingUDPServer類,用於提供多線程的UDP服務。還有ForkingTCPServer類,當操作系統支持fork操作的時候,可以實現多進程服務器。他們的用法和ThreadingTCPServer基本類似,大家可以自行嘗試。

熱文推薦:

全網首發!最全最新的Python學習教程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章