「Python网络编程」使用单线程实现多任务/epoll模型(完)

博主前言:

Python网络编程系列已经讲述了套接字编程、HTTP协议、TCP/IP协议、多线程、多进程以及协程等知识,就以这篇博客结束Python网络编程系列吧。在这篇博客中会讲述两种以单线程的方式完成多任务。

1. 单线程实现多任务

前面我们讲述以多线程多进程以及协程的方式实现多任务,那么能否以单线程的方式实现多任务呢?答案是可行的。我们先了解一种简单的方式 —— 单线程以轮询的方式实现多任务。

1.1 轮询

轮询,就是对每个套接字挨个的询问其是否有通信,
说白了就是对套接字列表死循环迭代操作。
我们以下列代码实现。

import socket
import time

def main():
    # 创建套接字对象
    tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    # 设置套接字对象为非阻塞,当套接字阻塞时会报错
    tcp_server_socket.setblocking(False) 
    # 绑定
    tcp_server_socket.bind(("192.168.111.128",13214))
    # 将套接字设置为监听套接字
    tcp_server_socket.listen(128)
    # 创建一个socket列表
    client_socket_list = list()
    # 轮询
    while True:
        try:
            new_socket,new_addr = tcp_server_socket.accept()    # 试图接收一个客户端的链接
        except Exception:
            print("No new client coming!")     # 若此时没有客户端链接,accept会阻塞,导致程序错误,执行except语句
            time.sleep(1)
        else:   # 若此时有一个客户端链接
            new_socket.setblocking(False)   # 设置服务套接字为非阻塞
            client_socket_list.append(new_socket)   # 将服务套接字添加进socket列表
            print("new client coming!")
        for client_socket in client_socket_list:    # 迭代socket列表
            try:
                data = client_socket.recv(1024)     # 试图接收客户端发送来的数据
            except Exception:
                print("the client have not data!")  # 若此时客户端没有发送数据,recv会阻塞,导致程序错误,执行except语句
            else:   # 若此时有客户端发来数据
                if data:    # 如果数据非空,即客户端不是关闭客户端的套接字,则执行if语句
                    print("the client had data!")
                    print(data)
                else:   # 如果数据为空,即客户端关闭了套接字,则执行else语句关闭服务套接字,且把服务套接字移出socket列表
                    client_socket.close()   
                    client_socket_list.remove(client_socket)

if __name__ == '__main__':
    main()

当运行上述代码,此时无客户端链接时,他会一直死循环accept并且执行打印操作,如下图。在这里插入图片描述
当此时有客户端链接时,他会将客户端服务套接字添加进套接字列表,进行迭代,且可同时为多个客户端链接服务。

1.2 epoll

epoll是Linux2.6下性能最好的多路I/O就绪通知方法。

epoll相对于poll/select有两个改进:

  1. 虽然epoll同样只告知那些就绪的文件描述符。但其使用了内存映射(mmap)技术,省掉了这些文件描述符在系统调用时复制的开销。
  2. 另一个改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用时便得到通知。

我们使用代码来理解epoll模型:

import socket
import select

def main():
    # 创建套接字对象
    tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    # 设置套接字对象为非阻塞,当套接字阻塞时会报错
    tcp_server_socket.setblocking(False)
    # 绑定
    tcp_server_socket.bind(("192.168.111.128",13144))
    # 将套接字设置为监听套接字
    tcp_server_socket.listen(128)
    # 创建一个epoll对象
    epl = select.epoll()
    # 将监听套接字对应的fd(文件描述符)注册到epoll中
    epl.register(tcp_server_socket.fileno(),select.EPOLLIN)
    # 创建一个字典用于回朔套接字
    fd_event_dict = dict()

    # 事件通知
    while True:
        fd_event_list = epl.poll()  # 默认会堵塞,直到系统检测到数据到来,通过事件通知方式,告诉这个程序,此时才会解堵塞
        # 返回类型[(fd,event),(套接字对应的文件描述符,这个文件描述符的事件)]
        for fd,event in fd_event_list:  # 拆包
            if fd == tcp_server_socket.fileno():    # 如果是监听套接字的fd,则表示此时有客户端链接
                new_socket,client_addr = tcp_server_socket.accept() # 接收客户端
                epl.register(new_socket.fileno(),select.EPOLLIN)    # 将服务套接字对应的fd祖册到epoll中
                fd_event_dict[new_socket.fileno()] = new_socket     # 以键值对的方式存储服务套接字和其fd
                print("successfully to add the socket!")
            else:   # 如果不是监听套接字的fd,则表示此时有数据发送了过来
                data = fd_event_dict[fd].recv(1024)      # 以键值对的方式,通过fd取出对应的服务套接字用于接收数据
                if data:    # 如果数据非空,则打印数据
                    print(data)
                else:   # 如果数据为空,则关闭服务套接字
                    fd_event_dict[fd].close()
                    epl.unregister(fd)  # 将服务套接字注销出epoll
                    del fd_event_dict[fd]   # 将服务套接字和其对应的fd从字典中删除

if __name__ == '__main__':
    main()         

epoll模型先将套接字的文件描述符注册进epoll中,
每当套接字进行通信时,会返回这个套接字的文件描述符,
此时epoll对所有监视的文件描述符进行扫描,
并迅速激活这个文件描述符。
在调用register函数注册时,第二个参数是epoll模型提供的注册方式。

可读:EPOLLIN
可写:EPOLLOUT
ET模式:EPOLLET

epoll对文件描述符的操作有两种模式:LT(level trigger)ET(edge trigger)
LT模式与ET模式的区别如下:

LT模式(默认):当epoll检测到描述符事件发⽣并将此事件通知应⽤程序,应⽤程序可以不⽴即处理该事件。下次调⽤epoll时,会再次响应应⽤程序并通知此事件。
 
ET模式:当epoll检测到描述符事件发⽣并将此事件通知应⽤程序,应⽤程序必须⽴即处理该事件。如果不处理下次调⽤epoll时,不会再次响应应⽤程序并通知此事件。

结语:

Python网络编程系列的分享博客算是圆满告终,通过以博客的方式,此部分的知识点,更加系统更加深刻的在我脑海里形成,此后我还会分享其他的知识点,例如数据库,网页框架等等,且行且学且分享。我爱CSDN。

发布了14 篇原创文章 · 获赞 9 · 访问量 3823

猜你喜欢

转载自blog.csdn.net/qq_43462005/article/details/104265327