/** * 有消息时触发该方法 * @param int $client_id 发消息的client_id * @param string $message 消息 * @return void */ public static function onMessage($client_id, $message) { $message = substr($message, 4); //解析传输的数据 $cmd = new PbCmd(); try { $cmd->parseFromString($message); } catch (Exception $ex) { return $ex->getMessage(); } //接收参数 $obj = $cmd->getObj(); $method = $cmd->getMethod(); $params = $cmd->getParams(); //统计数据 $class = substr($obj, 8); StatisticClient::tick($class, $method); try { //处理业务逻辑 $instance = new $obj(); $buffer = $instance->{$method}($params); StatisticClient::report($class, $method, 1, 0, 'successful'); } catch (Exception $ex) { $pbResult = new PbResult(); $pbResult->setCode($ex->getCode()); $pbResult->setMsg($ex->getMessage()); $buffer = $pbResult->SerializeToString(); $total_length = 4 + strlen($buffer); $buffer = pack('N', $total_length) . $buffer; StatisticClient::report($class, $method, 0, $ex->getCode(), $ex->getMessage()); } echo 'Login_Woker' . PHP_EOL; return Gateway::sendToCurrentClient($buffer); }
/** * 有消息时触发该方法 * @param int $client_id 发消息的client_id * @param string $message 消息 * @return void */ public static function onMessage($client_id, $message) { $message_data = TextProtocol::decode($message); // **************如果没有$_SESSION['name']说明没有设置过用户名,进入设置用户名逻辑************ if (empty($_SESSION['name'])) { $_SESSION['name'] = TextProtocol::decode($message); Gateway::sendToCurrentClient("chat room login success, your client_id is {$client_id}, name is {$_SESSION['name']}\r\nuse client_id:words send message to one user\r\nuse words send message to all\r\n"); // 广播所有用户,xxx come return GateWay::sendToAll(TextProtocol::encode("{$_SESSION['name']}[{$client_id}] come")); } // ********* 进入聊天逻辑 **************** // 判断是否是私聊 $explode_array = explode(':', $message, 2); // 私聊数据格式 client_id:xxxxx if (count($explode_array) > 1) { $to_client_id = (int) $explode_array[0]; GateWay::sendToClient($client_id, TextProtocol::encode($_SESSION['name'] . "[{$client_id}] said said to [{$to_client_id}] :" . $explode_array[1])); return GateWay::sendToClient($to_client_id, TextProtocol::encode($_SESSION['name'] . "[{$client_id}] said to You :" . $explode_array[1])); } // 群聊 return GateWay::sendToAll(TextProtocol::encode($_SESSION['name'] . "[{$client_id}] said :" . $message)); }
/** * 此链接的用户没调用GateWay::notifyConnectionSuccess($uid);前(即没有得到验证),都触发onConnect * 已经调用GateWay::notifyConnectionSuccess($uid);的用户有消息时,则触发onMessage * @param string $message 一般是传递的账号密码等信息 * @return void */ public static function onConnect($message) { /* * 通过message验证用户,并获得uid。 * 一般流程这里$message应该包含用户名 密码,然后根据用户名密码从数据库中获取uid * 这里只是根据时间戳生成uid,高并发下会有小概率uid冲突 */ $uid = self::checkUser($message); // 不合法踢掉 if (!$uid) { // 踢掉 return GateWay::kickCurrentUser(TextProtocol::encode('uid非法')); } $_SESSION['name'] = TextProtocol::decode($message); // [这步是必须的]合法,记录uid到gateway通信地址的映射 GateWay::storeUid($uid); // [这步是必须的]发送数据包到address对应的gateway,确认connection成功 GateWay::notifyConnectionSuccess($uid); Gateway::sendToCurrentUid("\nchart room login success, your uid is {$uid}, name is {$_SESSION['name']}\nuse uid:words send message to one user\nuse words send message to all\n"); // 广播所有用户,xxx come GateWay::sendToAll(TextProtocol::encode("{$_SESSION['name']}[{$uid}] come")); }
/** * 有消息时 * @param int $uid * @param string $message */ public static function onMessage($uid, $message) { // $message len < 7 可能是ping包,断开连接的包等暂时忽略 if (strlen($message) < 7) { return; } $message = WebSocket::decode($message); // 广播路线 Gateway::sendToAll(WebSocket::encode(pack('CVVVC', 2, 1, 1, $uid, 0) . substr($message, 1))); }
/** * 处理请求 * @see Man\Core.SocketWorker::dealProcess() */ public function dealProcess($recv_buffer) { $pack = new GatewayProtocol($recv_buffer); Context::$client_ip = $pack->header['client_ip']; Context::$client_port = $pack->header['client_port']; Context::$local_ip = $pack->header['local_ip']; Context::$local_port = $pack->header['local_port']; Context::$socket_id = $pack->header['socket_id']; Context::$client_id = $pack->header['client_id']; $_SERVER = array('REMOTE_ADDR' => Context::$client_ip, 'REMOTE_PORT' => Context::$client_port, 'GATEWAY_ADDR' => Context::$local_ip, 'GATEWAY_PORT' => Context::$local_port, 'GATEWAY_CLIENT_ID' => Context::$client_id); if ($pack->ext_data != '') { $_SESSION = Context::sessionDecode($pack->ext_data); } else { $_SESSION = null; } // 备份一次$pack->ext_data,请求处理完毕后判断session是否和备份相等,不相等就更新session $session_str_copy = $pack->ext_data; $cmd = $pack->header['cmd']; $interface = isset(self::$interfaceMap[$cmd]) ? self::$interfaceMap[$cmd] : $cmd; StatisticClient::tick(__CLASS__, $interface); try { switch ($cmd) { case GatewayProtocol::CMD_ON_GATEWAY_CONNECTION: Login::onGatewayConnect(Context::$client_id); break; case GatewayProtocol::CMD_ON_MESSAGE: Login::onMessage(Context::$client_id, $pack->body); break; case GatewayProtocol::CMD_ON_CLOSE: Login::onClose(Context::$client_id); break; } StatisticClient::report(__CLASS__, $interface, 1, 0, ''); } catch (\Exception $e) { $msg = 'client_id:' . Context::$client_id . "\tclient_ip:" . Context::$client_ip . "\n" . $e->__toString(); StatisticClient::report(__CLASS__, $interface, 0, $e->getCode() > 0 ? $e->getCode() : 201, $msg); } $session_str_now = $_SESSION !== null ? Context::sessionEncode($_SESSION) : ''; if ($session_str_copy != $session_str_now) { Gateway::updateSocketSession(Context::$socket_id, $session_str_now); } Context::clear(); }
/** * websocket协议握手 * @param string $message */ public static function checkHandshake($message) { // WebSocket 握手阶段 if (0 === strpos($message, 'GET')) { // 解析Sec-WebSocket-Key $Sec_WebSocket_Key = ''; if (preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/", $message, $match)) { $Sec_WebSocket_Key = $match[1]; } $new_key = base64_encode(sha1($Sec_WebSocket_Key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true)); // 握手返回的数据 $new_message = "HTTP/1.1 101 Switching Protocols\r\n"; $new_message .= "Upgrade: websocket\r\n"; $new_message .= "Sec-WebSocket-Version: 13\r\n"; $new_message .= "Connection: Upgrade\r\n"; $new_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n\r\n"; // 发送数据包到客户端 完成握手 Gateway::sendToCurrentClient($new_message); return true; } elseif (trim($message) === '<policy-file-request/>') { $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>' . ""; Gateway::sendToCurrentClient($policy_xml); return true; } return false; }
/** * 有消息时 * @param int $uid * @param string $message */ public static function onMessage($uid, $message) { if (WebSocket::isClosePacket($message)) { Gateway::kickUid($uid, ''); self::onClose($uid); return; } $message = WebSocket::decode($message); // debug echo "client:{$_SERVER['REMOTE_ADDR']}:{$_SERVER['REMOTE_PORT']} gateway:{$_SERVER['GATEWAY_ADDR']}:{$_SERVER['GATEWAY_PORT']} socketid:{$_SERVER['GATEWAY_SOCKET_ID']} uid:{$uid} onMessage:" . $message . "\n"; $message_data = json_decode($message, true); if (!$message_data) { return; } switch ($message_data['type']) { // 用户登录 message格式: {type:login, name:xx} ,添加到用户,广播给所有用户xx进入聊天室 case 'login': // 存储当前用户到用户列表 self::addUserToList($uid, htmlspecialchars($message_data['name'])); // 获取用户列表 $user_list = self::getUserList(); // 整理用户列表以便显示 $all_users = array(); if ($user_list) { foreach ($user_list as $tmp_uid => $name) { $all_users[] = array('uid' => $tmp_uid, 'name' => $name); } } // 发送给当前用户 内容是用户列表 message: {type:user_list, user_list:xxxx} Gateway::sendToUid($uid, WebSocket::encode(json_encode(array('type' => 'user_list', 'user_list' => $all_users)))); // 转播给所有用户,xx进入聊天室 message {type:login, uid:xx, name:xx} Gateway::sendToAll(WebSocket::encode(json_encode(array('type' => 'login', 'uid' => $uid, 'name' => htmlspecialchars($message_data['name']), 'time' => date('Y-m-d H:i:s'))))); return; // 用户发言 message: {type:say, to_uid:xx, content:xx} // 用户发言 message: {type:say, to_uid:xx, content:xx} case 'say': // 私聊 if ($message_data['to_uid'] != 'all') { $new_message = array('type' => 'say', 'from_uid' => $uid, 'to_uid' => $message_data['to_uid'], 'content' => nl2br(htmlspecialchars($message_data['content'])), 'time' => date('Y-m-d :i:s')); echo 'here'; return Gateway::sendToUid($message_data['to_uid'], WebSocket::encode(json_encode($new_message))); } // 向大家说 $new_message = array('type' => 'say', 'from_uid' => $uid, 'to_uid' => 'all', 'content' => nl2br(htmlspecialchars($message_data['content'])), 'time' => date('Y-m-d :i:s')); return Gateway::sendToAll(WebSocket::encode(json_encode($new_message))); } }
/** * 处理请求 * @see Man\Core.SocketWorker::dealProcess() */ public function dealProcess($recv_str) { $pack = new GatewayProtocol($recv_str); Context::$client_ip = $pack->header['client_ip']; Context::$client_port = $pack->header['client_port']; Context::$local_ip = $pack->header['local_ip']; Context::$local_port = $pack->header['local_port']; Context::$socket_id = $pack->header['socket_id']; Context::$uid = $pack->header['uid']; $_SERVER = array('REMOTE_ADDR' => Context::$client_ip, 'REMOTE_PORT' => Context::$client_port, 'GATEWAY_ADDR' => Context::$local_ip, 'GATEWAY_PORT' => Context::$local_port, 'GATEWAY_SOCKET_ID' => Context::$socket_id); if ($pack->ext_data != '') { $_SESSION = Context::sessionDecode($pack->ext_data); } else { $_SESSION = null; } // 备份一次$pack->ext_data,请求处理完毕后判断session是否和备份相等 $session_str_copy = $pack->ext_data; $cmd = $pack->header['cmd']; StatisticClient::tick(); $module = __CLASS__; $interface = isset(self::$interfaceMap[$cmd]) ? self::$interfaceMap[$cmd] : 'null'; $success = 1; $code = 0; $msg = ''; try { switch ($cmd) { case GatewayProtocol::CMD_ON_GATEWAY_CONNECTION: call_user_func_array(array('Event', 'onGatewayConnect'), array()); break; case GatewayProtocol::CMD_ON_CONNECTION: call_user_func_array(array('Event', 'onConnect'), array($pack->body)); break; case GatewayProtocol::CMD_ON_MESSAGE: call_user_func_array(array('Event', 'onMessage'), array(Context::$uid, $pack->body)); break; case GatewayProtocol::CMD_ON_CLOSE: call_user_func_array(array('Event', 'onClose'), array(Context::$uid)); break; } } catch (\Exception $e) { $success = 0; $code = $e->getCode() > 0 ? $e->getCode() : 500; $msg = 'uid:' . Context::$uid . "\tclient_ip:" . Context::$client_ip . "\n" . $e->__toString(); } $session_str_now = $_SESSION !== null ? Context::sessionEncode($_SESSION) : ''; if ($session_str_copy != $session_str_now) { Gateway::updateSocketSession(Context::$socket_id, $session_str_now); } Context::clear(); StatisticClient::report($module, $interface, $success, $code, $msg); }