心跳机制讲解及实例

news2024/12/24 8:26:03

什么是心跳机制

心跳机制出现在tcp长连接中,客户端和服务器之见定时发送一种特殊的数据包通知对方还在线,以确保tcp链接地可靠性,有可能tcp链接由于某些原因(列入网线被拔了,突然断电)导致客户端断了,但是服务器不知道客户端断了,服务器还保持与客户端连接的状态,所以为了不浪费资源,需要知道客户端非正常中断,服务器把断开客户端断开链接,需要加入心跳包机制

tcp 需不需要心跳?

需要心跳机制tcp本身内置了keeplive心跳机制,但是这种内置的心跳机制不足以满足所有的情况,所以有必要自己写心跳机制
有必要自己写心跳机制

3 那些网络情况下不满足keepalive心跳机制 

1 tcp 属于 keeepalive心跳机制 有些设备不会处理keepalive心跳包
2 keeepalive心跳机制只能说明连接是活的,应用实现心跳机制,可以保持连接是活的应用正常工作

心跳检测步骤

1.客户端每隔一个时间间隔发生一个探测包给服务器
2.客户端发包时启动一个超时定时器
3.服务器端接收到检测包,应该回应一个包
4. 如果客户机收到服务器的应答包,则说明服务器正常,删除超时定时器
5. 如果客户端的超时定时器超时,依然没有收到应答包,则说明服务器挂了

实例

拥有单发群发指定部分功能

客户端:

搭建客户端连接的界面

代码如下:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    // 点击链接按钮
    // 1 创建客户端对象
    // 2 连接服务器
    // 3 创建网络基础流发消息,write发消息
    // 4 创建网络基础流接消息,read接消息
    // 5 断开链接close()
    TcpClient client;
    private void button1_Click(object sender, EventArgs e)
    {
        if(button1.Text == "连接")
        {
            try
            {
                // 开始连接
                client = new TcpClient();
                client.Connect(comboBox1.Text, int.Parse(comboBox2.Text));
                button1.Text = "断开";
                // 读取数据
                StartRead();

                // 启动心跳机制
                HeartBeat();

            }
            catch(Exception ex)
            {
                MessageBox.Show("连接失败");
            }
        }
        else
        {
            // 断开连接
            client.Close();

            timer1.Stop(); // 关闭心跳包
            // 把心跳定时器关闭
            button1.Text = "连接";

        }

    }
    void StartRead()
    {
        byte[] bs = new byte[1024];
        Task.Run(() =>
        {
            try 
            {
                while (true)
                {
                    int count = client.GetStream().Read(bs,0,bs.Length);
                    string msg = Encoding.UTF8.GetString(bs,0,count);
                    richTextBox1.Invoke((Action)(() =>
                    {
                        richTextBox1.AppendText(msg+"\t\n");
                    }));
                }
            }catch(Exception ex)
            {
                button1.Text = "连接";
            }
        });


    }
    // 开启心跳方法
    Timer timer1;
    void HeartBeat()
    {
        timer1 = new Timer();
        timer1.Interval = 3000;
        timer1.Tick += Timer_Tick;
        timer1.Start();
    }

    // 定时器方法 定时发送心跳包
    private void Timer_Tick(object sender, EventArgs e)
    {
        // 心跳包发送数据的内容需要跟后台事先约定好,什么数据是心跳包
        // 
        client.GetStream().Write(new byte[] { 1 }, 0, 1);
    }

    // 点击发送按钮设置普通的消息包
    private void button2_Click(object sender, EventArgs e)
    {
        timer1.Stop();
        timer1.Interval = 3000;
        timer1.Tick += Timer_Tick;
        timer1.Start();
        byte[] bs = Encoding.UTF8.GetBytes(textBox1.Text);
        // 普通消息在发送时候,需要字节数组的第一位置为0 
        byte[] bs1 = new byte[bs.Length+1];
        bs1[0] = 0;// 字节数组的第一位设置为0
        bs.CopyTo(bs1, 1); // 把消息复制到新数组的第一位开始
        client.GetStream().Write(bs1,0,bs1.Length);
    }
}

服务端:

Server类:

internal class Server
{
    TcpListener listen;
    //1通过构造函数创建服务器对象
    public Server(IPAddress ip,int port) 
    {
       listen = new TcpListener(ip, port);

    }
    //2 封装开启监听的方法
    public void Start()
    {
        listen.Start(100);//开启监听

        //接受客户端的连接
        StartConnect();

        //调用扫描心跳方法
        SaoMiao();
       

    }
    //3接受客户端的连接 封装一监听客户端连接的方法
   
    //保存所有的客户端字典, 键是ip 值是客户端,
    Dictionary<string,TcpClient> clientDic = new Dictionary<string,TcpClient>();

    //字典保存客户端和当前连接服务器时间点
    Dictionary<string,DateTime> heartDic  = new Dictionary<string,DateTime>();

    public event Action<TcpClient> 有客户端连入的事件; //当客户端连入触发。绑定事件,
    void StartConnect()
    {
        Task.Run(() =>
        {
            while (true) //接入多个客户端
            {
             TcpClient client = listen.AcceptTcpClient();
             string ip = client.Client.RemoteEndPoint.ToString(); //获取远程ip
             //保存当前客户端
             clientDic.Add(ip, client);

             //记录当前客户端心跳 连接成功时候记录当前客户端时间点
              heartDic.Add(ip, DateTime.Now);

            //调用事件函数 触发事件,
              有客户端连入的事件?.Invoke(client);

              //4 接收客户端发来的消息
             ReceiveMsg(client);
            }
        });
    }
    //4 接收客户端发来的消息  
    //封装接受的消息
    public event Action<string> 客户端断开事件; //当客户端断开时候调用事件
    public event Action<TcpClient, byte[]> 接受到消息的事件;//接收到消息调用
    void ReceiveMsg(TcpClient t1)
    {
        NetworkStream stream = t1.GetStream();
        string ip = t1.Client.RemoteEndPoint.ToString() ;
        byte[] bs = new byte[1024];
        Task.Run(() =>
        {
            try
            {
                while (true)
                {
                   
                    int count = stream.Read(bs, 0, bs.Length);
                    if(count == 0)
                    {
                        //客服端断开
                        throw new Exception("客户端断开连接");
                    }
                    //如果接收到数据长度不为0,
                    //必须判断是否是心跳包 事先约定好:如果数据第一位是0的时候,当成普通数据包
                    //如果数据第一位是1说明是心跳包
                    switch (bs[0]) //判断第一位数据是不是0
                    {
                        case 0: //普通数据 取出来的时候不需要显示第一位标识符
                            //skip 从第一位开始截取
                            // take 到指定位置的元素为止
                            //第一位0、1代表是否是心跳包标识符,
                            //最后一位占位符
                          byte[] body= bs.Skip(1).Take(count - 1).ToArray();
                            //要么群发 要么单发
                           接受到消息的事件?.Invoke(t1, body);
                          break;

                        case 1: //发的是心跳包
                            //修改是心跳包发的时间点
                            heartDic[ip] = DateTime.Now;
                           break;

                    }
                }
            }
            catch(Exception e)
            {
                //从字典把客户端清除掉
                clientDic.Remove(ip);

                //如果客户端断开了,打印客户断开
                客户端断开事件?.Invoke(ip);
                //删除心跳记录
                heartDic.Remove(ip);
            }
        });
    }
    //遍历所有客户端 扫描是否在未超时的时间内
    void SaoMiao()
    {
        Task.Run(() =>
        {
            while (true)
            {
                Thread.Sleep(4000);//线程休眠4s
                DateTime now1 = DateTime.Now;
                foreach (var item in heartDic)//遍历所有心跳记录
                {
                    //now1 当前时间点
                    // item.Value 服务器接收客户端发来的心跳包时间
                    //new TimeSpan(0, 0, 4) 时分秒
                    if (now1 - item.Value > new TimeSpan(0, 0, 4))
                    {
                        Console.WriteLine(item.Key + "掉线了");
                        if (clientDic.Keys.Contains(item.Key))
                        {
                            Send("你已经掉线了骚年:" + item.Key, item.Key);
                            clientDic.Remove(item.Key);
                        }

                        else
                        {
                            Console.WriteLine(item.Key + "在线");

                        }
                    }
                }
            }
        });
    }
    // 无参数的构造函数
    public Server()
    {

    }
    //群发方法 向所有的客户端发消息
    public void Send(string content)
    {
        byte[] bs = Encoding.UTF8.GetBytes(content);
        foreach (var item in clientDic) //遍历所有的客户端
        {
            item.Value.GetStream().Write(bs, 0, bs.Length);
        }
    }
    //指定给谁发
    public void Send(string content, string ip)
    {
        byte[] bs = Encoding.UTF8.GetBytes(content);
        //根据ip取出客户端,从字典取
        clientDic[ip].GetStream().Write(bs, 0, bs.Length);
    }
    //指定给哪些客户端发
    //send("你好", ["192.","127"])
    public void Send(string content, string[] ips)
    {
        byte[] bs = Encoding.UTF8.GetBytes(content);
        foreach (var item in clientDic) //所有客户端
        {
            //item.key 键 ip字符串
            //item.value 值 客户端对象
            if (ips.Contains(item.Key))
            {
                //如果ips数组包含目标客户端
                item.Value.GetStream().Write(bs, 0, bs.Length);
            }

        }

    }


}

Program:

internal class Program
{
    static Server server;
    static void Main(string[] args)
    {
        server = new Server(IPAddress.Any,3333);
        server.Start(); // 除了服务器监听方法, 监听客户连接的方法 扫描客户端是否在线的方法
        //如果监听到有客户端连接的时候,打印哪个终端连入到服务器了 使用事件封装
        server.有客户端连入的事件 += 有客户端连入服务器方法; //绑定事件
        server.客户端断开事件 += f2;
        server.接受到消息的事件 += f3;

        Console.ReadKey();
    }
    //相当于点击之后的回调方法,再客户端连接成功之后调用这个方法
    public static void 有客户端连入服务器方法(object obj)
    {
        TcpClient t1 = obj as TcpClient;
        Console.WriteLine(t1.Client.RemoteEndPoint+"连接到服务器");
    
    }
    public static void f2(object obj)
    {
        Console.WriteLine(obj.ToString()+"断开连接");
    }
    public static void f3(TcpClient t1, byte[] b1)
    {

        // t1.GetStream().Write(b1, 0, b1.Length);
        string content = Encoding.UTF8.GetString(b1);
        // 如果群发
        // server.Send(content);

        // 如果单发
        // server.Send(content, "这里是端口号");

        // 如果指定部分
        string[] ips = new string[] { "这里是端口号", "这里是端口号" };
        server.Send(content, ips);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1836049.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用高斯混合模型(GMM)进行猫狗音频聚类(Kaggle Audio Cats and Dogs)

Audio Cats and Dogs | Kaggle 目录 一、实验目标 二、数据分析 三、实验结果 四、改进方向 一、实验目标 数据集包括164个标注为猫的.wav文件&#xff0c;总共1323秒和113个标注为狗叫声的.wav文件&#xff0c;总共598秒&#xff0c;要求判别每个音频是狗叫还是猫叫 二、…

Springboot + Mybatis 实现sql打印

参照这个视频&#xff1a;https://www.bilibili.com/video/BV1MS411N7mn/?vd_source90ebeef3261cec486646b6583e9f45f5 实现mybatis对外暴露的接口Interceptor 使用Intercepts接口,这里的写法参照mybatis-plus中的拦截器写法 Intercepts({Signature(type Executor.class, m…

FPGA开发Vivado安装教程

前言 非常遗憾的一件事情是&#xff0c;在选修课程时我避开了FPGA&#xff0c;选择了其他方向的课程。然而&#xff0c;令我没有想到的是&#xff0c;通信项目设计的题目竟然使用FPGA&#xff0c;这简直是背刺。在仅有的半个月时间里&#xff0c;准备这个项目确实是非常紧张的…

Corrupt JPEG data: 2 extraneous bytes before marker 0xd9

场景 异常&#xff1a;Corrupt JPEG data: 2 extraneous bytes before marker 0xd9 python语言&#xff0c;CV2读图像数据集&#xff0c;训练目标检测模型。在数据集分批送入模型训练过程中&#xff0c;出现大片图片异常情况。 &#xff08;建议直接去看修复图像方法二&…

华翰传媒集团横店影视基地盛大开业,汇剧视界APP震撼发布

2024年6月1日上午&#xff0c;横店影视华翰传媒集团携手腾烨影视隆、明艺影视重举办了横店影视基地的开业庆典。这一盛事不仅标志着华翰传媒集团在影视行业发展的重要里程碑&#xff0c;更彰显了其深耕影视产业、致力于打造高质量影视内容的决心与目标。 活动盛况空前&#xff…

vivado PIP or SITE_PIP、PKGPIN_BYTEGROUP

PIP是Xilinx部件上用于路由连接或网络的设备对象。PIP 称为ARC的连接多路复用器可以编程为将一根电线连接到 另一个&#xff0c;从而将节点连接在一起&#xff0c;以形成中特定NET所需的路由 设计。 SITE_PIP&#xff0c;也称为路由BEL&#xff0c;是SITE内部的连接多路复用器&…

vcs覆盖率相关

查看覆盖率是由哪几个tc覆盖的 选择要查看的覆盖率点&#xff0c;右键选择 show xxx tests&#xff1b; 覆盖率的合并

最新开源:英伟达Nemotron-4 340B,哔哩哔哩Index-1.9B,谷歌RecurrentGemma-9B...

文章目录 1. 英伟达开源Nemotron-4 340B2. 哔哩哔哩开源轻量级模型 Index-1.9B3. 微软开源混合模型 Samba4. 谷歌开源 RecurrentGemma-9B&#xff0c;性能与Gemma相同5. Stable Diffusion 3 Medium&#xff1a;“最强文生图开源 AI 模型” 1. 英伟达开源Nemotron-4 340B 当地时…

【AI绘画】新手小白看这篇就够啦!国产PS AI插件超好入门!

随着人工智能技术的飞速发展&#xff0c;Photoshop作为设计师们不可或缺的工具&#xff0c;也在不断地融入AI技术&#xff0c;以提升设计效率和效果。最近米兔用了一款AI绘画软件StartAI&#xff0c;被其强大的功能和易用性经验到了&#xff0c;下面跟大家详细分享一下这款ps插…

010-基于Sklearn的机器学习入门:聚类(上)

本节及后续章节将介绍深度学习中的几种聚类算法&#xff0c;所选方法都在Sklearn库中聚类模块有具体实现。本节为上篇&#xff0c;将介绍几种相对基础的聚类算法&#xff0c;包括K-均值算法和均值漂移算法。 目录 10.1 聚类概述 10.1.1 聚类的种类 10.1.2 Sklearn聚类子模…

Pycharm的基础使用

Pycharm的基础使用 一、修改主题 第一步&#xff1a;点击file->settings 第二步&#xff1a;找到Appearance&Behavior->Appearance->Theme选择主题 有五种主题可以选 二、修改默认字体和大小 第一步&#xff1a;打开设置与上面修改主题第一步一样&#xff1b…

硕思logo设计师下载-2024官方最新版-logo制作软件安装包下载

硕思​​Logo设计​​师是一款操作灵活简单、功能强大的logo制作​​软件​​。可以通过简单的点击就可以为网站、博客、论坛和邮件创建专业的logo、条幅、按钮、标题、图标和签名等。 硕思logo设计师提供了很多精心设计的模板和丰富的资源&#xff0c;为更好的创建logo艺术作品…

GPT3.5的PPO目标函数怎么来的:From PPO to PPO-ptx

给定当前优化的大模型 π \pi π&#xff0c;以及SFT模型 π S F T \pi_{SFT} πSFT​ 原始优化目标为: max ⁡ E ( s , a ) ∼ R L [ π ( s , a ) π S F T ( s , a ) A π S F T ( s , a ) ] \max E_{(s,a)\sim RL}[\frac{\pi(s,a)}{\pi_{SFT}(s,a)}A^{\pi_{SFT}}(s,a)] m…

光纤通信基础(光纤的构造、工作原理、色散、工作频段、损耗、分类、不同标准及应用、接口类型、常见标示方法、熔接)

文章目录 光纤的构造&#xff1a;纤芯、包层、涂覆层光纤的工作原理&#xff1a;利用全反射来传输光信号光纤的色散光纤的工作频段光纤的损耗光纤的分类光纤的不同标准及应用光纤的接口类型&#xff08;SC、LC、ST、FC&#xff09;光纤的常见标示方法&#xff1a;如“FC/PC”&a…

JSP之原理剖析

什么是JSP&#xff1a; java Server Pages: java服务端页面,也和Servlet一样&#xff0c;用于动态Web技术&#xff1f; 最大特点&#xff1a; 写jsp就像在写HTML区别&#xff1a; HTML只给用户提供静态的数据JSP页面中可以嵌入Java代码&#xff0c;为用户提供动态数据 JSP原…

基于S32K144驱动NSD8308

文章目录 1.前言2.芯片介绍2.1 芯片简介2.2 硬件特性2.3 软件资源2.4 芯片资料 3.测试环境4.软件驱动4.1 SPI4.2 寄存器4.3 SPI ON/OFF控制4.4 PWM控制 5.测试情况 1.前言 最近有些客户在前期调试NSD8308时&#xff0c;软件上遇到一些问题&#xff0c;正好笔者手上有一套NSD83…

学校分体空调集控系统

学校分体空调集控系统是一种先进的温度控制解决方案&#xff0c;它主要针对学校等公共场所的空调管理需求而设计。该系统通过集中控制和管理多台分体空调设备&#xff0c;实现了更高的能效、更便捷的操作和更舒适的室内环境。 需求与挑战&#xff1a;学校教学楼、办公楼、实验楼…

CorelDRAW2024破解版序列号注册码激活码最新

CorelDRAW2024&#xff0c;一个让你的设计创意无限飞扬的利器&#xff01;&#x1f3a8;✨ 你是否曾经为找不到合适的设计软件而苦恼&#xff1f;是否曾经为设计的局限性而感到束手无策&#xff1f;别担心&#xff0c;CorelDRAW2024将为你带来全新的设计体验&#xff01;&#…

《软件定义安全》之七:SDN安全案例

第7章 SDN安全案例 1.DDoS缓解 1.1 Radware DefenseFlow/Defense4All Radware在开源的SDN控制器平台OpenDaylight&#xff08;ODL&#xff09;上集成了一套抗DDoS的模块和应用&#xff0c;称为Defense4ALL。其架构如下图&#xff0c;主要有两部分&#xff1a;控制器中的安全…

软件测试面试题:性能测试关注哪些指标?

问题 在工作中&#xff0c;使用JMeter做压力测试时&#xff0c;需要关注其中的哪些指标&#xff1f; 性能测试关注哪些指标&#xff1f; 考察点 面试官想了解&#xff1a; 是否用过 JMeter 指标进行分析 技术点 涉及的技术点&#xff1a; JMeter 结果分析 回答 性能指…