對python socket編程的初探

對於python網絡編程來講,免不了要用到socket模塊。下面分享一下我的對python socket的一些理解。html

socket編程步驟

  1. 服務端建立一個socket,綁定地址和端口,而後監聽端口上傳入的鏈接,一旦有鏈接進來,就經過accept函數接收傳入的鏈接。python

  2. 客戶端也是建立一個socket。綁定遠程地址和端口,而後創建鏈接,發送數據。編程

服務端socket

下面經過一段實例代碼來詳細說明 服務端 socker_server.py安全

import socket
import sys

HOST = "127.0.0.1"               
PORT = 10000              
s = None
for res in socket.getaddrinfo(HOST, PORT, socket.AF_UNSPEC,
                              socket.SOCK_STREAM, 0, socket.AI_PASSIVE):
    af, socktype, proto, canonname, sa = res
    try:
        s = socket.socket(af, socktype, proto)
    except socket.error as msg:
        s = None
        continue
    try:
        s.bind(sa)
        s.listen(5)
    except socket.error as msg:
        s.close()
        s = None
        continue
    break
if s is None:
    print 'could not open socket'
    sys.exit(1)
conn, addr = s.accept()
print 'Connected by', addr
while 1:
    data = conn.recv(1024)
    if not data: break
    conn.send(data)
conn.close()

首先咱們經過socket.getaddrinnfo函數將host/port轉換成一個包含5元組的序列。這個5元組包含咱們建立一個socket鏈接所須要的全部必要參數。返回的5元組分別是 (family, sockettype, proto, canonname, sockaddr)服務器

family 地址簇,用與socket()函數的第一個參數。主要有如下幾個網絡

socket.AF_UNIX 用與單一機器下的進程通訊
socket.AF_INET 用與服務器之間相互通訊,一般都用這個。
socket.AF_INET6 支持IPv6
sockettype socket類型,用與socket()函數的第二個參數,經常使用的有多線程

socket.SOCK_STREAM 默認,用於TCP協議
socket.SOCK_DGRAM 用於UDP協議
proto 協議,用於socket()函數的第三個參數。 getaddrinnfo函數會根據地址格式和socket類型,返回合適的協議異步

canonname 一個規範化的host name。socket

sockaddr 描述了一個socket address .是一個二元組,主要用於bind()和connect()函數ide

接下來建立一個socket對象,傳入getaddrinnfo函數返回的af,sockettype,proto。

s = socket.socket(af, socktype, proto)

而後綁定個人socket address

s.bind(sa)

開啓監聽模式

s.listen(5)

listen函數會監聽鏈接到socket上的鏈接,參數表示在拒絕鏈接以前系統能夠掛起的最大鏈接隊列數量爲5。這些鏈接尚未被accept處理。數量不能無限大,一般指定5。

一旦咱們監聽到了鏈接,就會調用accept函數接收鏈接

conn, addr = s.accept()

accept函數返回一個二元組,conn是一個新的socket對象,用來接收和發送數據。addr表示另外一端的socket地址。

接下來咱們就能夠用conn對象發送和接收數據了

data = conn.recv(1024) # 接收數據, 這裏指定一次最多接收的字符數量爲1024
 conn.send(data) # 發送數據

這裏咱們接收到一個鏈接socket就會中止運行,因此若是要循環鏈接的話,將accept函數放入到一個死循環裏。

客戶端socket

客戶端socket編程相對比較簡單,經過connect和服務端創建鏈接以後,就能夠相互通訊了。socket_client.py以下

for res in socket.getaddrinfo(HOST, PORT, socket.AF_UNSPEC, socket.SOCK_STREAM):
    af, socktype, proto, canonname, sa = res
    try:
        s = socket.socket(af, socktype, proto)
    except socket.error as msg:
        s = None
        continue
    try:
        s.connect(sa)
    except socket.error as msg:
        s.close()
        s = None
        continue
    break
if s is None:
    print 'could not open socket'
    sys.exit(1)
s.sendall('Hello, world')
data = s.recv(1024)
s.close()
print 'Received', repr(data)

備註: 對於getaddrinfo函數,能夠參考下http://baike.baidu.com/link?u... 這個函數的做用是把協議相關性安全隱藏在這個底層庫函數內部。應用程序只要處理由getaddrinfo函數返回的數據便可。

以上主要是針對TCP流數據的socket編程。對於UDP協議的數據,處理略有不一樣。譬如發送接收UDP數據包處理函數爲:

socket.sendto(string, flags, address)
socket.recvfrom(bufsize[, flags]) #返回(string, address),string是返回的數據,address是發送方的socket地址

其餘詳細內容能夠參考 http://python.usyiyi.cn/trans...

SocketServer模塊

python中網絡編程除了socket模塊還提供了SocketServer模塊,這一模塊主要是對socket模塊進行了封裝,將socket的對象的建立,綁定,鏈接,接收,發送,關閉都封裝在裏面,大大簡化了網絡服務的編程。

此模塊提供瞭如下2個主要的網絡服務類,用於建立相應的套接字流

  1. TCPServer 建立TCP協議的套接字流

  2. UDPServer 建立UDP協議的套接字流

咱們有了套接字流對象,還須要一個請求處理類。SocketServer模塊提供了請求處理類有BaseRequestHandler,以及它的派生類StreamRequestHandler和DatagramRequestHandler。因此只要繼承這3個類中的一個,而後重寫handle函數,此函數將用來處理接收到的請求。下面看一個服務端的代碼示例

import SocketServer

class MyTCPHandler(SocketServer.StreamRequestHandler):
   """建立請求處理類,重寫handle方法。此外也能夠重寫setup()和finish()來作一些請求處理前和處理後的一些工做"""
    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", 10000

    server = SocketServer.TCPServer((HOST, PORT), MyTCPHandler)

    # Activate the server; this will keep running until you
    # interrupt the program with Ctrl-C
    # server.shutdown()
    server.serve_forever()  # 一直循環接收請求
    # server.handle_request() # 只處理一次請求就退出

看着是否是代碼簡單了不少,並且SocketServer模塊內部使用了多路複用IO技術,能夠實現更好的鏈接性能。看serve_forever函數的源代碼用到了select模塊。經過傳入socket對象調用select.select()來監聽socket對象的文件描述符,一旦發現socket對象就緒,就通知應用程序進行相應的讀寫操做。源代碼以下:

def serve_forever(self, poll_interval=0.5):
        """Handle one request at a time until shutdown.

        Polls for shutdown every poll_interval seconds. Ignores
        self.timeout. If you need to do periodic tasks, do them in
        another thread.
        """
        self.__is_shut_down.clear()
        try:
            while not self.__shutdown_request:
                # XXX: Consider using another file descriptor or
                # connecting to the socket to wake this up instead of
                # polling. Polling reduces our responsiveness to a
                # shutdown request and wastes cpu at all other times.
                r, w, e = _eintr_retry(select.select, [self], [], [],
                                       poll_interval)
                if self in r:
                    self._handle_request_noblock()
        finally:
            self.__shutdown_request = False
            self.__is_shut_down.set()

即便使用了select技術,TCPServer,UDPServer處理請求仍然是同步的,意味着一個請求處理完,才能處理下一個請求。但SocketServer模塊提供了另外2個類用來支持異步的模式。

  1. ForkingMixIn 利用多進程實現異步

  2. ThreadingMixIn 利用多線程實現異步

看名字就知道使用了mixin模式。而mixin模式能夠經過多繼承來實現,因此經過對網絡服務類進行多繼承的方式就能夠實現異步模式

class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    pass

針對ThreadindMixIn,實現異步的原理也就是在內部對每一個請求建立一個線程來處理。看源碼

def process_request(self, request, client_address):
        """Start a new thread to process the request."""
        t = threading.Thread(target = self.process_request_thread,
                             args = (request, client_address))
        t.daemon = self.daemon_threads
        t.start()

下面提供一個異步模式的示例

import socket
import threading
import SocketServer

class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler):

    def handle(self):
        data = self.request.recv(1024)
        cur_thread = threading.current_thread()
        response = "{}: {}".format(cur_thread.name, data)
        self.request.sendall(response)

class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    pass

def client(ip, port, message):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((ip, port))
    try:
        sock.sendall(message)
        response = sock.recv(1024)
        print "Received: {}".format(response)
    finally:
        sock.close()

if __name__ == "__main__":
    # Port 0 means to select an arbitrary unused port
    HOST, PORT = "localhost", 0

    server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
    ip, port = server.server_address

    # Start a thread with the server -- that thread will then start one
    # more thread for each request
    server_thread = threading.Thread(target=server.serve_forever)
    # Exit the server thread when the main thread terminates
    server_thread.daemon = True
    server_thread.start()
    print "Server loop running in thread:", server_thread.name

    client(ip, port, "Hello World 1")
    client(ip, port, "Hello World 2")
    client(ip, port, "Hello World 3")

    server.shutdown()
    server.server_close()

更多對SocketServer模塊的瞭解參考https://docs.python.org/2/lib... 本文所使用的示例就來自官網。畢竟官網的例子實在太好了。

以上是本人對socket相關的理解,有什麼不當或錯誤之處,還請指出。

相關文章
相關標籤/搜索