C# Socket TCP 2 heartbeat protocol + anti-stain packet + proactively disconnect from the server

In the previous code, a C# .net TcpClient implemented the full-duplex communication protocol_tcpclient protocol_Green Lemon 676’s Blog-CSDN Blog

We have implemented the heartbeat of TCP and introduced the full-duplex communication protocol. Today we will take you to understand what TCP packet immersion is and how TCP prevents packet immersion.

So let’s make a long story short

Why does TCP stick to packets?

By default, the TCP connection will enable the delayed transmission algorithm (Nagle algorithm), buffering data before sending them. If multiple data are sent in a short period of time, it will be buffered until the initial sending (buffering socket.bufferSize ), which can reduce IO consumption and improve performance.

How do I deal with TCP packet leakage?

In order to solve the problem, we need to add a packet header to each TCP call. The packet header represents its number of bytes. Because the packet header is an integer, it is usually a 4-byte packet. When reading, I can know how many bytes I should read, then the theory exists and practice begins.

Server-side code

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("The server has been started, waiting for client connection...");

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

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

            Console.WriteLine("Client connected: {0}", client.Client.RemoteEndPoint);

            clients.Add(client);

            for (int i = 0; i < 1000; i + + )
            {
                //Send data to the client, including headers
                SendDataWithHeader(client, "Lemon19928" + i);
                Console.WriteLine("Send once");
            }

            //Start the thread and receive client messages
            ThreadPool.QueueUserWorkItem(ReceiveMessage, client);
        }
    }

    /// <summary>
    /// send data
    /// </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>
    /// Method to send a 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("Failed to send message to client {0}: {1}", client.Client.RemoteEndPoint, ex.Message);
                clients.Remove(client);
            }
        }
    }

    /// <summary>
    /// Read client information
    /// </summary>
    /// <param name="state"></param>
    static voidReceiveMessage(object state)
    {
        TcpClient client = (TcpClient)state;
        NetworkStream stream = client.GetStream();

        while(true)
        {
            // Read the header and get the size of the message
            int messageSize = ReadHeader(stream);

            //Read message content
            byte[] buffer = new byte[messageSize];

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

            if (bytesRead < messageSize)
            {
                Console.WriteLine("Unable to read the message, the connection may be disconnected: {0}", client.Client.RemoteEndPoint);
                clients.Remove(client);
                break;
            }

            string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);
            Console.WriteLine("Received client message: {0}", message);

            if (message == "keepAlive")
            {
                //The client sends a heartbeat command and closes the connection
                Console.WriteLine("The client sent a heartbeat command and closed the connection: {0}", client.Client.RemoteEndPoint);
                clients.Remove(client);
                client.Close();
                break;
            }
        }
    }

    /// <summary>
    /// Parse how many bytes there are in the header
    /// </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("Unable to read the header, the connection may be disconnected");
            return -1;
        }

        return BitConverter.ToInt32(header, 0);
    }

    /// <summary>
    /// Wait for all streams to be obtained, and then throw them out
    /// </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)
            {
                // The connection was lost or the end of the stream was encountered
                return bytesRead;
            }
            bytesRead + = result;
        }
        return bytesRead;
    }
}

Client code

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>
    /// Read server information
    /// </summary>
    /// <param name="stream"></param>
    static void Write(NetworkStream stream)
    {
        int i = 0;

        while(true)
        {
            // Read the header and get the size of the message
            int messageSize = ReadHeader(stream);

            if (messageSize == -1)
            {
                Console.WriteLine("Disconnected from the server");
                break;
            }
            //Read message content
            byte[] buffer = new byte[messageSize];

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

            if (bytesRead < messageSize)
            {
                Console.WriteLine("Unable to read the message, the connection may be disconnected");
                break;
            }

            string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);
            Console.WriteLine("Received server message: {0}", message);
            i + = 1;
            SendDataWithHeader(stream, "Hello server, I am the client packet" + i + "I received" + messageSize + "pieces of data");
        }
    }

    /// <summary>
    /// Send information to the server
    /// </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>
    /// Send heartbeat
    /// </summary>
    /// <param name="state"></param>
    static void SendInstruction(object state)
    {
        TcpClient client = (TcpClient)state;
        NetworkStream stream = client.GetStream();

        while(true)
        {
            //Send an instruction to the server every 2 minutes
            Thread.Sleep(122000);
            SendDataWithHeader(stream, "keepAlive");
            Console.WriteLine("Send a heartbeat command to close the connection");
        }
    }

    /// <summary>
    /// Parse how many bytes there are in the header
    /// </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("Unable to read the header, the connection may be disconnected");
            return -1;
        }

        return BitConverter.ToInt32(header, 0);
    }

    /// <summary>
    /// Wait for all streams to be obtained, and then throw them out
    /// </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)
            {
                // The connection was lost or the end of the stream was encountered
                return bytesRead;
            }
            bytesRead + = result;
        }
        return bytesRead;
    }
}