C# 将流复制到流挂起(TCP)

C# 将流复制到流挂起(TCP),c#,tcp,stream,copy,C#,Tcp,Stream,Copy,这个问题与这个问题直接相关: 我的TCP网络文件传输机制有问题。基本上,客户端(文件发送方)和服务器(文件接收方)通过一个简单的消息系统进行通信 客户端通过发送一条消息来启动传输,该消息包含一个Send命令,后跟文件名的长度,后跟实际文件名。服务器解析消息,让用户决定是接受还是拒绝该文件。然后将相应的消息发送回客户端。如果客户端读取了Accept命令,则初始化文件传输。此部分使用Stream.CopyTo()方法或通过我的自定义解决方案成功完成 这也是问题发生的地方。服务器不会移动超过那行代码

这个问题与这个问题直接相关:

我的TCP网络文件传输机制有问题。基本上,客户端(文件发送方)和服务器(文件接收方)通过一个简单的消息系统进行通信

客户端通过发送一条消息来启动传输,该消息包含一个Send命令,后跟文件名的长度,后跟实际文件名。服务器解析消息,让用户决定是接受还是拒绝该文件。然后将相应的消息发送回客户端。如果客户端读取了Accept命令,则初始化文件传输。此部分使用Stream.CopyTo()方法或通过我的自定义解决方案成功完成

这也是问题发生的地方。服务器不会移动超过那行代码(如下所示的代码),CopyTo()只是无限期地坐在那里,但是当我关闭应用程序时,文件成功地被传输。可能是线程问题,我不确定

关于线程 这两种方法都是在各自独立的线程中启动的,如下所示

Thread t = new Thread(StartListening);
t.IsBackground = true;
t.Start();

if (!String.IsNullOrEmpty(_path))
{
    var t = new Thread(SendFile);
    t.IsBackground = true;
    t.Start();
}
else
{
    MessageBox.Show("You have to choose a file!", "File error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
监视和发送文件 发送文件: 消息类和方法 此外,由于我必须使其完全异步(我知道客户端连接的线程是非常糟糕的做法),那么实现这一点的最佳方法是什么

如果有人能帮我解决这个问题,我将不胜感激,给你买杯啤酒吧!:)

致以最良好的祝愿,
D6mi

我找到了这个问题的答案。问题在于接收方使用了CopyTo(),因为CopyTo()试图复制整个流,并且出于这种目的,网络流是无限流,因此CopyTo()永远不会结束,从而阻塞整个应用程序

唯一可以这样做的情况是,当客户端在发送文件后关闭连接时,服务器知道流已结束,但这不适用于我的使用场景

我在MSDN论坛上问了同样的问题,从那里得到了这些信息,这里是该线程的链接

    private void StartListening()
    {
        _listener = new TcpListener(_localEndPoint);
        _listener.Start();

        try
        {
            while (!done)
            {
                // Buffer for reading.
                byte[] buffer = new byte[4096];
                int bytesRead;

                SetText("SERVER : Listening for connections...\r\n");
                using (TcpClient client = _listener.AcceptTcpClient())
                {
                    SetText("SERVER : A client connected!\r\n");
                    using (NetworkStream netStream = client.GetStream())
                    {
                        SetText("SERVER : Waiting for the initial message...\r\n");
                        bytesRead = netStream.Read(buffer, 0, buffer.Length);

                        // Create a new Message based on the data read.
                        var message = new Message(buffer);

                        // Ask the user whether he/she wants to accept the file.
                        DialogResult dr = MessageBox.Show("Do you want to accept this file : " + message.Filename, "Accept or reject?", MessageBoxButtons.OKCancel);

                        // If the user says yes, send the accept response and start accepting the file data.
                        if (dr == DialogResult.OK)
                        {
                            SetText("SERVER : The user accepted the file! Sending the accept response and ready for transfer...\r\n");

                            // The Message class static methods for transforming commands into byte arrays.
                            byte[] responseBytes = Message.ConvertCommandToBytes(Commands.Accept);

                            // Send the accept response.
                            netStream.Write(responseBytes, 0, responseBytes.Length);

                            // Open or create the file for saving.
                            using (FileStream fileStream = new FileStream((@"E:\" + message.Filename), FileMode.Create))
                            {
                                SetText("Before CopyTo()\r\n");

                                // Copy the network stream to the open filestream. "DefaultBufferSize" is set to the "short.MaxValue"
                                // !!!!!!!!!!!!!!!!!
                                // This line never ends, it gets stuck on this line.
                                // !!!!!!!!!!!!!!!!!
                                netStream.CopyTo(fileStream, DefaultBufferSize);

                                SetText("After CopyTo()\r\n");

                                // Check whether the file was transfered (will add more precise checks).
                                if (File.Exists(@"E:\" + message.Filename))
                                    _fileCopied = true;
                            }
                        }
                        // If the user rejected the transfer, send the Reject response.
                        else
                        {
                            SetText("SERVER : The user rejected the file! Sending reject response...\r\n");
                            byte[] responseBytes = Message.ConvertCommandToBytes(Commands.Reject);
                            netStream.Write(responseBytes, 0, responseBytes.Length);
                        }
                    }
                }

                // If the file was successfully transfered, send the Success message notifying the client that
                // the operation ended successfully.
                if (_fileCopied)
                {
                    DialogResult dr = MessageBox.Show("Do you want to open the directory where the file was saved?",
                        "Confirmation", MessageBoxButtons.OKCancel);

                    if (dr == DialogResult.OK)
                        Process.Start(@"E:\");
                }
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.ToString());
        }
    }
    // Initiates the file transfer.
    private void SendFile()
    {
        // The Ip Address is user defined, read from a TextBox.
        IPAddress ipAddress = IPAddress.Parse(ipAddressBox.Text);

        // Create the IpEndPoint for the Tcp Client to connect to.
        _remoteEndPoint = new IPEndPoint(ipAddress, ListenPort);

        byte[] buffer = new byte[4096];
        int bytesRead;
        try
        {
            using (TcpClient client = new TcpClient())
            {
                SetText("CLIENT : Connecting to the host...\r\n");

                // Attempt to connect to the Server
                client.Connect(_remoteEndPoint);

                SetText("CLIENT : Connected to the host!\r\n");

                using (NetworkStream netStream = client.GetStream())
                {
                    // The Message class has a constructor for the initial message. It just needs
                    // the Filename and it will construct the initial message that contains the
                    // [Send] command, file length and the actually filename.
                    Message message = new Message(_filename);

                    // Convert the message to a byte array.
                    byte[] messageBytes = message.ToBytes();

                    SetText("CLIENT : Sending the initial message!\r\n");

                    // Send the initial message to the server.
                    netStream.Write(messageBytes, 0, messageBytes.Length);
                    SetText("CLIENT : Initial message sent! \r\n");
                    SetText("CLIENT : Waiting for the response...\r\n");

                    // Wait for the response for the server. [Accept] or [Reject].
                    bytesRead = netStream.Read(buffer, 0, buffer.Length);
                    SetText(String.Format("CLIENT : Received the response - {0} bytes. Analyzing...\r\n", bytesRead));

                    // Try to convert the read bytes to a command.
                    Commands command = Message.ConvertBytesToCommand(buffer);
                    SetText("CLIENT : Received this response : " + command + "\r\n");

                    // Determine the appropriate action based on the command contents.
                    if (command == Commands.Accept)
                    {
                        SetText("CLIENT : The host accepted the request. Starting file transfer...\r\n");

                        // Open the chosen file for reading. "_path" holds the user specified path.
                        using (FileStream fileStream = new FileStream(_path, FileMode.Open))
                        {
                            // Initiate the file transfer.
                            fileStream.CopyTo(netStream, DefaultBufferSize);
                            SetText("CLIENT : Successfully sent the file to the host!\r\n");
                        }

                        // Wait for the [Success] or [Error] response.
                        netStream.Read(buffer, 0, bytesRead);

                        // Convert the bytes received to a command.
                        command = Message.ConvertBytesToCommand(buffer);

                        // Act appropriately.
                        if (command == Commands.Success)
                            MessageBox.Show("The host successfully received the file!");
                        else
                            MessageBox.Show("The transfer was unsuccessful!");

                    }
                    else if(command == Commands.Reject)
                    {
                        MessageBox.Show("The host rejected the transfer!");
                    }
                }
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.ToString());
        }
    }
public enum Commands
{
    Send,
    Accept,
    Reject,
    Success,
    Error
}

class Message
{
    private Commands _command;
    private String _filename;

    public Commands Command
    {
        get { return _command; }
    }

    public String Filename
    {
        get { return _filename; }
    }

    public Message(string filename)
    {
        _command = Commands.Send;
        _filename = filename;
    }

    // Create a message from the passed byte array.
    public Message(byte[] bytes)
    {
        // The first four bytes is the command.
        _command = (Commands) BitConverter.ToInt32(bytes, 0);

        // The seconds four bytes is the filename length.
        int nameLength = BitConverter.ToInt32(bytes, 4);

        // If there is a filename specified, "nameLength" WILL always be larger than zero.
        if (nameLength > 0)
        {
            // Get the filename from the received byte array.
            _filename = Encoding.UTF8.GetString(bytes, 8, nameLength);
        }
    }

    // Convert the message to a byte array.
    public byte[] ToBytes()
    {
        var result = new List<byte>();

        // Add four bytes to the List.
        result.AddRange(BitConverter.GetBytes((int) _command));

        // Get the filename length.
        int nameLength = _filename.Length;

        // Store the length into the List. If it's zero, store the zero.
        if(nameLength > 0)
            result.AddRange(BitConverter.GetBytes(nameLength));
        else
            result.AddRange(BitConverter.GetBytes(0));

        // Store the filename into the List.
        result.AddRange(Encoding.UTF8.GetBytes(_filename));

        // Transform the List into an array and return it.
        return result.ToArray();
    }

    public override string ToString()
    {
        return _command + " " + _filename;
    }

    public static byte[] ConvertCommandToBytes(Commands command)
    {
        return BitConverter.GetBytes((int) command);
    }

    public static Commands ConvertBytesToCommand(byte[] data)
    {
        Commands command = (Commands)BitConverter.ToInt32(data, 0);
        return command;
    }
}
    public void SetText(string text)
    {
        if (statusBox.InvokeRequired)
        {
            SetTextCallback c = SetText;
            Invoke(c, new object[] {text});
        }
        else
        {
            statusBox.Text += text;
        }
    }

    private delegate void SetTextCallback(string text);