• 展开微博窗口
  • QQ:52619941
  • 微信:cnmemory
  • 展开分类目录
  • 还没有账号?

Memory

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服务端》

评论