PHP 原生 Socket 网络编程:Select 实现高性能网络服务器
Socket 编程493 字
你好,我是一笑。
再上一篇文章中,我们使用 fork 的形式完成了高性能网络服务的开发,但是由于 fork 的形式开销比较大,我们需要使用另外一种方式来进行实现,也就是这篇文章中要讲解的 select。
相对来说 select 要比 fork 高效很多,而我们常说的 select 其实就是Linux 为我们提供的异步事件 I/O,当发生事件的时候系统内核会给我们发送通知,与多进程多线程相比,异步 IO最大的优势就是开销小,因为无论是进程、线程在上下文切换的时候都会有一定的开销。同时使用异步 IO,我们就不必再去维护进程或者线程的状态 。
select 可以看做是半自动,处理的并不是特别好,不是完全按照事件进行处理,在应用层,我们还需要使用循环来进行遍历文件句柄的变化。
在 PHP 中为我们提供了 socket_select 函数,我们可以通过这个函数来监听文件句柄的变化。
<?php
//创建服务端的socket套接流,net协议为IPv4,protocol协议为TCP
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1)) {
echo "Failed to set socket options!";
exit();
}
socket_set_nonblock($socket);
// 绑定接收的套接流主机和端口,与客户端相对应
if (!socket_bind($socket, '0.0.0.0', 8888)) {
echo 'server bind fail:' . socket_strerror(socket_last_error());
exit();
}
// 监听套接流
if (!socket_listen($socket, 4)) {
echo 'server listen fail:' . socket_strerror(socket_last_error());
exit();
}
$all_sock[] = $socket;
//让服务器无限获取客户端传过来的信息
for (; ;) {
$read = $write = $except = [];
$read = $all_sock;
$changed = socket_select($read, $write, $except, null);
if (!$changed) {
print "[error]socket_select() failed
(" . socket_strerror(socket_last_error()) . ")\n";
}
if ($changed < 1) {
continue;
}
foreach ($read as $key => $rsock) {
// 服务端连接
if ($rsock === $socket) {
$client = socket_accept($socket);
if (!$client) {
echo "socket_accept error:!" . socket_strerror(socket_last_error());
exit();
}
$all_sock[] = $client;
} else {
//客户端连接
//读socket_read的作用就是读出socket_accept()的资源并把它转化为字符串
$string = socket_read($rsock, 1024);
echo 'server receive is :' . $string . PHP_EOL;//PHP_EOL为php的换行预定义常量
$return_client = 'server receive is : ' . $string . PHP_EOL;
socket_write($rsock, $return_client, strlen($return_client));
}
}
}
}
socket_close($socket);
select 有两个不足:
- select 会受到单个进程最大打开描述符的限制。它只能监听的最大文件描述符个数由__FD_SIZE 决定,它的默认值是 1024;
- select 返回后,需要遍历文件描述符集才能找到哪些文件描述符就绪了,文件描述符集在内核空间和用户空间频繁拷贝提升了系统的开销。
在下一章节,我们将使用 epoll 来解决这个问题。