【Python基础】网络编程之Epoll使用一(符实操:基于epoll实现的实时聊天室)

00

🌈欢迎来到Python专栏
🙋🏾‍♀️作者介绍:前PLA队员 目前是一名普通本科大三的软件工程专业学生
🌏IP坐标:湖北武汉
🍉 目前技术栈:C/C++、Linux系统编程、计算机网络、数据结构、Mysql、Python(目前在学)
🍇 博客介绍:通过分享学习过程,加深知识点的掌握,也希望通过平台能认识更多同僚,如果觉得文章有帮助,请您动动发财手点点赞,本人水平有限,有不足之处欢迎大家扶正~
🍓 最后送大家一句话共勉:知不足而奋进,望远山而前行。愿大家都能早日进大厂实现财富自由~

1.什么是epoll

“epoll”是输入/输出事件通知的系统调用。它是 Linux 内核提供的一项功能,用于有效处理大量文件描述符或套接字。“epoll”在需要监视许多 I/O 操作的情况下特别有用,例如在处理大量并发连接的服务器中。

以下是关于“epoll”的一些关键点:

  1. 效率: 与“select”和“poll”等旧机制不同,“epoll”旨在随着文件描述符的数量而有效扩展。它特别适用于具有大量开放套接字的方案。

  2. 事件驱动: ‘epoll’ 是事件驱动的,这意味着当文件描述符上发生特定事件时,它可以通知您的程序。事件可以包括可供读取的数据、可供写入的空间或文件描述符上的错误。

  3. 边沿触发和电平触发模式:** “epoll”支持边沿触发和电平触发模式。在边缘触发模式下,仅当文件描述符的状态发生更改时,才会触发事件。在关卡触发模式下,只要条件成立,就会触发事件。

  4. 可扩展性: ‘epoll’ 以其可扩展性而闻名。它可以有效地处理大量文件描述符,而不会出现旧机制中的性能下降。

  5. 系统调用: 与 ‘epoll’ 相关的主要系统调用是 ‘epoll_create’、‘epoll_ctl’ 和 ‘epoll_wait’。

“epoll”的使用通常出现在高性能网络服务器中,例如 Web 服务器或代理,在这些服务器中,处理大量并发连接对于效率至关重要。

需要注意的是,虽然 ‘epoll’ 是特定于 Linux 的,但其他操作系统也有类似的机制,但名称不同,例如 FreeBSD 和 macOS 上的 ‘kqueue’

1.1通俗例子说明:

“epoll”就像一个繁忙的办公楼的智能接待员。想象一下,你有很多房间(文件描述符或套接字),人们(数据或事件)一直在来来去去。

  1. 效率: 智能接待员(带有“epoll”的 Linux 内核)不会浪费时间不断检查每个房间。相反,它确切地知道哪些房间发生了一些重要的事情。

  2. 事件驱动: 当房间内发生重要事件(例如要读取的新数据或要写入的空间)时,智能接待员会立即通知您。你不必一直问每个房间是否有变化。

  3. 通知类型: 当重要的事情只发生一次(边缘触发)或只要是真的(级别触发)时,智能接待员就可以通知您。这就像告诉你什么时候有人进入房间,或者什么时候有人在那里。

  4. 处理多个房间: 即使您有很多房间(大量文件描述符或套接字),聪明的接待员也擅长跟踪所有房间而不会不知所措。

  5. 智能系统呼叫: 要与智能接待员合作,您可以采取特殊操作,例如告诉接待员开始关注新房间(“epoll_create”)、要求它注意房间的某些事件(“epoll_ctl”)和等待通知(“epoll_wait”)。

因此,简单来说,“epoll”是 Linux 中的一个智能系统,它可以帮助程序有效地管理和跟踪同时发生的许多事情,例如在不减慢速度的情况下处理大量互联网连接。这就像拥有一个智能助手,可以准确地告诉您在繁忙的办公室中何时何地发生事情。

1.2 epoll实现过程介绍

  1. 创建“epoll”实例:
  • 首先使用“epoll_create()”或“epoll_create1()”系统调用创建一个“epoll”实例。这将返回与“epoll”实例关联的文件描述符。
  1. 注册文件描述符:
  • 告诉“epoll”实例要监视哪些文件描述符(套接字,在网络上下文中)以及您感兴趣的事件(例如,读取、写入或错误事件)。这是使用“epoll_ctl()”系统调用完成的。
  • 您可以在一次调用中注册对多个文件描述符和事件的兴趣。
  1. 等待事件:
  • 然后,程序使用“epoll_wait()”系统调用来等待事件。
  • ‘epoll_wait()’ 阻止程序,直到一个或多个注册事件发生。
  1. 处理事件:
  • 当事件发生时,‘epoll_wait()’ 返回,您将获得有关事件的信息。
  • 然后,您可以循环访问返回的事件,并根据事件类型(例如,读取或写入数据)执行必要的操作。
  1. 边沿触发与电平触发:
  • ‘epoll’ 支持边沿触发和电平触发模式。
  • 在边缘触发模式下,仅当文件描述符的状态发生变化时,您才会收到通知,而不一定在数据可用时通知。
  • 在电平触发模式下,只要条件成立,您就会收到通知。
  1. 修改和注销文件描述符:**
  • 您可以动态修改正在监控的文件描述符集,或使用“epoll_ctl()”取消注册它们。
  1. 关闭“epoll”实例:
  • 完成后,使用“close()”系统调用关闭“epoll”实例。

关键思想是“epoll”提供了一种有效的方法来管理大量文件描述符的 I/O 事件,而无需持续轮询。它允许程序是事件驱动的,仅在有活动时响应,而不是主动检查更改。这样可以提高资源使用效率,并为同时处理多个连接的应用程序提供更好的性能。

一般步骤

1.  Create an epoll object——创建 1 个 epoll 对象

2.  Tell the epoll object to monitor specific events on specific sockets— —告诉 epoll 对象,在指定的 socket 上监听指定的事件

3.  Ask the epoll object which sockets may have had the specified event

since the last query——询问 epoll 对象,从上次查询以来,哪些 socket发生了哪些指定的事件

4.  Perform some action on those sockets——在这些 socket 上执行一些

操作

5.  Tell the epoll object to modify the list of sockets and/or events to

monitor——告诉 epoll 对象,修改 socket 列表和(或)事件,并监控

6.  Repeat steps 3 through 5 until finished——重复步骤 3-5,直到完成

7.  Destroy the epoll object——销毁 epoll 对象

2.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 对象的控制文件描述符

3.基于epoll实现的实时聊天室

  • 服务器代码:
#!/usr/bin/python
# author X_Dragon
# E-mail:3270895551@qq.com
# @Time : 2023/11/13 14:21
# 即时聊天室
import socket
import select
import sys


def tcp_server():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    addr = ("175.178.47.72", 2001)
    print(addr)
    s.bind(addr)  # 绑定端口 此时并没有激活
    s.listen(128)  # listen时 端口才会激活
    new_client, client_addr = s.accept()  # 这里的new_client是指tcp服务器分配给客户端单独的服务
    print("[客户端add:%d]", client_addr)
    epoll = select.epoll()  # 创建一个epoll对象
    # 让epoll健=监控new_client sys.stdin
    epoll.register(new_client.fileno(), select.EPOLLIN)
    epoll.register(sys.stdin.fileno(), select.EPOLLIN)
    while True:
        # 谁的缓冲区有数据,就填写到events,events是列表里边存的是元组,(fd,事件)
        events = epoll.poll(-1)
        for fd, event in events:
            if fd == new_client.fileno():
                data = new_client.recv(100)
                if data:
                    print(f"接收到地址为{client_addr}发送的信息:{data.decode('utf8')}")
                else:
                    print(f"地址为{client_addr}的客户端已断开链接")
                    new_client.close()
                    return
            elif fd == sys.stdin.fileno():
                try:
                    data = input()  # 服务器发消息给客户端
                except EOFError:  # 按下 ctrl+d 让服务器断开
                    print("I want to go")
                    new_client.close()
                    s.close()
                    return
                new_client.send(data.encode('utf8'))

        # Move these outside the loop
    new_client.close()
    s.close()


if __name__ == '__main__':
    tcp_server()

  • 客户端代码
#!/usr/bin/python
# author X_Dragon
# E-mail:3270895551@qq.com
# @Time : 2023/11/13 14:39
import socket
import select
import sys


def tcp_client():
    if len(sys.argv) == 1:
        return
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    dest_addr = ("175.178.47.72", 2001)
    client.connect(dest_addr)
    epoll = select.epoll()  # 创建一个epoll对象
    # 让epoll监控new_client sys.stdin
    epoll.register(client.fileno(), select.EPOLLIN)
    epoll.register(sys.stdin.fileno(), select.EPOLLIN)
    while True:
        # 谁的缓冲区有数据 就填写进events
        events = epoll.poll(-1)
        for fd, event in events:
            if fd == client.fileno():
                data = client.recv(200)
                if data:
                    print("客户端[%d]接收到服务器消息:%s", dest_addr, data.decode('utf8'))
                else:
                    print("对方断开了...")
            elif fd == sys.stdin.fileno():
                data = input()  # 客户端端说话 发给对方
                client.send(data.encode('utf8'))
    client.close()


if __name__ == '__main__':
    tcp_client()

运行结果 :

01

创作不易 点赞支持~
000

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
网络编程中,可以使用epoll实现多个客户端转发到多个服务器的功能。epoll是Linux下的一种高性能I/O事件通知机制,可以有效地处理大量的并发连接。 以下是使用epoll实现多个客户端转发到多个服务器的基本步骤: 1. 创建一个监听套接字,用于接收客户端连接。将该套接字添加到epoll事件集合中,监听读事件。 2. 创建一个epoll实例,通过epoll_create函数进行创建。 3. 将监听套接字添加到epoll实例中,使用epoll_ctl函数进行添加。 4. 进入事件循环,通过epoll_wait函数等待事件的发生。该函数会阻塞,直到有事件发生或超时。 5. 当有事件发生时,通过遍历事件数组,处理相应的事件。 6. 如果是监听套接字上有读事件发生,说明有新的客户端连接请求。使用accept函数接受连接,并将新的客户端套接字添加到epoll实例中。 7. 如果是客户端套接字上有读事件发生,说明有客户端发送数据到服务器。可以读取数据,并根据转发策略选择目标服务器,将数据转发给相应的服务器。 8. 如果是服务器套接字上有读事件发生,说明有服务器返回数据给客户端。可以读取数据,并将数据发送给相应的客户端。 9. 处理完事件后,将相应的套接字从epoll实例中删除。 10. 重复步骤4-9,直到事件循环结束。 通过以上步骤,可以实现多个客户端同时连接到服务器,并进行数据转发的功能。需要注意的是,epoll是一种边缘触发模式,需要在处理事件时确保将所有数据都读取完毕或发送完毕,以免遗漏数据或造成阻塞。另外,需要根据具体的业务需求来确定转发策略和服务器选择算法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值