我有一个允许数千个客户端连接的套接字应用程序。它将它们存储在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);
}
现在我需要更改此应用程序以允许客户随时向我发送任何内容;即使没有我提出要求。其中 - 我认为 - 需要不断接收插座和完全不同的方法。
我的想法:
免责声明:这部分有点长且完全是假设的。如果您对上述问题有答案,可以跳过此步骤。
我的想法:
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
。
答案 0 :(得分:1)
几年前我写了TCP/IP .NET Sockets FAQ来解决一些常见问题(例如message framing,continuous reading和explanations 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
,以便在有人进入时执行委托。