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