Skip to content

Commit d7cb0df

Browse files
committed
fix: 修复对客户端关闭帧的正确响应
1 parent 4e7fa88 commit d7cb0df

2 files changed

Lines changed: 90 additions & 29 deletions

File tree

Cyaim.WebSocketServer/Cyaim.WebSocketServer/Infrastructure/Handlers/MvcHandler/MvcChannelHandler.cs

Lines changed: 80 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,11 @@ await clusterManager.RegisterConnectionAsync(
232232
context.Request.Path,
233233
remoteIpAddress,
234234
remotePort);
235-
logger.LogDebug($"Registered connection {context.Connection.Id} with cluster manager");
235+
logger.LogDebug(string.Format(I18nText.WS_INTERACTIVE_TEXT_TEMPALTE, context.Connection.RemoteIpAddress, context.Connection.RemotePort, context.Connection.Id, I18nText.ConnectionEntry_ClusterManagerRegistered));
236236
}
237237
catch (Exception ex)
238238
{
239-
logger.LogWarning(ex, $"Failed to register connection {context.Connection.Id} with cluster manager");
239+
logger.LogWarning(ex, string.Format(I18nText.WS_INTERACTIVE_TEXT_TEMPALTE, context.Connection.RemoteIpAddress, context.Connection.RemotePort, context.Connection.Id, I18nText.ConnectionEntry_ClusterManagerRegisterFailed));
240240
}
241241
}
242242

@@ -255,7 +255,7 @@ await clusterManager.RegisterConnectionAsync(
255255
{
256256
if (webSocket.CloseStatus == null && webSocket.State == WebSocketState.Open)
257257
{
258-
await webSocket.CloseAsync(WebSocketCloseStatus.PolicyViolation, string.Empty, CancellationToken.None).ConfigureAwait(false);
258+
//await webSocket.CloseAsync(WebSocketCloseStatus.PolicyViolation, string.Empty, CancellationToken.None).ConfigureAwait(false);
259259
webSocket.Abort();
260260
}
261261
webSocketCloseStatus = webSocket.CloseStatus;
@@ -303,11 +303,13 @@ private async Task MvcForward(HttpContext context, WebSocket webSocket, WebSocke
303303
{
304304
string wsCloseDesc = string.Empty;
305305
using MemoryStream wsReceiveReader = new MemoryStream(ReceiveTextBufferSize);
306+
bool connectionClosed = false;
306307
do
307308
{
308309
long requestTime = DateTime.Now.Ticks;
309310
WebSocketReceiveResult result = null;
310311
SemaphoreSlim endPointSlim = null;
312+
bool receivedClose = false;
311313
try
312314
{
313315
// Connection level restrictions
@@ -320,7 +322,8 @@ private async Task MvcForward(HttpContext context, WebSocket webSocket, WebSocke
320322
{
321323
if (webSocket.State == WebSocketState.Aborted || webSocket.State == WebSocketState.CloseReceived || webSocket.State == WebSocketState.Closed)
322324
{
323-
// exit
325+
// 连接已关闭,设置标志并退出
326+
connectionClosed = true;
324327
break;
325328
}
326329
else
@@ -341,14 +344,32 @@ private async Task MvcForward(HttpContext context, WebSocket webSocket, WebSocke
341344

342345
try
343346
{
344-
while (!messageComplete)
347+
while (!messageComplete && !receivedClose)
345348
{
346349
// 接收数据帧
347350
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
348351

349-
// 如果接收到Close消息,直接退出
352+
// 如果接收到Close消息,保存状态并退出接收循环
350353
if (result.MessageType == WebSocketMessageType.Close)
351354
{
355+
receivedClose = true;
356+
connectionClosed = true;
357+
wsCloseDesc = result.CloseStatusDescription;
358+
// 响应Close帧(如果连接状态允许)
359+
if (webSocket.State == WebSocketState.Open || webSocket.State == WebSocketState.CloseReceived)
360+
{
361+
try
362+
{
363+
await webSocket.CloseAsync(
364+
result.CloseStatus ?? WebSocketCloseStatus.NormalClosure,
365+
result.CloseStatusDescription ?? string.Empty,
366+
CancellationToken.None);
367+
}
368+
catch (Exception ex)
369+
{
370+
logger.LogDebug(string.Format(I18nText.WS_INTERACTIVE_TEXT_TEMPALTE, context.Connection.RemoteIpAddress, context.Connection.RemotePort, context.Connection.Id, I18nText.ConnectionEntry_CloseResponseFailed + Environment.NewLine + ex.Message));
371+
}
372+
}
352373
break;
353374
}
354375

@@ -444,26 +465,31 @@ await bandwidthLimitManager.WaitForBandwidthAsync(
444465
// 归还buffer
445466
ArrayPool<byte>.Shared.Return(buffer);
446467
}
447-
// 如果接收到的消息是Close时,断开连接
448-
if (result.MessageType == WebSocketMessageType.Close)
468+
469+
// 如果接收到Close消息,直接退出当前循环,不再处理数据
470+
if (receivedClose)
449471
{
472+
// 设置连接关闭标志,退出外层循环
473+
connectionClosed = true;
450474
break;
451475
}
476+
452477
// 缩小Capacity避免Getbuffer出现0x00
453478
if (wsReceiveReader.Capacity > wsReceiveReader.Length)
454479
{
455480
wsReceiveReader.Capacity = (int)wsReceiveReader.Length;
456481
}
457482
#endregion
458483

459-
// 执行AfterReceivingData管道
460-
_ = await InvokePipeline(RequestPipelineStage.AfterReceivingData, PipelineContext.CreateReceive(context, webSocket, result, wsReceiveReader.GetBuffer(), webSocketOption));
461-
462-
if (result == null)
484+
// 如果result为null或接收到Close消息,跳过后续处理
485+
if (result == null || receivedClose)
463486
{
464487
continue;
465488
}
466489

490+
// 执行AfterReceivingData管道
491+
_ = await InvokePipeline(RequestPipelineStage.AfterReceivingData, PipelineContext.CreateReceive(context, webSocket, result, wsReceiveReader.GetBuffer(), webSocketOption));
492+
467493
// 在接收完数据后,应用端点级别的限速策略
468494
string endpoint = null;
469495
if (bandwidthLimitManager != null && wsReceiveReader.Length > 0)
@@ -578,13 +604,19 @@ await bandwidthLimitManager.WaitForBandwidthAsync(
578604
}
579605
finally
580606
{
581-
wsCloseDesc = result?.CloseStatusDescription;
607+
// 保存Close状态信息(如果还没有保存)
608+
if (result != null && !string.IsNullOrEmpty(result.CloseStatusDescription) && string.IsNullOrEmpty(wsCloseDesc))
609+
{
610+
wsCloseDesc = result.CloseStatusDescription;
611+
}
582612

613+
// 重置接收缓冲区
583614
wsReceiveReader.Flush();
584615
wsReceiveReader.SetLength(0);
585616
wsReceiveReader.Seek(0, SeekOrigin.Begin);
586617
wsReceiveReader.Position = 0;
587618

619+
// 释放信号量
588620
if (ParallelForwardLimitSlim != null)
589621
{
590622
ParallelForwardLimitSlim.Release();
@@ -595,15 +627,34 @@ await bandwidthLimitManager.WaitForBandwidthAsync(
595627
}
596628
}
597629

598-
} while (!appLifetime.ApplicationStopping.IsCancellationRequested);
630+
} while (!appLifetime.ApplicationStopping.IsCancellationRequested && !connectionClosed);
631+
632+
// 连接断开处理
633+
// 如果连接仍然打开,需要关闭它
634+
if (webSocket.State == WebSocketState.Open || webSocket.State == WebSocketState.CloseReceived)
635+
{
636+
try
637+
{
638+
// 如果已经收到了Close消息,使用接收到的Close状态
639+
// 否则使用默认的关闭状态
640+
WebSocketCloseStatus closeStatus = webSocket.CloseStatus ??
641+
(webSocket.State == WebSocketState.Aborted ?
642+
WebSocketCloseStatus.InternalServerError :
643+
WebSocketCloseStatus.NormalClosure);
599644

600-
// 连接断开
601-
if (webSocket.State == WebSocketState.Open || webSocket.State == WebSocketState.CloseSent)
645+
string closeDescription = wsCloseDesc ?? string.Empty;
646+
647+
await webSocket.CloseAsync(closeStatus, closeDescription, CancellationToken.None);
648+
}
649+
catch (Exception ex)
650+
{
651+
logger.LogDebug(string.Format(I18nText.WS_INTERACTIVE_TEXT_TEMPALTE, context.Connection.RemoteIpAddress, context.Connection.RemotePort, context.Connection.Id, I18nText.ConnectionEntry_CloseConnectionError + Environment.NewLine + ex.Message));
652+
}
653+
}
654+
// 如果已经发送了Close消息,等待对方关闭
655+
else if (webSocket.State == WebSocketState.CloseSent)
602656
{
603-
await webSocket.CloseAsync(webSocket.CloseStatus == null ?
604-
webSocket.State == WebSocketState.Aborted ?
605-
WebSocketCloseStatus.InternalServerError : WebSocketCloseStatus.NormalClosure
606-
: webSocket.CloseStatus.Value, wsCloseDesc, CancellationToken.None);
657+
// 连接正在关闭中,不需要额外操作
607658
}
608659
}
609660
catch (Exception ex)
@@ -948,7 +999,7 @@ public static async Task<MvcResponseScheme> MvcDistributeAsync(WebSocketRouteOpt
948999
catch (FormatException ex)
9491000
{
9501001
// ConvertTo 抛出 类型转换失败
951-
logger.LogTrace(string.Format(I18nText.WS_INTERACTIVE_TEXT_TEMPALTE, context.Connection.RemoteIpAddress, context.Connection.RemotePort, context.Connection.Id, $"{requestPath}.{item.Name}" + I18nText.MvcForwardSendData_RequestBodyParameterFormatError + ex.Message + Environment.NewLine + ex.StackTrace));
1002+
logger.LogTrace(string.Format(I18nText.WS_INTERACTIVE_TEXT_TEMPALTE, context.Connection.RemoteIpAddress, context.Connection.RemotePort, context.Connection.Id, string.Concat(requestPath, ".", item.Name, I18nText.MvcForwardSendData_RequestBodyParameterFormatError, ex.Message, Environment.NewLine, ex.StackTrace)));
9521003
}
9531004
args[i] = parmVal;
9541005
}
@@ -1121,7 +1172,7 @@ private async Task MvcChannel_OnDisconnected(HttpContext context, WebSocketClose
11211172
msg = I18nText.WebSocketCloseStatus_ConnectionShutdown;
11221173
}
11231174

1124-
logger.LogInformation(string.Format(I18nText.WS_INTERACTIVE_TEXT_TEMPALTE, context.Connection.RemoteIpAddress, context.Connection.RemotePort, context.Connection.Id, I18nText.OnDisconnected_Disconnected + msg + Environment.NewLine + $"Status:{webSocketCloseStatus.ToString() ?? "NoHandshakeSucceeded"}"));
1175+
logger.LogInformation(string.Format(I18nText.WS_INTERACTIVE_TEXT_TEMPALTE, context.Connection.RemoteIpAddress, context.Connection.RemotePort, context.Connection.Id, string.Concat(I18nText.OnDisconnected_Disconnected, msg, Environment.NewLine, "Status:", webSocketCloseStatus?.ToString() ?? "NoHandshakeSucceeded")));
11251176

11261177
try
11271178
{
@@ -1148,11 +1199,11 @@ private async Task MvcChannel_OnDisconnected(HttpContext context, WebSocketClose
11481199
try
11491200
{
11501201
await clusterManager.UnregisterConnectionAsync(context.Connection.Id);
1151-
logger.LogDebug($"Unregistered connection {context.Connection.Id} from cluster manager");
1202+
logger.LogDebug(string.Format(I18nText.WS_INTERACTIVE_TEXT_TEMPALTE, context.Connection.RemoteIpAddress, context.Connection.RemotePort, context.Connection.Id, I18nText.ConnectionEntry_ClusterManagerUnregistered));
11521203
}
11531204
catch (Exception ex)
11541205
{
1155-
logger.LogWarning(ex, $"Failed to unregister connection {context.Connection.Id} from cluster manager");
1206+
logger.LogWarning(ex, string.Format(I18nText.WS_INTERACTIVE_TEXT_TEMPALTE, context.Connection.RemoteIpAddress, context.Connection.RemotePort, context.Connection.Id, I18nText.ConnectionEntry_ClusterManagerUnregisterFailed));
11561207
}
11571208
}
11581209
}
@@ -1193,22 +1244,22 @@ public virtual async Task<bool> MvcChannel_OnBeforeConnection(HttpContext contex
11931244
case AccessDeniedAction.ReturnForbidden:
11941245
context.Response.StatusCode = 403;
11951246
await context.Response.WriteAsync(policy.DenialMessage ?? "Access denied");
1196-
logger.LogWarning($"Access denied for IP {ipAddress} from {context.Request.Path}: {policy.DenialMessage}");
1247+
logger.LogWarning(string.Format(I18nText.ConnectionEntry_AccessDeniedWithMessage, ipAddress, context.Request.Path, policy.DenialMessage ?? string.Empty));
11971248
break;
11981249
case AccessDeniedAction.ReturnUnauthorized:
11991250
context.Response.StatusCode = 401;
12001251
await context.Response.WriteAsync(policy.DenialMessage ?? "Unauthorized");
1201-
logger.LogWarning($"Access denied for IP {ipAddress} from {context.Request.Path}: {policy.DenialMessage}");
1252+
logger.LogWarning(string.Format(I18nText.ConnectionEntry_AccessDeniedWithMessage, ipAddress, context.Request.Path, policy.DenialMessage ?? string.Empty));
12021253
break;
12031254
case AccessDeniedAction.CloseConnection:
12041255
default:
1205-
logger.LogWarning($"Access denied for IP {ipAddress} from {context.Request.Path}: {policy.DenialMessage}");
1256+
logger.LogWarning(string.Format(I18nText.ConnectionEntry_AccessDeniedWithMessage, ipAddress, context.Request.Path, policy.DenialMessage ?? string.Empty));
12061257
break;
12071258
}
12081259
}
12091260
else
12101261
{
1211-
logger.LogWarning($"Access denied for IP {ipAddress} from {context.Request.Path}");
1262+
logger.LogWarning(string.Format(I18nText.ConnectionEntry_AccessDenied, ipAddress, context.Request.Path));
12121263
}
12131264

12141265
return false;
@@ -1217,7 +1268,7 @@ public virtual async Task<bool> MvcChannel_OnBeforeConnection(HttpContext contex
12171268
}
12181269
catch (Exception ex)
12191270
{
1220-
logger.LogError(ex, "Error checking access control");
1271+
logger.LogError(ex, I18nText.ConnectionEntry_AccessControlError);
12211272
// Allow connection on error to avoid blocking legitimate users / 出错时允许连接,避免阻止合法用户
12221273
}
12231274
}

Cyaim.WebSocketServer/Cyaim.WebSocketServer/Infrastructure/I18nText.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,16 @@ public static void UpdateI18nText(string i18nResourcePath)
144144

145145
public static readonly string MvcForwardSendData_RequestIdRequired = "Request must contain an Id property. The client cannot distinguish the response source without an Id." + Environment.NewLine;
146146

147+
public static readonly string ConnectionEntry_ClusterManagerRegistered = "Registered connection with cluster manager.";
148+
public static readonly string ConnectionEntry_ClusterManagerRegisterFailed = "Failed to register connection with cluster manager.";
149+
public static readonly string ConnectionEntry_ClusterManagerUnregistered = "Unregistered connection from cluster manager.";
150+
public static readonly string ConnectionEntry_ClusterManagerUnregisterFailed = "Failed to unregister connection from cluster manager.";
151+
public static readonly string ConnectionEntry_CloseResponseFailed = "Failed to send Close response.";
152+
public static readonly string ConnectionEntry_CloseConnectionError = "Error closing WebSocket connection.";
153+
public static readonly string ConnectionEntry_AccessDenied = "Access denied for IP {0} from {1}.";
154+
public static readonly string ConnectionEntry_AccessDeniedWithMessage = "Access denied for IP {0} from {1}: {2}";
155+
public static readonly string ConnectionEntry_AccessControlError = "Error checking access control.";
156+
147157

148158
}
149159
}

0 commit comments

Comments
 (0)