侧边栏壁纸
  • 累计撰写 185 篇文章
  • 累计创建 77 个标签
  • 累计收到 17 条评论

目 录CONTENT

文章目录

[C#]中TCP连接使用KeepAlive实现自动重连和保活

码峰
2022-10-10 / 0 评论 / 3 点赞 / 1,824 阅读 / 1,211 字 / 正在检测是否收录...
广告 广告

前言

在网络通讯中,默认情况下如果TCP客户端或服务端异常断开了(由于网络原因),另外一端不收发数据的情况下,是无法知道对方已经断开了的,如果后续要继续发送出数据,就会产生异常。Server/Client真的会不知道已经建立连接的对方已经不在了吗❓ 会他当然会知道但是预设为两小时后😶。通过查阅资料和测试,发现在C#中原来可以设定Keep-Alive来保持Socket长连接,并且侦测网络异常抛出Exception,当Server/Client端发生不正常断线时,Server端将会立刻知道~~~
TCP KeepAlive

C#中KeepAlive的实现

KeepAlive配置数据

通过以下函数来实现KeepAlive的配置数据的生成:

    /// <summary>
    /// 获取KeepAlive的控制参数
    /// </summary>
    /// <param name="onOff">是否开启KeepAlive</param>
    /// <param name="keepAliveTime">当开启KeepAlive后,经过多长时间开始侦测,单位ms</param>
    /// <param name="keepAliveInterval">侦测间隔时间,单位ms</param>
    /// <returns></returns>
    private static byte[] GetKeepAliveConfig(int onOff, int keepAliveTime, int keepAliveInterval)
    {
        byte[] buffer = new byte[12];
        BitConverter.GetBytes(onOff).CopyTo(buffer, 0);
        BitConverter.GetBytes(keepAliveTime).CopyTo(buffer, 4);
        BitConverter.GetBytes(keepAliveInterval).CopyTo(buffer, 8);
        return buffer;
    }

服务端的代码

服务端的主要流程就是创建Socket并绑定到要监听的端口,然后通过IOControl配置KeepAlive,并启动异步接收。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace TCP_Server
{
    class Program
    {
        /// <summary>
        /// 获取KeepAlive的控制参数
        /// </summary>
        /// <param name="onOff">是否开启KeepAlive</param>
        /// <param name="keepAliveTime">当开启KeepAlive后,经过多长时间开始侦测,单位ms</param>
        /// <param name="keepAliveInterval">侦测间隔时间,单位ms</param>
        /// <returns></returns>
        private static byte[] GetKeepAliveConfig(int onOff, int keepAliveTime, int keepAliveInterval)
        {
            byte[] buffer = new byte[12];
            BitConverter.GetBytes(onOff).CopyTo(buffer, 0);
            BitConverter.GetBytes(keepAliveTime).CopyTo(buffer, 4);
            BitConverter.GetBytes(keepAliveInterval).CopyTo(buffer, 8);
            return buffer;
        }

        static byte[] tcpRecvBuffer = new byte[65536];
        static void Main(string[] args)
        {
            //ip地址
            IPAddress ip = IPAddress.Any;
            //端口号
            IPEndPoint point = new IPEndPoint(ip, 61003);
            //创建监听用的Socket            
            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);            
            //绑定监听的端口
            socket.Bind(point);
            socket.Listen(10);
            socket.IOControl(IOControlCode.KeepAliveValues, GetKeepAliveConfig(1, 1000, 1000), null);
            while (true)
            {
                try
                {
                    var newClient = socket.Accept();
                    Console.WriteLine("Connect from {0} at {1}", newClient.RemoteEndPoint, DateTime.Now.TimeOfDay);
                    newClient.BeginReceive(tcpRecvBuffer, 0, tcpRecvBuffer.Length, 0, new AsyncCallback(ReceivedCallBack), newClient);
                }
                catch
                {

                }
            }
        }

        private static void ReceivedCallBack(IAsyncResult ar)
        {
            Socket state = (Socket)ar.AsyncState;
            if (state.Connected == true)
            {
                try
                {
                    int bytesRead = state.Available;
                    if (bytesRead > 0) //当 bytesRead大于0时,表示Client传送数据过来,为0时表示Client"正常"断线
                    {
                        Console.WriteLine("bytesRead={0}", bytesRead); 
                        state.EndReceive(ar);
                        state.BeginReceive(tcpRecvBuffer, 0, tcpRecvBuffer.Length, 0, new AsyncCallback(ReceivedCallBack), state);
                    }
                    else
                    {
                        //处理Client正常断开
                        Console.WriteLine("{0}正常断线", state.RemoteEndPoint); 
                    }
                }
                catch (Exception ee)
                {
                    //客户端异常断开会执行到这里
                    Console.WriteLine("{0}异常断线", state.RemoteEndPoint); 
                }
            }
            else
            {
                state.Close();
                Console.WriteLine("{0}已断开", state.RemoteEndPoint); 
            }
        }
    }
}

客户端的代码

客户端先创建Socket并与服务器建立连接,然后通过IOControl配置KeepAlive,并启动异步接收。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace TCP_Client
{
    class Program
    {
        private static byte[] GetKeepAliveConfig(int onOff, int keepAliveTime, int keepAliveInterval)
        {
            byte[] buffer = new byte[12];
            BitConverter.GetBytes(onOff).CopyTo(buffer, 0);
            BitConverter.GetBytes(keepAliveTime).CopyTo(buffer, 4);
            BitConverter.GetBytes(keepAliveInterval).CopyTo(buffer, 8);
            return buffer;
        }

        private static byte[] recvBuffer = new byte[65536];

        static void Main(string[] args)
        {
            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socket.Bind(new IPEndPoint(IPAddress.Any, 0));
            socket.Connect("172.16.1.11", 61003);
            socket.IOControl(IOControlCode.KeepAliveValues, GetKeepAliveConfig(1, 1000, 1000), null);
            socket.BeginReceive(recvBuffer, 0, recvBuffer.Length, 0, new AsyncCallback(ReceivedCallBack), socket);
            Console.ReadKey();
        }

        private static void ReceivedCallBack(IAsyncResult ar)
        {
            Socket state = (Socket) ar.AsyncState;
            if (state.Connected == true)
            {
                try
                {
                    int bytesRead = state.EndReceive(ar);
                    if (bytesRead == 0)
                    {
                        Console.WriteLine("正常断开");
                    }
                    else
                    {
                        state.BeginReceive(recvBuffer, 0, recvBuffer.Length, 0, new AsyncCallback(ReceivedCallBack), state);
                    }
                }
                catch
                {
                    Console.WriteLine("异常断开");
                }
            }
            else
            {
                Console.WriteLine("已断开");
            }
        }
    }
}

测试结果

先运行服务端,再运行客户端(分别配置KeepAlive和不配置KeepAlive),客户端与服务端建立连接后,将客户端的网线拔出,造成客户端异常断线的场景。我们程序中设置的KeepAlive的间隔是1秒,KeepAlive机制会重试10次,如果都无法保活成功,这认为连接已经断掉了,运行结果下图所示:

  • 服务端:
Connect from 172.16.1.8:50527 at 16:32:05.2478575
172.16.1.8:50527异常断线
  • 客户端1(配置了KeepAlive)等待10秒左右
异常断线
  • 客户端2(未配置了KeepAlive)等待10秒左右
    并无打印输出,说明未配置KeepAlive短时间之内并不能检测到与服务端已经断线了。
3
广告 广告

评论区