Python套接字


1、客戶端/服務器架構

  什么是客戶端/服務器架構?對於不同的人來說,它意味着不同的東西,這取決於你問誰以及描述的是軟件還是硬件系統。在這兩種情況中的任何一種下,前提都很簡單:服務器就是一系列硬件或軟件,為一個或多個客戶端(服務的用戶)提供所需的“服務”。它存在唯一目的就是等待客戶端的請求,並響應它們(提供服務),然后等待更多請求。另一方面,客戶端因特定的請求而聯系服務器,並發送必要的數據,然后等待服務器的回應,最后完成請求或給出故障的原因。服務器無限地運行下去,並不斷地處理請求;而客戶端會對服務進行一次性請求,然后接收該服務,最后結束它們之間的事務。客戶端在一段時間后可能會再次發出其他請求,但這些都被當作不同的事務。

2、客戶端/服務器編程

2.1套接字

  套接字的起源可以追溯到20 世紀70 年代,它是加利福尼亞大學的伯克利版本UNIX(稱為BSD UNIX)的一部分。因此,有時你可能會聽過將套接字稱為伯克利套接字或BSD 套接字。套接字最初是為同一主機上的應用程序所創建,使得主機上運行的一個程序(又名一個進程)與另一個運行的程序進行通信。這就是所謂的進程間通信(Inter Process Communication,IPC)。有兩種類型的套接字:基於文件的和面向網絡的。UNIX 套接字是我們所講的套接字的第一個家族,並且擁有一個“家族名字”AF_UNIX(又名AF_LOCAL,在POSIX1.g 標准中指定),它代表地址家族(address family):UNIX。包括Python 在內的大多數受歡迎的平台都使用術語地址家族及其縮寫AF;其他比較舊的系統可能會將地址家族表示成域(domain)或協議家族(protocol family),並使用其縮寫PF 而非AF。類似地,AF_LOCAL(在2000~2001 年標准化)將代替AF_UNIX。然而,考慮到后向兼容性,很多系統都同時使用二者,只是對同一個常數使用不同的別名。Python 本身仍然在使用AF_UNIX。因為兩個進程運行在同一台計算機上,所以這些套接字都是基於文件的,這意味着文件
系統支持它們的底層基礎結構。這是能夠說得通的,因為文件系統是一個運行在同一主機上的多個進程之間的共享常量

  第二種類型的套接字是基於網絡的,它也有自己的家族名字AF_INET,或者地址家族:因特網。另一個地址家族AF_INET6 用於第6 版因特網協議(IPv6)尋址。此外,還有其他的地址家族,這些要么是專業的、過時的、很少使用的,要么是仍未實現的。在所有的地址家族之中,目前AF_INET 是使用得最廣泛的。

  Python 2.5 中引入了對特殊類型的Linux 套接字的支持。套接字的AF_NETLINK 家族允許使用標准的BSD 套接字接口進行用戶級別和內核級別代碼之間的IPC。針對Linux 的另一種特性(Python 2.6 中新增)就是支持透明的進程間通信(TIPC)協議。TIPC 允許計算機集群之中的機器相互通信,而無須使用基於IP 的尋址方式。Python 對TIPC 的支持以AF_TIPC 家族的方式呈現。

2.2套接字地址:主機-端口對

  有效的端口號范圍為0~65535(盡管小於1024 的端口號預留給了系統)。

2.3面向連接的套接字

  TCP 套接字,必須使用SOCK_STREAM 作為套接字類型。

2.4無連接的套接字

  實現這種連接類型的主要協議是用戶數據報協議(更為人熟知的是其縮寫UDP)。為了創建UDP 套接字,必須使用SOCK_DGRAM 作為套接字類型。

3、python中的網絡編程

3.1socket 模塊

socket模塊屬性

套接字創建:

socket(socket_family, socket_type, protocol=0)
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ud pSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

socket對象方法:

服務器創建:

ss = socket() # 創建服務器套接字
ss.bind() # 套接字與地址綁定
ss.listen() # 監聽連接
inf_loop: # 服務器無限循環
cs = ss.accept() # 接受客戶端連接
comm_loop: # 通信循環
cs.recv()/cs.send() # 對話(接收/發送)
cs.close() # 關閉客戶端套接字
ss .close() # 關閉服務器套接字#(可選)

為服務器實現一個智能的退出方案時,建議調用close()方法。

客戶端創建:

cs = socket() # 創建客戶端套接字
cs.connect() # 嘗試連接服務器
comm_loop: # 通信循環
cs.send()/cs.recv() # 對話(發送/接收)
cs .close() # 關閉客戶端套接字

 I/O多路復用

多路復用是指使用一個線程來檢查多個文件描述符(Socket)的就緒狀態,比如調用select和poll函數,傳入多個文件描述符,如果有一個文件描述符就緒,則返回,否則阻塞直到超時。得到就緒狀態后進行真正的操作可以在同一個線程里執行,也可以啟動線程執行(比如使用線程池)。 這樣在處理1000個連接時,只需要1個線程監控就緒狀態,對就緒的每個連接開一個線程處理就可以了,這樣需要的線程數大大減少,減少了內存開銷和上下文切換的CPU開銷。

舉一個例子,模擬一個tcp服務器處理30個客戶socket。

假設你是一個老師,讓30個學生解答一道題目,然后檢查學生做的是否正確,你有下面幾個選擇:

1. 第一種選擇: 按順序逐個檢查,先檢查A,然后是B,之后是C、D。。。這中間如果有一個學生卡主,全班都會被耽誤。
這種模式就好比,你用循環挨個處理socket,根本不具有並發能力。
2. 第二種選擇:你 創建30個分身,每個分身檢查一個學生的答案是否正確。 這種類似於為每一個用戶創建一個進程或者線程處理連接。
3. 第三種選擇,你 站在講台上等,誰解答完誰舉手。這時C、D舉手,表示他們解答問題完畢,你下去依次檢查C、D的答案,然后繼續回到講台上等。此時E、A又舉手,然后去處理E和A。。。
這種就是IO復用模型,Linux下的select、poll和epoll就是干這個的。將用戶socket對應的fd注冊進epoll,然后epoll幫你監聽哪些socket上有消息到達,這樣就避免了大量的無用操作。此時的socket應該采用 非阻塞模式
這樣,整個過程只在調用select、poll、epoll這些調用的時候才會阻塞,收發客戶消息是不會阻塞的,整個進程或者線程就被充分利用起來,這就是 事件驅動,所謂的reactor模式。

方法:

windows python:
    提供: select
Mac Python:
    提供: select
Linux Python:
    提供: select、poll、epoll
 
select方法:
select  -在單線程網絡服務中器程序中,管理多個套接字連接
select的原型為(rlist,wlist,xlist[,timeout]),其中rlist是等待讀取的對象,wlist是等待寫入的對象,xlist是等待異常的對象,最后一個是可選對象,指定等待的時間,單位是s.
select()方法的返回值是准備好的對象的三元組,若在timeout的時間內,沒有對象准備好,那么返回值將是空的列表,它采用的是輪詢的方式來實現異步通信的。

服務器:

    #!/usr/bin/python  
      
    'test TCP server'  
      
    from socket import *  
    from time import ctime  
    import select  
    import sys  
      
    HOST = ''  
    PORT = 21567  
    BUFSIZ = 1024  
    ADDR = (HOST, PORT)  
      
    tcpSerSock = socket(AF_INET, SOCK_STREAM)  
    tcpSerSock.bind(ADDR)  
    tcpSerSock.listen(5)  
    input = [tcpSerSock, sys.stdin]     #input是一個列表,初始有歡迎套接字以及標准輸入  
      
    while True:  
        print 'waiting for connection...'  
        tcpCliSock, addr = tcpSerSock.accept()  
        print '...connected from:',addr  
        input.append(tcpCliSock)    #將服務套接字加入到input列表中  
        while True:  
            readyInput,readyOutput,readyException = select.select(input,[],[])  #從input中選擇,輪流處理client的請求連接(tcpSerSock),client發送來的消息(tcpCliSock),及服務器端的發送消息(stdin)  
            for indata in readyInput:  
                if indata==tcpCliSock:    #處理client發送來的消息  
                    data = tcpCliSock.recv(BUFSIZ)  
                    print data  
                    if data=='88':  
                        input.remove(tcpCliSock)  
                        break  
                else:             #處理服務器端的發送消息  
                    data = raw_input('>')  
                    if data=='88':  
                        tcpCliSock.send('%s' %(data))  
                        input.remove(tcpCliSock)  
                        break  
                    tcpCliSock.send('[%s] %s' %(ctime(), data))  
            if data=='88':  
                break  
        tcpCliSock.close()  
    tcpSerSock.close()  

客戶端:

    #!/usr/bin/python  
      
    'test tcp client'  
      
    from socket import *  
    from time import ctime  
    import select  
    import sys  
      
    HOST = 'localhost'  
    PORT = 21567  
    BUFSIZ = 1024  
    ADDR = (HOST, PORT)  
    tcpCliSock = socket(AF_INET, SOCK_STREAM)  
    tcpCliSock.connect(ADDR)  
    input = [tcpCliSock,sys.stdin]  
      
    while True:  
        readyInput,readyOutput,readyException = select.select(input,[],[])  
        for indata in readyInput:  
            if indata==tcpCliSock:  
                data = tcpCliSock.recv(BUFSIZ)  
                print data  
                if data=='88':  
                    break     
            else:  
                data = raw_input('>')  
                if data=='88':    
                    tcpCliSock.send('%s' %(data))  
                    break  
                tcpCliSock.send('[%s] %s' %(ctime(), data))  
        if data=='88':    
            break  
    tcpCliSock.close()  

 

3.2 socketserver 模塊

SocketServer內部使用 IO多路復用 以及 “多線程” 和 “多進程” ,從而實現並發處理多個客戶端請求的Socket服務端。即:每個客戶端請求連接到服務器時,Socket服務端都會在服務器是創建一個“線程”或者“進程” 專門負責處理當前客戶端的所有請求。

該模塊中的類

基本用法:

 創建SocketServer TCP 服務器

# 請求處理程序MyRequestHandler,作為SocketServer
# 中StreamRequestHandler 的一個子類,並重寫了它的handle()方法,該方法在基類Request 中
# 默認情況下沒有任何行為。
# def handle(self):
# pass
# 當接收到一個來自客戶端的消息時,它就會調用handle()方法。而StreamRequestHandler
# 類將輸入和輸出套接字看作類似文件的對象,因此我們將使用readline()來獲取客戶端消息,
# 並利用write()將字符串發送回客戶端。
from socketserver import TCPServer as TCP,StreamRequestHandler as SRH
from time import  ctime
HOST=''
PORT='21567'
ADDR=(HOST,PORT)

class MyRequestHandle(SRH):
    def handle(self):
        print('...connected from:{0}',self.client_address)
        self.wfile.write('[%s]%s'%(ctime(),self.rfile.readline()))
tcpSever=TCP(ADDR,MyRequestHandle)
print('waiting for connection')
tcpSever.serve_forever()

 創建TCP客戶端

from socket import *
HOST='localhost'
PORT='21567'
BUFSIZ=1024
ADDR=(HOST,PORT)


while True:
    tcpClisocket=socket(ADDR,SOCK_STREAM)
    tcpClisocket.connect(ADDR)
    data=input('>')
    if not data:
        break
        # 因為這里使用的處理程序類對待套
        # 接字通信就像文件一樣,所以必須發送行終止符(回車和換行符)
        # 而服務器只是保留並重用這里發送的終止符。當得到從服務器返回的消息時,用strip()
        # 函數對其進行處理並使用由print聲明自動提供的換行符。
    tcpClisocket.send('%s\r\n'%data)
    data=tcpClisocket.recv(BUFSIZ)
    if not data:
        break
    print(data.strip())
    tcpClisocket.close()

 ThreadingTCPServer

ThreadingTCPServer實現的Soket服務器內部會為每個client創建一個 “線程”,該線程用來和客戶端進行交互。

參照:http://www.cnblogs.com/wupeiqi/articles/5040823.html

  • 創建一個繼承自 SocketServer.BaseRequestHandler 的類
  • 類中必須定義一個名稱為 handle 的方法
  • 啟動ThreadingTCPServer

 

#     啟動服務端程序
#     執行 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方法)
# 服務器
# #!/usr/bin/env python
# # -*- coding:utf-8 -*-
import SocketServer

class MyServer(SocketServer.BaseRequestHandler):

    def handle(self):
        # print self.request,self.client_address,self.server
        conn = self.request
        conn.sendall('歡迎致電 10086,請輸入1xxx,0轉人工服務.')
        Flag = True
        while Flag:
            data = conn.recv(1024)
            if data == 'exit':
                Flag = False
            elif data == '0':
                conn.sendall('通過可能會被錄音.balabala一大推')
            else:
                conn.sendall('請重新輸入.')


if __name__ == '__main__':
    server = SocketServer.ThreadingTCPServer(('127.0.0.1',8009),MyServer)
    server.serve_forever()

ForkingTcpSever

ForkingTCPServer和ThreadingTCPServer的使用和執行流程基本一致,只不過在內部分別為請求者建立 “線程”  和 “進程”。

3.3 Twisted模塊

Twisted 是一個完整的事件驅動的網絡框架,利用它既能使用也能開發完整的異步網絡應用程序和協議。它提供了大量的支持來建立完整的系統,包括網絡協議、線程、安全性和身份驗證、聊天/ IM、DBM 及RDBMS 數據庫集成、Web/因特網、電子郵件、命令行參數、GUI 集成工具包等。與SocketServer 類似,Twisted 的大部分功能都存在於它的類中。

 Twisted Reactor TCP服務器

基於protocol 類創建TSServProtocol,重寫connectionMade()和dataReceived()方法,當一個客戶端連接到服務器時就會執行connectionMade()方法,而當服務器接收到客戶端通過網絡發送的一些數據時就會調用dataReceived()方法。
reactor 會作為該方法的一個參數在數據中傳輸,這樣就能在無須自己提取它的情況下訪問它。在服務器代碼的最后部分中,創建了一個協議工廠。它之所以被稱為工廠,是因為每次得到一個接入連接時,都能“制造”協議的一個實例。然后在reactor 中安裝一個TCP 監聽器,以此檢查服務請求。當它接收到一個請求時,就會創建一個TSServProtocol 實例來處理那個客戶端的事務。

from twisted.internet import protocol,reactor
from time import ctime
PORT=21567
class TSServProtocal(protocol.Protocol):
    def connectionMade(self):
        clnt=self.clnt=self.transport.getPeer().host
        print('...conected from:',clnt)
    def dataReceived(self, data):
        self.transport.write('[%s]%s'%(ctime(),data))

factory=protocol.Factory()
factory.protocol=TSServProtocal
print('waiting for conneciton')
reactor.listenTCP(PORT,factory)
reactor.run()

 Twisted Reactor TCP客戶端

from twisted.internet import protocol, reactor
HOST='localhost'
PORT=21567
class TSClntProtocal(protocol.Protocol):
    def sendData(self):
        data=input('>')
        if data:
            print('...sending%...'%data)
        else:
            self.transport.loseConnection()
    def connectionMade(self):
        self.sendData()
    def dataReceived(self, data):
        print(data)
class TSClntFactory(protocol.Factory):
    protocol=TSClntProtocal
    clientConnectionLost=clientConnectionFailed=\
    lambda self,connector,reason:reactor.stop()
reactor.connectTCP(HOST,PORT,TSClntFactory())
reactor.run()

 


免責聲明!

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



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