對於python socket程式設計的初探

高洛峰
發布: 2016-11-21 16:29:00
原創
1219 人瀏覽過

socket編程步驟

服務端創建一個socket,綁定地址和端口,然後監聽端口上傳入的連接,一旦有連接進來,就通過accept函數接收傳入的連接。

客戶端也是創建一個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.於socket()函數的第三個參數。 getaddrinnfo函數會根據位址格式和socket類型,傳回適當的協定

canonname 一個規範化的host name。

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

接下來創建一個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)
登入後複製

以上主要是針對TCP流資料的socket程式設計。對於UDP協議的數據,處理略有不同。譬如發送接收UDP封包處理函數為:

socket.sendto(string, flags, address)
socket.recvfrom(bufsize[, flags]) #返回(string, address),string是返回的数据,address是发送方的socket地址
登入後複製

SocketServer模組

python中網路程式設計除了socket模組也提供了SocketServer模組,這個模組主要是對socket模組進行了封裝,將socket的物件的創建,Server模組,這個模組主要是對socket模組進行了封裝,將socket的物件的創建,Server模組定,連接,接收,發送,關閉都封裝在裡面,大大簡化了網路服務的編程。

此模組提供了以下2個主要的網路服務類,用於建立對應的套接字流

TCPServer 建立TCP協定的套接字流

UDPServer 建立UDP協定的套接字流

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个类用来支持异步的模式。

ForkingMixIn 利用多进程实现异步

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()
登入後複製

以上是本人对socket相关的理解,有什么不当或错误之处,还请指出。

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!