C# TcpListener每5秒发送一次心跳信号,并从客户端异步读取消息

C# TcpListener每5秒发送一次心跳信号,并从客户端异步读取消息,c#,asynchronous,async-await,tcplistener,C#,Asynchronous,Async Await,Tcplistener,我正在编写一个TcpListener服务,它作为Windows服务运行,供应商软件客户端将连接到该服务。我不能更改客户端的实现,必须遵守它们的规范 我拥有接收消息并返回响应的部分。我遇到的问题是,客户端希望每5秒发送一条心跳消息(S0000E),并用相同的消息回复该消息。我不知道如何在处理从客户机接收到的真实消息所做的异步/等待工作中添加该功能 启动 _serverListenerTask = Task.Run(() => AcceptClientsAsync(_listener, _ca

我正在编写一个TcpListener服务,它作为Windows服务运行,供应商软件客户端将连接到该服务。我不能更改客户端的实现,必须遵守它们的规范

我拥有接收消息并返回响应的部分。我遇到的问题是,客户端希望每5秒发送一条心跳消息(
S0000E
),并用相同的消息回复该消息。我不知道如何在处理从客户机接收到的真实消息所做的异步/等待工作中添加该功能

启动

_serverListenerTask = Task.Run(() => AcceptClientsAsync(_listener, _cancellationToken.Token));
AcceptClientsAsync

static async Task AcceptClientsAsync(TcpListener listener, CancellationToken ct)
{
    var clientCounter = 0;
    while (!ct.IsCancellationRequested)
    {
        TcpClient client = await listener.AcceptTcpClientAsync()
                                         .ConfigureAwait(false);
        clientCounter++;
        await ReceiveMessageAsync(client, clientCounter, ct);
    }
}
ReceiveMessageAsync

static async Task ReceiveMessageAsync(TcpClient client, int clientIndex, CancellationToken ct)
{
    Log.Info("New client ({0}) connected", clientIndex);
    using (client)
    {
        var buffer = new byte[4096];
        var stream = client.GetStream();
        while (!ct.IsCancellationRequested)
        {
            var timeoutTask = Task.Delay(TimeSpan.FromSeconds(15));
            var amountReadTask = stream.ReadAsync(buffer, 0, buffer.Length, ct);

            var completedTask = await Task.WhenAny(timeoutTask, amountReadTask)
                                          .ConfigureAwait(false);

            if (completedTask == timeoutTask)
            {
                var msg = Encoding.ASCII.GetBytes("Client timed out");
                await stream.WriteAsync(msg, 0, msg.Length);
                break;
            }

            var bytesRead = amountReadTask.Result;
            if (bytesRead == 0)
            {
                // Nothing was read
                break;
            }

            // Snip... Handle message from buffer here

            await stream.WriteAsync(responseBuffer, 0, responseBuffer.Length, ct)
                        .ConfigureAwait(false);
        }
    }
    Log.Info("Client ({0}) disconnected", clientIndex);
}

我想我可以在
任务中添加一个心跳任务。当任何时候,
,但这会导致心跳总是启动,我永远无法读取响应。我还尝试在超时和读取任务之前发送心跳信号,这对发送起到了作用,但是我要么正在读取心跳信号响应,而不是队列中的下一条消息,要么超时任务将完成并断开客户端的连接。本质上,如果心跳交换成功,那么客户端不应该在15秒延迟后断开连接。

实现TCP服务器客户端不是一项简单的任务。但是,如果您对其进行改进以提高资源效率,则以下实施方式可能是一个切实可行的解决方案:

服务器:

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

namespace Server
{
    public class Program
    {
        static List<SocketBuffer> clients = new List<SocketBuffer>();
        public static void Main(string[] args)
        {
            //Receive from any IP, listen on port 65000 in this machine
            var listener = new TcpListener(IPAddress.Any, 65000);
            var t = Task.Run(() =>
            {
                while (true)
                {
                    listener.Start();
                    var task = listener.AcceptTcpClientAsync();
                    task.Wait();
                    clients.Add(new SocketBuffer(task.Result, new byte[4096]));
                }
            });
            t.Wait();    //It will remain here, do in a better way if you like !
        }
    }

    /// <summary>
    /// We need this class because each TcpClient will have its own buffer
    /// </summary>
    class SocketBuffer
    {
        public SocketBuffer(TcpClient client, byte[] buffer)
        {
            this.client = client;
            stream = client.GetStream();
            this.buffer = buffer;

            receiveData(null);
        }

        private TcpClient client;
        private NetworkStream stream;
        private byte[] buffer;

        private object _lock = new object();
        private async void receiveData(Task<int> result)
        {
            if (result != null)
            {
                lock (_lock)
                {
                    int numberOfBytesRead = result.Result;
                    //If no data read, it means we are here to be notified that the tcp client has been disconnected
                    if (numberOfBytesRead == 0)
                    {
                        onDisconnected();
                        return;
                    }
                    //We need a part of this array, you can do it in more efficient way if you like
                    var segmentedArr = new ArraySegment<byte>(buffer, 0, numberOfBytesRead).ToArray();
                    OnDataReceived(segmentedArr);
                }

            }
            var task = stream.ReadAsync(buffer, 0, buffer.Length);
            //This is not recursion in any sense because the current 
            //thread will be free and the call to receiveData will be from a new thread
            await task.ContinueWith(receiveData);       
        }

        private void onDisconnected()
        {
            //Add your code here if you want this event
        }

        private void OnDataReceived(byte[] dat)
        {
            //Do anything with the data, you can reply here. I will just pring the received data from the demo client
            string receivedTxt = Encoding.ASCII.GetString(dat);
            Console.WriteLine(receivedTxt);
        }
    }
}

如何将一条消息与另一条消息区分开来?我读对了吗?协议通常是客户机请求->服务器响应,但在这个特定实例中是服务器心跳->客户机心跳?协议中是否有特定规则,例如,当响应未完成时,允许/不允许您发送心跳消息?明确定义此处涉及的(类型)消息及其相关规则(即“协议”)将有所帮助。如果服务器选择在客户机选择发送请求的同一时刻发送心跳信号,则这似乎也具有内在的灵活性。您应该能够放弃超时要求。如果您定期发送心跳,您将检测到断开的连接。@Damien_The_unsiver是的,通常客户端正在发送消息,服务器必须以确认消息作为响应。但是,心跳信号应该从服务器发送,从客户端返回。我不知道什么时候可以/不能发送邮件是否有规定。遗憾的是,没有关于此传统供应商协议的文档。
using System;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace Client
{
    public class Program
    {
        public static void Main(string[] args)
        {
            TcpClient client = new TcpClient();
            var task = client.ConnectAsync("localhost", 65000);
            task.Wait();
            if(client.Connected)
            {
                Console.WriteLine("Client connected");
                var stream = client.GetStream();
                var data = Encoding.ASCII.GetBytes("test");
                stream.Write(data, 0, data.Length);
            }
            else
            {
                Console.WriteLine("Client NOT connected");
            }
            Thread.Sleep(60000);
        }
    }
}