php基于libevent实现并发socket服务端
通常提到PHP
,很多人都认为它只能做Web
项目,当然,在Web
开发时也的确能够体现出PHP
迅敏开发的优势。
那么我们能否单纯的用PHP
开发网络服务端?我们从最简单的做法开始尝试。
如下面一段代码,想要用PHP
来实现一个socket
服务端相对来说还是比较简单的。
$ip = '127.0.0.1';
$port = 8001;
//创建一个socket流
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
//绑定IP和端口
socket_bind($socket, $ip, $port);
//监听socket流
socket_listen($socket);
//接受一个连接
$fd = socket_accept($socket);
while($fd) {
//接收客户端发过来的数据
if (($msg = socket_read($fd, 1024)) != false) {
echo $msg . PHP_EOL;
}
}
socket_close($sock);
可想而知,这样一个单进程
、单线程
的同步阻塞代码,服务端一次只能监听一个连接的请求,没法应对多个客户端,怎么办呢?
上面既然说了是单进程
、单线程
,所以我们可以转变为多进程
、多线程
的并发模型,比如在socket_accept
的时候去fork
一个子进程负责处理这个客户端的连接,以达到处理多个客户端的连接。
可是问题又来了,如果客户端的连接很多,总不可能无止境的创建进程、线程吧。这个时候,多路复用IO就很好的解决了这个问题。
什么是多路复用IO呢?知乎上有一个比较通俗易懂的解答:
IO 多路复用是什么意思?
在PHP中,我们可以通过libevent
扩展来实现多路复用IO。
Libevent 是一个用C语言编写的、轻量级的开源高性能事件通知库,主要有以下几个亮点:事件驱动( event-driven),高性能;轻量级,专注于网络,不如 ACE 那么臃肿庞大;源代码相当精炼、易读;跨平台,支持 Windows、 Linux、 *BSD 和 Mac Os;支持多种 I/O 多路复用技术, epoll、 poll、 dev/poll、 select 和 kqueue 等;支持 I/O,定时器和信号等事件;注册事件优先级。
具体代码的实现:
老规矩,创建一个socket流
$socket = stream_socket_server("tcp://0.0.0.0:8001");
//初始化一个event_base对象
$base = event_base_new();
//初始化一个事件
$event = event_new();
/**
* event_set
* 设置这个事件的触发条件
* EV_READ 表示FD就绪,可以读取的时候 ,事件成为激活状态
* EV_PERSIST 表示事件是持久的
* 既当fd可读取时,事件将会执行ev_accept函数
*/
event_set($event, $socket, EV_READ | EV_PERSIST, 'ev_accept', array($event, $base));
//将指定事件和base对象关联起来
event_base_set($event, $base);
//将事件追加到当前base对象的事件队列
event_add($event);
//处理事件循环
event_base_loop($base);
$sockets = [];
/**
* 处理客户端向服务端write的事件
* 因为read也是个阻塞过程,所以也用event的方式来处理
*
* @param $socket
* @param $event
* @param $args
*/
function ev_accept($socket, $event, $args)
{
global $sockets;
$base = $args[1];
$connection = stream_socket_accept($socket);
stream_set_blocking($connection, 0);
$sockets[] = $connection;
$ev_read = event_new();
event_set($ev_read, $connection, EV_READ | EV_PERSIST, 'ev_read', array($ev_read, $base));
event_base_set($ev_read, $base);
event_add($ev_read);
}
/**
* 把消息转发到其他的fd上
*
* @param $connection
* @param $event
* @param $args
*/
function ev_read($connection, $event, $args)
{
global $sockets;
$read = stream_socket_recvfrom($connection, 1024);
foreach ($sockets as $socket) {
if ($socket != $connection) {
fwrite($socket, $read);
}
}
}
让我们写一个客户端来测试一下,客户端我们用两个进程,一个负责读,一个负责写。
$socket = stream_socket_client("tcp://127.0.0.1:8001");
$pid = pcntl_fork();
if ($pid == 0) {
//子进程负责读
while(true) {
while(($read = stream_socket_recvfrom($socket, 1024))) {
echo $read;
}
}
} else if ($pid > 0) {
//父进程负责写
$input = STDIN;
while($input) {
$text = fgets($input);
if (!empty(trim($text))) {
if (stream_socket_sendto($socket, $text) === -1) {
echo "error";
}
}
}
} else if ($pid == -1) {
exit("fork failed");
}
这样一个基于事件驱动的并发socket服务端就完成了。
码字很辛苦,转载请注明来自雨林寒舍的《php基于libevent实现并发socket服务端》
2017-05-10
编程语言
评论