zoukankan      html  css  js  c++  java
  • epoll真正实现高并发服务器

    epoll真正实现高并发服务器

    epoll是IO模型中的一种,属于多路复用IO模型;

    select也是一种多路复用的IO模型,但是其单个select最多只能同时处理1024个socket,效率实在算不上高

    注意:epoll仅在linux中可用

    select实现并发的思路:

    1.当网卡收到数据后会现将数据写入到缓冲区

    2.发送中断信号给CPU

    3.CPU执行中断程序,将数据从内核copy到socket的缓冲区

    4.唤醒进程,即将进程A切换到就绪态,同时从socket的等待队列中移除这个进程引用

    从上面的过程中不难看出

    1.select,需要遍历socket列表,频繁的对等待队列进行添加移除操作,

    2.数据到达后还需要给变量所有socket才能获知哪些socket有数据

    两个操作消耗的时间随着要监控的socket的数量增加而大大增加,

    处于效率考虑才规定了最大只能监视1024个socket

    epoll基于select的基础上实现并发思路:

    1.创建epoll对象,epoll也会对应一个文件,由文件系统管理

    2.执行register时,将epoll对象 添加到socket的等待队列中

    3.数据到达后,CPU执行中断程序,将数据copy给socket

    4.在epoll中,中断程序接下来会执行epoll对象中的回调函数,传入就绪的socket对象

    5.将socket,添加到就绪列表中

    6.唤醒epoll等待队列中的进程,

    进程唤醒后,由于存在就绪列表,所以不需要再遍历socket了,直接处理就绪列表即可

    解决了这两个问题后,并发量得到大幅度提升,最大可同时维护上万级别的socket

    #epoll相关函数
    import select 导入select模块
    
    epoll = select.epoll() 创建一个epoll对象
    
    epoll.register(文件句柄,事件类型) 注册要监控的文件句柄和事件
    
    事件类型:
    
      select.EPOLLIN    可读事件
    
      select.EPOLLOUT   可写事件
    
      select.EPOLLERR   错误事件
    
      select.EPOLLHUP   客户端断开事件
    
    epoll.unregister(文件句柄)   销毁文件句柄
    
    epoll.poll(timeout)  当文件句柄发生变化,则会以列表的形式主动报告给用户进程,timeout
    
                         为超时时间,默认为-1,即一直等待直到文件句柄发生变化,如果指定为1
    
                         那么epoll每1秒汇报一次当前文件句柄的变化情况,如果无变化则返回空
    
    epoll.fileno() 返回epoll的控制文件描述符(Return the epoll control file descriptor)
    
    epoll.modfiy(fineno,event) fineno为文件描述符 event为事件类型  作用是修改文件描述符所对应的事件
    
    epoll.fromfd(fileno) 从1个指定的文件描述符创建1个epoll对象
    
    epoll.close()   关闭epoll对象的控制文件描述符
    
    #理解版
    import socket
    import select
    
    s = socket.socket()
    s.bind(("127.0.0.1",1689))
    s.listen()
    
    # 创建一个epoll对象
    epoll = select.epoll()
    
    # 注册读就绪事件 (有数据可以读取了)
    # s.fileno()用于获取文件描述符
    epoll.register(s.fileno(),select.EPOLLIN)
    
    
    # 存储文件描述符与socket的对应关系
    fd_sockets = {s.fileno():s}
    
    
    while True:
        # 该函数是阻塞会直到你关注的事件发生
        # 返回值为文件描述符与发生的事件类型  是一个列表 列表中是元组  第一个是描述符 第二个是事件
        for fd,event in epoll.poll():
            print("有socket 搞事情了!")
            sock = fd_sockets[fd] # 取出对应的socket对象
    
            # 判断socket是服务器还是客户端
            if sock == s:
                # 执行对应的接收或发送
                client,addr = sock.accept()
                # 注册客户端的事件
                epoll.register(client.fileno(),select.EPOLLIN)
                # 将对应关系存储到字典中
                fd_sockets[client.fileno()] = client
                print("来了一个客户端....")
    
            elif event == select.EPOLLIN: #客户端的处理
                data = sock.recv(1024)
                if not data:
                    epoll.unregister(fd) # 注销事件
                    fd_sockets.pop(fd) # 从字典中删除
                    sock.close()  # 关闭socket
                    continue
    
                print("%s 发来问候:%s" % (sock,data.decode("utf-8")))
    
                #将事件转换为可写
                epoll.modify(fd,select.EPOLLOUT)
            else:
                sock.send("我是服务器  你丫是谁?".encode("utf-8"))
                # 将事件转换为可读
                epoll.modify(fd, select.EPOLLIN)
    
    #正规版
    # coding:utf-8
    import socket, select
    
    server = socket.socket()
    server.bind(("127.0.0.1", 1688))
    server.listen(5)
    
    msgs = []
    
    
    fd_socket = {server.fileno(): server}
    epoll = select.epoll()
    # 注册服务器的 写就绪
    epoll.register(server.fileno(), select.EPOLLIN)
    
    while True:
        for fd, event in epoll.poll():
            sock = fd_socket[fd]
            print(fd, event)
            # 返回的是文件描述符 需要获取对应socket
            if sock == server:  # 如果是服务器 就接受请求
                client, addr = server.accept()
                # 注册客户端写就绪
                epoll.register(client.fileno(), select.EPOLLIN)
                # 添加对应关系
                fd_socket[client.fileno()] = client
    
            # 读就绪
            elif event == select.EPOLLIN:
                data = sock.recv(2018)
                if not data:
                    # 注销事件
                    epoll.unregister(fd)
                    # 关闭socket
                    sock.close()
                    # 删除socket对应关系
                    del fd_socket[fd]
                    print(" somebody fuck out...")
                    continue
    
                print(data.decode("utf-8"))
                # 读完数据 需要把数据发回去所以接下来更改为写就绪=事件
                epoll.modify(fd, select.EPOLLOUT)
                #记录数据
                msgs.append((sock,data.upper()))
            elif event == select.EPOLLOUT:
                for item in msgs[:]:
                    if item[0] == sock:
                        sock.send(item[1])
                        msgs.remove(item)
                # 切换关注事件为写就绪
                epoll.modify(fd,select.EPOLLIN)
    
  • 相关阅读:
    【洛谷4725】【模板】多项式对数函数(多项式 ln)
    【洛谷4516】[JSOI2018] 潜入行动(树上背包)
    【洛谷4463】[集训队互测2012] calc(动态规划+拉格朗日插值)
    【洛谷1973】[NOI2011] NOI 嘉年华(DP)
    【BZOJ2958】序列染色(动态规划)
    【CF1037H】Security(后缀自动机+线段树合并)
    【洛谷5308】[COCI2019] Quiz(WQS二分+斜率优化DP)
    【BZOJ3512】DZY Loves Math IV(杜教筛)
    【洛谷2178】[NOI2015] 品酒大会(后缀数组+单调栈)
    【BZOJ2878】[NOI2012] 迷失游乐园(基环树DP)
  • 原文地址:https://www.cnblogs.com/bruce123/p/11184452.html
Copyright © 2011-2022 走看看