参考:

http://www.360doc.com/content/11/1119/15/2660674_165748138.shtml

http://blog.csdn.net/my2010sam/article/details/9877717


#!/usr/bin/env python
'''
epoll实现服务器时,需要用到register()和unregister()方法,作用是加入和移除对象,
epoll()的返回值包括了文件描述符和事件,
polling的事件常量有
POLLIN,读取数据
POLLPRI,紧急数据
POLLPOUT,文件描述符已经准备好
POLLERR,文件描述符出错
POLLHUP,连接丢失
POLLVAL,无效请求。
epoll 对象操作流程
建立一个epoll对象
告诉epoll对象, 对于一些socket监控一些事件.
问epoll, 从上次查询以来什么socket产生了什么事件.
针对这些socket做特定操作.
告诉epoll, 修改监控socket和/或监控事件.
重复第3步到第5步, 直到结束.
销毁epoll对象.
采用异步socket的时候第3步重复了第2步的事情. 这里的程序更复杂, 因为一个线程需要和多个客户端交互.
http://www.360doc.com/content/11/1119/15/2660674_165748138.shtml
'''
import socket
import select
import argparse

SERVER_HOST = ''

EOL1 = b'\n\n'
EOL2 = b'\n\r\n'
SERVER_RESPONSE  = b"""HTTP/1.1 200 OK\r\nDate: Mon, 1 Apr 2013 01:01:01 GMT\r\nContent-Type: text/plain\r\nContent-Length: 25\r\n\r\nHello from Epoll Server!"""


class EpollServer(object):
    """ A socket server using Epoll"""

    def __init__(self, host=SERVER_HOST, port=0):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.sock.bind((host, port))
        self.sock.listen(5)
        self.sock.setblocking(0)#设置socket为非阻塞模式
        self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
        print "Started Epoll Server"
        self.epoll = select.epoll() #建立一个epoll对象.
        self.epoll.register(self.sock.fileno(), select.EPOLLIN)#注册服务器socket, 监听读取事件. 服务器socket接收一个连接的时候, 产生一个读取事件. fileno是文件描述符, 是一个整型数.

    def run(self):
        """Executes epoll server operation"""
        try:
            #connections表映射文件描述符(file descriptors, 整型)到对应的socket对象上面
            #requests
            #responses
            connections = {}; requests = {}; responses = {}
            while True:
                #epoll对象查询一下是否有感兴趣的事件发生, 参数1说明我们最多等待1秒的时间. 如果有对应事件发生, 立刻会返回一个事件列表
                events = self.epoll.poll(1)
                #返回的events是一个(fileno, event code)tuple元组列表. fileno是文件描述符, 是一个整型数.
                for fileno, event in events:
                    #如果是服务器socket的事件, 那么需要针对新的连接建立一个socket.
                    if fileno == self.sock.fileno():
                        connection, address = self.sock.accept()
                        connection.setblocking(0)#设置socket为非阻塞模式
                        self.epoll.register(connection.fileno(), select.EPOLLIN) #注册socket的read(EPOLLIN)事件.
                        connections[connection.fileno()] = connection
                        requests[connection.fileno()] = b''
                        responses[connection.fileno()] = SERVER_RESPONSE
                    elif event & select.EPOLLIN:
                        #如果读取事件发生, 从客户端读取新数据.(打印client http 请求头)
                        requests[fileno] += connections[fileno].recv(1024)
                        if EOL1 in requests[fileno] or EOL2 in requests[fileno]:
                            #一旦完整的http请求接收到, 取消注册读取事件, 注册写入事件(EPOLLOUT), 写入事件在能够发送数据回客户端的时候产生
                            self.epoll.modify(fileno, select.EPOLLOUT)
                            #打印完整的http请求, 展示即使通讯是交错的, 数据本身是作为一个完整的信息组合和处理的.
                            print('-'*40 + '\n' + requests[fileno].decode()[:-2])
                    elif event & select.EPOLLOUT:
                        # 如果写入事件发生在一个客户端socket上面, 我们就可以发送新数据到客户端了.
                        # 一次发送一部分返回数据, 直到所有数据都交给操作系统的发送队列.
                        byteswritten = connections[fileno].send(responses[fileno])
                        responses[fileno] = responses[fileno][byteswritten:]
                        if len(responses[fileno]) == 0:
                            self.epoll.modify(fileno, 0)#一旦所有的返回数据都发送完, 取消监听读取和写入事件.
                            connections[fileno].shutdown(socket.SHUT_RDWR)#如果连接被明确关闭掉, 这一步是可选的. 这个例子采用这个方法是为了让客户端首先断开, 告诉客户端没有数据需要发送和接收了, 然后让客户端断开连接.
                    elif event & select.EPOLLHUP:#HUP(hang-up)事件表示客户端断开了连接(比如 closed), 所以服务器这端也会断开. 不需要注册HUP事件, 因为它们都会标示到注册在epoll的socket.
                        self.epoll.unregister(fileno)
                        connections[fileno].close()
                        del connections[fileno]
        finally:
            #在这里的异常捕捉的作用是, 我们的例子总是采用键盘中断来停止程序执行.
            #虽然开启的socket不需要手动关闭, 程序退出的时候会自动关闭, 明确写出来这样的代码, 是更好的编码风格.
            self.epoll.unregister(self.sock.fileno())
            self.epoll.close()
            self.sock.close()


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Socket Server Example with Epoll')
    parser.add_argument('--port', action="store", dest="port", type=int, required=True)
    given_args = parser.parse_args()
    port = given_args.port
    server = EpollServer(host=SERVER_HOST, port=port)
    server.run()


Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐