python基礎之socket與socketserver


---引入

Socket的英文原義是“孔”或“插座”,在Unix的進程通信機制中又稱為‘套接字’。套接字實際上並不復雜,它是由一個ip地址以及一個端口號組成。Socket正如其英文原意那樣,像一個多孔插座。一台主機猶如布滿各種插座(ip地址)的房間,每個插座有很多插口(端口),通過這些插口接入電線(進程)我們可以燒水,看電視,玩電腦……  

應用程序通常通過"套接字"向網絡發出請求或者應答網絡請求

套接字的作用之一就是用來區分不同應用進程,當某個進程綁定了本機ip的某個端口,那么所有傳送至這個ip地址上的這個端口的所有數據都會被內核送至該進程進行處理。

---python中的socket

Python 提供了兩個基本的 socket 模塊。

   第一個是 Socket,它提供了標准的 BSD Sockets API。

   第二個是 SocketServer, 它提供了服務器中心類,可以簡化網絡服務器的開發。

----socket

   先來說第一個。

我們知道,現在的應用程序大多為C/S架構,也就是分為客戶端/服務器端。

  服務器端:服務器端進程需要申請套接字,然后自己綁定在這個套接字上,並對這個套接字進行監聽。當有客戶端發送數據了,則接受數據進行處理,處理完成后對客戶端進行響應。

  客戶端:客戶端則相對簡單些,客戶端只需要申請一個套接字,然后通過這個套接字連接服務器端的套接字,連接建立后就可以進行后續操作了。

python編寫服務器端的步驟:

  1  創建套接字

import socket
s1=socket.socket(family,type)
#family參數代表地址家族,可為AF_INET或AF_UNIX。AF_INET家族包括Internet地址,AF_UNIX家族用於同一台機器上的進程間通信。
#type參數代表套接字類型,可為SOCK_STREAM(流套接字,就是TCP套接字)和SOCK_DGRAM(數據報套接字,就是UDP套接字)。 
#默認為family=AF_INET  type=SOCK_STREM    
#返回一個整數描述符,用這個描述符來標識這個套接字

  2  綁定套接字

s1.bind( address ) 
#由AF_INET所創建的套接字,address地址必須是一個雙元素元組,格式是(host,port)。host代表主機,port代表端口號。
#如果端口號正在使用、主機名不正確或端口已被保留,bind方法將引發socket.error異常。
#例: ('192.168.1.1',9999)

  3  監聽套接字

s1.listen( backlog ) 
#backlog指定最多允許多少個客戶連接到服務器。它的值至少為1。收到連接請求后,這些請求需要排隊,如果隊列滿,就拒絕請求。 

  4  等待接受連接

connection, address = s1.accept()
#調用accept方法時,socket會時入“waiting”狀態,也就是處於阻塞狀態。客戶請求連接時,方法建立連接並返回服務器。
#accept方法返回一個含有兩個元素的元組(connection,address)。
#第一個元素connection是所連接的客戶端的socket對象(實際上是該對象的內存地址),服務器必須通過它與客戶端通信;
#第二個元素 address是客戶的Internet地址。

  5  處理階段

connection.recv(bufsize[,flag])
#注意此處為connection
#接受套接字的數據。數據以字符串形式返回,bufsize指定最多可以接收的數量。flag提供有關消息的其他信息,通常可以忽略 connection.send(string[,flag]) #將string中的數據發送到連接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。即:可能未將指定內容全部發送。

  6  傳輸結束,關閉連接

s1.close()
#關閉套接字

python編寫客戶端

  1  創建socket對象

import socket
s2=socket.socket()

  2  連接至服務器端

s2.connect(address)
#連接到address處的套接字。一般,address的格式為元組(hostname,port),如果連接出錯,返回socket.error錯誤。

  3  處理階段

s2.recv(bufsize[,flag])
#接受套接字的數據。數據以字符串形式返回,bufsize指定最多可以接收的數量。flag提供有關消息的其他信息,通常可以忽略

s2.send(string[,flag])
#將string中的數據發送到連接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。即:可能未將指定內容全部發送。

  4  連接結束,關閉套接字

s2.close()

socket中還有許多方法 :

 

socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0) #獲取要連接的對端主機地址

sk.bind(address)
  將套接字綁定到地址。address地址的格式取決於地址族。在AF_INET下,以元組(host,port)的形式表示地址。

sk.listen(backlog) 開始監聽傳入連接。backlog指定在拒絕連接之前,可以掛起的最大連接數量。 backlog等於5,表示內核已經接到了連接請求,但服務器還沒有調用accept進行處理的連接個數最大為5,這個值不能無限大,因為要在內核中維護連接隊列 sk.setblocking(bool) 是否阻塞(默認True),如果設置False,那么accept和recv時一旦無數據,則報錯。
sk.accept() 接受連接並返回(conn,address),其中conn是新的套接字對象,可以用來接收和發送數據。address是連接客戶端的地址。接收TCP 客戶的連接(阻塞式)等待連接的到來 sk.connect(address) 連接到address處的套接字。一般,address的格式為元組(hostname,port),如果連接出錯,返回socket.error錯誤。 sk.connect_ex(address) 同上,只不過會有返回值,連接成功時返回 0 ,連接失敗時候返回編碼,例如:
10061 sk.close() 關閉套接字 sk.recv(bufsize[,flag]) 接受套接字的數據。數據以字符串形式返回,bufsize指定最多可以接收的數量。flag提供有關消息的其他信息,通常可以忽略。 sk.recvfrom(bufsize[.flag]) 與recv()類似,但返回值是(data,address)。其中data是包含接收數據的字符串,address是發送數據的套接字地址。 sk.send(string[,flag]) 將string中的數據發送到連接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。即:可能未將指定內容全部發送。 sk.sendall(string[,flag]) 將string中的數據發送到連接的套接字,但在返回之前會嘗試發送所有數據。成功返回None,失敗則拋出異常。內部通過遞歸調用send,將所有內容發送出去。 sk.sendto(string[,flag],address) 將數據發送到套接字,address是形式為(ipaddr,port)的元組,指定遠程地址。返回值是發送的字節數。該函數主要用於UDP協議。 sk.settimeout(timeout) 設置套接字操作的超時期,timeout是一個浮點數,單位是秒。值為None表示沒有超時期。一般,超時期應該在剛創建套接字時設置,
因為它們可能用於連接的操作(如 client 連接最多等待5s ) sk.getpeername() 返回連接套接字的遠程地址。返回值通常是元組(ipaddr,port)。 sk.getsockname() 返回套接字自己的地址。通常是一個元組(ipaddr,port) sk.fileno() 套接字的文件描述符 socket.sendfile(file, offset
=0, count=None) 發送文件

 

 

好了,介紹完socket現在該介紹socketserver了。

----socketserver

  雖說用Python編寫簡單的網絡程序很方便,但復雜一點的網絡程序還是用現成的框架比較 好。這樣就可以專心事務邏輯,而不是套接字的各種細節。SocketServer模塊簡化了編寫網絡服務程序的任務。同時SocketServer模塊也 是Python標准庫中很多服務器框架的基礎。

socketserver在python2中為SocketServer,在python3種取消了首字母大寫,改名為socketserver。

socketserver中包含了兩種類,一種為服務類(server class),一種為請求處理類(request handle class)。前者提供了許多方法:像綁定,監聽,運行…… (也就是建立連接的過程) 后者則專注於如何處理用戶所發送的數據(也就是事務邏輯)。

  一般情況下,所有的服務,都是先建立連接,也就是建立一個服務類的實例,然后開始處理用戶請求,也就是建立一個請求處理類的實例

 

我們分析一下源碼,來看一看服務類是如何與請求處理類建立聯系的。

class BaseServer:
#我們創建服務類時,需要指定(地址,端口),服務處理類。
def __init__(self, server_address, RequestHandlerClass): """Constructor. May be extended, do not override.""" self.server_address = server_address self.RequestHandlerClass = RequestHandlerClass self.__is_shut_down = threading.Event() self.__shutdown_request = False #…………此處省略n多代碼,當我們執行server_forever方法時,里面就會調用很多服務類中的其他方法,但最終會調用finish_request方法。 def finish_request(self, request, client_address): """Finish one request by instantiating RequestHandlerClass.""" self.RequestHandlerClass(request, client_address, self) #finish_request方法中執行了self.RequestHandlerClass(request, client_address, self)。self.RequestHandlerClass是什么呢?
#self.RequestHandlerClass = RequestHandlerClass(就在__init__方法中)。所以finish_request方法本質上就是創建了一個服務處理實例。


class BaseRequestHandler: def __init__(self, request, client_address, server): self.request = request self.client_address = client_address self.server = server self.setup() try: self.handle() finally: self.finish()
#當我們創建服務處理類實例時,就會運行handle()方法,而handle()方法則一般是我們處理事務邏輯的代碼塊。
#…………此處省略n多代碼

 

我們接下來介紹一下這兩個類

先來看服務類:

5種類型:BaseServer,TCPServer,UnixStreamServer,UDPServer,UnixDatagramServer。

BaseServer不直接對外服務。

TCPServer針對TCP套接字流

UDPServer針對UDP數據報套接字

UnixStreamServer和UnixDatagramServer針對UNIX域套接字,不常用。

他們之間的繼承關系:

 

 

服務類的方法:

     class SocketServer.BaseServer:這是模塊中的所有服務器對象的超類。它定義了接口,如下所述,但是大多數的方法不實現,在子類中進行細化。

    BaseServer.fileno():返回服務器監聽套接字的整數文件描述符。通常用來傳遞給select.select(), 以允許一個進程監視多個服務器。

    BaseServer.handle_request():處理單個請求。處理順序:get_request(), verify_request(), process_request()。如果用戶提供handle()方法拋出異常,將調用服務器的handle_error()方法。如果self.timeout內沒有請求收到, 將調用handle_timeout()並返回handle_request()。

    BaseServer.serve_forever(poll_interval=0.5): 處理請求,直到一個明確的shutdown()請求。每poll_interval秒輪詢一次shutdown。忽略self.timeout。如果你需要做周期性的任務,建議放置在其他線程。

    BaseServer.shutdown():告訴serve_forever()循環停止並等待其停止。python2.6版本。

    BaseServer.address_family: 地址家族,比如socket.AF_INET和socket.AF_UNIX。

    BaseServer.RequestHandlerClass:用戶提供的請求處理類,這個類為每個請求創建實例。

    BaseServer.server_address:服務器偵聽的地址。格式根據協議家族地址的各不相同,請參閱socket模塊的文檔。

    BaseServer.socketSocket:服務器上偵聽傳入的請求socket對象的服務器。

服務器類支持下面的類變量:

    BaseServer.allow_reuse_address:服務器是否允許地址的重用。默認為false ,並且可在子類中更改。

    BaseServer.request_queue_size

請求隊列的大小。如果單個請求需要很長的時間來處理,服務器忙時請求被放置到隊列中,最多可以放request_queue_size個。一旦隊列已滿,來自客戶端的請求將得到 “Connection denied”錯誤。默認值通常為5 ,但可以被子類覆蓋。

    BaseServer.socket_type:服務器使用的套接字類型; socket.SOCK_STREAM和socket.SOCK_DGRAM等。

    BaseServer.timeout:超時時間,以秒為單位,或 None表示沒有超時。如果handle_request()在timeout內沒有收到請求,將調用handle_timeout()。

下面方法可以被子類重載,它們對服務器對象的外部用戶沒有影響。

    BaseServer.finish_request():實際處理RequestHandlerClass發起的請求並調用其handle()方法。 常用。

    BaseServer.get_request():接受socket請求,並返回二元組包含要用於與客戶端通信的新socket對象,以及客戶端的地址。

    BaseServer.handle_error(request, client_address):如果RequestHandlerClass的handle()方法拋出異常時調用。默認操作是打印traceback到標准輸出,並繼續處理其他請求。

    BaseServer.handle_timeout():超時處理。默認對於forking服務器是收集退出的子進程狀態,threading服務器則什么都不做。

    BaseServer.process_request(request, client_address) :調用finish_request()創建RequestHandlerClass的實例。如果需要,此功能可以創建新的進程或線程來處理請求,ForkingMixIn和ThreadingMixIn類做到這點。常用。

    BaseServer.server_activate():通過服務器的構造函數來激活服務器。默認的行為只是監聽服務器套接字。可重載。

    BaseServer.server_bind():通過服務器的構造函數中調用綁定socket到所需的地址。可重載。

    BaseServer.verify_request(request, client_address):返回一個布爾值,如果該值為True ,則該請求將被處理,反之請求將被拒絕。此功能可以重寫來實現對服務器的訪問控制。默認的實現始終返回True。client_address可以限定客戶端,比如只處理指定ip區間的請求。 常用。
View Code

這個幾個服務類都是同步處理請求的:一個請求沒處理完不能處理下一個請求。要想支持異步模型,可以利用多繼承讓server類繼承ForkingMixIn 或 ThreadingMixIn mix-in classes。

ForkingMixIn利用多進程(分叉)實現異步。

ThreadingMixIn利用多線程實現異步。

 

請求處理器類:

要實現一項服務,還必須派生一個handler class請求處理類,並重寫父類的handle()方法。handle方法就是用來專門是處理請求的。該模塊是通過服務類和請求處理類組合來處理請求的。

SocketServer模塊提供的請求處理類有BaseRequestHandler,以及它的派生類StreamRequestHandler和DatagramRequestHandler。從名字看出可以一個處理流式套接字,一個處理數據報套接字。

 

請求處理類有三種方法:

setup()

Called before the handle() method to perform any initialization actions required. The default implementation does nothing.

也就是在handle()之前被調用,主要的作用就是執行處理請求之前的初始化相關的各種工作。默認不會做任何事。(如果想要讓其做一些事的話,就要程序員在自己的請求處理器中覆蓋這個方法(因為一般自定義的請求處理器都要繼承python中提供的BaseRequestHandler,ps:下文會提到的),然后往里面添加東西即可)

handle()

This function must do all the work required to service a request. The default implementation does nothing. Several instance attributes are available to it; the request is available as self.request; the client address as self.client_address; and the server instance as self.server, in case it needs access to per-server information.

The type of self.request is different for datagram or stream services. For stream services,self.request is a socket object; for datagram services, self.request is a pair of string and socket.

handle()的工作就是做那些所有與處理請求相關的工作。默認也不會做任何事。他有數個實例參數:self.request    self.client_address   self.server

finish()

Called after the handle() method to perform any clean-up actions required. The default implementation does nothing. If setup() raises an exception, this function will not be called.

在handle()方法之后會被調用,他的作用就是執行當處理完請求后的清理工作,默認不會做任何事

class BaseRequestHandler:

    """Base class for request handler classes.

    This class is instantiated for each request to be handled.  The
    constructor sets the instance variables request, client_address
    and server, and then calls the handle() method.  To implement a
    specific service, all you need to do is to derive a class which
    defines a handle() method.

    The handle() method can find the request as self.request, the
    client address as self.client_address, and the server (in case it
    needs access to per-server information) as self.server.  Since a
    separate instance is created for each request, the handle() method
    can define arbitrary other instance variariables.

    """

    def __init__(self, request, client_address, server):
        self.request = request
        self.client_address = client_address
        self.server = server
        self.setup()
        try:
            self.handle()
        finally:
            self.finish()

    def setup(self):
        pass

    def handle(self):
        pass

    def finish(self):
        pass


# The following two classes make it possible to use the same service
# class for stream or datagram servers.
# Each class sets up these instance variables:
# - rfile: a file object from which receives the request is read
# - wfile: a file object to which the reply is written
# When the handle() method returns, wfile is flushed properly


class StreamRequestHandler(BaseRequestHandler):

    """Define self.rfile and self.wfile for stream sockets."""

    # Default buffer sizes for rfile, wfile.
    # We default rfile to buffered because otherwise it could be
    # really slow for large data (a getc() call per byte); we make
    # wfile unbuffered because (a) often after a write() we want to
    # read and we need to flush the line; (b) big writes to unbuffered
    # files are typically optimized by stdio even when big reads
    # aren't.
    rbufsize = -1
    wbufsize = 0

    # A timeout to apply to the request socket, if not None.
    timeout = None

    # Disable nagle algorithm for this socket, if True.
    # Use only when wbufsize != 0, to avoid small packets.
    disable_nagle_algorithm = False

    def setup(self):
        self.connection = self.request
        if self.timeout is not None:
            self.connection.settimeout(self.timeout)
        if self.disable_nagle_algorithm:
            self.connection.setsockopt(socket.IPPROTO_TCP,
                                       socket.TCP_NODELAY, True)
        self.rfile = self.connection.makefile('rb', self.rbufsize)
        self.wfile = self.connection.makefile('wb', self.wbufsize)

    def finish(self):
        if not self.wfile.closed:
            try:
                self.wfile.flush()
            except socket.error:
                # An final socket error may have occurred here, such as
                # the local error ECONNABORTED.
                pass
        self.wfile.close()
        self.rfile.close()


class DatagramRequestHandler(BaseRequestHandler):

    # XXX Regrettably, I cannot get this working on Linux;
    # s.recvfrom() doesn't return a meaningful client address.

    """Define self.rfile and self.wfile for datagram sockets."""

    def setup(self):
        from io import BytesIO
        self.packet, self.socket = self.request
        self.rfile = BytesIO(self.packet)
        self.wfile = BytesIO()

    def finish(self):
        self.socket.sendto(self.wfile.getvalue(), self.client_address)
Handler源碼

從源碼中可以看出,BaseRequestHandler中的setup()/handle()/finish()什么內容都沒有定義,而他的兩個派生類StreamRequestHandler和DatagramRequestHandler則都重寫了setup()/finish()。

因此當我們需要自己編寫socketserver程序時,只需要合理選擇StreamRequestHandler和DatagramRequestHandler之中的一個作為父類,然后自定義一個請求處理類,並在其中重寫handle()方法即可。

 

用socketserver創建一個服務的步驟:

1  創建一個request handler class(請求處理類),合理選擇StreamRequestHandler和DatagramRequestHandler之中的一個作為父類(當然,使用BaseRequestHandler作為父類也可),並重寫它的handle()方法。

2  實例化一個server class對象,並將服務的地址和之前創建的request handler class傳遞給它。

3  調用server class對象的handle_request() 或 serve_forever()方法來開始處理請求。

 代碼實例

import socketserver
 
class MyTCPHandler(socketserver.BaseRequestHandler):
    """
    The request handler class for our server.
 
    It is instantiated once per connection to the server, and must
    override the handle() method to implement communication to the
    client.
    """
 
    def handle(self):
        # self.request is the TCP socket connected to the client
        self.data = self.request.recv(1024).strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        # just send back the same data, but upper-cased
        self.request.sendall(self.data.upper())
 
if __name__ == "__main__":
    HOST, PORT = "localhost", 9999
 
    # Create the server, binding to localhost on port 9999
    server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
 
    # Activate the server; this will keep running until you
    # interrupt the program with Ctrl-C
    server.serve_forever()
服務器端
import socket
import sys
 
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
 
# Create a socket (SOCK_STREAM means a TCP socket)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
try:
    # Connect to server and send data
    sock.connect((HOST, PORT))
    sock.sendall(bytes(data + "\n", "utf-8"))
 
    # Receive data from the server and shut down
    received = str(sock.recv(1024), "utf-8")
finally:
    sock.close()
 
print("Sent:     {}".format(data))
print("Received: {}".format(received))
客戶端

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM