【发布时间】:2021-04-02 13:53:17
【问题描述】:
我在我的 C# 网络项目中使用 Stateless,主要是因为它是添加功能的好方法,例如套接字连接后的线级授权、重新连接延迟等。
话虽如此,我自己也遇到了一些竞争条件和死锁 - 就以下状态的最佳处理方式寻求建议:
enum State { Stopped, Disconnected, Connecting, Connected, Resetting }
enum Trigger { Start, Stop, Connect, SetConnectComplete, Reset, SetResetComplete }
class StateMachine : StateMachine<State, Trigger>
{
public StateMachine(Action OnDisconnected, Action OnConnecting, Action OnConnected, Action OnResetting) : base(State.Stopped)
{
this.Configure(State.Stopped)
.Permit(Trigger.Start, State.Disconnected);
this.Configure(State.Disconnected)
.OnEntry(OnDisconnected)
.Permit(Trigger.Connect, State.Connecting);
this.Configure(State.Connecting)
.OnEntry(OnConnecting)
.Permit(Trigger.SetConnectComplete, State.Connected)
.Permit(Trigger.Reset, State.Resetting);
this.Configure(State.Connected)
.OnEntry(OnConnected)
.Permit(Trigger.Reset, State.Resetting);
this.Configure(State.Resetting)
.OnEntry(OnResetting)
.Permit(Trigger.SetResetComplete, State.Disconnected);
}
}
这个功能是套接字将自动重新连接,并在连接时启动接收循环。如果发生套接字错误,它应该返回以释放资源,然后循环返回以重新启动。
但是,当我处理对象时,连接的套接字中止,这也释放了资源,并且它尝试等待自己。
我相信这与等待自身的线程有关,所以我的设计/状态结构从根本上肯定是关闭的,并且很欣赏可以完全避免死锁的更好结构的指针。
public class ManagedWebSocket : IDisposable
{
readonly StateMachine stateMachine;
Task backgroundReaderTask;
private ClientWebSocket webSocket;
private readonly ITargetBlock<byte[]> target;
private readonly ILogger<ManagedWebSocket> logger;
private CancellationTokenSource cancellationTokenSource;
bool isDisposing;
public ManagedWebSocket(string uri, ITargetBlock<byte[]> target, ILogger<ManagedWebSocket> logger)
{
this.stateMachine = new StateMachine(OnDisconnected, OnConnecting, OnConnected, OnResetting);
this.target = target;
this.logger = logger;
}
private void OnConnecting()
{
this.backgroundReaderTask = Task.Run(async () =>
{
this.cancellationTokenSource = new CancellationTokenSource();
this.webSocket = new ClientWebSocket();
webSocket.Options.KeepAliveInterval = KeepAliveInterval;
try
{
await this.webSocket.ConnectAsync(this.uri, cancellationTokenSource.Token);
}
catch(WebSocketException ex)
{
this.logger.LogError(ex.Message, ex);
await this.stateMachine.FireAsync(Trigger.Reset);
}
this.stateMachine.Fire(Trigger.SetConnectComplete);
});
}
private void OnDisconnected()
{
if (isDisposing == false)
this.stateMachine.Fire(Trigger.Connect);
}
private void OnResetting()
{
FreeResources();
this.stateMachine.Fire(Trigger.SetResetComplete);
}
private void OnConnected()
{
this.backgroundReaderTask = Task.Run( async () => {
try
{
// returns when the internal frame loop completes with websocket close, or by throwing an exception
await this.webSocket.ReceiveFramesLoopAsync(target.SendAsync, 2048, this.cancellationTokenSource.Token);
}
catch (Exception ex)
{
this.logger.LogError(ex.Message, ex);
}
await this.stateMachine.FireAsync(Trigger.Reset);
});
}
public async Task SendAsync(byte[] data, WebSocketMessageType webSocketMessageType)
{
if (this.stateMachine.State != State.Connected)
throw new Exception($"{nameof(ManagedWebSocket)} is not yet connected.");
try
{
await webSocket
.SendAsChunksAsync(data, webSocketMessageType, 2048, this.cancellationTokenSource.Token)
.ConfigureAwait(false);
}
catch (Exception ex)
{
this.logger.LogError(ex, ex.Message);
await this.stateMachine.FireAsync(Trigger.Reset);
}
}
public void Start()
{
this.stateMachine.Fire(Trigger.Start);
}
public void FreeResources()
{
this.logger.LogDebug($"{nameof(ManagedWebSocket.FreeResources)}");
this.cancellationTokenSource?.Cancel();
this.backgroundReaderTask?.Wait();
this.cancellationTokenSource?.Dispose();
this.backgroundReaderTask?.Dispose();
}
public void Dispose()
{
if (isDisposing)
return;
isDisposing = true;
FreeResources();
}
}
【问题讨论】:
-
你为什么使用 websocket?您是建立 HTTP 连接还是 TCP 连接?一个 weboscket 你只有一个请求和一个响应。 KeepAlive 没有意义。
-
是的,这是按照github.com/polygon-io/client-cs/blob/master/websocket_example/… 设计的,我需要订阅、验证订阅等,因此需要查看状态机
-
一个 websocket 是 HTTP 并且使用 TCP 作为传输层。在网络托管库中,您无法从 websocket 访问 TCP(您正在使用的状态)。一个 websocket 你得到一个请求和一个响应。因此,您要么得到响应,要么在没有得到响应时检查超时。如果你想要 TCP 状态,那么你需要一个使用 TCP 连接实现 HTTP 协议的库。
-
感谢.gbr 回复 jdweng,websocket 绝对是一个数据流,根据我发送的链接,每秒传输数万条单独的消息。似乎我们的术语可能会跨越电线,我使用底层 ClientWebSocket 并且它以流的形式发送数据。其他库(例如链接中的库)使用 similair .net 类。
-
您在使用 Netstat 时是否收到“等待时间”错误? TCP 连接只能在连接的一端关闭。自 1970 年代以来,由于 RFC 规范的问题,TCP 存在一个已知的竞争条件。当 TCP 连接的两端同时关闭时,就会出现竞争条件。 TCP 每个命令都会收到一个 ACK。当连接的两端同时关闭时,一端没有收到 ACK。在 Windows 中,您使用 Netstat 获得时间等待状态。这意味着连接正在等待对 Close 命令的 ACK。
标签: c# state-machine