PHP的socket扩展提供了一个多路I/O事件复用select
IO复用
I/O复用函数本身也是阻塞I/O,发起系统调用同样的要被挂起,直到就绪事件发生,才会运行。所以也称为同步I/O模型。
I/O复用函数一般用的有select,epoll,poll,kqueue,devpoll等PHP只提供了一个select。
同步I/O模型与异步I/O模型
同步IO模型是向应用程序通知的是I/O就绪事件,而异步I/O模型则是向应用程序通知的是完成事件,后续我们在编写代码过程会进行模拟异步I/O的实现。
测试源码
$ip = "0.0.0.0";
$port = $argv[1];
$sockefd = socket_create(AF_INET,SOCK_STREAM,0);
socket_set_option($sockefd,SOL_SOCKET,SO_REUSEPORT,1);
socket_bind($sockefd,$ip,$port);
socket_listen($sockefd,5);
$connectionfds = [];
$readfds = [$sockefd];
$writefds = [$sockefd];
$exceptionfds = [$sockefd];
$bufferMessage = "hello,world";
$recvMessage = "";
$remoteIP;
$remoteAddr;
fprintf(STDOUT,"server pid=%d\n",posix_getpid());
$i=0;
while ("server") {
$read_fds = $readfds;
$write_fds = $writefds;
$exception_fds = $exceptionfds;
$connectNum = socket_select($read_fds, $write_fds, $exception_fds, NULL);
if (!$connectNum) {
break;
}
foreach ($read_fds as $fd) {
if ($fd == $sockefd) {
$connfd = socket_accept($sockefd);
socket_set_nonblock($connfd);
socket_getpeername($connfd,$remoteIP,$remoteAddr);
fprintf(STDOUT, "new connection %s ,ip is %s,port is %d\n", $connfd,$remoteIP,$remoteAddr);
$readfds[] = $connfd;
$exceptionfds[] = $connfd;
$connectionfds[] = $connfd;
} else {
$recvMessage = socket_read($fd, 8192);
unset($readfds[array_search($fd, $readfds)]);
if (strcasecmp("QUIT",$recvMessage)){
socket_send($fd,"quit\n",6,0);
socket_close($fd);
unset($exceptionfds[array_search($fd, $exceptionfds)]);
unset($writefds[array_search($fd, $writefds)]);
}
if (!$recvMessage) {
socket_close($fd);
unset($exceptionfds[array_search($fd, $exceptionfds)]);
unset($writefds[array_search($fd, $writefds)]);
} else {
fprintf(STDOUT, "recv from client msg:%s\n", $recvMessage);
array_push($writefds, $fd);
}
}
}
foreach ($write_fds as $fd) {
$writeBytes = socket_write($fd, $bufferMessage, strlen($bufferMessage));
unset($writefds[array_search($fd, $writefds)]);
if (!$writeBytes) {
socket_close($fd);
unset($readfds[array_search($fd, $readfds)]);
unset($exceptionfds[array_search($fd, $exceptionfds)]);
} else {
array_push($readfds, $fd);
}
}
foreach ($exception_fds as $fd) {
socket_close($fd);
unset($readfds[array_search($fd, $readfds)]);
unset($exceptionfds[array_search($fd, $exceptionfds)]);
unset($writefds[array_search($fd, $writefds)]);
}
}
socket_close($sockefd);
启动服务
然后阻塞在selectI/O函数
第二个客户端连接时
总结
select的基本功能:就是为了能监听多个客户端的连接,它本身还是并没有特别的地方,但是它也是有缺点的,就是每次都得传递文件描述符集合给它,同时返回的就绪文件也要自己遍历,如果监听太多,性能也不怎么样,更关键的是它最多只能监听1024个文件描述符,毕竟它在内核中是以位形式存储。
原理图:【实在找不到吊炸天的画图软件了】
该原理图请配合select函数亲自测试并调试
文章评论