HttpContext.Response底层的套接字意外关闭

6
我有一个HttpListener,监听特定端口的本地主机上任何文件请求(例如192.168.0.10/Foobar.ext),在这种情况下,它使用.m3u8头文件和.ts视频文件作为HLS流。但该系统适用于任何类型的文件。
IAsyncResult result = listener.BeginGetContext(new AsyncCallback(HttpRequestListenerCallback), listener);
result.AsyncWaitHandle.WaitOne(1);

当发出请求时,回调函数会为所请求的文件(仅限该文件)创建一个 HttpListenerContext,并像这样提取文件名:
HttpListenerContext context = listener.EndGetContext(result);
string fileName = context.Request.Url.AbsolutePath.Substring(1);

上下文被添加到名为httpContexts的字典中,并链接到一个整数commandSequenceNumber以跟踪请求。

如果文件名有效,则会向服务器发送请求以下载文件。文件被下载并放入名为totalBufferdata的字节数组中。到这里,一切都工作得很完美。

现在我想将所请求的(视频)文件的字节数据写回到请求该文件的上下文(HttpListenerContext.Response)的响应中。

为此,我使用以下代码片段(这发生在文件完全下载之后):

HttpListenerResponse response = httpContexts[commandSequenceNumber].Response; //Get the appropriate context from the dictionary
response.ContentLength64 = totalBufferdata.Count;//set the length response body
Stream responseStream = response.OutputStream; //The stream to which the repsonse needs to be written
try
{
   currentContext.Response.OutputStream.Write(dataNoResponseHeader, 0, dataNoResponseHeader.Length);
}
catch (IOException e)
{
   Debug.LogError("Exception in writing to response::" + e);
   Debug.LogError("innerException:: " + e.InnerException);
}
currentContext.Close();//close the request and response stream

这将响应发送回请求的上下文(即192.168.0.10/Foobar.ext,通过相同的端口)。

现在这很好用,只要有快速、可靠的互联网连接。当互联网连接变慢或不稳定时,我开始收到异常:

System.IO.IOException: Unable to write data to the transport connection: The socket has been shut down. ---> System.Net.Sockets.SocketException: The socket has been shut down

内部异常为:

System.Net.Sockets.SocketException (0x80004005): The socket has been shut down

我已经查了一下0x80004005的HResult在MSDN上的对应内容,但那只是“E_FAIL未指定的失败”,所以没有什么帮助。

我一直未能弄清楚为什么会抛出套接字关闭异常(而且为什么它会发生在本地主机上,但只有当连接质量不佳时)。我确保所有所需的数据都在totalBufferData中,因此低网速不应影响它,因为所有数据在写入响应之前都已经下载完毕。我还确保在我的代码中没有过早关闭上下文的情况。

到目前为止,我一直未能找到获取HttpListener底层套接字的方法。我还尝试将response.OutputStream强制转换为NetworkStream,并从NetworkStream获取套接字,但该强制转换无效(这让我感到困惑,因为它们都是IO流?)。考虑可能是一个关闭问题,我也尝试了:

using(Stream testStream = response.OutputStream)
{
    testStream.Write(totalBufferdata.ToArray(), 0, totalBufferdata.Count);
}

我觉得这个问题与某个地方的超时有关。但是监听器没有达到默认的超时时间。根据MSDN,所有默认的超时时间应该为2分钟。但是我远远达不到这个时间。而且我认为,在超时的情况下返回的异常应该是ObjectDisposedException Write(Byte[], Int32, Int32) was called after the stream was closed.,因为我想象超时会处理连接,而不是崩溃?这是误解吗?
请求和响应是在不同的线程上完成的。但是,当响应仍在进行时完成请求的请求会被排队,并等待响应完成后再启动新的响应。
是否有一种方法可以获取和调试底层套接字,以找出它为什么关闭/崩溃?或者还有其他方法在本地主机上请求文件并对其进行响应,而不使用HttpListener

一些额外的信息:这是一个使用脚本运行时版本为.Net 4.x等效的Unity应用程序(Unity 2019.1),具有.Net 4.x API兼容性级别和IL2CPP脚本后端。但是,处理请求或响应的类都不继承自monobevahiour(在unity中线程上甚至不可能)。构建针对Android。


赏金已经结束,但是我会为那些拥有有价值信息的人开设新的赏金!
3个回答

0

不幸的是,Unity使用的mono版本中没有实现PushStreamContent,而且stream本身可以正常工作,但是关闭响应的内容无法正常运行。我认为使用这种方法也不能解决这个问题。 - Remy

0

这听起来像是我之前遇到过的问题,根据提供的信息,互联网速度慢或不可靠,我建议可能考虑使用UDP套接字而不是TCP,因为它们在连接短暂中断或在传输过程中丢失少量数据时不会抛出异常,请参见此处。API非常相似,请参见此处。重新实现可能有点麻烦,但我认为它可以解决您的问题。

我的另一个见解是,您的try catch块指定仅接受IOExceptions,即使它捕获SocketException,大多数情况下我只使用通用的Exception类来避免尝试确定从哪里抛出异常。

只需更改:

catch (IOException e)

catch (Exception e)

IOException和SocketException都继承自Exception类,因此代码的其余部分保持不变。希望这能为您提供更多的问题特定信息。


我尝试使用catch(Exception e)捕获所有异常,但不幸的是这并没有产生任何额外的异常。 UDP的问题在于它不会保持Response的打开状态,并且由于UDP不会抛出异常,因此恢复变得更加困难。 - Remy

-1

你可能会遇到一些问题。

首先可能是超时的情况。由于你在互联网上遇到了一些问题,请求和响应之间的时间可能比指定的时间长(如果你没有设置,默认为60秒)。

另一件事是文件大小可能太大,无法完整地写入一个单独的包响应中。但这种情况会发生在任何请求中,而不仅仅是在“糟糕”的互联网连接时刻。

还有可能是因为互联网连接不稳定,你的“服务器”检测到“客户端”断开连接(即使是短暂的),因此关闭了套接字。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接