C# Socket TCP 2 心跳协议+防沾包+主动向服务端断开连接

在之前的代码中一个C# .net TcpClient实现的全双工通讯协议_tcpclient协议_青柠檬676的博客-CSDN博客

我们实现了TCP的心跳加上全双工通讯协议的介绍,今天将带大家了解什么是TCP沾包和TCP如何防止沾包

那么我们长话短说

TCP为什么会沾包?

默认情况下, TCP 连接会启⽤延迟传送算法 (Nagle 算法), 在数据发送之前缓存他们. 如果短时间有多个数据发送, 会缓冲到⼀起作⼀次发送 (缓冲⼤⼩⻅ socket.bufferSize ), 这样可以减少 IO 消耗提⾼性能.

我是如何处理TCP的沾包问题的?

为了处理问题,我们需要在TCP每次通话时加上一个包头,包头代表着他的字节数,因为包头是整型的,所以通常是4字节的一个包,这时读取的时候就可以知道我该读多少字节了,那么理论存在,实践开始.

服务端代码

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

class Program
{
    static List<TcpClient> clients = new List<TcpClient>();

    static void Main(string[] args)
    {
        TcpListener listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 8080);
        listener.Start();

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

        Timer timer = new Timer(SendHelloMessage, null, TimeSpan.Zero, TimeSpan.FromSeconds(20));

        while (true)
        {
            TcpClient client = listener.AcceptTcpClient();

            Console.WriteLine("客户端已连接:{0}", client.Client.RemoteEndPoint);

            clients.Add(client);

            for (int i = 0; i < 1000; i++)
            {
                // 向客户端发送数据,包括包头
                SendDataWithHeader(client, "柠檬19928" + i);
                Console.WriteLine("发送一次");
            }

            // 启动线程,接收客户端消息
            ThreadPool.QueueUserWorkItem(ReceiveMessage, client);
        }
    }

    /// <summary>
    /// 发送数据
    /// </summary>
    /// <param name="client"></param>
    /// <param name="message"></param>
    static void SendDataWithHeader(TcpClient client, string message)
    {
        byte[] messageBytes = Encoding.UTF8.GetBytes(message);
        byte[] header = BitConverter.GetBytes(messageBytes.Length);

        byte[] data = new byte[header.Length + messageBytes.Length];
        Array.Copy(header, 0, data, 0, header.Length);
        Array.Copy(messageBytes, 0, data, header.Length, messageBytes.Length);

        NetworkStream stream = client.GetStream();
        stream.Write(data, 0, data.Length);
    }

    /// <summary>
    /// 发送一个Hello的方法
    /// </summary>
    /// <param name="state"></param>
    static void SendHelloMessage(object state)
    {
        foreach (TcpClient client in clients)
        {
            try
            {
                SendDataWithHeader(client, "Hello!");
            }
            catch (Exception ex)
            {
                Console.WriteLine("向客户端 {0} 发送消息失败:{1}", client.Client.RemoteEndPoint, ex.Message);
                clients.Remove(client);
            }
        }
    }

    /// <summary>
    /// 读取客户端信息
    /// </summary>
    /// <param name="state"></param>
    static void ReceiveMessage(object state)
    {
        TcpClient client = (TcpClient)state;
        NetworkStream stream = client.GetStream();

        while (true)
        {
            // 读取包头,获取消息的大小
            int messageSize = ReadHeader(stream);

            // 读取消息内容
            byte[] buffer = new byte[messageSize];

            int bytesRead = ReadExactly(stream, buffer, 0, messageSize);

            if (bytesRead < messageSize)
            {
                Console.WriteLine("无法读取消息,可能是连接断开:{0}", client.Client.RemoteEndPoint);
                clients.Remove(client);
                break;
            }

            string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);
            Console.WriteLine("收到客户端消息:{0}", message);

            if (message == "keepAlive")
            {
                // 客户端发送心跳指令,关闭连接
                Console.WriteLine("客户端发送了心跳指令,关闭连接:{0}", client.Client.RemoteEndPoint);
                clients.Remove(client);
                client.Close();
                break;
            }
        }
    }

    /// <summary>
    /// 解析包头里面有多少字节
    /// </summary>
    /// <param name="stream"></param>
    /// <returns></returns>
    static int ReadHeader(NetworkStream stream)
    {
        byte[] header = new byte[4];
        int headerBytesRead = stream.Read(header, 0, 4);

        if (headerBytesRead < 4)
        {
            Console.WriteLine("无法读取包头,可能是连接断开");
            return -1;
        }

        return BitConverter.ToInt32(header, 0);
    }

    /// <summary>
    /// 等待流全部获取到,然后丢出去
    /// </summary>
    /// <param name="stream"></param>
    /// <param name="buffer"></param>
    /// <param name="offset"></param>
    /// <param name="count"></param>
    /// <returns></returns>
    static int ReadExactly(NetworkStream stream, byte[] buffer, int offset, int count)
    {
        int bytesRead = 0;
        while (bytesRead < count)
        {
            int result = stream.Read(buffer, offset + bytesRead, count - bytesRead);
            if (result == 0)
            {
                // 连接断开或遇到流的末尾
                return bytesRead;
            }
            bytesRead += result;
        }
        return bytesRead;
    }
}

客户端代码

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        TcpClient client = new TcpClient("127.0.0.1", 8080);
        byte[] buffer = new byte[1024];

        NetworkStream stream = client.GetStream();

        Task.Factory.StartNew(() => { Write(stream); });

        ThreadPool.QueueUserWorkItem(SendInstruction, client);

        while (true)
        {
            string sendMsg = Console.ReadLine();
            SendDataWithHeader(stream, sendMsg);
        }
    }

    /// <summary>
    /// 读取服务端信息
    /// </summary>
    /// <param name="stream"></param>
    static void Write(NetworkStream stream)
    {
        int i = 0;

        while (true)
        {
            // 读取包头,获取消息的大小
            int messageSize = ReadHeader(stream);

            if (messageSize == -1)
            {
                Console.WriteLine("与服务端连接断开");
                break;
            }
            // 读取消息内容
            byte[] buffer = new byte[messageSize];

            int bytesRead = ReadExactly(stream, buffer, 0, messageSize);

            if (bytesRead < messageSize)
            {
                Console.WriteLine("无法读取消息,可能是连接断开");
                break;
            }

            string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);
            Console.WriteLine("收到服务器消息:{0}", message);
            i += 1;
            SendDataWithHeader(stream, "你好 服务端,我是客户端小包" + i + "我收到了" + messageSize + "条数据");
        }
    }

    /// <summary>
    /// 发送信息给服务端
    /// </summary>
    /// <param name="stream"></param>
    /// <param name="message"></param>
    static void SendDataWithHeader(NetworkStream stream, string message)
    {
        byte[] messageBytes = Encoding.UTF8.GetBytes(message);
        byte[] header = BitConverter.GetBytes(messageBytes.Length);

        byte[] data = new byte[header.Length + messageBytes.Length];
        Array.Copy(header, 0, data, 0, header.Length);
        Array.Copy(messageBytes, 0, data, header.Length, messageBytes.Length);

        stream.Write(data, 0, data.Length);
    }

    /// <summary>
    /// 发送心跳
    /// </summary>
    /// <param name="state"></param>
    static void SendInstruction(object state)
    {
        TcpClient client = (TcpClient)state;
        NetworkStream stream = client.GetStream();

        while (true)
        {
            // 每隔2分钟向服务端发送一条指令
            Thread.Sleep(122000);
            SendDataWithHeader(stream, "keepAlive");
            Console.WriteLine("发送心跳指令关闭连接");
        }
    }

    /// <summary>
    /// 解析包头中有多少字节
    /// </summary>
    /// <param name="stream"></param>
    /// <returns></returns>
    static int ReadHeader(NetworkStream stream)
    {
        byte[] header = new byte[4];
        int headerBytesRead = stream.Read(header, 0, 4);

        if (headerBytesRead < 4)
        {
            Console.WriteLine("无法读取包头,可能是连接断开");
            return -1;
        }

        return BitConverter.ToInt32(header, 0);
    }

    /// <summary>
    /// 等待流全部获取到,然后丢出去
    /// </summary>
    /// <param name="stream"></param>
    /// <param name="buffer"></param>
    /// <param name="offset"></param>
    /// <param name="count"></param>
    /// <returns></returns>
    static int ReadExactly(NetworkStream stream, byte[] buffer, int offset, int count)
    {
        int bytesRead = 0;
        while (bytesRead < count)
        {
            int result = stream.Read(buffer, offset + bytesRead, count - bytesRead);
            if (result == 0)
            {
                // 连接断开或遇到流的末尾
                return bytesRead;
            }
            bytesRead += result;
        }
        return bytesRead;
    }
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值