socket编程 - Event扩展的使用(一)

PHP默认没有提供epoll的函数,需要安装llibevent或者event扩展,建议使用event扩展。
不管是llibevent扩展或者event扩展,都需要先安装libevent程序来提供函数库,libevent封装了对于select, poll, epoll, kqueue等IO多路复用器的统一调用方式,并会根据当前系统选择一个合适的复用器。

安装libevent与event
https://blog.csdn.net/raoxiaoya/article/details/106170760

epoll实现原理
https://blog.csdn.net/raoxiaoya/article/details/106185479

libevent的功能

Libevent提供了事件通知,io缓存事件,定时器,超时,异步解析dns,事件驱动的http server以及一个rpc框架。

事件通知:当文件描述符可读可写时将执行回调函数。

Io缓存:缓存事件提供了输入输出缓存,能自动的读入和写入,用户不必直接操作io。

定时器:libevent提供了定时器的机制,能够在一定的时间间隔之后调用回调函数。

信号:触发信号,执行回调。

异步的dns解析:libevent提供了异步解析dns服务器的dns解析函数集。

事件驱动的http服务器:libevent提供了一个简单的,可集成到应用程序中的HTTP服务器。

RPC客户端服务器框架:libevent为创建RPC服务器和客户端创建了一个RPC框架,能自动的封装和解封数据结构。

event扩展提供的内容
在这里插入图片描述
event扩展是对libevent程序的调用的封装,所以对epoll的操作只是很小的一个部分。了解epoll实现原理有助于理解event扩展。

这里,我先使用 Event 和 EventBase 两个类来实现,先来介绍一下 $fd$cb

Event 构造函数
在这里插入图片描述
mixed $fd 可以有五种值:

1、由 sream族函数创建的资源。
2、由 socket族创建的资源。
3、操作系统的文件描述符编号,整型。
4、-1,对应为定时器。
5、信号值,整型,对应为信号事件。

Event callbacks
文档 https://www.php.net/manual/zh/event.callbacks.php
在这里插入图片描述
callback 最后一个参数 mixed $arg 就是 Event 构造函数的最后一个值,如果有的话。

下面来说说 $fd
1、PHP的资源类型保存了对外部资源的引用,但是它们的ID号是没有规律对应的,比如,一个socket资源,资源ID为7,通过该资源就可以找到对应操作系统上的文件描述符fd,比如3;这也就是为什么它可以接受 stream resource, socket resource, numeric file descriptor 作为参数,因为通过stream resource或者socket resource也可以得到numeric file descriptor的值,并且,最终是以操作系统的fd号numeric file descriptor来作为epoll函数的参数的。

2、传入Event的$fd参数是什么类型,那么回调函数中的$fd就是什么类型,会原样传入。

一个简单的epoll调用
epoll_create, epoll_waitEventBase 类实现
epoll_ctlEvent 类实现
在这里插入图片描述
启动服务

php server/socketServerEpoll.php

查看进程ID

ps -ef |grep Epoll
3138

查看进程打开的 fd

cd /proc/3138/fd
ll

在这里插入图片描述
可知监听的 fd 编号为3 ,由上面的分析可知,将 Event 构造中的 $this->serverSocket 换成 3 也是一样的。
在这里插入图片描述
但是,监听 fd 分配的编号不一定是 3,有可能是4,5,6,所以最好还是使用 $this->serverSocket 。

由epoll原理可知,操作系统会通知应用程序某fd发生了什么事件,比如 EPOLLIN,EPOLLOUT事件,应用程序只得到了fd编号事件编号,所以,某 fd 的某个事件对应的回调函数是注册在应用程序一方的,并且由应用程序决定如何调用回调函数,比如 [fd, EPOLLIN, callback],此处的 fd 为系统的fd号,从这一点来看,你可以为不同的 fd 的不同事件定义不同的回调处理。

Event与libevent的关系

实际上libevent为php程序提供了动态链接库(.so),也就是扩展,而event.so依赖于此扩展,在安装event的时候要正确指定libevent的位置,这样event才能去引入并调用它提供的方法,下面是libevent提供的动态连接库(.so)和静态链接库(.a),so是在程序运行阶段才被加载进内存的,a则是在链接期间就被结合到程序中。
在这里插入图片描述
查看 socketServerEpoll.php 打开的全部文件
lsof -p
其实就和加载PHP扩展一样,不同的是有的是PHP内核来加载,有的是由某个扩展来加载,此处是event扩展再去加载libevent的扩展,它们都是程序启动时就被加载而不管是否被使用。

特别注意的是:
一个连接断开后,一定要调用 Event 对象的 del() 或者 free(() 方法来移除注册在应用程序上的 [fd, EPOLLIN, callback];否则后面的连接可能无法注册事件回调。主要原因是系统的fd号会被重复使用(但是依然是不同的连接,因为socket的编号不一样)。

现象如下:
1、本地客户端连本地服务端,服务端得到7,设置事件并加入到epoll的fd池。
2、发送信息,可正常通讯。
3、正常断开此连接,重新建立连接,得到的还是7,然后居然没有触发任何回调。
4、利用strace监控服务端程序,发现连接断开后应用程序没有发起 EPOLL_CTL_DEL 调用,不过这也没关系,因为操作系统会自动将此fd从“fd池”移除;重建连接后应用程序没有发起 EPOLL_CTL_ADD调用,这是因为应用程序自己还维护了一个列表(上面提到的 [fd, EPOLLIN, callback] ),因为之前这里没有移除7,所以应用程序会以为7是重复添加,直接忽略;然后导致新建的连接没有 EPOLL_CTL_ADD ,所以操作系统并不会通知新7的任何事件。

具体代码
https://gitee.com/phprao/socket/blob/master/server/socketServerEpoll.php

调用监控
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我明白了你的问题。首先,让我们来介绍一下UDP通信和socket编程。 UDP通信是一种无连接的通信方式,它不需要事先建立连接,只需要知道对方的IP地址和端口号就可以直接发送数据。与TCP不同,UDP不保证数据传输的可靠性和有序性,因此在应用层需要对数据进行分块排序以实现可靠传输。 在Python中,可以使用socket模块来进行UDP通信。具体操作流程如下: 1. 创建socket对象,指定协议族和协议类型: ``` import socket # 创建UDP socket对象 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) ``` 2. 绑定IP地址和端口号: ``` # 绑定IP地址和端口号 udp_socket.bind(('127.0.0.1', 8888)) ``` 3. 发送数据: ``` # 发送数据 data = b'Hello, World!' udp_socket.sendto(data, ('127.0.0.1', 8888)) ``` 4. 接收数据: ``` # 接收数据 data, addr = udp_socket.recvfrom(1024) print('Received from %s:%s.' % addr) print(data.decode()) ``` 关于多线程编程和Event控制,可以使用Python标准库中的threading模块来实现。具体操作流程如下: 1. 创建线程: ``` import threading # 定义发送数据线程 class SendThread(threading.Thread): def __init__(self, addr, data): threading.Thread.__init__(self) self.addr = addr self.data = data def run(self): # 发送数据 udp_socket.sendto(self.data, self.addr) # 定义接收确认信息线程 class RecvThread(threading.Thread): def run(self): while True: # 接收确认信息 data, addr = udp_socket.recvfrom(1024) print('Received confirmation from %s:%s.' % addr) ``` 2. 创建Event对象: ``` # 创建Event对象 event = threading.Event() ``` 3. 在发送数据线程中等待确认信息: ``` # 发送数据 data = b'Hello, World!' send_thread = SendThread(('127.0.0.1', 8888), data) send_thread.start() # 等待确认信息 event.wait() ``` 4. 在接收确认信息线程中设置Event对象: ``` # 接收确认信息 recv_thread = RecvThread() recv_thread.start() # 发送确认信息 udp_socket.sendto(b'Confirmation message.', ('127.0.0.1', 8888)) event.set() ``` 以上就是UDP通信、socket编程、多线程编程和Event控制的基本操作。根据你的要求,我们可以对程序进行如下扩展: 1. 对发送的数据进行分块排序: ``` # 对数据进行分块排序 block_size = 1024 with open('file.txt', 'rb') as f: data = f.read() block_count = len(data) // block_size + 1 blocks = [] for i in range(block_count): block = data[i * block_size : (i + 1) * block_size] blocks.append(block) blocks.sort() # 对块进行排序 ``` 2. 创建子线程发送文件数据和接收确认信息: ``` # 定义发送数据线程 class SendThread(threading.Thread): def __init__(self, addr, blocks): threading.Thread.__init__(self) self.addr = addr self.blocks = blocks def run(self): # 发送数据 for block in self.blocks: udp_socket.sendto(block, self.addr) udp_socket.sendto(b'End of file.', self.addr) print('All blocks have been sent.') # 定义接收确认信息线程 class RecvThread(threading.Thread): def __init__(self, block_count): threading.Thread.__init__(self) self.block_count = block_count self.confirmation = [False] * block_count def run(self): while self.confirmation.count(True) < self.block_count: # 接收确认信息 data, addr = udp_socket.recvfrom(1024) block_index = int(data.decode()) self.confirmation[block_index] = True print('Received confirmation for block %d from %s:%s.' % (block_index, addr[0], addr[1])) print('All blocks have been confirmed.') ``` 3. 在发送数据线程中记录每块的起始地址: ``` # 记录每块的起始地址 block_size = 1024 with open('file.txt', 'rb') as f: data = f.read() block_count = len(data) // block_size + 1 blocks = [] for i in range(block_count): block = data[i * block_size : (i + 1) * block_size] blocks.append(block) block_addresses = [] for i, block in enumerate(blocks): block_addresses.append((i, block)) ``` 完整代码如下:

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值