我有一个使用HttpListener的应用程序,我需要知道客户端何时断开连接,目前我所有的代码都放在try/catch块中,这样做很丑陋,也不是一个好的实践。
如何知道客户端是否已断开连接?
谢谢!
我有一个使用HttpListener的应用程序,我需要知道客户端何时断开连接,目前我所有的代码都放在try/catch块中,这样做很丑陋,也不是一个好的实践。
如何知道客户端是否已断开连接?
谢谢!
简短回答:你无法关闭客户端的连接。如果客户端停止通信,底层套接字可能会保持打开状态而永远不会关闭;它只会超时。检测这种情况的方法是尝试在该连接上执行操作,如果该连接不再有效,则会引发某种异常,具体取决于发生了什么。如果异步使用HttpListener,可能会在try/catch方面使您的代码更加整洁,但不幸的是,这就是您所困扰的问题。没有触发客户端断开连接事件的事件。
CancellationToken GetClientDisconnectToken(HttpListenerRequest request)
HttpListener
实际上为内置身份验证实现了客户端断开连接通知。我创建了一个从Hashtable
派生的类型,用于连接到HttpListener
用于未完成的客户端断开连接通知的结构,以便在其预期目的之外利用该代码。HTTP.SYS
使用的ConnectionId
。此代码使用反射并创建一个Func<>
,以获取任何HttpListenerRequest
的此ID:private static Func<HttpListenerRequest, ulong> GetConnectionId()
{
var field = typeof(HttpListenerRequest).GetField("m_ConnectionId",
BindingFlags.Instance | BindingFlags.NonPublic);
if (null == field)
throw new InvalidOperationException();
return request => (ulong)field.GetValue(request);
}
private static Func<ulong, IAsyncResult> GetRegisterForDisconnectNotification(HttpListener httpListener)
{
var registerForDisconnectNotification = typeof(HttpListener)
.GetMethod("RegisterForDisconnectNotification", BindingFlags.Instance | BindingFlags.NonPublic);
if (null == registerForDisconnectNotification)
throw new InvalidOperationException();
var finishOwningDisconnectHandling =
typeof(HttpListener).GetNestedType("DisconnectAsyncResult", BindingFlags.NonPublic)
.GetMethod("FinishOwningDisconnectHandling", BindingFlags.Instance | BindingFlags.NonPublic);
if (null == finishOwningDisconnectHandling)
throw new InvalidOperationException();
IAsyncResult RegisterForDisconnectNotification(ulong connectionId)
{
var invokeAttr = new object[] { connectionId, null };
registerForDisconnectNotification.Invoke(httpListener, invokeAttr);
var disconnectedAsyncResult = invokeAttr[1];
if (null != disconnectedAsyncResult)
finishOwningDisconnectHandling.Invoke(disconnectedAsyncResult, null);
return disconnectedAsyncResult as IAsyncResult;
}
return RegisterForDisconnectNotification;
}
这个反射创建了一个 Func<>
,它的输入是一个 ConnectionId
,返回一个包含正在进行请求状态的 IAsyncResult
。在内部,它调用了 HttpListener
上的一个私有方法:
private unsafe void RegisterForDisconnectNotification(ulong connectionId,
ref HttpListener.DisconnectAsyncResult disconnectResult)
void HttpListener.DisconnectedAsyncResult.FinishOwningDisconnectHandling()
。该方法将此结构的状态从“在HandleAuthentication中”更改为“正常”,这是需要调用IO完成回调的状态,该回调将调用HashTable
上的Remove
。拦截此调用非常简单-创建派生类型并覆盖Remove
:public override void Remove(object key)
{
base.Remove(key);
var connectionId = (ulong)key;
if (!_clientDisconnectTokens.TryRemove(connectionId, out var cancellationTokenSource))
return;
Cancel(cancellationTokenSource);
}
private static void Cancel(CancellationTokenSource cancellationTokenSource)
{
// Use TaskScheduler.UnobservedTaskException for caller to catch exceptions
Task.Run(() => cancellationTokenSource.Cancel());
}
Cancel
往往会抛出异常,因此我们使用TPL来调用它,这样您就可以通过订阅TaskScheduler.UnobservedTaskException
来捕获任何取消期间抛出的异常。HttpListenerHashtable
派生类型CancellationTokenSource
实例HttpListener
的HashTable
字段设置或替换为HttpListenerHashtable
(最好在创建HttpListener
实例后立即执行此操作)我遇到了同样的问题,我执行了一条长的 SQL 查询,如果用户不断按 F5,它就会崩溃。
HttpListener 不公开其底层套接字,因此您无法轮询它。
所以我想出了这个解决方法:
IDbCommand cmd = LongSqlQuery();
while (!taskCompleted)
{
try
{
//send disposable data at times to check connection
byte[] dummy = new byte[1] { 0xFF };
outputStream.Write(dummy,0,1);
Task.Delay(100).Wait();
}
catch
{
//Client disconnect
cmd.Cancel();
}
}
//Redirect client to results page
response.Redirect("http://yourserver.com/results?query=yourquery");