1.1. IO 模型笔记

1.1.1. BIO

  • 同步阻塞 IO

测试代码

  • 使用 php 的进程模式 如果是 java 可以理解为线程也是一样的
<?php
set_time_limit(0);
error_reporting(E_ALL ^ E_NOTICE);

$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
$ip = '127.0.0.1';
$port = 8089;
$res = socket_bind($socket, $ip, $port);
if (!$res) {
    throw new Exception(
        "socket bind Err. \n" . socket_strerror(socket_last_error($sock))
    );
}
$listen_res = socket_listen($socket, 1);
if (!$listen_res) {
    throw new Exception(
        "socket bilisten_resnd Err. \n" .
            socket_strerror(socket_last_error($socket))
    );
}
echo "Socket Listen {$ip}:{$port} " . PHP_EOL;
while (true) {
    sleep(1);
    if (($fd = socket_accept($socket)) !== false) {
        echo "Client $fd has connected\n";

        //创建子进程
        $pid = pcntl_fork();
        if ($pid == -1) {
            /* fork failed */
            echo "fork failure!\n";
            die();
        } elseif ($pid == 0) {
            /* child process */
            while (true) {
                sleep(1);
                $buf = 'This is my buffer.';
                if (
                false !== ($bytes = socket_recv($fd, $buf, 2048, MSG_DONTWAIT))
            ) {
                    echo "Read $bytes bytes from socket_recv().".PHP_EOL;
                    if ($bytes > 0) {
                        print_r($buf);
                    } else {
                        socket_close($fd);
                        break;
                    }
                }
            }
            return ;
        }
    }
}
socket_close($socket);
  • 同时开启 3 个终端 测试链接
$ nc 127.0.0.1 8089
  • 终端输出
$ php BIO.php
Socket Listen 127.0.0.1:8089
Client Resource id #5 has connected
Client Resource id #6 has connected
Client Resource id #7 has connected

结论

  • 查看进程数 4 个 每加一个链接 增加一个进程
  • accept 和 recv 都会存在 IO 阻塞
  • BIO 使用开启子进程(或线程)处理 IO 的阻塞处理
  • 缺点 : 一个链接一个进程 会开辟大量的进程内存空间占用浪费资源,CPU 调度消耗大
  • 优点 : 可以接收很多的链接,实现相对简单

1.1.2. NIO

  • 非阻塞 IO
  • NON BLOCKING
<?php
set_time_limit(0);
error_reporting(E_ALL ^ E_NOTICE);

$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
$ip = '127.0.0.1';
$port = 8090;
$res = socket_bind($socket, $ip, $port);
if (!$res) {
    throw new Exception(
        "socket bind Err. \n" . socket_strerror(socket_last_error($sock))
    );
}
$listen_res = socket_listen($socket, 1);
if (!$listen_res) {
    throw new Exception(
        "socket bilisten_resnd Err. \n" .
            socket_strerror(socket_last_error($socket))
    );
}
echo "Socket Listen {$ip}:{$port} " . PHP_EOL;
$nonblock = socket_set_nonblock($socket); ## 开启NONBLOCK
if (!$nonblock) {
    throw new Exception(
        "socket socket_set_nonblock Err. \n" .
            socket_strerror(socket_last_error($socket))
    );
}
$clients = [];

while (true) {
    sleep(1);
    if (($fd = socket_accept($socket)) !== false) {
        echo "Client $fd has connected\n";
        socket_set_nonblock($fd); ## client 开启NONBLOCK
        $clients[] = $fd;
    }
    foreach ($clients as $k=>$fd) {
        $buf = 'This is my buffer.';
        if (false !== ($bytes = socket_recv($fd, $buf, 2048, MSG_DONTWAIT))) {
            echo "Read $bytes bytes from socket_recv()." . PHP_EOL;
            if ($bytes === 0) {
                socket_close($fd);
                unset($clients[(int)$k]);
            } else {
                print_r($buf);
            }
        }
    }
}
socket_close($socket);
  • 同时开启 3 个终端 测试链接
$ nc 127.0.0.1 8090
  • 终端输出
$ php NIO.php
Socket Listen 127.0.0.1:8090
Client Resource id #5 has connected
Client Resource id #6 has connected
Client Resource id #7 has connected

结论

  • 查看进程数 1 个
  • accept 和 recv 都会存在 IO 阻塞
  • NIO 开启 NONBLOCK 处理 accept 非阻塞 IO
  • 缺点 : resv 的检测依赖循环 效率比较低下
  • 优点 : 避免了多进程(线程)的问题

1.1.3. 多路复用器 select

<?php
set_time_limit(0);
error_reporting(E_ALL ^ E_NOTICE);

$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
$ip = '127.0.0.1';
$port = 8091;
$res = socket_bind($socket, $ip, $port);
if (!$res) {
    throw new Exception(
        "socket bind Err. \n" . socket_strerror(socket_last_error($sock))
    );
}
$listen_res = socket_listen($socket, 1);
if (!$listen_res) {
    throw new Exception(
        "socket bilisten_resnd Err. \n" .
            socket_strerror(socket_last_error($socket))
    );
}
echo "Socket Listen {$ip}:{$port} " . PHP_EOL;
$all_sock[(int)$socket] = $socket;
var_dump($all_sock);
while (true) {
    sleep(1);
    $read = $write = $except = [];
    $tv_sec = 5;
    $tv_usec = 5000;
    $read = $all_sock;

    $changed = socket_select($read, $write, $except, $tv_sec, $tv_sec);

    if (false === $changed) {
        print "[error]socket_select() failed
        (" .socket_strerror(socket_last_error()) . ")\n";
    }
    if ($changed < 1) {
        continue;
    }
    foreach ($read as $k=>$rsock) {
        // 服务端连接
        if ($rsock === $socket) {
            $client = socket_accept($socket);
            echo "Client $client has connected\n";
            $all_sock[(int)$client] = $client;
        } else {//客户端连接
            $buf = 'This is my buffer.';
            if (false !== ($bytes = socket_recv($rsock, $buf, 2048, MSG_DONTWAIT))) {
                echo "Read {$bytes} bytes from socket_recv()." . PHP_EOL;
                if ($bytes === 0) {
                    socket_close($rsock);
                    unset($all_sock[(int)$k]);
                } else {
                    print_r($buf);
                }
            }
        }
    }
}
socket_close($sock);
  • 同时开启 3 个终端 测试链接
$ nc 127.0.0.1 8092
  • 终端输出
$ php mults.php
Socket Listen 127.0.0.1:8092
array(1) {
  [4]=>
  resource(4) of type (Socket)
}
Client Resource id #5 has connected
Client Resource id #6 has connected
Client Resource id #7 has connected

结论

  • 查看进程数 1 个
  • accept 和 recv 都会存在 IO 阻塞
  • socket_select 在给定的套接字数组上以指定的超时时间运行 select 系统调用
  • 缺点 : 限制了数量不能超过 1024 个,效率较低
  • 优点 : 一次系统调用返回多个可用的 socket 资源
  • select 方式,遍历所有的 socket,如果某个达到了可读条件就进行拿出来执行,最多监听 1024 个
  • 用户空间保存所有的连接
  • 内核空间遍历,得到可读的
  • 转交给用户空间处理
  • poll 方式,和 select 方式相似,只是没有监听个数限制,1w 长连接,就要遍历 1w 个,即使有很多都没有信息,浪费 cpu
  • epoll,没有描述符限制,也没有遍历,当描述符状态改变时,触发事件回调,事件监听机制
  • 需要安装 libevent 库(linux),php 需要安装 event 扩展,pecl install event,才能使用 epoll
  • 将每个 fd 和回调添加到内核中,内核监听网卡等状态,当有请求数据是针对某个 fd 时,就将其执行 read 的回调放到就绪队列中,等待执行
  • fd 在内核中以树状(红黑树)存储,方便查找

results matching ""

    No results matching ""