在之前的http_server进行改进,区别在于:
1代阻塞,2代非阻塞
1代accept()函数阻塞执行,即每次只能处理一个连接,需close()掉,才能再次accept()处理连接。好比客服妹子只有一台电话机,电话未挂断,这个连接就占用这个电话机,无法再接听电话。
2代非阻塞模式stream_set_blocking(sock,0),accept()非阻塞,一个连接未挂断,其他连接可以进来。好比一个号码有多台电话机,类似分机,一个电话通话中,其他电话响起也可以接听。
那么开启非阻塞后,切身思考下美女客服接待几个电话机的场景:
你可能想到了,对应上面的三点:
是的,这就是事件机制,不过这篇文章采用比较老旧的技术select机制,select好比一个帮手在帮助客服,它做什么事情呢?当有新的电话打来,就取出一个电话机分机插上电话线。哪些电话对方说话了,交给客服,客服回复完了,再交给帮手继续监听,两个人来回传递电话机,客服面向客户可以理解为用户空间,后台帮手理解为内核空间,资源在用户空间和内核空间不断传递,不仅累还占用两个地方存储电话机(消耗内存)。而电话机还没有发明提示和免提功能,这个帮手需循环靠近听筒来判断用户的状态:(很恐怖吧哈哈),不过计算机比人快的多。
比一代的好处,在不挂断连接的情况下同时处理多个连接。并且通过select机制,来处理接电话、接听、回复的时机。
select系统几个重点函数:
总结:select系统可以处理多个连接,由于采用轮询的方式效率不高(较消耗性能偏cpu消耗);而且保持的连接数有限(1024个电话机,偏内存消耗),数据在用户空间和内核空间不断资源复制,比较消耗内存。
服务端
socket=stream_socket_server($sock_addr);
stream_set_blocking($this->socket,0);//@1
$this->_sockList[(int)$this->socket]=$this->socket;//(int)后即fd
}
public function start()
{
while(true)
{
//select系统
$except=[];
$write=$read=$this->_sockList;
if(false === ($status=@stream_select($read,$write,$except,60))){
die("stream_select fail!");
}
//select系统会动态将可读、可写的fd进行分类到read,write数组中
foreach ($read as $fd => $vsock)
{
echo "处理socket:".(int)$vsock.PHP_EOL;
if($vsock === $this->socket)
{
//服务器socket可读代表有连接进来
$clnt_sock = stream_socket_accept($this->socket,3600);
//将有客户打来的电话机交给select筛选
$this->_sockList[(int)$clnt_sock]=$clnt_sock;
}else{
$buf=fread($vsock,1024);
if(!empty($buf)){
var_dump('read list:',$read);//打印可读列表
echo 'read msg..'.PHP_EOL;
echo 'read from client '.(int)$vsock.$buf.' at time: '.time().PHP_EOL;//读取信息
var_dump('write list:',$write);//打印可写列表
fwrite($vsock,'i am server!'.time().PHP_EOL);//发送信息
}else{
if(!is_resource($vsock) || feof($vsock)){
echo 'clnt_sock:'.(int)$vscok.' is not a resource or fread feof!will be closed!'.PHP_EOL;
fclose($vsock);
unset($this->_sockList[(int)$vsock]);
continue;
}
}
}
}
}
}
}
$http_server=new Http_server('tcp://0.0.0.0:6001');
$http_server->start();
客户端
connect('127.0.0.1', 6001, -1);
for ($i=0; $i < 5 ; $i++) {
sleep(1); //每一秒发送依次数据
$client->send(' :hello!');
echo $client->recv().PHP_EOL;
}
$client->close();
如果懒的自己测试三张截图发给大家,
服务端:
[root@izhp3fuhf0tnxi9u27z7d5z swoole]# php http_server.php
处理socket:5 //服务器socket 接受客户端fd6
处理socket:5 //服务器socket 接受客户端fd7
处理socket:6
string(10) "read list:"
array(1) {
[6]=>
resource(6) of type (stream)
}
read msg..
read from client 6 hello! at time: 1554571055
string(11) "write list:"
array(2) {
[6]=>
resource(6) of type (stream)
[7]=>
resource(7) of type (stream)
}
处理socket:7
string(10) "read list:"
array(1) {
[7]=>
resource(7) of type (stream)
}
read msg..
read from client 7 hello! at time: 1554571055
string(11) "write list:"
array(2) {
[6]=>
resource(6) of type (stream)
[7]=>
resource(7) of type (stream)
}
处理socket:6
string(10) "read list:"
array(1) {
[6]=>
resource(6) of type (stream)
}
read msg..
read from client 6 hello! at time: 1554571056
string(11) "write list:"
array(2) {
[6]=>
resource(6) of type (stream)
[7]=>
resource(7) of type (stream)
}
处理socket:7
string(10) "read list:"
array(1) {
[7]=>
resource(7) of type (stream)
}
read msg..
read from client 7 hello! at time: 1554571056
string(11) "write list:"
array(2) {
[6]=>
resource(6) of type (stream)
[7]=>
resource(7) of type (stream)
}
...中间省略
处理socket:6
clnt_sock:0 is not a resource or fread feof!will be closed!
处理socket:7
string(10) "read list:"
array(1) {
[7]=>
resource(7) of type (stream)
}
read msg..
read from client 7 hello! at time: 1554571059
string(11) "write list:"
array(1) {
[7]=>
resource(7) of type (stream)
}
处理socket:7
clnt_sock:0 is not a resource or fread feof!will be closed!
客户端socket:6
[root@izhp3fuhf0tnxi9u27z7d5z swoole]# php client.php
i am server!1554571055
i am server!1554571056
i am server!1554571057
i am server!1554571058
i am server!1554571059
客户端socket:7
[root@izhp3fuhf0tnxi9u27z7d5z swoole]# php client.php
i am server!1554571055
i am server!1554571056
i am server!1554571057
i am server!1554571058
i am server!1554571059
只是简单为了压测,如果要封装为一个http服务器处理request,response请求,请看上一篇文章。
只需替换这部分代码:
if(!empty($buf)){
echo '接收客户端'.(int)$vsock.': '.strlen($buf).'长度的数据'.PHP_EOL;
echo '开始回复'.PHP_EOL;
$content='nihao';
$http_resonse = "HTTP/1.1 200 OK\r\n";
$http_resonse .= "Content-Type: text/html;charset=UTF-8\r\n";
$http_resonse .= "Connection: keep-alive\r\n";
$http_resonse .= "Server: php socket server\r\n";
$http_resonse .= "Content-length: ".strlen($content)."\r\n\r\n";
$http_resonse .= $content;
fwrite($vsock, $http_resonse);
}else{ ...
压测少量请求
ab -n 6 -c 3 -k http://39.xxx.xxx.xxx:6001/a
服务端打印
接收客户端8: 111长度的数据
开始回复
接收客户端8: 111长度的数据
开始回复
接收客户端9: 111长度的数据
开始回复
接收客户端8: 111长度的数据
开始回复
接收客户端10: 111长度的数据
开始回复
接收客户端9: 111长度的数据
开始回复
clnt_sock:0 is not a resource or fread feof!will be closed!
clnt_sock:0 is not a resource or fread feof!will be closed!
clnt_sock:0 is not a resource or fread feof!will be closed!
关闭所有打印输出(打印可能会引起网络阻塞),压测大量请求
D:\phpstudy2018\PHPTutorial\Apache\bin>ab.exe -c 200 -n 200 -k http://39.xx.xx.xx:6001/
外网压测,受网络影响,虽然效果不太理想,但是比一代比起来是一个飞跃。首先第二代服务器保持住了连接,无需close,等待客户端关闭连接后,再主动关闭连接,回收资源。
Warning: stream_select(): You MUST recompile PHP with a larger value of FD_SETSIZE.
It is set to 1024, but you have descriptors numbered at least as high as 1770.
--enable-fd-setsize=2048 is recommended, but you may want to set it
to equal the maximum number of open files supported by your system,
in order to avoid seeing this error again at a later date. in /home/swoole/http_server.php on line 45
警告:stream_select():必须使用较大的FD_SETSIZE值重新编译PHP。
它被设置为1024,但是您的描述符编号至少高达1770。
——enable-fd-setsize=2048是推荐的,但是您可能需要设置它
要等于系统支持的打开文件的最大数量,
以避免以后再次看到此错误。在第45行/home/swoole/http_server.php中