<?php 
 | 
include_once "/Common/Logging.php"; 
 | 
include_once '/db/RedisOper.php'; 
 | 
  
 | 
\Logging\CreateLogging("chatmonitorserver.php"); 
 | 
  
 | 
$interfaceConfig = parse_ini_file("/InterfaceConfig.php", true); 
 | 
$GameName = $interfaceConfig["ServerInfo"]["GameName"]; 
 | 
$ChannelList = explode(",", $interfaceConfig["ServerInfo"]["ChannelList"]); 
 | 
$address = $interfaceConfig["Chatmonitor"]["SocketHost"]; 
 | 
$port = $interfaceConfig["Chatmonitor"]["SocketPort"]; 
 | 
  
 | 
$logDate = date("Y-m-d"); 
 | 
LogConsole(""); 
 | 
LogConsole("GameName:" . $GameName); 
 | 
LogConsole("ChannelList:" . json_encode($ChannelList)); 
 | 
  
 | 
if (($master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) == false) { 
 | 
    LogConsole("socket_create() failed: reason: " . socket_strerror(socket_last_error()), true); 
 | 
} 
 | 
if (($ret = socket_bind($master, $address, $port)) == false) { 
 | 
    LogConsole("socket_bind() failed: reason: " . socket_strerror(socket_last_error($master)), true); 
 | 
} 
 | 
if (($ret = socket_listen($master, 3)) == false) { 
 | 
    LogConsole("socket_listen() failed: reason: " . socket_strerror(socket_last_error($master)), true); 
 | 
} 
 | 
  
 | 
LogConsole("chatmonitor server start ok. " . $address . ":" . $port); 
 | 
  
 | 
// $master = null;  //socket的resource,即前期初始化socket时返回的socket资源 
 | 
$all_sockets = [$master];    // socket 集合 
 | 
$channelInfo = array();  // 渠道对应socket信息 
 | 
foreach ($ChannelList as $spid) { 
 | 
    $channelInfo[$spid] = array( 
 | 
        "clients" => array(), 
 | 
        "broadcastTime" => 0 
 | 
    ); 
 | 
} 
 | 
  
 | 
do { 
 | 
    $copy_sockets = $all_sockets;   // 单独拷贝一份 
 | 
  
 | 
    // 因为客户端是长连接,如果客户端非正常断开,服务端会在 socket_accept 阻塞,现在使用 select 非阻塞模式 socket 
 | 
    $num_changed_sockets = socket_select($copy_sockets, $write, $except, 0); 
 | 
    if ($num_changed_sockets === false) { 
 | 
        /* 错误处理 */ 
 | 
        LogConsole("sosket_select error: " . socket_strerror(socket_last_error()), true); 
 | 
        continue; 
 | 
    } else if ($num_changed_sockets > 0) { 
 | 
        /*至少有一个套接字发生改变 */ 
 | 
    } 
 | 
    // echo "all_sockets count:", count($all_sockets), " num_changed_sockets:", $num_changed_sockets, PHP_EOL; 
 | 
  
 | 
    // 接收第一次 socket 连入,连入后移除服务端 socket 
 | 
    if (in_array($master, $copy_sockets)) { 
 | 
        $client = socket_accept($master); 
 | 
        if ($client) { 
 | 
            $buf = socket_read($client, 1024); 
 | 
            // echo $buf; 
 | 
  
 | 
            // 匹配 Sec-Websocket-Key 标识 
 | 
            if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/i", $buf, $match)) { 
 | 
                // 需要将 Sec-WebSocket-Key 值累加字符串,并依次进行 SHA-1 加密和 base64 加密 
 | 
                $key = base64_encode(sha1($match[1] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true)); 
 | 
                // 拼凑响应内容 
 | 
                $res = "HTTP/1.1 101 Switching Protocol" . PHP_EOL 
 | 
                    . "Upgrade: WebSocket" . PHP_EOL 
 | 
                    . "Connection: Upgrade" . PHP_EOL 
 | 
                    . "WebSocket-Location: ws://" . $address . ":" . $port . PHP_EOL 
 | 
                    . "Sec-WebSocket-Accept: " . $key . PHP_EOL . PHP_EOL;  // 注意这里,需要两个换行 
 | 
                // 向客户端应答 Sec-WebSocket-Accept 
 | 
                socket_write($client, $res, strlen($res)); 
 | 
  
 | 
                $key = uniqid(rand(1000, 9999), true); 
 | 
  
 | 
                // 加入客户端 socket 
 | 
                $all_sockets[$key] = $client; 
 | 
                LogConsole("new client key:" . $key . " now count:" . (count($all_sockets) - 1)); 
 | 
            } 
 | 
            // 移除服务端 socket 
 | 
            $key = array_search($master, $copy_sockets); 
 | 
            unset($copy_sockets[$key]); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    // 循环所有客户端 sockets 
 | 
    foreach ($copy_sockets as $key => $s) { 
 | 
        // 获取客户端发给服务端的内容 
 | 
        $buf = socket_read($s, 8024); 
 | 
        // echo strlen($buf), '---', PHP_EOL; 
 | 
        // 代表客户端主动关闭 
 | 
        if (strlen($buf) < 9) { 
 | 
            $key = array_search($s, $all_sockets); 
 | 
            unset($all_sockets[$key]); 
 | 
            LogConsole("client close key:" . $key . " remain client count:" . (count($all_sockets) - 1)); 
 | 
            foreach ($channelInfo as $spid => $info) { 
 | 
                $clients = $info["clients"]; 
 | 
                if (array_key_exists($key, $clients)) { 
 | 
                    unset($clients[$key]); 
 | 
                    $channelInfo[$spid]["clients"] = $clients; 
 | 
                    LogConsole("    remove from spid:" . $spid . " key:" . $key . " spid clients count:" . count($channelInfo[$spid]["clients"])); 
 | 
                    break; 
 | 
                } 
 | 
            } 
 | 
            socket_close($s); 
 | 
            continue; 
 | 
        } 
 | 
  
 | 
        $data = message($buf); 
 | 
        $data = json_decode($data, true); 
 | 
        if (!isset($data)) { 
 | 
            send($s, "HEARTBEAT", "hello"); 
 | 
            continue; 
 | 
        } 
 | 
  
 | 
        if ($data["MsgType"] == "SPID") { 
 | 
            $spid = $data["Msg"]; 
 | 
            if (!in_array($spid, $ChannelList)) { 
 | 
                continue; 
 | 
            } 
 | 
            $clients = $channelInfo[$spid]["clients"]; 
 | 
            $clients[$key] = $s; 
 | 
            $channelInfo[$spid]["clients"] = $clients; 
 | 
            LogConsole("update spid client spid:" . $spid . " key:" . $key . " spid clients count:" . count($channelInfo[$spid]["clients"])); 
 | 
            // 同步最近历史聊天,待扩展,有需要再说 
 | 
        } 
 | 
  
 | 
        $curSpid = ""; 
 | 
        foreach ($channelInfo as $spid => $info) { 
 | 
            if (array_key_exists($key, $info["clients"])) { 
 | 
                $curSpid = $spid; 
 | 
                break; 
 | 
            } 
 | 
        } 
 | 
        if ($curSpid && broadcastChat($curSpid)) { 
 | 
            continue; 
 | 
        } 
 | 
        send($s, "HEARTBEAT", "hello"); 
 | 
    } 
 | 
  
 | 
    // 广播最新消息 
 | 
  
 | 
} while (true); 
 | 
socket_close($master); 
 | 
  
 | 
function broadcastChat($spid) 
 | 
{ 
 | 
    global $channelInfo, $GameName; 
 | 
  
 | 
    $info = $channelInfo[$spid]; 
 | 
    $broadcastTime = $info["broadcastTime"]; 
 | 
  
 | 
    $curTime = time(); 
 | 
    // 1 秒最多广播一次 
 | 
    if (($curTime - $broadcastTime) < 1) { 
 | 
        return; 
 | 
    } 
 | 
    $channelInfo[$spid]["broadcastTime"] = $curTime; 
 | 
    if (count($info["clients"]) <= 0) { 
 | 
        return; 
 | 
    } 
 | 
  
 | 
    $redisKey = \RedisOper\GetFCRedisKey($GameName, "Chatmonitor", $spid); 
 | 
    if (!\RedisOper\ListRange($redisKey, $retArray, 0, -1)) { 
 | 
        return; 
 | 
    } 
 | 
    if (!isset($retArray) || count($retArray) <= 0) { 
 | 
        return; 
 | 
    } 
 | 
    \RedisOper\DelKey($redisKey); 
 | 
    LogConsole("broadcast spid:" . $spid . " clients count:" . count($info["clients"]) . " chatCount:" . count($retArray)); 
 | 
    sendAll($info["clients"], "CHAT", $retArray); 
 | 
    return true; 
 | 
} 
 | 
  
 | 
function LogConsole($msg, $isErr = false) 
 | 
{ 
 | 
    global $logDate; 
 | 
    if ($logDate != date("Y-m-d")) { 
 | 
        $logDate = date("Y-m-d"); 
 | 
        echo "new logDate", $logDate, PHP_EOL; 
 | 
        \Logging\CreateLogging("chatmonitorserver.php", true); 
 | 
    } 
 | 
    echo date("Y-m-d H:i:s"), " ", $msg, PHP_EOL; 
 | 
    if ($isErr) { 
 | 
        \Logging\LogError($msg); 
 | 
    } else { 
 | 
        \Logging\LogInfo($msg); 
 | 
    } 
 | 
} 
 | 
  
 | 
/** 
 | 
 * 解析接收数据 
 | 
 * @param $buffer 
 | 
 * @return null|string 
 | 
 */ 
 | 
function message($buffer) 
 | 
{ 
 | 
    $len = $masks = $data = $decoded = null; 
 | 
    $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); 
 | 
    } 
 | 
    for ($index = 0; $index < strlen($data); $index++) { 
 | 
        $decoded .= $data[$index] ^ $masks[$index % 4]; 
 | 
    } 
 | 
    return $decoded; 
 | 
} 
 | 
  
 | 
/** 
 | 
 * 消息广播 
 | 
 */ 
 | 
function sendAll($clients, $msgType, $msg) 
 | 
{ 
 | 
    global $all_sockets, $master; 
 | 
    $sendData = getsenddata($msgType, $msg); 
 | 
    foreach ($clients as $key => $so) { 
 | 
        if ($so != $master && array_key_exists($key, $all_sockets)) { 
 | 
            socket_write($so, $sendData, strlen($sendData)); 
 | 
        } 
 | 
    } 
 | 
} 
 | 
  
 | 
/** 
 | 
 * 发送数据 
 | 
 */ 
 | 
function send($client, $msgType, $msg) 
 | 
{ 
 | 
    $sendData = getsenddata($msg, $msgType, $msg); 
 | 
    socket_write($client, $sendData, strlen($sendData)); 
 | 
} 
 | 
  
 | 
function getsenddata($msgType, $msg) 
 | 
{ 
 | 
    // HEARTBEAT MSG CHAT 
 | 
    $msg = array("MsgType" => $msgType, "Msg" => $msg); 
 | 
    $msg = json_encode($msg) . "#end#"; 
 | 
    $msg = frame($msg); 
 | 
    return $msg; 
 | 
} 
 | 
  
 | 
/** 
 | 
 * 处理数据帧 
 | 
 * 
 | 
 * @param [type] $s 
 | 
 */ 
 | 
function frame($s) 
 | 
{ 
 | 
    $a = str_split($s, 125); 
 | 
    if (count($a) == 1) { 
 | 
        return "\x81" . chr(strlen($a[0])) . $a[0]; 
 | 
    } 
 | 
    $ns = ""; 
 | 
    foreach ($a as $o) { 
 | 
        $ns .= "\x81" . chr(strlen($o)) . $o; 
 | 
    } 
 | 
    return $ns; 
 | 
} 
 |