c# - TcpListener + TcpClient - 等待客户端在关闭前读取数据
问题描述
我正在使用 TcpClient 为 PDF 文件构建一个简单的 HTTP 服务器。它运行良好,但是 TcpClient 在浏览器下载 PDF 完成之前关闭。如何强制 TcpClient 等到远程客户端在关闭之前获得所有写入的内容?
//pdf is byte[]
TcpListener server = new TcpListener(address, port);
server.Start();
TcpClient client = server.AcceptTcpClient(); //Wait for connection
var ns = client.GetStream();
string headers;
using (var writer = new StringWriter())
{
writer.WriteLine("HTTP/1.1 200 OK");
//writer.WriteLine("Accept: text/html");
writer.WriteLine("Content-type: application/pdf");
writer.WriteLine("Content-length: " + pdf.Length);
writer.WriteLine();
headers = writer.ToString();
}
var bytes = Encoding.UTF8.GetBytes(headers);
ns.Write(bytes, 0, bytes.Length);
ns.Write(pdf, 0, pdf.Length);
Thread.Sleep(TimeSpan.FromSeconds(10)); //Adding this line fixes the problem....
client.Close();
server.Stop();
我可以替换那个丑陋的“Thread.Sleep”黑客吗?
编辑:下面的代码有效,基于答案:
TcpListener Server = null;
public void StartServer()
{
Server = new TcpListener(IPAddress.Any, Port);
Server.Start();
Server.BeginAcceptTcpClient(AcceptClientCallback, null);
}
void AcceptClientCallback(IAsyncResult result)
{
var client = Server.EndAcceptTcpClient(result);
var ns = client.GetStream();
string headers;
byte[] pdf = //get pdf
using (var writer = new StringWriter())
{
writer.WriteLine("HTTP/1.1 200 OK");
//writer.WriteLine("Accept: text/html");
writer.WriteLine("Content-type: application/pdf");
writer.WriteLine("Content-length: " + pdf.Length);
writer.WriteLine();
headers = writer.ToString();
}
var bytes = Encoding.UTF8.GetBytes(headers);
ns.Write(bytes, 0, bytes.Length);
ns.Write(pdf, 0, pdf.Length);
client.Client.Shutdown(SocketShutdown.Send);
byte[] buffer = new byte[1024];
int byteCount;
while ((byteCount = ns.Read(buffer, 0, buffer.Length)) > 0)
{
}
client.Close();
Server.Stop();
}
解决方案
您的代码中的主要问题是您的服务器(文件主机)忽略了从它写入文件的套接字读取,因此无法检测,更不用说等待客户端关闭连接。
代码可能会更好,但至少您可以通过在client.Close();
语句之前添加类似这样的内容来使其工作:
// Indicate the end of the bytes being sent
ns.Socket.Shutdown(SocketShutdown.Send);
// arbitrarily-sized buffer...most likely nothing will ever be written to it
byte[] buffer = new byte[4096];
int byteCount;
while ((byteCount = ns.Read(buffer, 0, buffer.Length)) > 0)
{
// ignore any data read here
}
当一个端点启动一个优雅的闭包(例如通过调用Socket.Shutdown(SocketShutdown.Send);
),这将允许网络层识别数据流的结束。一旦另一个端点读取了远程端点发送的所有剩余字节,下一个读取操作将以字节长度为零完成。那是另一个端点的信号,表明已经到达流的末尾,是时候关闭连接了。
任一端点都可以使用“发送”关闭原因启动正常关闭。另一个端点可以通过使用“两者”关闭原因完成发送它想要发送的任何内容后确认它,此时两个端点都可以关闭它们的套接字(或流或侦听器或它们可能使用的任何其他更高级别的抽象)包裹插座)。
当然,在正确实现的协议中,您会提前知道远程端点是否会实际发送任何数据。如果您知道永远不会,您可以使用一个长度为零的缓冲区,如果您知道一些数据可能会从客户端发回,那么您实际上会对这些数据做一些事情(而不是空循环上面的身体)。
无论如何,以上内容严格来说是一个杂乱无章的东西,以使您发布的已经杂乱无章的代码开始工作。请不要将其误认为是要在生产质量代码中看到的内容。
话虽如此,您发布的代码距离那么好还有很长的路要走。您不仅在实现基本的 TCP 连接,而且显然正在尝试重新实现 HTTP 协议。这样做没有意义,因为 .NET 已经内置了 HTTP 服务器功能(参见例如System.Net.HttpListener)。如果您确实打算重新发明 HTTP 服务器,那么您需要的代码比您发布的代码要多得多。单独缺乏错误处理是一个重大缺陷,会引起各种令人头疼的问题。
如果你打算编写低级网络代码,你应该做更多的研究和实验。Winsock Programmer's FAQ是一个非常好的资源。当然,它的主要关注点是针对 Winsock API 的程序员。但是那里也有大量通用信息,无论如何,所有各种套接字 API 都非常相似,因为它们都基于相同的低级概念。
您可能还想查看各种现有的 Stack Overflow Q&A。以下是与您的具体问题密切相关的几个问题:
如何正确使用 TPL 和 TcpClient?
通过 tcp 连接发送大文件
不过要小心。那里的坏建议几乎和好的建议一样多。不乏人在他们不是网络编程专家时表现得像网络编程专家一样,所以对你阅读的所有内容持保留态度(包括我上面的建议!)。
推荐阅读
- vue.js - 带有 Vue-cli3 和静态站点的部署问题
- c++ - 库。检查窗口是否重叠
- image-processing - 如何用flutter实现过饱和效果
- c# - Elasticsearch _update_by_query 无论如何都会更新文档,即使在版本冲突的情况下也是如此
- python - Tarfile/Zipfile extractall() 更改某些文件的文件名
- python - 如何根据我的条件定义一个二进制变量是 1 还是 0?
- shell - 如何从另一个字符串中删除所有出现的带有`/`的字符串
- sql-server - 实体框架 - 如果我希望我的存储过程什么都不返回怎么办
- php - 尝试从数据库中获取列值时获取非对象错误的属性
- java - 无法将 .txt 文件移动到 java 中的存档文件夹