我正在使用System.Net.WebSockets.ClientWebSocket
创建一个C#WebSocket客户端以连接到服务器,但我不拥有该服务器的源代码。到目前为止,一切工作正常,但我希望“正确”断开客户端连接。这是我的源代码的缩写版本:
public class Client : IDisposable
{
private ClientWebSocket socket;
private string endpoint;
private Task receiveTask;
public Client(string endpoint)
{
this.endpoint = endpoint;
this.socket = new ClientWebSocket();
}
public async Task Initialize()
{
byte[] contentBuffer = Encoding.UTF8.GetBytes("notify message");
// Connect to the server, and send a message to notify
// it of the client's availability to receive data.
await OpenConnection();
await socket.SendAsync(new ArraySegment<byte>(contentBuffer), WebSocketMessageType.Text, true, CancellationToken.None);
}
private async Task OpenConnection()
{
if (socket.State != WebSocketState.Open)
{
await socket.ConnectAsync(new Uri(endpoint), CancellationToken.None);
receiveTask = Task.Run(async () => await Receive());
}
}
private async Task Receive()
{
while (socket.State == WebSocketState.Open)
{
byte[] buffer = new byte[1024];
var result = await m_sessionSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken);
if (result.MessageType == WebSocketMessageType.Close)
{
break;
}
else
{
using (var stream = new MemoryStream())
{
stream.Write(buffer, 0, result.Count);
while (!result.EndOfMessage)
{
result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
stream.Write(buffer, 0, result.Count);
}
stream.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(stream, Encoding.UTF8))
{
string message = reader.ReadToEnd();
// Do stuff with received message
}
}
}
}
}
public void Dispose()
{
// NOTE: This is a gross oversimplification. Assume in the
// actual project that a proper implementation of the Dispose
// pattern has been created.
if (socket.State == WebSocketState.Open)
{
// How do I notify the server of disconnection?
// The below has some sort of race condition, whereby
// it hangs the client.
Task.Run(async () => await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None)).Wait();
}
if (receiveTask != null)
{
receiveTask.Dispose();
}
socket.Dispose()
}
}
现在我要面对的挑战。在完成数据接收之前,我无法处置Task
来运行数据接收。该任务被阻止,等待从服务器接收。尝试从CloseAsync
内部调用Dispose
似乎遇到了某种死锁,因此这可能不是正确的方法。
很显然,我可以使用CancellationTokenSource
将CancellationToken
传递给Receive
方法,然后将其传递给WebSocket的ReceiveAsync
方法。如果这样做,并告诉令牌源标记该令牌,它将释放该块,并且我可以在receive方法内检查令牌中的IsCancellationRequested
标志,并在看到该标志时退出。但是,如果我这样做,它将引发异常。我是否真的需要在Dispose方法中捕获一个异常(糟糕!),只是想取消任务?另外,如果我取消任务,是否可以正确通知服务器断开连接?还是我首先只是通过长时间运行(未等待)Task
运行ReceiveAsync
方法来做到这一点?
答案 0 :(得分:0)
在这种情况下,您不应使用IDisposable。正常关闭Websocket连接涉及异步I / O。它需要干净的“关闭握手”,即发送方发送CLOSE帧而接收方发送回CLOSE帧。只有这样,连接过渡到干净的套接字(使用FIN而不是RST)才会关闭。
如果需要“ IDisposable”语义,则可以研究最新.NET Core中引入的新IAsyncDisposable模式。但是总的来说,使用同步Dispose()模式不是一个好主意。