心跳机制讲解及实例

什么是心跳机制

心跳机制出现在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);
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PDM 测试

文章目录 硬件拓扑AP 生成 PDM输出数据路径AP 输入时域数据频域数据逻辑分析与抓包硬件拓扑 如果使用 AP 需要注意公地 AP 生成 PDM输出 AP 的 output 选择 PDM,MCU 提供 Bit clock,AP 生成 PDM 数据,AP 配置如下 数据路径 AP sin data -> PDM -> codec -> RA…

从11个视角看全球Rust程序员4/4:深度解读JetBrains最新报告

讲动人的故事,写懂人的代码 8 Rust程序员最喜欢用什么工具调试程序? 用println!或dbg!宏来调试一下 2022年:55%2023年:55%在IDE里玩玩UI调试 2022年:27%2023年:29%在控制台里调试调试 2022年:11%2023年:10%不调试,任性 2022年:5%2023年:6%有其他奇思妙想 2022年:1%…

软考阅卷将完成?!软考成绩有望六月底公布!

2024上半年软考考试已于5月25日-28日举行&#xff0c;考完试后大家最关心的事情莫过于查分了。 一、最新消息 1、不同地区在报名时对成绩公布的时间有所预示&#xff0c;但并没有一个统一的日期举个例子&#xff0c;江苏考区预计在6月下旬公布成绩&#xff0c;而黑龙江考区则预…

自注意力与卷积高效融合!多SOTA、兼顾低成本与高性能

在自注意力机制中&#xff0c;模型计算输入序列中不同位置的相关性得分&#xff0c;以生成连接权重&#xff0c;从而关注序列中的重要部分。而卷积通过滑动窗口的方式&#xff0c;在输入上应用相同权重矩阵来提取局部特征。 如果将以上两者结合&#xff0c;就可以同时利用自注…

爱死了,4款逆天IOS App推荐!

河马喝水提醒 「河马喝水提醒」是一款生活小助手应用&#xff0c;旨在帮助用户建立并维持定时喝水的良好习惯。该应用不仅是一个提醒闹钟&#xff0c;更是健康生活的贴心伙伴。它每天定时推送提醒&#xff0c;帮助用户在繁忙的日常中不忘补充水分。此外&#xff0c;该应用还提供…

软件性能测试之负载测试、压力测试详情介绍

负载测试和压力测试是软件性能测试中的两个重要概念&#xff0c;它们在保证软件质量和性能方面起到至关重要的作用&#xff0c;本文将从多个角度详细介绍这两种测试类型。 一、软件负载测试   负载测试是在特定条件下对软件系统进行长时间运行和大数据量处理的测试&#xff…

电能表厂家的研发能力是实力的体现

电能表厂家的研发能力无疑是其整体实力的核心体现。一个拥有强大研发能力的电能表厂家&#xff0c;不仅能够持续推出具有竞争力的新产品&#xff0c;满足市场需求&#xff0c;还能引领行业发展&#xff0c;塑造企业品牌形象。 一、研发能力对电能表厂家的重要性 研发能力是电…

如何预防最新的Mallox变种hmallox勒索病毒感染您的计算机?

一、引言 近年来&#xff0c;网络安全问题日益严重&#xff0c;勒索病毒成为了其中的一大威胁。其中&#xff0c;.hmallox勒索病毒作为Malox勒索软件家族的新变种&#xff0c;凭借其高度的攻击性和隐蔽性&#xff0c;给全球用户的数据安全带来了严重威胁。本文将深入分析.hmal…

部署yum仓库

目录 安装软件包 yum 配置文件 缓存功能操作步骤 创建并配置本地仓库文件 yum相关命令 yum install __ yum repolist yum list __ yum info __ yum search __ yum whatprovides __ yum remove __ yum -y update __ yum history yum grouplist yum groupinstall…

【C语言】回调函数 和 部分库函数的用法以及模拟实现

一、回调函数&#xff1a; 1、定义&#xff1a; 回调函数就是一个通过函数指针调用的函数。如果你把函数的指针&#xff08;地址&#xff09;作为参数传递给另一个函数&#xff0c;当这个指针被用来调用其所指向的函数时&#xff0c;我们就说这是回调函数。 2、qsort的模拟实现…

PHP基础语法【上】

文章目录 一、环境安装二、代码应该写在哪里&#xff1f;三、什么是错误&#xff1f;四、变量无需声明变量变量命名规则变量的作用域可变变量 五、常量常量的定义常量的特性 六、数据类型NULL 空值Boolean 布尔类型Integer 整型Float 浮点型String 字符串Array 数组Object 对象…

有效招聘营销策略的六个组成部分

任何想吸引更多人购买其产品的公司都必须投资于市场营销。然而&#xff0c;当涉及到让更多的人了解公司的工作时&#xff0c;许多有效的营销活动可能不是招聘团队的首要考虑因素。为了超越招聘委员会上的“发布祈祷”策略&#xff0c;有必要包括有效招聘营销策略的所有组成部分…

Redis 6.0新特性详解

Redis 6.0新特性主要有3个&#xff1a;多线程、Client Side Cache、Acls。下面详细说明一下。 1.多线程 redis 6.0 提供了多线程的支持&#xff0c;redis 6 以前的版本&#xff0c;严格来说也是多线程&#xff0c;只不过执行用户命令的请求时单线程模型&#xff0c;还有一些线…

【Axure高保真原型】动态统计中继器表格项目数

今天和大家分享动态统计中继器表格项目数的原型模板&#xff0c;具体包括以下功能&#xff1a; 表格下方可以自动根据表格内容统计表格的总项目数、启用和禁用数、选中和未选中数 我们可以点击开发切换启用和禁用 点击多选按钮&#xff0c;选中或取消选中对应行内容 选中后可…

跨境电商源码支持,多国语言与货币切换功能全解析

一、背景介绍 跨境电商的兴起&#xff0c;使得供货商和代理商们面临着一个全新的挑战&#xff1a;如何管理跨国交易和多语言 的销售平台。为了解决这一问题&#xff0c;跨境电商源码应运而生。本文将全面解析供货商和代理商后 台所具备的跨境电商源码支持功能&…

环境搭建---nginx

nginx离线安装 下载地址&#xff1a;https://nginx.org/download/ 一、安装编译工具及库文件 [rootVM-20-14-centos ~]# yum -y install make zlib zlib-devel gcc-c libtool openssl openssl-devel二、安装 PCRE [rootVM-20-14-centos ~]# tar -zxvf pcre-8.35.tar.gz [r…

从热潮到理性,大模型迎来产业「拐点」

前言 无人不谈大模型&#xff0c;是今年上半年科技界的真实写照。 从市场热闹程度来看&#xff0c;大模型已经成为各家科技厂商争先涌入的赛道&#xff0c;无论是互联网巨头&#xff0c;还是科技公司&#xff0c;甚至是研究机构&#xff0c;均已加入这场大模型混战&#xff0…

k8s快速上手实操

前言 Kubernetes&#xff08;简称K8s&#xff09;是由Google开源的一个用于自动化部署、扩展和管理容器化应用程序的系统。自2014年发布以来&#xff0c;Kubernetes已经迅速成长为容器编排领域的标准&#xff0c;并在全球范围内得到了广泛的采用和认可。 Kubernetes作为现代容…

彩色图像批处理实例

在数字图像处理过程中&#xff0c;经常需要对一批图像进行处理&#xff0c;下面以自然场景下花背景分割为例&#xff0c;给出其主要处理过程和处理结果。 处理的主要步骤&#xff1a;1. 六张彩色图像存放在flower文件夹中&#xff0c;图像文件名为1.jpg 2.jpg 3.jpg 4.jpg 5.j…

同三维T80006EHL-4K30CN 单路4K30 HDMI编码器(全国产化)

同三维T80006EHL-4K30CN 单路4K30 HDMI编码器 带1路HDMI环出和1路3.5音频输入&#xff0c;支持4K30&#xff0c;所有元器件全国产 一、 产品简介&#xff1a; T80006EHL-4K30CN 4K编码器&#xff08;采集盒&#xff09;是一款全国产化的专业4K HDMI音视频编码产品&#xff0c;…