run方法代码如下:
function run() {
//将服务器的socket添加到初始化socket列表中
array_push($this->socketList, $this->serverSocket);
//工作流程开始
while (true) {
//read为所有存在的socket列表
$read = $this->socketList;
//如果shutdown变量设置为true,服务器关闭,退出循环
if ($this->shutdown) {
$this->onshutdown();
return;
}
if ($this->debug) {
echo "Waiting for socket_select\n";
}
//该函数会从所有可读写的socket中选取一个socket进行处理,该方法会阻塞流程,只有在收到连接时该方法才会返回
if (socket_select($read, $write, $except, NULL) === false) {
if ($this->debug) {
echo $this->getLastErrMsg();
}
continue;
}
foreach ($read as $socketItem) {
//如果选取的socket是服务器监听的socket,则此时是新连接接入
if ($socketItem === $this->serverSocket) {
//接受socket连接
$socket = $this->socketAccept();
if ($socket) {
//执行连接方法
$this->connect($socket);
}
} else {
//此时是连接过的socket,获取socketId
$socketId = $this->getSocketId($socketItem);
if ($socketId === FALSE) {
//获取socketId失败,则将该socket断开连接
$this->disconnectBySocket($socketItem);
continue;
}
//接收传来的数据
$data = $this->socketRecv($socketId);
if (strlen($data) > 0) {
//收到的数据长度不为空时,需要重置连接错误计数
$this->socketListMap[$socketId]['errorCnt'] = 0;
if (!isset($this->socketListMap[$socketId])) {
$this->disconnect($socketId);
continue;
} else if (!$this->socketListMap[$socketId]['handshake']) {
//尚未进行WebSocket协议握手,尝试读取连接缓冲区,如果缓冲区中没有数据,则将socketId记录到握手中列表
//这是为了防止握手包被分成多个包进行传递(正常情况下不会出现此问题)
//但根据HTTP协议,并未规定HTTP请求头不能被分割,故应该根据协议中的\r\n\r\n来判断请求头已发送完毕
if (strlen($this->socketListMap[$socketId]['buffer']) === 0) {
$this->handshakingList[$socketId] = time();
}
//将数据写入缓冲区
$this->socketListMap[$socketId]['buffer'] .= $data;
//比较后4个字节是否为\r\n\r\n
if (substr_compare($this->socketListMap[$socketId]['buffer'], str_repeat(chr(0x0D) . chr(0x0A), 2), -4) === 0) {
//进行握手处理
$this->doHandShake($socketId);
} else {
//数据没有传送完毕,需要缓冲数据直到全部接收请求头(这个可以通过Telnet命令直接连接,每输入一个字节都会立即传给服务器,这时服务器应该缓存内容。但同时也应该设置超时时间,防止恶意占用服务器资源。)
$this->onUpgradePartReceive($socketId);
}
} else if ($this->parseFrame($data, $socketId)) {
//parseFrame会解析数据帧,如果该帧FIN标识为1则函数会返回true,交给businessHandler进行业务逻辑处理,数据在socketListMap的buffer中,所以只需要提供socketId即可找到该socket的所有信息。
$this->businessHandler($socketId);
}
} else {
$this->socketListMap[$socketId]['errorCnt'] += 1;
if ($this->debug){
echo "Receive empty data![$errorCnt]\n";
}
if ($errorCnt >= 3) {
$this->disconnect($socketId);
}
}
}
}
//每次处理完连接后,判断是否需要健康检查,检查之后会移除不健康的socket
if (time() - $this->lastHealthCheck > $this->healthCheckInterval) {
$this->healthCheck();
}
$this->removeUnhandshakeConnect();
}
}