python socket和socketserver


Python提供了兩個基本的socket模塊。一個是socket,它提供了標准的BSD Socket API;另一個是socketServer,它提供了服務器中心類,可以簡化網絡服務器的開發。

        下面先簡要介紹socket模塊包含的類及其使用。

        1.開始了解socket模塊前,先熟悉下Python的網絡編程模塊主要支持的兩種Intent協議:TCP和UDP。TCP協議是一種面向連接的可靠協議,用於建立機器之間的雙向通信流。UDP協議是一種較低級別的、以數據包為基礎的協議(無連接傳輸模式)。與TCP不同,UDP信息不可靠。

         他們的區別如下圖所示:左圖為TCP連接協議,右圖為UDP連接協議

                     

  2.socket模塊的部分類方法介紹

類方法

說明

socket.socket(family, type[,proto])

創建並返回一個新的 socket對象

socket.getfqdn(name)

將使用點號分隔的 IP地址字符串轉換成一個完整的域名

socket.gethostbyname(hostname)

將主機名解析為一個使用點號分隔的 IP地址字符串

socket.gethostbyname_ex(name)

它返回一個包含三個元素的元組,從左到右分別是給定地址的主要的主機名、同一IP地址的可選的主機名的一個列表、關於同一主機的同一接口的其它IP地址的一個列表(列表可能都是空的)。

socket.gethostbyaddr(address)

作用與gethostbyname_ex相同,只是你提供給它的參數是一個IP地址字符串

Socket.getservbyname(service,protocol)

它要求一個服務名(如'telnet'或'ftp')和一個協議(如'tcp'或'udp'),返回服務所使用的端口號

socket.fromfd(fd, family, type)

從現有的文件描述符創建一個 socket對象

 

 3.socket對象的部分方法介紹

實例方法

說明

sock.bind( (adrs, port) )

將 socket綁定到一個地址和端口上

sock.accept()

返回一個客戶機 socket(帶有客戶機端的地址信息)

sock.listen(backlog)

將 socket設置成監聽模式,能夠監聽 backlog 外來的連接請求

sock.connect( (adrs, port) )

將 socket連接到定義的主機和端口上

sock.recv( buflen[, flags] )

從 socket中接收數據,最多 buflen 個字符

sock.recvfrom( buflen[, flags] )

從 socket中接收數據,最多 buflen 個字符,同時返回數據來源的遠程主機和端口號

sock.send( data[, flags] )

通過 socket發送數據

sock.sendto( data[, flags], addr )

通過 socket發送數據

sock.close()

關閉 socket

sock.getsockopt( lvl, optname )

獲得指定 socket 選項的值

sock.setsockopt( lvl, optname, val )

設置指定 socket選項的值

 

4.編寫socket測試程序

(a)編寫server的步驟

第一步是創建socket對象。調用socket構造函數。如:

socket = socket.socket( family, type )

family參數代表地址家族,可為AF_INET或AF_UNIX。AF_INET家族包括Internet地址,AF_UNIX家族用於同一台機器上的進程間通信。

type參數代表套接字類型,可為SOCK_STREAM(流套接字)和SOCK_DGRAM(數據報套接字)。

第二步是將socket綁定到指定地址。這是通過socket對象的bind方法來實現的:

socket.bind( address ) 

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

第三步是使用socket套接字的listen方法接收連接請求。

socket.listen( backlog )

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

第四步是服務器套接字通過socket的accept方法等待客戶請求一個連接。

connection, address = socket.accept()

調 用accept方法時,socket會時入“waiting”狀態。客戶請求連接時,方法建立連接並返回服務器。accept方法返回一個含有兩個元素的元組(connection,address)。第一個元素connection是新的socket對象,服務器必須通過它與客戶通信;第二個元素 address是客戶的Internet地址。

第 五步是處理階段,服務器和客戶端通過send和recv方法通信(傳輸 數據)。服務器調用send,並采用字符串形式向客戶發送信息。send方法返回已發送的字符個數。服務器使用recv方法從客戶接收信息。調用recv 時,服務器必須指定一個整數,它對應於可通過本次方法調用來接收的最大數據量。recv方法在接收數據時會進入“blocked”狀態,最后返回一個字符 串,用它表示收到的數據。如果發送的數據量超過了recv所允許的,數據會被截短。多余的數據將緩沖於接收端。以后調用recv時,多余的數據會從緩沖區 刪除(以及自上次調用recv以來,客戶可能發送的其它任何數據)。 

傳輸結束,服務器調用socket的close方法關閉連接。

(b)編寫client的步驟

首先創建一個socket以連接服務器:socket =socket.socket( family, type ) 

使用socket的connect方法連接服務器。對於AF_INET家族,連接格式如下:

socket.connect( (host,port) )

host代表服務器主機名或IP,port代表服務器進程所綁定的端口號。如連接成功,客戶就可通過套接字與服務器通信,如果連接失敗,會引發socket.error異常。

處理階段,客戶和服務器將通過send方法和recv方法通信。 

Server:

import socket
port=8081
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
#從指定的端口,從任何發送者,接收UDP數據
s.bind(('',port))
print('正在等待接入...')
while True:
    #接收一個數據
    data,addr=s.recvfrom(1024)
    print('Received:',data,'from',addr)

Client:

import socket
port=8081
host='localhost'
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.sendto(b'hello,this is a test info !',(host,port))

很簡單 。下面是TCP方式:

Server:

#-*- coding: utf-8 -*-
from socket import *
from time import ctime

HOST=''
PORT=12345
BUFSIZ=1024
ADDR=(HOST, PORT)
sock=socket(AF_INET, SOCK_STREAM)

sock.bind(ADDR)

sock.listen(5)
while True:
    print('waiting for connection')
    tcpClientSock, addr=sock.accept()
    print('connect from ', addr)
    while True:
        try:
            data=tcpClientSock.recv(BUFSIZ)
        except:
            print(e)
            tcpClientSock.close()
            break
        if not data:
            break
        s='Hi,you send me :[%s] %s' %(ctime(), data.decode('utf8'))
        tcpClientSock.send(s.encode('utf8'))
        print([ctime()], ':', data.decode('utf8'))
tcpClientSock.close()
sock.close()

Client:

#-*- coding: utf-8 -*-
from socket import *

class TcpClient:
    HOST='127.0.0.1'
    PORT=12345
    BUFSIZ=1024
    ADDR=(HOST, PORT)
    def __init__(self):
        self.client=socket(AF_INET, SOCK_STREAM)
        self.client.connect(self.ADDR)

        while True:
            data=input('>')
            if not data:
                break
            self.client.send(data.encode('utf8'))
            data=self.client.recv(self.BUFSIZ)
            if not data:
                break
            print(data.decode('utf8'))
            
if __name__ == '__main__':
    client=TcpClient()

上面的TCP方式有個問題,不能退出,好吧,我們改造一下,使這個程序可以發送quit命令以退出:

Server:

#-*- coding: utf-8 -*-
from socket import *
from time import ctime
from time import localtime
import time

HOST=''
PORT=1122  #設置偵聽端口
BUFSIZ=1024
ADDR=(HOST, PORT)
sock=socket(AF_INET, SOCK_STREAM)

sock.bind(ADDR)

sock.listen(5)
#設置退出條件
STOP_CHAT=False
while not STOP_CHAT:
    print('等待接入,偵聽端口:%d' % (PORT))
    tcpClientSock, addr=sock.accept()
    print('接受連接,客戶端地址:',addr)
    while True:
        try:
            data=tcpClientSock.recv(BUFSIZ)
        except:
            #print(e)
            tcpClientSock.close()
            break
        if not data:
            break
        #python3使用bytes,所以要進行編碼
        #s='%s發送給我的信息是:[%s] %s' %(addr[0],ctime(), data.decode('utf8'))
        #對日期進行一下格式化
        ISOTIMEFORMAT='%Y-%m-%d %X'
        stime=time.strftime(ISOTIMEFORMAT, localtime())
        s='%s發送給我的信息是:%s' %(addr[0],data.decode('utf8'))
        tcpClientSock.send(s.encode('utf8'))
        print([stime], ':', data.decode('utf8'))
        #如果輸入quit(忽略大小寫),則程序退出
        STOP_CHAT=(data.decode('utf8').upper()=="QUIT")
        if STOP_CHAT:
            break
tcpClientSock.close()
sock.close()

Client:

#-*- coding: utf-8 -*-
from socket import *

class TcpClient:
    #測試,連接本機
    HOST='127.0.0.1'
    #設置偵聽端口
    PORT=1122 
    BUFSIZ=1024
    ADDR=(HOST, PORT)
    def __init__(self):
        self.client=socket(AF_INET, SOCK_STREAM)
        self.client.connect(self.ADDR)

        while True:
            data=input('>')
            if not data:
                break
            #python3傳遞的是bytes,所以要編碼
            self.client.send(data.encode('utf8'))
            print('發送信息到%s:%s' %(self.HOST,data))
            if data.upper()=="QUIT":
                break            
            data=self.client.recv(self.BUFSIZ)
            if not data:
                break
            print('從%s收到信息:%s' %(self.HOST,data.decode('utf8')))
            
            
if __name__ == '__main__':
    client=TcpClient()


下面先簡要介紹socketserver模塊包含的類及其使用

SocketServer是標准庫中一個高級別的模塊。用於簡化網絡客戶與服務器的實現。模塊中,已經實現了一些可供使用的類。

我們將再次實現之前的那個基本TCP的例子。你會注意到新實現與之前有很多相似之處,但你也要注意到,現在很多繁雜的事情已經被封裝好了,你不用再去關心那個樣板代碼了。例子給出的是一個最簡單的同步服務器。

為了要隱藏實現的細節。我們現在寫程序時會使用類,這是與之前代碼的另一個不同。用面向對象的方法可以幫助我們更好的組織數據與邏輯功能。你也會注意到,我們的程序現在是“事件驅動”了。這就意味着,只有在事件出現的時候,程序才有“反應”。

在之前的服務循環中,我們阻塞等待請求,有請求來的時候就處理請求,然后再回去繼續等待。現在的服務循環中,就不用在服務器里寫代碼了,改成定義一個處理器,服務器在收到進來的請求的時候,可以調用你的處理函數。

1. 創建一個SocketServerTCP服務器:

在代碼中,先導入我們的服務器類,然后像之前一樣定義主機常量。主機常量后就是我們的請求處理器類,然后是啟動代碼。在下面的代碼片斷中可以看到更多細節。

#!/usr/bin/env python
 
from SocketServer import (TCPServer as TCP,
                          StreamRequestHandler as SRH)
from time import ctime
 
HOST = ''
PORT = 12346
ADDR = (HOST, PORT)
 
class MyRequestHandler(SRH):
    def handle(self):
        print '...connected from:', self.client_address
        self.wfile.write('[%s] %s' % (ctime(), self.rfile.readline()))
 
tcpServ = TCP(ADDR, MyRequestHandler)
print 'waiting for connection...'
tcpServ.serve_forever()

我們從SocketServer的StreamRequestHandler類中派生出一個子類,並重寫handle()函數。在BaseRequest類中,這個函數什么也不做。在有客戶消息進來的時候,handle()函數就會被調用。StreamRequestHandler 類支持像操作文件對象那樣操作輸入輸出套接字。我們可以用readline()函數得到客戶消息,用write()函數把字符串發給客戶。

為了保持一致性,我們要在客戶與服務器兩端的代碼里都加上回車與換行。實際上,你在代碼中看不到這個,因為,我們重用了客戶傳過來的回車與換行。

2. 創建SocketServerTCP客戶端

#!/usr/bin/env python
 
from socket import *
 
HOST = 'localhost'
PORT = 12346
BUFSIZE = 1024
ADDR = (HOST, PORT)
 
while True:
    tcpCliSock = socket(AF_INET, SOCK_STREAM)
    tcpCliSock.connect(ADDR)
    data = raw_input('>')
    if not data:
        break
    tcpCliSock.send('%s\r\n' % data)
    data = tcpCliSock.recv(BUFSIZE)
    if not data:
        break
    print data.strip()
    tcpCliSock.close()

SocketServer的請求處理器的默認行為是接受連接,得到請求,然后就關閉連接。這使得我們不能在程序的運行時,一直保持連接狀態,要每次發送數據到服務器的時候都要創建一個新的套接字。這種行為使得TCP 服務器的行為有些像UDP服務器。不過,這種行為也可以通過重寫請求處理器中相應的函數來改變。

現在,我們的客戶端有點完全不一樣了(我們得每次都創建一個連接)。其它的小區別在服務器代碼的逐行解釋中已經看到了:我們使用的處理器類像文件一樣操作套接字,所以我們每次都要發送行結束字符(回車與換行)。服務器只是保留並重用我們發送的行結束字符。當我們從服務器得到數據的時候,我們使用strip()函數去掉它們,然后使用print語句提供的回車。

 

    3.   使用SocketServer處理多連接

    上面的例子一次只能連接一個客戶機並出力它的請求,如果要處理多連接問題,那么有三種主要的方法能實現這個目的:分叉(forking)、線程(threading)以及異步I/O(asynchronous I/O)。通過對SocketServer服務器使用混入類(mix-in class),派生進程和線程很容易處理。即使要自己實現它們,這些方法也很容易使用。它們確實有缺點:分叉占據資源,並且如果有太多的客戶端時分叉不能很好分叉(盡管如此,對於合理數量的客戶端,分叉在現代的UNIX或者Linux系統中是很高效的,如果有一個多CPU系統,那系統效率會更高);線程處理能導致同步問題。使用SocketServer框架創建分叉或者線程服務器非常簡單: 

分叉SocketServer服務器:

#!/usr/bin/env python
 
from SocketServer import (TCPServer as TCP,
                          StreamRequestHandler as SRH,
                          ForkingMixIn as FMI)
from time import ctime
 
HOST = ''
PORT = 12346
ADDR = (HOST, PORT)
 
class Server(FMI, TCP):
    pass
 
class MyRequestHandler(SRH):
    def handle(self):
        print '...connected from:', self.client_address
        self.wfile.write('[%s] %s' % (ctime(), self.rfile.readline()))
 
tcpServ = Server(ADDR, MyRequestHandler)
print 'waiting for connection...'
tcpServ.serve_forever()

多線程SocketServer服務器:

#!/usr/bin/env python
 
from SocketServer import (TCPServer as TCP,
                          StreamRequestHandler as SRH,
                          ThreadingMixIn as TMI)
from time import ctime
 
HOST = ''
PORT = 12346
ADDR = (HOST, PORT)
 
class Server(TMI, TCP):
    pass
 
class MyRequestHandler(SRH):
    def handle(self):
        print '...connected from:', self.client_address
        self.wfile.write('[%s] %s' % (ctime(), self.rfile.readline()))
 
tcpServ = Server(ADDR, MyRequestHandler)
print 'waiting for connection...'
tcpServ.serve_forever()

 








免責聲明!

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



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