提问人:Chris.J 提问时间:2/7/2022 更新时间:2/8/2022 访问量:805
使用 System.Net.WebSockets.SendAsync 同步发送
Sending syncronously with System.Net.WebSockets.SendAsync
问:
我完全赞成多线程和异步任务,但放弃控制不是我的菜。
我正在做一个项目,其中一个进程通过 websocket 与多个目标通信,发送显然应该尽可能快,但必须相互同步。 考虑将视频流同步流式传输到多个设备。 因此,我不想在第一帧被目标接收到之前继续发送下一帧。
使用 .net WebSockets 时,我已经尝试过使用 HttpListner 和 Kestrel 实现(我猜它们或多或少是相同的),然后是套接字。SendAsync 似乎正在接收数据并在发送数据之前在本地缓冲它,在实际发送之前返回 TaskCompleted。这意味着发送任务基本上是即时完成的。
我当然“可以”实现一个 ACK 模式,其中收件人在收到整个数据包时发送 ACK,但这本身会减慢该过程,我想避免这种情况,因为系统本身已经知道这一点,因为它确实会尝试连续发送排队的包。
这基本上是负责发送数据的代码,以 2048b 的块为单位:
Log.Info($"Send {sendBytes.Length}b");
if (socket!.State == WebSocketState.Open)
{
await socket.SendAsync(
sendBuffer,
WebSocketMessageType.Binary,
endOfMessage: true,
cancellationToken: cts
);
}
Log.Info($"Done ");
我假设这里的等待实际上会等待传输完成,这是不行的,但它似乎在等待任务完成。
在接收方,这是处理数据,调试响应在之前和之后:
wsClientSession.send("REC " + (String)payloadLength + "b");
ProcessFrame(payload, payloadLength);
wsClientSession.send("ACK " + (String)payloadLength + "b");
发送方端的日志记录显示以下内容:
- 线程 28 是发送线程,它完成的速度快得令人难以置信。它需要发送 6198 字节,并在 4 次传输中将其分块。显然,这一切都是在第一个数据包离开服务器之前完成的。
- 其余线程是调试响应的异步接收者。
- (大小差异是由于数据加密造成的)
2022-02-07 16:03:34.8887 threadid:28 # Info # Send 6198b to 5d8263c6-909c-47a1-b4d0-57d74d6a4665
2022-02-07 16:03:34.8887 threadid:28 # Info # Send 2048b
2022-02-07 16:03:34.8887 threadid:28 # Info # Done
2022-02-07 16:03:34.8887 threadid:28 # Info # Send 2048b
2022-02-07 16:03:34.8887 threadid:28 # Info # Done
2022-02-07 16:03:34.8887 threadid:28 # Info # Send 2048b
2022-02-07 16:03:34.8887 threadid:28 # Info # Done
2022-02-07 16:03:34.8887 threadid:28 # Info # Send 128b
2022-02-07 16:03:34.8887 threadid:28 # Info # Done
2022-02-07 16:03:34.8887 threadid:28 # Info # Send done to 5d8263c6-909c-47a1-b4d0-57d74d6a4665
2022-02-07 16:03:35.0024 threadid:7 # Info # 5d8263c6-909c-47a1-b4d0-57d74d6a4665 - REC 2030b
2022-02-07 16:03:35.0090 threadid:7 # Info # 5d8263c6-909c-47a1-b4d0-57d74d6a4665 - ACK 2030b
2022-02-07 16:03:35.0090 threadid:17 # Info # 5d8263c6-909c-47a1-b4d0-57d74d6a4665 - REC 2030b
2022-02-07 16:03:35.0260 threadid:33 # Info # 5d8263c6-909c-47a1-b4d0-57d74d6a4665 - ACK 2030b
2022-02-07 16:03:35.0260 threadid:33 # Info # 5d8263c6-909c-47a1-b4d0-57d74d6a4665 - REC 2030b
2022-02-07 16:03:35.0260 threadid:30 # Info # 5d8263c6-909c-47a1-b4d0-57d74d6a4665 - ACK 2030b
2022-02-07 16:03:35.0373 threadid:17 # Info # 5d8263c6-909c-47a1-b4d0-57d74d6a4665 - REC 108b
2022-02-07 16:03:35.0373 threadid:4 # Info # 5d8263c6-909c-47a1-b4d0-57d74d6a4665 - ACK 108b
现在,我对这个 WebSocket 实现的理解是错误的吗?是否不可能对发送的数据进行回调/事件或类似处理?我能用另一种方式实现这一点吗? 同步执行这个 true 将是一个解决方案,但我似乎无法强迫这样做。
嘎嘎!!
答:
我正在做一个项目,其中一个进程通过 websocket 与多个目标通信,发送显然应该尽可能快,但必须相互同步。考虑将视频流同步流式传输到多个设备。因此,我不想在第一帧被目标接收到之前继续发送下一帧。
井。。。实际上。。。如今,视频和音频传输都倾向于使用 UDP,并且故意设计为允许丢弃数据包和帧。在 30(或 60)fps 的源中丢失一帧几乎不明显,但在重新获取旧帧时在视频上放置微调器非常明显。
因此,也许首先要考虑的是您可能希望(或需要)放宽您的要求。网络与硬连线控件不同,任何这样的解决方案本质上都是分布式的,因此必须具有某种最终一致性或某种分区不容忍(请参阅:CAP 定理)。
套接字。SendAsync 似乎正在接收数据并在发送数据之前在本地缓冲它,在实际发送之前返回 TaskCompleted。这意味着发送任务基本上是即时完成的。
这不仅仅是 websockets、.NET,甚至是 Windows。这是(故意)所有 TCP/IP API 的工作方式。当“发送”到达操作系统缓冲区时,它被视为完成。在 TCP/IP 环境中,可以保证数据最终会到达接收方(可能在重试后),或者您的套接字最终会进入错误状态。
我当然“可以”实现一个 ACK 模式,其中收件人在收到整个数据包时发送 ACK,但这本身会减慢该过程,我想避免这种情况,因为系统本身已经知道这一点,因为它确实会尝试连续发送排队的包。
Websocket 确实具有消息抽象,并为您维护消息边界。但是 websocket 是通过 TCP/IP 实现的,TCP/IP 是一种流抽象(不是消息或数据包)。网络本身使用数据包抽象,而数据包是 ACK/RST/etc 发挥作用的地方。因此,操作系统知道数据包级 ACK/etc,并将从 TCP/IP 流缓冲区中删除字节。但是 websocket 没有消息级 ACK,因此无法知道何时收到消息。
Websockets 建立在 TCP/IP 之上,因此您可以获得相同的保证:您知道发送的消息(最终)将被接收,或者(最终)您将收到错误通知。这几乎就是你所得到的;与 TCP/IP 的唯一主要区别是,您得到的是消息抽象而不是流抽象。
因此,您的选择是:
- 接受最终一致性。同时向所有设备发送消息,并接受某些更新可能会延迟。
- 接受分区不容错。添加 ACK 响应消息,并在所有设备都 ACKED 后发送下一次更新。(这不能容忍分区,因为如果一个设备断开连接,它们都会中断)。
如果您确实使用 ACK-lockstep 方法,您可能需要考虑使用 UDP/IP 而不是基于 TCP/IP 的协议,但这是一项繁重的工作。
同步执行这个 true 将是一个解决方案,但我似乎无法强迫这样做。
同步 API 不是解决方案。没有 API 可以做你想做的事;所有 TCP/IP 写入在到达操作系统时都认为写入“完成”;无论同步/异步、语言、运行时或操作系统如何,都是如此。
评论
SO_SNDBUF
0
评论
something()
actually wait for the transmission to complete, which is doesn't do, but it seems to await the task completedness.
async void
await
async void
Thread 28 is the sending thread, which completes impossibly fast. It needs to send 6198 bytes and chuncks this up in 4 transmissions. It is obviously all done before the first packet has left the server.
如果您忘记了实际调用 .发布足够的代码来重现问题await
SendAsync