$buffer = @\fread($socket, self::READ_BUFFER_SIZE); // 65535
每次读取最多65535字节,放到用户程序的缓冲区(这里指的是php tcpConnection对象的_recvBuffer 属性)
每读一次就尝试解析 用户程序的缓冲区(_recvBuffer) ,使用$parser::input 方法去解析,如果返回一个> 0 数字证明,_recvBuffer 有足够多的 够一个 完整frame 的数据了(怎么叫完整呢,对HTTP来说就是完整的页面/ 完整post请求,不管这个页面/ post请求多大,对websocket来说如果数据比较大那可能就是 由多个FIN =0 和最后一个FIN=1组成 ),接下来就可以 调用 decode去解析frame了,对于websocket 来说 解掩码处理就是其中一项工作。
public static function input($recv_buffer, ConnectionInterface $connection);
return 0 : 意味着需要更多数据
false : 出现错误了
> 0 : 意味着够一个包的数据了, 数值 的大小: 如果是 HTTP 就是 HTTP请求头+HTTP请求体的大小,如果是websocket, 并且这个数据包很大(由 几个 FIN = 0 及最后FIN=1) 组成,那么这个数值指的是最后一个FIN=1 frame的大小。
如果是 非数据类的 frame ,则直接处理了 ,如(ping, pong close) frame (这些属于控制类frame)。
返回 >0 ,代表当前是数据 frame 。
//头部解析 FIN, opcode, mask bit, masking-key
$firstbyte = \ord($buffer[0]);
$secondbyte = \ord($buffer[1]);
$data_len = $secondbyte & 127;
$is_fin_frame = $firstbyte >> 7;
$masked = $secondbyte >> 7;
......
......
$opcode = $firstbyte & 0xf;
//计算frame的长度
$data_len = $secondbyte & 127;
......
// Calculate packet length.
$head_len = 6;
if ($data_len === 126) {
$head_len = 8;
if ($head_len > $recv_len) {
return 0;
}
$pack = \unpack('nn/ntotal_len', $buffer);
$data_len = $pack['total_len'];
} else {
if ($data_len === 127) {
$head_len = 14;
if ($head_len > $recv_len) {
return 0;
}
$arr = \unpack('n/N2c', $buffer);
$data_len = $arr['c1']*4294967296 + $arr['c2'];
}
}
//4个字节的 mask-key 的解析
$len = \ord($buffer[1]) & 127;
if ($len === 126) {
$masks = \substr($buffer, 4, 4);
} else {
if ($len === 127) {
$masks = \substr($buffer, 10, 4);
} else {
$masks = \substr($buffer, 2, 4);
}
}
它的中心思想:
返回 0 代表需要更多数据; 或 , 还不足够是 一个包
返回 false 代表发了错误
返回 代表 得到了包的长度 (对websocket来说 分两种情况, 只有一个FIN==1 自成一个包, 由 FIN==0, FIN==0… FIN==1 组成),这里的长度都是 指的最后一个 FIN包的长度。
如果FIN == 1 是 ping ,pong 包 处理了,如果$recv_len > $current_frame_length, 则递归处理下一个frame(即调用input)
如果FIN == 1 && 是数据包, 则返回 包长度 $current_frame_length
如果FIN == 0 && 是数据包 , 则:
$connection->websocketCurrentFrameLength = $current_frame_length;
收到的数据 ($recv_len)正好是 一个 frame 的长度: 则 decode 解包处理了,把$connection->websocketCurrentFrameLength 为0 并且 return 0
收到的数据 ($recv_len)大于 一个frame的长度, 则 decode 解包处理了,把$connection->websocketCurrentFrameLength 为0, 递归地处理下一个 frame (即调用 input )
收到的数据 ($recv_len)小于 一个 frame 的长度, return 0
在baseRead中,tcp希望得到一个完整的解析好的数据,后才回调 tcp的 onMessage 回调。
“完整”含义:像 websocket 有可能是 FIN==0 ,FIN==0, FIN ==1 3个组成的,所有的数据frame都到了,并被decode了,才算完整。
对于,webSocket来说就是把客户端发来的数据 解掩码处理了。
对于,http来说就是把客户端发送来的请求,解析为 request对象,一些数据解析道全局变量中。
$dataLength = \strlen($data);
$masks = \str_repeat($masks, \floor($dataLength / 4)) . \substr($masks, 0, $dataLength % 4);
$decoded = $data ^ $masks;
/**
* Websocket decode.
*
* @param string $buffer
* @param ConnectionInterface $connection
* @return string
*/
public static function decode($buffer, ConnectionInterface $connection)
{
$len = \ord($buffer[1]) & 127;
if ($len === 126) {
$masks = \substr($buffer, 4, 4);
$data = \substr($buffer, 8);
} else {
if ($len === 127) {
$masks = \substr($buffer, 10, 4);
$data = \substr($buffer, 14);
} else {
$masks = \substr($buffer, 2, 4);
$data = \substr($buffer, 6);
}
}
$dataLength = \strlen($data);
$masks = \str_repeat($masks, \floor($dataLength / 4)) . \substr($masks, 0, $dataLength % 4);
$decoded = $data ^ $masks;
if ($connection->websocketCurrentFrameLength) { // 当前是 FIN == 0 , 后边一定有FIN ==1 的frame
$connection->websocketDataBuffer .= $decoded;
return $connection->websocketDataBuffer;
} else {
if ($connection->websocketDataBuffer !== '') { // FIN ==1 这个frame 前面有 几个 FIN == 0
$decoded = $connection->websocketDataBuffer . $decoded;
$connection->websocketDataBuffer = '';
}
return $decoded; // FIN ==1 这是一个完整的包
// 注意后边两种情况 返回的数据才有意义,才需要去接收,处理。
// "return $connection->websocketDataBuffer" 这个地方的返回是没有意义的,实际上也没有被接收
}
}
解掩码后的数据 ,放到 tcpConnection对象的 websocketDataBuffer 属性上。这是个动态属性 后加上的。
7. 封包 encode
如果没有设置$masked , 就关闭连接
除了 0x0, 0x1, 0x2, 0x8, 0x9, 0xa; 其他都认为是错误的 opcode ,就关闭连接$connection->close();
注意websocket的握手请求包,和websocket的握手响应包 都是HTTP协议格式
/**
* Websocket handshake.
*
* @param string $buffer
* @param TcpConnection $connection
* @return int
*/
public static function dealHandshake($buffer, TcpConnection $connection)
{
if (0 === \strpos($buffer, 'GET')) {
// Find \r\n\r\n.
$heder_end_pos = \strpos($buffer, "\r\n\r\n");
if (!$heder_end_pos) {
return 0;
}
$header_length = $heder_end_pos + 4;
// Get Sec-WebSocket-Key.
$Sec_WebSocket_Key = '';
if (\preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/i", $buffer, $match)) {
$Sec_WebSocket_Key = $match[1];
} else {
$connection->send("HTTP/1.1 200 Websocket\r\nServer: workerman/".Worker::VERSION."\r\n\r\n<div style=\"text-align:center\"><h1>Websocket</h1><hr>powered by <a href=\"https://www.workerman.net\">workerman ".Worker::VERSION."</a></div>",
true);
$connection->close();
return 0;
}
// Calculation websocket key.
$new_key = \base64_encode(\sha1($Sec_WebSocket_Key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true));
// Handshake response data.
$handshake_message = "HTTP/1.1 101 Switching Protocols\r\n"
."Upgrade: websocket\r\n"
."Sec-WebSocket-Version: 13\r\n"
."Connection: Upgrade\r\n"
."Sec-WebSocket-Accept: " . $new_key . "\r\n";
10. 发送数据
把数据放到 tcp的 对应socket的 发送缓冲区中,
至于怎么放,使用socket_write 还是 epoll ?缓冲区是否满了怎么办? 成功写入一部分怎么办? 这些属于 tcp 的问题范畴,不在这里讨论。
20. 用到的php函数
ord
unpack
pcntl_signal_dispatch