TcpListener每5秒发送一次心跳并从客户端异步

时间:2017-03-31 12:40:40

标签: c# asynchronous async-await tcplistener

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

我有接收消息并返回响应的部分。我遇到的问题是客户希望每隔5秒发送一次心跳消息(S0000E),并使用相同的消息回复该消息。我不知道如何在async / await中添加该功能,以处理从客户端收到的真实消息。

的OnStart

_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);
}

我以为我可以在Task.WhenAny添加一个心跳任务,但这会导致心跳总是开始,我永远无法读取响应。我也尝试在超时之前发送心跳并读取任务,这对于发送起作用,但后来我正在读取心跳响应而不是下一行消息或超时任务将完成并断开客户端。基本上,如果心跳交换成功,那么在15秒延迟后客户端不应该断开连接。

1 个答案:

答案 0 :(得分:2)

实现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);
        }
    }
}

演示客户:

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);
        }
    }
}