c# socket心跳超时检测的思路(适用于超大量TCP连接情况下)

yizhihongxing

让我来详细讲解C# Socket心跳超时检测的思路和实现方法。

什么是心跳超时检测?

在Socket编程中,心跳超时检测就是指客户端和服务端之间保持网络连接的一种机制。当客户端和服务端之间的网络连接闲置一段时间后,为了避免网络连接被认为已经中断,我们需要在一定时间间隔内发送心跳数据包来维持网络连接。如果在规定的时间内没有收到心跳数据包,就意味着网络连接已经中断,就需要重新建立连接。

实现思路

  • 在服务器端,创建一个容器,存储所有心跳正常的客户端连接对象;
  • 在客户端和服务端之间,定期交换心跳数据包,以维持网络连接;
  • 在服务端,开启一个定时器,定期检测容器中的客户端连接是否已经超时,如果超时,就将这些连接从容器中剔除并关闭连接。

代码实现

在服务器端

public class Server
{
    private List<Socket> _clients = new List<Socket>();    // 存储所有可用的客户端连接
    private Timer _timer;   // 定时器,用于定期检测心跳是否超时

    public Server()
    {
        // 在构造函数中初始化定时器
        _timer = new Timer(OnTimerTick, null, 1000, 1000);
    }

    private void OnTimerTick(object state)
    {
        lock (_clients)
        {
            // 检测每个客户端是否已经超时,如果超时,则关闭连接
            foreach (var client in _clients)
            {
                UserData userData = client.Tag as UserData;    // 获取客户端连接对象的自定义数据
                if (userData != null && DateTime.Now.Subtract(userData.LastActiveTime).TotalSeconds > 30)
                {
                    Console.WriteLine($"{client.RemoteEndPoint}已经超时,强制关闭连接");
                    client.Close();
                }
            }

            // 从容器中删除已经关闭的连接
            _clients.RemoveAll(c => !c.Connected);
        }
    }

    private void OnClientConnected(IAsyncResult ar)
    {
        Socket client = _server.EndAccept(ar);
        lock (_clients)
        {
            _clients.Add(client);
        }

        Console.WriteLine($"{client.RemoteEndPoint}已经连接");

        StartReceive(client);   // 开始接收客户端发送的数据
        StartSend(client);      // 开始向客户端发送数据
    }

    private void StartReceive(Socket client)
    {
        // 在Socket连接中设置自定义数据
        UserData userData = new UserData();
        userData.Buffer = new byte[1024];
        userData.Socket = client;
        userData.LastActiveTime = DateTime.Now;
        client.Tag = userData;

        try
        {
            client.BeginReceive(userData.Buffer, 0, userData.Buffer.Length, SocketFlags.None, OnDataReceived, client);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"读取数据出错:{ex.Message}");
            client.Close(); // 出现异常关闭连接
        }
    }

    private void OnDataReceived(IAsyncResult ar)
    {
        UserData userData = ar.AsyncState as UserData;
        if (userData == null) return;

        try
        {
            int length = userData.Socket.EndReceive(ar);

            // 如果接收到的数据长度为0表示连接已经断开
            if (length <= 0)
            {
                Console.WriteLine($"{userData.Socket.RemoteEndPoint}已经断开连接");
                userData.Socket.Close();
                return;
            }

            userData.LastActiveTime = DateTime.Now;  // 更新客户端连接的最近活动时间

            string data = Encoding.UTF8.GetString(userData.Buffer, 0, length);
            Console.WriteLine($"接收到客户端({userData.Socket.RemoteEndPoint})的数据:{data}");

            // 处理接收到的数据...

            // 继续从客户端接收数据
            userData.Socket.BeginReceive(userData.Buffer, 0, userData.Buffer.Length, SocketFlags.None, OnDataReceived, userData);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"读取数据出错:{ex.Message}");
            userData.Socket.Close();    // 出现异常关闭连接
        }
    }

    private void StartSend(Socket client)
    {
        // 给客户端发送心跳数据包
        byte[] buffer = Encoding.UTF8.GetBytes("Heartbeat");

        while (client.Connected)
        {
            if (client.Poll(0, SelectMode.SelectWrite))
            {
                try
                {
                    client.Send(buffer);
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"发送数据出错:{ex.Message}");
                    client.Close(); // 出现异常关闭连接
                    return;
                }
            }
            Thread.Sleep(3000); // 每隔3秒发送一次心跳数据包
        }
    }

    public void Start()
    {
        // 在本机的8080端口监听客户端连接
        _server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        _server.Bind(new IPEndPoint(IPAddress.Any, 8080));
        _server.Listen(10);

        Console.WriteLine("服务器已经启动,等待客户端连接...");

        // 开始接受客户端连接
        _server.BeginAccept(OnClientConnected, null);
    }
}

public class UserData
{
    public Socket Socket { get; set; }              // 当前连接的Socket对象
    public byte[] Buffer { get; set; }              // 保存从客户端接收到的数据
    public DateTime LastActiveTime { get; set; }    // 记录最近一次活动时间
}

在客户端

public class Client
{
    private Socket _client; // 客户端Socket对象
    private Timer _timer;   // 定时器,用于定期发送心跳数据包

    public Client()
    {
        _timer = new Timer(OnTimerTick, null, 1000, 1000);
    }

    private void OnTimerTick(object state)
    {
        try
        {
            // 给服务端发送心跳数据包
            byte[] buffer = Encoding.UTF8.GetBytes("Heartbeat");
            _client.Send(buffer);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"发送心跳数据出错:{ex.Message}");
            _client.Close(); // 出现异常关闭连接
        }
    }

    public void Connect(string ip, int port)
    {
        // 连接到指定的服务器端口
        _client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        try
        {
            _client.Connect(ip, port);
            Console.WriteLine($"连接到服务端({ip}:{port})成功");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"连接到服务端({ip}:{port})失败:{ex.Message}");
            return;
        }

        StartReceive(); // 开始接受服务端发送的数据
        StartSend();    // 开始向服务端发送数据
    }

    private void StartReceive()
    {
        // 创建自定义数据对象,用于保存和处理客户端接收到的数据
        UserData userData = new UserData();
        userData.Buffer = new byte[1024];
        userData.Socket = _client;

        try
        {
            // 开始接收服务端发送的数据
            _client.BeginReceive(userData.Buffer, 0, userData.Buffer.Length, SocketFlags.None, OnDataReceived, userData);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"读取数据出错:{ex.Message}");
            _client.Close(); // 出现异常关闭连接
        }
    }

    private void OnDataReceived(IAsyncResult ar)
    {
        UserData userData = ar.AsyncState as UserData;
        if (userData == null) return;

        try
        {
            int length = userData.Socket.EndReceive(ar);

            // 如果数据的长度为0表示服务端已经断开连接
            if (length <= 0)
            {
                Console.WriteLine($"服务端({userData.Socket.RemoteEndPoint})已经断开连接");
                userData.Socket.Close();
                return;
            }

            userData.LastActiveTime = DateTime.Now;  // 更新客户端连接的最近活动时间

            string data = Encoding.UTF8.GetString(userData.Buffer, 0, length);
            Console.WriteLine($"接收到服务端数据:{data}");

            // 处理接收到的数据...

            // 继续从服务端接收数据
            userData.Socket.BeginReceive(userData.Buffer, 0, userData.Buffer.Length, SocketFlags.None, OnDataReceived,
                userData);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"读取数据出错:{ex.Message}");
            userData.Socket.Close();    // 出现异常关闭连接
        }
    }

    private void StartSend()
    {
        // 向服务端发送数据
        string data = "Hello World";
        byte[] buffer = Encoding.UTF8.GetBytes(data);

        while (_client.Connected)
        {
            if (_client.Poll(0, SelectMode.SelectWrite))
            {
                try
                {
                    _client.Send(buffer);
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"发送数据出错:{ex.Message}");
                    _client.Close(); // 出现异常关闭连接
                    return;
                }
            }
            Thread.Sleep(2000); // 每隔2秒向服务端发送一次数据
        }
    }
}

public class UserData
{
    public Socket Socket { get; set; }              // 当前连接的Socket对象
    public byte[] Buffer { get; set; }              // 保存从客户端接收到的数据
    public DateTime LastActiveTime { get; set; }    // 记录最近一次活动时间
}

在上述代码中,客户端和服务端都在定时器中定期发送心跳数据包来维持连接,而服务端在定时器中还要检测是否有连接超时的情况。对于客户端,可以在定时器中定期发送心跳数据包,如果发送数据出错,说明连接已经中断,就可以关闭客户端连接。对于服务端,在定时器中定期检测客户端连接的最近活动时间,如果超过指定的时间,就认为该客户端已经超时,就可以从服务器容器中移除该客户端并关闭连接,以释放服务器资源。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:c# socket心跳超时检测的思路(适用于超大量TCP连接情况下) - Python技术站

(0)
上一篇 2023年6月1日
下一篇 2023年6月1日

相关文章

  • 编写简易Android天气应用的代码示例

    编写Android天气应用是一个常见的练手项目,可以帮助开发者熟悉Android开发的基本流程和技术。本文将提供一个简易的Android天气应用的代码示例,包括两个示例。 示例1:获取天气数据 要编写一个天气应用,首先需要获取天气数据。可以使用第三方天气API来获取天气数据。以下是一个示例: public class WeatherAPI { private…

    C# 2023年5月15日
    00
  • .net实现网站用户登录认证

    下面是“.NET实现网站用户登录认证”的完整攻略: 1. 创建一个ASP.NET Web应用程序 首先,打开Visual Studio IDE,然后在File菜单中选择New -> Project。在新建项目窗口中选择Web -> ASP.NET Web应用程序。 在下一步中,给你的应用程序命名,并选择“Empty”模板。 2. 配置Web.co…

    C# 2023年5月31日
    00
  • C#通过经纬度计算2个点之间距离的实现代码

    计算两个点之间的距离通常使用地理坐标系中的经纬度作为计算的基础。下面是C#实现经纬度计算距离的完整攻略: 步骤一:确定计算方法 在地图中,经纬度之间的直线距离可以使用大圆距离公式(Haversine formula)计算。该公式将两点之间的直线距离表示为球面距离,考虑地球的尺寸与弧度的转换,计算公式如下: dist = 2R * arcsin(sqrt(si…

    C# 2023年5月31日
    00
  • C# 9.0新特性——只初始化设置器

    当我们声明一个类时,经常需要为该类的字段或属性提供一个初始值,以确保在对象创建后这些值处于可用状态。在C# 9.0中,新特性“只初始化设置器”(init-only setters)允许我们在对象创建后,通过只读属性的方式对属性进行初始化。 什么是只初始化设置器 只初始化设置器(init-only setters)是C# 9.0中新出现的特性,只初始化设置器允…

    C# 2023年5月31日
    00
  • asp.net 截取字符串代码

    ASP.NET 截取字符串有多种方法,以下是两种示例代码: 使用 Substring() 方法截取字符串 Substring() 方法可以用于截取一个字符串的一部分,该方法接受两个参数:第一个参数是截取字符串的起点位置,第二个参数是截取字符串的长度。以下是使用 Substring() 方法截取字符串的示例代码: string originalString =…

    C# 2023年5月31日
    00
  • c#如何实现接口事件

    在C#中,接口事件是一种常见的编程模式,它可以帮助开发者实现松耦合的代码结构。在本攻略中,我们将介绍如何在C#中实现接口事件,并提供两个示例来说明其用法。 以下是两个示例,介绍如何在C#中实现接口事件: 示例一:使用委托实现接口事件 首先,我们需要定义一个接口,其中包含一个事件: public interface IMyInterface { event E…

    C# 2023年5月15日
    00
  • ASP.NET ASHX中获得Session的方法

    首先,我们需要了解在 ASP.NET ASHX 中获取 Session 的方法。 在 ASP.NET ASHX 中,我们可以通过 HttpContext.Current.Session 属性访问当前会话(Session)。Session 是一种在服务器端保存用户数据的机制,它可以在同一个用户的多个请求之间共享数据。 以下是一个简单的示例,展示如何在 ASHX…

    C# 2023年6月1日
    00
  • 在Asp.net用C#建立动态Excel

    建立动态Excel是Asp.net应用程序中非常常见的功能需求,通过C#代码动态生成Excel,可以直接展示数据并且有良好的展示效果。 下面是实现“在Asp.net用C#建立动态Excel”的完整攻略: 步骤一:安装相关组件 创建动态Excel需要使用Microsoft Office Excel插件,因此我们需要安装相关组件来支持这一功能。同时,还需要引用M…

    C# 2023年6月7日
    00
合作推广
合作推广
分享本页
返回顶部