/** * Check the integrity of the package. * * @param string $recv_buffer * @param TcpConnection $connection * @return int */ public static function input($recv_buffer, TcpConnection $connection) { if (!strpos($recv_buffer, "\r\n\r\n")) { // Judge whether the package length exceeds the limit. if (strlen($recv_buffer) >= TcpConnection::$maxPackageSize) { $connection->close(); return 0; } return 0; } list($header, ) = explode("\r\n\r\n", $recv_buffer, 2); if (0 === strpos($recv_buffer, "POST")) { // find Content-Length $match = array(); if (preg_match("/\r\nContent-Length: ?(\\d+)/i", $header, $match)) { $content_length = $match[1]; return $content_length + strlen($header) + 4; } else { return 0; } } elseif (0 === strpos($recv_buffer, "GET")) { return strlen($header) + 4; } else { $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n", true); return 0; } }
/** * @param TcpConnection $connection * @param $data */ function onMessage($connection, $data) { //记录进入数据 MyLog::debug("[IN] [LEN] %s [USER_ID] %s [CMD] %x [body] %s ", $data['pack_len'], $data['user_id'], $data['cmd'], json_encode($data['body'])); //根据cmd进行路由 \Workerman\route\CrouteFunc::deal($data, $response); //记录发出数据 MyLog::debug("[OUT] [USER_ID] %s [CMD] 0x%08x [RET] %s [body] %s ", $response['user_id'], $response['cmd'], $response['return_code'], json_encode($response['body'])); //返回数据 $connection->send($response); }
/** * Emit when http message coming. * * @param Connection\TcpConnection $connection * @return void */ public function onMessage($connection) { // REQUEST_URI. $workerman_url_info = parse_url($_SERVER['REQUEST_URI']); if (!$workerman_url_info) { Http::header('HTTP/1.1 400 Bad Request'); $connection->close('<h1>400 Bad Request</h1>'); return; } $workerman_path = isset($workerman_url_info['path']) ? $workerman_url_info['path'] : '/'; $workerman_path_info = pathinfo($workerman_path); $workerman_file_extension = isset($workerman_path_info['extension']) ? $workerman_path_info['extension'] : ''; if ($workerman_file_extension === '') { $workerman_path = ($len = strlen($workerman_path)) && $workerman_path[$len - 1] === '/' ? $workerman_path . 'index.php' : $workerman_path . '/index.php'; $workerman_file_extension = 'php'; } $workerman_root_dir = isset($this->serverRoot[$_SERVER['SERVER_NAME']]) ? $this->serverRoot[$_SERVER['SERVER_NAME']] : current($this->serverRoot); $workerman_file = "{$workerman_root_dir}/{$workerman_path}"; if ($workerman_file_extension === 'php' && !is_file($workerman_file)) { $workerman_file = "{$workerman_root_dir}/index.php"; if (!is_file($workerman_file)) { $workerman_file = "{$workerman_root_dir}/index.html"; $workerman_file_extension = 'html'; } } // File exsits. if (is_file($workerman_file)) { // Security check. if (!($workerman_request_realpath = realpath($workerman_file)) || !($workerman_root_dir_realpath = realpath($workerman_root_dir)) || 0 !== strpos($workerman_request_realpath, $workerman_root_dir_realpath)) { Http::header('HTTP/1.1 400 Bad Request'); $connection->close('<h1>400 Bad Request</h1>'); return; } $workerman_file = realpath($workerman_file); // Request php file. if ($workerman_file_extension === 'php') { $workerman_cwd = getcwd(); chdir($workerman_root_dir); ini_set('display_errors', 'off'); ob_start(); // Try to include php file. try { // $_SERVER. $_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp(); $_SERVER['REMOTE_PORT'] = $connection->getRemotePort(); include $workerman_file; } catch (\Exception $e) { // Jump_exit? if ($e->getMessage() != 'jump_exit') { echo $e; } } $content = ob_get_clean(); ini_set('display_errors', 'on'); if (strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") { $connection->send($content); } else { $connection->close($content); } chdir($workerman_cwd); return; } // Send file to client. return self::sendFile($connection, $workerman_file); } else { // 404 Http::header("HTTP/1.1 404 Not Found"); $connection->close('<html><head><title>404 File not found</title></head><body><center><h3>404 Not Found</h3></center></body></html>'); return; } }
/** * Websocket handshake. * * @param string $buffer * @param \Workerman\Connection\TcpConnection $connection * @return int */ protected static function dealHandshake($buffer, $connection) { // HTTP protocol. 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 400 Bad Request\r\n\r\n<b>400 Bad Request</b><br>Sec-WebSocket-Key not found.<br>This is a WebSocket service and can not be accessed via HTTP.", 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"; $handshake_message .= "Upgrade: websocket\r\n"; $handshake_message .= "Sec-WebSocket-Version: 13\r\n"; $handshake_message .= "Connection: Upgrade\r\n"; $handshake_message .= "Server: workerman/" . Worker::VERSION . "\r\n"; $handshake_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n\r\n"; // Mark handshake complete.. $connection->websocketHandshake = true; // Websocket data buffer. $connection->websocketDataBuffer = ''; // Current websocket frame length. $connection->websocketCurrentFrameLength = 0; // Current websocket frame data. $connection->websocketCurrentFrameBuffer = ''; // Consume handshake data. $connection->consumeRecvBuffer($header_length); // Send handshake response. $connection->send($handshake_message, true); // There are data waiting to be sent. if (!empty($connection->tmpWebsocketData)) { $connection->send($connection->tmpWebsocketData, true); $connection->tmpWebsocketData = ''; } // blob or arraybuffer if (empty($connection->websocketType)) { $connection->websocketType = self::BINARY_TYPE_BLOB; } // Try to emit onWebSocketConnect callback. if (isset($connection->onWebSocketConnect)) { self::parseHttpHeader($buffer); try { call_user_func($connection->onWebSocketConnect, $connection, $buffer); } catch (\Exception $e) { Worker::log($e); exit(250); } catch (\Error $e) { Worker::log($e); exit(250); } if (!empty($_SESSION) && class_exists('\\GatewayWorker\\Lib\\Context')) { $connection->session = \GatewayWorker\Lib\Context::sessionEncode($_SESSION); } $_GET = $_SERVER = $_SESSION = $_COOKIE = array(); } if (strlen($buffer) > $header_length) { return self::input(substr($buffer, $header_length), $connection); } return 0; } elseif (0 === strpos($buffer, '<polic')) { $policy_xml = '<?xml version="1.0"?><cross-domain-policy><site-control permitted-cross-domain-policies="all"/><allow-access-from domain="*" to-ports="*"/></cross-domain-policy>' . ""; $connection->send($policy_xml, true); $connection->consumeRecvBuffer(strlen($buffer)); return 0; } // Bad websocket handshake request. $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n<b>400 Bad Request</b><br>Invalid handshake data for websocket. ", true); $connection->close(); return 0; }
/** * 当worker发来数据时 * @param TcpConnection $connection * @param mixed $data * @throws \Exception */ public function onWorkerMessage($connection, $data) { $cmd = $data['cmd']; switch ($cmd) { // 向某客户端发送数据,Gateway::sendToClient($client_id, $message); case GatewayProtocol::CMD_SEND_TO_ONE: if (isset($this->_clientConnections[$data['client_id']])) { $this->_clientConnections[$data['client_id']]->send($data['body']); } break; // 关闭客户端连接,Gateway::closeClient($client_id); // 关闭客户端连接,Gateway::closeClient($client_id); case GatewayProtocol::CMD_KICK: if (isset($this->_clientConnections[$data['client_id']])) { $this->_clientConnections[$data['client_id']]->destroy(); } break; // 广播, Gateway::sendToAll($message, $client_id_array) // 广播, Gateway::sendToAll($message, $client_id_array) case GatewayProtocol::CMD_SEND_TO_ALL: // $client_id_array不为空时,只广播给$client_id_array指定的客户端 if ($data['ext_data']) { $client_id_array = unpack('N*', $data['ext_data']); foreach ($client_id_array as $client_id) { if (isset($this->_clientConnections[$client_id])) { $this->_clientConnections[$client_id]->send($data['body']); } } } else { foreach ($this->_clientConnections as $client_connection) { $client_connection->send($data['body']); } } break; // 更新客户端session // 更新客户端session case GatewayProtocol::CMD_UPDATE_SESSION: if (isset($this->_clientConnections[$data['client_id']])) { $this->_clientConnections[$data['client_id']]->session = $data['ext_data']; } break; // 获得客户端在线状态 Gateway::getOnlineStatus() // 获得客户端在线状态 Gateway::getOnlineStatus() case GatewayProtocol::CMD_GET_ONLINE_STATUS: $online_status = json_encode(array_keys($this->_clientConnections)); $connection->send($online_status); break; // 判断某个client_id是否在线 Gateway::isOnline($client_id) // 判断某个client_id是否在线 Gateway::isOnline($client_id) case GatewayProtocol::CMD_IS_ONLINE: $connection->send((int) isset($this->_clientConnections[$data['client_id']])); break; // 将client_id与uid绑定 // 将client_id与uid绑定 case GatewayProtocol::CMD_BIND_UID: $uid = $data['ext_data']; if (empty($uid)) { echo "uid empty" . var_export($uid, true); return; } $client_id = $data['client_id']; if (!isset($this->_clientConnections[$client_id])) { return; } $client_connection = $this->_clientConnections[$client_id]; if (!isset($client_connection->uids)) { $client_connection->uids = array(); } $client_connection->uids[$uid] = $uid; $this->_uidConnections[$uid][$client_id] = $client_connection; break; // 发送数据给uid // 发送数据给uid case GatewayProtocol::CMD_SEND_TO_UID: $uid_array = json_decode($data['ext_data'], true); foreach ($uid_array as $uid) { if (!empty($this->_uidConnections[$uid])) { foreach ($this->_uidConnections[$uid] as $connection) { $connection->send($data['body']); } } } break; default: $err_msg = "gateway inner pack err cmd={$cmd}"; throw new \Exception($err_msg); } }
/** * This method pulls all the data out of a readable stream, and writes it to the supplied destination. * * @param TcpConnection $dest * @return void */ public function pipe($dest) { $source = $this; $this->onMessage = function ($source, $data) use($dest) { $dest->send($data); }; $this->onClose = function ($source) use($dest) { $dest->destroy(); }; $dest->onBufferFull = function ($dest) use($source) { $source->pauseRecv(); }; $dest->onBufferDrain = function ($dest) use($source) { $source->resumeRecv(); }; }
/** * 当worker发来数据时 * @param TcpConnection $connection * @param mixed $data * @throws \Exception */ public function onWorkerMessage($connection, $data) { $cmd = $data['cmd']; switch ($cmd) { case GatewayProtocol::CMD_WORKER_CONNECT: $connection->remoteAddress = $connection->getRemoteIp() . ':' . $connection->getRemotePort(); $this->_workerConnections[$connection->remoteAddress] = $connection; return; // 向某客户端发送数据,Gateway::sendToClient($client_id, $message); // 向某客户端发送数据,Gateway::sendToClient($client_id, $message); case GatewayProtocol::CMD_SEND_TO_ONE: if (isset($this->_clientConnections[$data['connection_id']])) { $this->_clientConnections[$data['connection_id']]->send($data['body']); } return; // 关闭客户端连接,Gateway::closeClient($client_id); // 关闭客户端连接,Gateway::closeClient($client_id); case GatewayProtocol::CMD_KICK: if (isset($this->_clientConnections[$data['connection_id']])) { $this->_clientConnections[$data['connection_id']]->destroy(); } return; // 广播, Gateway::sendToAll($message, $client_id_array) // 广播, Gateway::sendToAll($message, $client_id_array) case GatewayProtocol::CMD_SEND_TO_ALL: // $client_id_array不为空时,只广播给$client_id_array指定的客户端 if ($data['ext_data']) { $connection_id_array = unpack('N*', $data['ext_data']); foreach ($connection_id_array as $connection_id) { if (isset($this->_clientConnections[$connection_id])) { $this->_clientConnections[$connection_id]->send($data['body']); } } } else { foreach ($this->_clientConnections as $client_connection) { $client_connection->send($data['body']); } } return; // 更新客户端session // 更新客户端session case GatewayProtocol::CMD_UPDATE_SESSION: if (isset($this->_clientConnections[$data['connection_id']])) { $this->_clientConnections[$data['connection_id']]->session = $data['ext_data']; } return; // 获得客户端在线状态 Gateway::getALLClientInfo() // 获得客户端在线状态 Gateway::getALLClientInfo() case GatewayProtocol::CMD_GET_ALL_CLIENT_INFO: $client_info_array = array(); foreach ($this->_clientConnections as $connection_id => $client_connection) { $client_info_array[$connection_id] = $client_connection->session; } $connection->send(json_encode($client_info_array) . "\n", true); return; // 判断某个client_id是否在线 Gateway::isOnline($client_id) // 判断某个client_id是否在线 Gateway::isOnline($client_id) case GatewayProtocol::CMD_IS_ONLINE: $connection->send((int) isset($this->_clientConnections[$data['connection_id']]) . "\n", true); return; // 将client_id与uid绑定 // 将client_id与uid绑定 case GatewayProtocol::CMD_BIND_UID: $uid = $data['ext_data']; if (empty($uid)) { echo "uid empty" . var_export($uid, true); return; } $connection_id = $data['connection_id']; if (!isset($this->_clientConnections[$connection_id])) { return; } $client_connection = $this->_clientConnections[$connection_id]; if (isset($client_connection->uid)) { $current_uid = $client_connection->uid; unset($this->_uidConnections[$current_uid][$connection_id]); if (empty($this->_uidConnections[$current_uid])) { unset($this->_uidConnections[$current_uid]); } } $client_connection->uid = $uid; $this->_uidConnections[$uid][$connection_id] = $client_connection; return; // client_id与uid解绑 Gateway::unbindUid($client_id, $uid); // client_id与uid解绑 Gateway::unbindUid($client_id, $uid); case GatewayProtocol::CMD_UNBIND_UID: $connection_id = $data['connection_id']; if (!isset($this->_clientConnections[$connection_id])) { return; } $client_connection = $this->_clientConnections[$connection_id]; if (isset($client_connection->uid)) { $current_uid = $client_connection->uid; unset($this->_uidConnections[$current_uid][$connection_id]); if (empty($this->_uidConnections[$current_uid])) { unset($this->_uidConnections[$current_uid]); } $client_connection->uid_info = ''; $client_connection->uid = null; } return; // 发送数据给uid Gateway::sendToUid($uid, $msg); // 发送数据给uid Gateway::sendToUid($uid, $msg); case GatewayProtocol::CMD_SEND_TO_UID: $uid_array = json_decode($data['ext_data'], true); foreach ($uid_array as $uid) { if (!empty($this->_uidConnections[$uid])) { foreach ($this->_uidConnections[$uid] as $connection) { $connection->send($data['body']); } } } return; // 将$client_id加入用户组 Gateway::joinGroup($client_id, $group); // 将$client_id加入用户组 Gateway::joinGroup($client_id, $group); case GatewayProtocol::CMD_JOIN_GROUP: $group = $data['ext_data']; if (empty($group)) { echo "group empty" . var_export($group, true); return; } $connection_id = $data['connection_id']; if (!isset($this->_clientConnections[$connection_id])) { return; } $client_connection = $this->_clientConnections[$connection_id]; if (!isset($client_connection->groups)) { $client_connection->groups = array(); } $client_connection->groups[$group] = $group; $this->_groupConnections[$group][$connection_id] = $client_connection; return; // 将$client_id从某个用户组中移除 Gateway::leaveGroup($client_id, $group); // 将$client_id从某个用户组中移除 Gateway::leaveGroup($client_id, $group); case GatewayProtocol::CMD_LEAVE_GROUP: $group = $data['ext_data']; if (empty($group)) { echo "leave group empty" . var_export($group, true); return; } $connection_id = $data['connection_id']; if (!isset($this->_clientConnections[$connection_id])) { return; } $client_connection = $this->_clientConnections[$connection_id]; if (!isset($client_connection->groups[$group])) { return; } unset($client_connection->groups[$group], $this->_groupConnections[$group][$connection_id]); return; // 向某个用户组发送消息 Gateway::sendToGroup($group, $msg); // 向某个用户组发送消息 Gateway::sendToGroup($group, $msg); case GatewayProtocol::CMD_SEND_TO_GROUP: $group_array = json_decode($data['ext_data'], true); foreach ($group_array as $group) { if (!empty($this->_groupConnections[$group])) { foreach ($this->_groupConnections[$group] as $connection) { $connection->send($data['body']); } } } return; // 获取某用户组成员信息 Gateway::getClientInfoByGroup($group); // 获取某用户组成员信息 Gateway::getClientInfoByGroup($group); case GatewayProtocol::CMD_GET_CLINET_INFO_BY_GROUP: $group = $data['ext_data']; if (!isset($this->_groupConnections[$group])) { $connection->send("[]\n", true); return; } $client_info_array = array(); foreach ($this->_groupConnections[$group] as $connection_id => $client_connection) { $client_info_array[$connection_id] = $client_connection->session; } $connection->send(json_encode($client_info_array) . "\n", true); return; // 获取用户组成员数 Gateway::getClientCountByGroup($group); // 获取用户组成员数 Gateway::getClientCountByGroup($group); case GatewayProtocol::CMD_GET_CLIENT_COUNT_BY_GROUP: $group = $data['ext_data']; if (!isset($this->_groupConnections[$group])) { $connection->send("0\n", true); return; } $connection->send(count($this->_groupConnections[$group]) . "\n", true); return; // 获取与某个uid绑定的所有client_id Gateway::getClientIdByUid($uid); // 获取与某个uid绑定的所有client_id Gateway::getClientIdByUid($uid); case GatewayProtocol::CMD_GET_CLIENT_ID_BY_UID: $uid = $data['ext_data']; if (empty($this->_uidConnections[$uid])) { $connection->send("[]\n", true); return; } $connection->send(json_encode(array_keys($this->_uidConnections[$uid])) . "\n", true); return; default: $err_msg = "gateway inner pack err cmd={$cmd}"; throw new \Exception($err_msg); } }
/** * Send websocket handshake. * * @param \Workerman\Connection\TcpConnection $connection * @return void */ public static function sendHandshake($connection) { if (!empty($connection->handshakeStep)) { return; } // Get Host. $port = $connection->getRemotePort(); $host = $port === 80 ? $connection->getRemoteHost() : $connection->getRemoteHost() . ':' . $port; // Handshake header. $header = 'GET ' . $connection->getRemoteURI() . " HTTP/1.1\r\n" . "Host: {$host}\r\n" . "Connection: Upgrade\r\n" . "Upgrade: websocket\r\n" . "Origin: " . (isset($connection->websocketOrigin) ? $connection->websocketOrigin : '*') . "\r\n" . "Sec-WebSocket-Version: 13\r\n" . "Sec-WebSocket-Key: " . base64_encode(sha1(uniqid(mt_rand(), true), true)) . "\r\n\r\n"; $connection->send($header, true); $connection->handshakeStep = 1; $connection->websocketCurrentFrameLength = 0; $connection->websocketDataBuffer = ''; $connection->tmpWebsocketData = ''; }