【資料整理】Python - 簡單的epoll server代碼解讀


      做爲 Python 的初學者,學習別人的代碼也是必要過程,本文主要是針對 《 Python中使用epoll開發服務端程序 》中的代碼,進行源碼註釋說明。從代碼中至少能夠學習到以下三點:
  1. logging 模塊的使用;
  2. epoll 的使用;
  3. 如何利用 try...catch 來處理「讀穿」。
============ 我是分割線 ==============

server端代碼:
#!/usr/bin/python
#-*- coding:utf-8 -*-

import socket, logging
import select, errno
    
logger = logging.getLogger("network-server")
    
def InitLog():
    logger.setLevel(logging.DEBUG)
    
    fh = logging.FileHandler("network-server.log")
    fh.setLevel(logging.DEBUG)
    ch = logging.StreamHandler()
    ch.setLevel(logging.ERROR)
    
    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
    ch.setFormatter(formatter)
    fh.setFormatter(formatter)
    
    logger.addHandler(fh)
    logger.addHandler(ch)
        
        
if __name__ == "__main__":
    InitLog()

    try:
        # 建立 TCP socket 做爲監聽 socket
        listen_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
    except socket.error, msg:
        logger.error("create socket failed")

    try:
        # 設置 SO_REUSEADDR 選項
        listen_fd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    except socket.error, msg:
        logger.error("setsocketopt SO_REUSEADDR failed")

    try:
        # 進行 bind -- 此處未指定 ip 地址,即 bind 了所有網卡 ip 上
        listen_fd.bind(('', 2003))
    except socket.error, msg:
        logger.error("bind failed")

    try:
        # 設置 listen 的 backlog 數
        listen_fd.listen(10)
    except socket.error, msg:
        logger.error(msg)
    
    try:
        # 建立 epoll 句柄
        epoll_fd = select.epoll()
        # 向 epoll 句柄中註冊 監聽 socket 的 可讀 事件
        epoll_fd.register(listen_fd.fileno(), select.EPOLLIN)
    except select.error, msg:
        logger.error(msg)
        
    connections = {}
    addresses = {}
    datalist = {}
    while True:
        # epoll 進行 fd 掃描的地方 -- 未指定超時時間則爲阻塞等待
        epoll_list = epoll_fd.poll()

        for fd, events in epoll_list:
            # 若爲監聽 fd 被激活
            if fd == listen_fd.fileno():
                # 進行 accept -- 得到鏈接上來 client 的 ip 和 port,以及 socket 句柄
                conn, addr = listen_fd.accept()
                logger.debug("accept connection from %s, %d, fd = %d" % (addr[0], addr[1], conn.fileno()))
                # 將鏈接 socket 設置爲 非阻塞
                conn.setblocking(0)
                # 向 epoll 句柄中註冊 鏈接 socket 的 可讀 事件
                epoll_fd.register(conn.fileno(), select.EPOLLIN | select.EPOLLET)
                # 將 conn 和 addr 信息分別保存起來
                connections[conn.fileno()] = conn
                addresses[conn.fileno()] = addr
            elif select.EPOLLIN & events:
                # 有 可讀 事件激活
                datas = ''
                while True:
                    try:
                        # 從激活 fd 上 recv 10 字節數據
                        data = connections[fd].recv(10)
                        # 若當前沒有接收到數據,而且以前的累計數據也沒有
                        if not data and not datas:
                            # 從 epoll 句柄中移除該 鏈接 fd
                            epoll_fd.unregister(fd)
                            # server 側主動關閉該 鏈接 fd
                            connections[fd].close()
                            logger.debug("%s, %d closed" % (addresses[fd][0], addresses[fd][1]))
                            break
                        else:
                            # 將接收到的數據拼接保存在 datas 中
                            datas += data
                    except socket.error, msg:
                        # 在 非阻塞 socket 上進行 recv 須要處理 讀穿 的狀況
                        # 這裏其實是利用 讀穿 出 異常 的方式跳到這裏進行後續處理
                        if msg.errno == errno.EAGAIN:
                            logger.debug("%s receive %s" % (fd, datas))
                            # 將已接收數據保存起來
                            datalist[fd] = datas
                            # 更新 epoll 句柄中鏈接d 註冊事件爲 可寫
                            epoll_fd.modify(fd, select.EPOLLET | select.EPOLLOUT)
                            break
                        else:
                            # 出錯處理
                            epoll_fd.unregister(fd)
                            connections[fd].close()
                            logger.error(msg)
                            break
            elif select.EPOLLHUP & events:
                # 有 HUP 事件激活
                epoll_fd.unregister(fd)
                connections[fd].close()
                logger.debug("%s, %d closed" % (addresses[fd][0], addresses[fd][1]))
            elif select.EPOLLOUT & events:
                # 有 可寫 事件激活
                sendLen = 0
                # 經過 while 循環確保將 buf 中的數據所有發送出去        
                while True:
                    # 將以前收到的數據發回 client -- 經過 sendLen 來控制發送位置
                    sendLen += connections[fd].send(datalist[fd][sendLen:])
                    # 在所有發送完畢後退出 while 循環
                    if sendLen == len(datalist[fd]):
                        break
                # 更新 epoll 句柄中鏈接 fd 註冊事件爲 可讀
                epoll_fd.modify(fd, select.EPOLLIN | select.EPOLLET)
            else:
                # 其餘 epoll 事件不進行處理
                continue
client 端代碼(比較簡單,未加註釋):
import socket
import time
import logging

logger = logging.getLogger("network-client")
logger.setLevel(logging.DEBUG)

fh = logging.FileHandler("network-client.log")
fh.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)

formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
ch.setFormatter(formatter)
fh.setFormatter(formatter)

logger.addHandler(fh)
logger.addHandler(ch)

if __name__ == "__main__":
    try:
        connFd = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
    except socket.error, msg:
        logger.error(msg)

    try:
        connFd.connect(("127.0.0.1", 2003))
        logger.debug("connect to network server success")
    except socket.error,msg:
        logger.error(msg)

    for i in range(1, 11):
        data = "The Number is %d" % i
        if connFd.send(data) != len(data):
            logger.error("send data to network server failed")
            break
        readData = connFd.recv(1024)
        print readData
        time.sleep(1)

    connFd.close()
運行結果:

client 命令行輸出。
[root@Betty Python]# python epoll_client.py 
The Number is 1
The Number is 2
The Number is 3
The Number is 4
The Number is 5
The Number is 6
The Number is 7
The Number is 8
The Number is 9
The Number is 10
[root@Betty Python]#
client 日誌文件。
[root@Betty Python]# vi network-client.log 

2013-07-26 15:36:43,995 - network-client - DEBUG - connect to network server success
server 日誌文件。
[root@Betty Python]# vi network-server.log 

2013-07-26 15:36:43,995 - network-server - DEBUG - accept connection from 127.0.0.1, 34861, fd = 6
2013-07-26 15:36:43,995 - network-server - DEBUG - 6 receive The Number is 1
2013-07-26 15:36:44,996 - network-server - DEBUG - 6 receive The Number is 2
2013-07-26 15:36:45,996 - network-server - DEBUG - 6 receive The Number is 3
2013-07-26 15:36:46,996 - network-server - DEBUG - 6 receive The Number is 4
2013-07-26 15:36:47,996 - network-server - DEBUG - 6 receive The Number is 5
2013-07-26 15:36:48,997 - network-server - DEBUG - 6 receive The Number is 6
2013-07-26 15:36:49,998 - network-server - DEBUG - 6 receive The Number is 7
2013-07-26 15:36:50,999 - network-server - DEBUG - 6 receive The Number is 8
2013-07-26 15:36:52,000 - network-server - DEBUG - 6 receive The Number is 9
2013-07-26 15:36:53,001 - network-server - DEBUG - 6 receive The Number is 10
2013-07-26 15:36:54,002 - network-server - DEBUG - 127.0.0.1, 34861 closed

============ 我是分割線 ==============

感謝原做者!
相關文章
相關標籤/搜索