请求 - 响应逻辑,允许处理不响应的消息

时间:2012-12-21 13:04:05

标签: c# .net sockets logic communication

我有一个允许数千个客户端连接的套接字应用程序。它将它们存储在ConcurrentDictionary<int, Socket>中,仅用于请求 - 响应情况:

  • 当我需要数据时,我找到相关的套接字并发送请求,询问我需要的数据。
  • 发送请求后,我会收到字节,直到发送响应为止。然后我停止接收。

像这样:

public Task<Message> Request(int clientId, Message message)
{
    Socket client;
    return Clients.TryGetValue(clientId, out client)
        ? RequestInternal(client, message);
        : _EmptyTask;
}

public async Task<Message> RequestInternal(Socket client, Message message)
{
    await SendAsync(client, message).ConfigureAwait(false);
    return await ReceiveOneAsync(client).ConfigureAwait(false);
}

现在我需要更改此应用程序以允许客户随时向我发送任何内容;即使没有我提出要求。其中 - 我认为 - 需要不断接收插座和完全不同的方法。

的问题:

  1. 此类应用程序的赌注已知方法(最佳实践)是什么?
  2. 你可以告诉我任何问题,或者你可以指点我的任何指南吗?

  3. 我的想法:
    免责声明:这部分有点长且完全是假设的。如果您对上述问题有答案,可以跳过此步骤。

    我的想法:

    • 不断接收字节并将汇编的PDU添加到BlockingCollection<Message>
    • 使用BlockingCollection的{​​{1}}方法创建专门处理收到的消息的线程。

    处理线程将执行此操作:

    GetConsumingEnumerable

    有了这个,我可以接收并处理客户端发送的所有内容,但是区分发送的消息以回复我的请求,因为客户端需要这些消息是一个问题。

    我想我可以使用请求发送唯一标识符字节(对于该特定客户端是唯一的)。然后客户端可以在响应中将该标识符发送给我,我可以用它来区分响应。

    foreach (var message in Messages.GetConsumingEnumerable())
        ProcessMessage(message);
    

    这意味着我还必须存储请求。 以下是ProcessMessage(Message msg) { // msg is a message from msg.Sender. if (msg.Id == 0) { // msg is not a response, do processing. } else { // msg is a response to the message that's sent with msg.Id. // Find the request that: // * ...is made to msg.Sender // * ...and has the msg.Id as identifier. // And process the response according to that. } } 的假设版本: 修改 在Stephen Cleary的回答之后用RequestInternal替换了Wait次来电。

    await

    private async Task RequestInternal(Socket client, Message message) { var request = new Request(client, message); Requests.Add(request); await SendAsync(client, message).ConfigureAwait(false); return await request.Source.Task.ConfigureAwait(false); } 类:

    Request

    private sealed class Request { public readonly byte Id; public readonly Socket Client; public readonly Message Message; public readonly TaskCompletionSource<Message> Source; public Request(Socket client, Message message) { Client = client; Message = message; Source = new TaskCompletionSource<Message>(); // Obtain a byte unique to that socket... Id = GetId(client); } } 变成了这个:

    ProcessMessage

    虽然我不知道ProcessMessage(Message msg) { if (msg.Id == 0) OnReceived(msg); // To raise an event. else { // Method to find a request using msg.Sender and msg.Id var request = Requests.Find(msg); if (request != null) request.Source.SetResult(msg); } } 会是什么样的集合类型。

    编辑:我使用了Requests,其中ConcurrentDictionary<Key, Request>是一个私有结构,其Key(套接字的ID)和{{1} (消息的id)字段。它还实现了Int32

1 个答案:

答案 0 :(得分:1)

几年前我写了TCP/IP .NET Sockets FAQ来解决一些常见问题(例如message framingcontinuous readingexplanations of common errors)。代码示例都使用Socket类,但相同的概念适用于所有TCP / IP套接字。

关于您的协议设计和请求/响应匹配,整体方法听起来不错。您需要确保线程安全(例如,Requests可能是ConcurrentDictionary)。此外,您应该await SendAsync而不是致电Wait

我已经玩过但尚未投入生产的替代方法基于TPL Dataflow。您可以创建一个块,表示每个客户端的“输出”,另一个块表示“输入”。然后,您可以在其上对消息框架进行分层,并将请求/响应与其匹配,然后将任何剩余(未经请求的)消息发送到单个共享BufferBlock

因此,您的“最终用户”API最终会如下所示:

// Send a request and asynchronously receive a matching response.
Task<Message> RequestAsync(int clientId, Message message);

// Endpoint for unsolicited messages.
IReceivableSourceBlock<Tuple<int, Message>> UnsolicitedMessages { get; }

然后,您可以将ActionBlock连接到UnsolicitedMessages,以便在有人进入时执行委托。