2019-4-5 升级单进程阻塞为单进程非阻塞

理论先行

在之前的http_server进行改进,区别在于:

  • 1代阻塞,2代非阻塞

    1代accept()函数阻塞执行,即每次只能处理一个连接,需close()掉,才能再次accept()处理连接。好比客服妹子只有一台电话机,电话未挂断,这个连接就占用这个电话机,无法再接听电话。

    2代非阻塞模式stream_set_blocking(sock,0),accept()非阻塞,一个连接未挂断,其他连接可以进来。好比一个号码有多台电话机,类似分机,一个电话通话中,其他电话响起也可以接听。

那么开启非阻塞后,切身思考下美女客服接待几个电话机的场景:

  • 一个人同时监听多个电话机,怎么知道有新的连接进来?
  • 一般客服不会主动挂断电话,怎么知道哪些对方已挂断?
  • 怎么知道哪个电话机的客户在问问题,不会发生没听到的情况?

你可能想到了,对应上面的三点:

  • 电话机连接进来时有铃音,提示有个电话机打来电话了
  • 挂断有铃音滴滴滴提示,提示有个电话机对方挂断了
  • 开免提,一台电话机对方说过,可以听见是哪一台有消息了,然后回复

是的,这就是事件机制,不过这篇文章采用比较老旧的技术select机制,select好比一个帮手在帮助客服,它做什么事情呢?当有新的电话打来,就取出一个电话机分机插上电话线。哪些电话对方说话了,交给客服,客服回复完了,再交给帮手继续监听,两个人来回传递电话机,客服面向客户可以理解为用户空间,后台帮手理解为内核空间,资源在用户空间和内核空间不断传递,不仅累还占用两个地方存储电话机(消耗内存)。而电话机还没有发明提示和免提功能,这个帮手需循环靠近听筒来判断用户的状态:(很恐怖吧哈哈),不过计算机比人快的多。

比一代的好处,在不挂断连接的情况下同时处理多个连接。并且通过select机制,来处理接电话、接听、回复的时机。

select系统几个重点函数:

  1. stream_set_blocking(sock,0)
    socket系列函数非阻塞运行
  2. stream_select()
    遍历服务器socket,当可读时代表有客户端连接进来。
    遍历客户端socket,当可读时代表客户端发消息过来。
    遍历客户端socket,当客户未在讲话时,可以向客户讲话
  3. feof() 函数
    file end of file ,tcp客户端断开流连接发送eof符,代表客户已挂断电话
  4. is_resource()函数
    分配的电话机是坏的,accept()函数返回的错误的资源

总结:select系统可以处理多个连接,由于采用轮询的方式效率不高(较消耗性能偏cpu消耗);而且保持的连接数有限(1024个电话机,偏内存消耗),数据在用户空间和内核空间不断资源复制,比较消耗内存。

非阻塞IO复用

2019-4-5 升级单进程阻塞为单进程非阻塞_第1张图片

select系统机制

2019-4-5 升级单进程阻塞为单进程非阻塞_第2张图片

代码

服务端

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服务器

只是简单为了压测,如果要封装为一个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/

2019-4-5 升级单进程阻塞为单进程非阻塞_第3张图片

总结

外网压测,受网络影响,虽然效果不太理想,但是比一代比起来是一个飞跃。首先第二代服务器保持住了连接,无需close,等待客户端关闭连接后,再主动关闭连接,回收资源。

当并发大于1024时

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中

你可能感兴趣的:(swoole)