C# TcpListener+;TcpClient-在关闭前等待客户端读取数据

C# TcpListener+;TcpClient-在关闭前等待客户端读取数据,c#,.net,tcpclient,C#,.net,Tcpclient,我正在使用TcpClient为PDF文件构建一个简单的HTTP服务器。它工作得很好,但是TcpClient在浏览器下载PDF完成之前关闭。如何强制TcpClient在关闭前等待远程客户端获得所有写入的内容 //pdf is byte[] TcpListener server = new TcpListener(address, port); server.Start(); TcpClient client = server.AcceptTcpClient(); //Wait for co

我正在使用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();
我能换掉那个难看的“睡眠线”吗

编辑:以下代码根据答案起作用:

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()之前添加类似的内容使其正常工作语句:

//指示正在发送的字节的结尾
ns.Socket.Shutdown(SocketShutdown.Send);
//任意大小的缓冲区…很可能不会写入任何内容
字节[]缓冲区=新字节[4096];
int字节数;
而((字节数=ns.Read(buffer,0,buffer.Length))>0)
{
//忽略此处读取的任何数据
}
当一个端点启动一个优雅的闭包时(例如,通过调用
Socket.Shutdown(SocketShutdown.Send);
),这将允许网络层识别数据流的结尾。一旦另一个端点读取了远程端点发送的所有剩余字节,下一个读取操作将以零字节长度完成。这是另一个端点的信号,表示已到达流的末尾,是时候关闭连接了

任一端点都可以启动带有“发送”关闭原因的优雅关闭。另一个端点可以在完成发送它想要发送的任何内容后通过使用“两者”关闭原因来确认它,在这一点上,两个端点都可以关闭它们的套接字(或流或侦听器,或它们可能正在使用的任何其他更高级别的抽象)

当然,在正确实现的协议中,您将提前知道远程端点是否会发送任何数据。如果您知道永远都不会有,那么您可以使用零长度缓冲区,如果您知道一些数据可能会从客户端发送回来,那么您实际上会对这些数据做一些事情(与上面的空循环体相反)

在任何情况下,上面的内容都是为了让您发布的已经很糟糕的代码正常工作。请不要将其误认为是产品质量代码中的内容

尽管如此,你发布的代码还远没有那么好。您不仅要实现一个基本的TCP连接,而且显然要尝试重新实现HTTP协议。这样做没有任何意义,因为.NET已经内置了HTTP服务器功能(参见示例)。如果您确实打算重新发明HTTP服务器,那么您需要比您发布的代码多得多的代码。缺少错误处理本身就是一个主要缺陷,它会引起各种各样的麻烦

如果您打算编写低级网络代码,您应该做更多的研究和实验。一个很好的资源是。当然,它的主要关注点是针对Winsock API的程序员。但是这里也有大量的通用信息,而且在任何情况下,各种套接字API都非常相似,因为它们都基于相同的低级概念

您可能还想查看各种现有的堆栈溢出问题解答。这里有几个与您的具体问题密切相关的问题解答:



不过一定要小心。外面的坏建议几乎和好建议一样多。不缺少像网络编程专家一样四处走动的人,而他们不是,所以对你读到的每一篇文章(包括我上面的建议!)都要小心谨慎。

监听器应该向客户端发送一条消息,表明它获得了所有的数据。然后,客户端应该在收到消息后关闭。当客户端关闭连接时,服务器将自动关闭,这样您就不必在服务器端执行任何操作。@jdweng:在上面的代码中,侦听器正在发送数据,而不是接收数据。在任何情况下,都不需要确认数据。接收端点只需关闭连接(例如调用
Socket.shutdown(SocketShutdown.Send)
)即可指示连接已完成。本地端点也应该这样做,以便远程端点知道数据流何时完成。您绝对需要确认消息。如果你不同意,我建议你读一本好的传播理论书。服务器不应关闭连接。它是奴隶。客户机是主机,而服务器所要做的就是接受命令、处理数据和返回响应。@jdweng:恕我直言,你不知道自己在说什么。一旦建立了TCP连接,唯一的硬性规定是两个端点都需要就其使用的协议达成一致。如果这意味着服务器(侦听端)只是发送一个数据流,然后关闭带有“发送”原因的套接字,那么这就是需要做的一切。无需明确确认收到数据;一旦客户端获得了所有数据,就足以以“两者”的理由关闭客户端,然后两个端点都可以关闭套接字。@jdweng:是的,关于实现服务器/客户端体系结构的“最佳”方式有各种“理论”。但这里唯一重要的是什么会起作用,以及TCP/IP协议支持什么。你的说法与事实不符。我知道代码很糟糕。使用更糟糕。我试图通过WebBrowser控件在WPF应用程序中显示PDF,我试图避免1)临时文件和2)HTTP保留等。欢迎提供任何建议。如果一切正常