Socket心跳机制

Stella981
• 阅读 406

本文是我在实际工作中用到的Socket通信,关于心跳机制的维护方式,特意总结了一下,希望对朋友们有所帮助。

Socket应用:首先Socket 封装了tcp协议的,通过长连接的方式来与服务器通信,是由服务器和客户端两部分组成的,当客户端成功连接之后,服务器会记录这个用户,并为它分配资源,当客户端断开连接后,服务器会自动释放资源。

但在实际的网络环境中会有很多因素的导致服务器不知道客户端断开,或者客户端不知道服务器宕机,等等,比如网络中使用了路由器、交换机等等;这就带来一个问题:此时此刻服务器如何知道能否同客户端正常通信?解决这个问题的办法就是采用心跳简单的说就是:在客户端和服务器连接成功后,隔一段时间服务器询问一下客户端是否还在,客户端收到后应答服务器"我还在",如果服务器超出一定时间(一般40-50秒)未收到客户端的应答,就判定它已经无法通信了,这时候需要释放资源,断开这个客户端用户。

客户端JS代码:

<!DOCTYPE html>
<html>
 
<head lang="en">
    <meta charset="utf-8">
    <title></title>
</head>
 
<body>
    <h3>WebSocket协议的客户端程序</h3>
    <button id="btConnect">连接到WS服务器</button>
    <button id="btSendAndReceive">向WS服务器发消息并接收消息</button>
    <button id="btClose">断开与WS服务器的连接</button>
    <div id="val"></div>
<script type="text/javascript">
    var wsClient=null;
    var lastHealthTime = 0; //记录最近一次心跳更新时间
    var heartbeatTimer = null;//心跳执行timer
    var checkTime = 10000; //心跳检查时间间隔-毫秒 10秒
    var healthTimeOut = 20000;//心跳时间间隔-毫秒  20秒

    var reconnectTimer = null;//重连接timer
    var reconnectTime = 10000;//重连接时间10秒后
    var uid = "20";

    var connectStatus = 3; //状态

    function connect(){
        if (connectStatus == 3){
            wsClient=new WebSocket('ws://127.0.0.1:8000'); //这个端口号和容器监听的端口号一致
            console.log("连接中...");
            console.log("readyState:"+wsClient.readyState);
            if (reconnectTimer){
                clearTimeout(reconnectTimer);
            }

            //连接成功
            wsClient.onopen = function(){
                connectStatus = wsClient.readyState;
                // 表名自己是uid1
                var data = uid; //1标识连接
                wsClient.send(data);
                console.log('ws客户端已经成功连接到服务器上');
                msg.innerHTML="连接成功...";
                console.log("readyState:"+wsClient.readyState);
                var time = new Date();
                lastHealthTime = time.getTime();

                if(heartbeatTimer){
                   clearInterval(heartbeatTimer);
                }

                heartbeatTimer = setInterval(function(){keepalive(wsClient)}, checkTime);
            };

            //收到消息
            wsClient.onmessage = function(e){
                console.log('ws客户端收到一个服务器消息:'+e.data);
                console.log("readyState:"+wsClient.readyState);
                val.innerHTML=e.data;
                var data = e.data;
                if (data){
                    var msg_type = data.substr(0,1);
                    var uid = data.substr(1);var time = new Date();
                    lastHealthTime = time.getTime();//更新客户端的最后一次心跳时间
                }
            }

            //错误
            wsClient.onerror = function(e){
                connectStatus = wsClient.readyState;
                console.log("error");
                console.log("readyState:"+wsClient.readyState);
                msg.innerHTML="连接错误...";
            };

            //关闭
            wsClient.onclose = function(){
                connectStatus = wsClient.readyState;
                console.log('到服务器的连接已经断开');
                msg.innerHTML="连接断开...";
                console.log("readyState:"+wsClient.readyState);
                //n秒后重连接
                reconnectTimer = setTimeout(function(){
                    connect();
                },reconnectTime);
            }
        }
    }
    
    btConnect.onclick = function(){
        connect();
    }
    btSendAndReceive.onclick = function(){
        wsClient.send('Hello Server');
    }
    btClose.onclick = function(){
        console.log("断开连接");
        console.log(wsClient.readyState);
        wsClient.close();
    }



    function keepalive(ws){
        var time = new Date();
        console.log(time.getTime()-lastHealthTime);
        if ((time.getTime()-lastHealthTime)>healthTimeOut){
            msg.innerHTML="心跳超时,请连接断开...";

            if (heartbeatTimer){
                clearInterval(heartbeatTimer);

                //n秒后重连接
                ws.close();
                reconnectTimer = setTimeout(function(){
                    connect();
                },reconnectTime);
            }
        }
        else{
            msg.innerHTML="我依然在连接状态";
            ws.send(data);
        }
    }
</script>
<div id="msg"></div>
</body>
</html>

服务端代码:

这里我采用的是PHP语言,使用workman来实现的socket服务器端

<?phprequire_once __DIR__ .'/Autoloader.php';  
use Workerman\Worker;
use Workerman\Lib\Timer;


define('HEARTBEAT_TIME', 40);//心跳间隔时间
define('CHECK_HEARTBEAT_TIME', 10); // 检查连接的间隔时间
// 初始化一个worker容器,监听1234端口
$worker = new Worker('websocket://0.0.0.0:8000');
// 这里进程数必须设置为1
$worker->count = 1;
// worker进程启动后建立一个内部通讯端口
$worker->onWorkerStart = function($worker)
{
    Timer::add(CHECK_HEARTBEAT_TIME, function()use($worker){
        $time_now = time();  
        foreach($worker->connections as $connection) {    

            // 有可能该connection还没收到过消息,则lastMessageTime设置为当前时间
            if (empty($connection->lastMessageTime)) {
                $connection->lastMessageTime = $time_now;                
                continue;
            }            
             
            // 上次通讯时间间隔大于心跳间隔,则认为客户端已经下线,关闭连接
            if ($time_now - $connection->lastMessageTime > HEARTBEAT_TIME) {
                $connection->close();
            }
        }
    });
};
// 新增加一个属性,用来保存uid到connection的映射
$worker->uidConnections = array();
// 当有客户端发来消息时执行的回调函数
$worker->onMessage = function($connection, $data)use($worker)
{
     $uid = $data; //uid

     //echo 'connection...'.$uid.'\n';
    // 判断当前客户端是否已经验证,既是否设置了uid
    if(!isset($connection->uid))
    {
        if (intval($msg_type) === 1){ //连接
            //上次收到的心跳消息时间
            $connection->lastMessageTime = time();

            // 没验证的话把第一个包当做uid(这里为了方便演示,没做真正的验证)
            
            $connection->uid = $uid;
            /* 保存uid到connection的映射,这样可以方便的通过uid查找connection,
             * 实现针对特定uid推送数据
             */
            $worker->uidConnections[$connection->uid] = $connection;
            echo  'MSG USER COUNT:'.count($worker->uidConnections); 
            echo '\n';
            return;
        }
    }
    else{
        if ($connection->uid === $uid){
          //服务器收到心跳
                //echo 'U-heart:'.$connection->uid.'\n';
                $connection->lastMessageTime = time();

                echo 'back send:';
                $buffer = $uid;
                $ret =  sendMessageByUid($uid, $buffer);
                $result = $ret ? 'ok' : 'fail';
              //  echo $result;
        }
    }

};
// 当有客户端连接断开时
$worker->onClose = function($connection)use($worker)
{
    global $worker;
    if(isset($connection->uid))
    {
        // 连接断开时删除映射
        unset($worker->uidConnections[$connection->uid]);

        echo  'CLOSE USER COUNT:'.count($worker->uidConnections); 
        echo '-'.$connection->uid.' closed';
        echo '\n';
    }
};
// 向所有验证的用户推送数据
function broadcast($message)
{
    global $worker;
    foreach($worker->uidConnections as $connection)
    {
        $connection->send($message);
    }
}
// 针对uid推送数据
function sendMessageByUid($uid, $message)
{
    global $worker;
    if(isset($worker->uidConnections[$uid]))
    {
        $connection = $worker->uidConnections[$uid];
        $connection->send($message);
        return true;
    }
    return false;
}

// 运行所有的worker(其实当前只定义了一个)
Worker::runAll();
点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
实现服务器和客户端数据交互,Java Socket有妙招
本文分享自华为云社区《JavaSocket如何实现服务器和客户端数据交互》,作者:jackwangcumt。1Socket概述根据百度百科的定义,Socket译为套接字,它是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个Socket实例就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。Socket向上连接各种应用
Wesley13 Wesley13
2年前
java通过ServerSocket与Socket实现通信
首先说一下ServerSocket与Socket.1.ServerSocketServerSocket是用来监听客户端Socket连接的类,如果没有连接会一直处于等待状态.ServetSocket有三个构造方法:(1)ServerSocket(intport);这个使用指定的端口
Stella981 Stella981
2年前
Android Socket 通信
Androidsocket通信安卓编写Socket客户端,实现连接Socket服务端通信。创建Socket连接并获取服务端数据先创建几个全局变量吧privateBufferedWriterwriternull;Socketsocket;
Stella981 Stella981
2年前
Kerberos无约束委派的攻击和防御
 0x00前言简介当ActiveDirectory首次与Windows2000Server一起发布时,Microsoft就提供了一种简单的机制来支持用户通过Kerberos对Web服务器进行身份验证并需要授权用户更新后端数据库服务器上的记录的方案。这通常被称为Kerberosdoublehopissue(双跃点问题),
Stella981 Stella981
2年前
Python socket网络模块
一、基于TCP协议的socket通信以打电话为理解方式进行TCP的通信。Server端代码:importsocketphonesocket.socket(socket.AF_INET,socket.SOCK_STREAM)购买电话卡,AF_INET服务器之间网络
Wesley13 Wesley13
2年前
GO语言网络编程
socket编程Socket是BSDUNIX的进程通信机制,通常也称作”套接字”,用于描述IP地址和端口,是一个通信链的句柄。Socket可以理解为TCP/IP网络的API,它定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序。电脑上运行的应用程序通常通过”套接字”向网络发出请求或者应答网络请求。Socke
Stella981 Stella981
2年前
Netty 如何实现心跳机制与断线重连?
作者:sprinkle\_lizwww.jianshu.com/p/1a28e48edd92心跳机制何为心跳所谓心跳,即在 TCP 长连接中,客户端和服务器之间定期发送的一种特殊的数据包,通知对方自己还在线,以确保 TCP 连接的有效性.注:心跳包还有另一个作用,经常被忽略,即:一个连接如果长时间不用,防
Stella981 Stella981
2年前
C# Socket之异步TCP客户端断线重连
  我们知道TCP通信是一种面向连接的Socket,针对于面向连接的TCP服务应用,安全,但是效率低,它首先需要服务端开启服务,然后客户端才可以去连接,如果服务端没有开启通信服务或者连接之后再中途因为某些原因断开连接了,那么都是会通信失败的,所以我们这篇博客主要是对TCP通信加入两个机制。1,客户端开启后未连接成功,自动重连请求2,若通信途中因为某些原因断