将流复制到流 - 挂起(TCP)

时间:2014-03-14 11:58:30

标签: c# tcp stream copy

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

Transfering files over TCP with CopyTo()

我的TCP-over-network文件传输机制存在问题。基本上发生了什么,客户端(文件发送者)和服务器(文件接收者)通过简单的消息系统进行通信。

客户端通过发送包含发送命令的消息来启动传输,然后是文件名的长度,然后是实际的文件名服务器解析消息并让用户决定是否要接受拒绝该文件。然后将相应的消息发送回客户端。如果客户端读取接受命令,则初始化文件传输。此部分使用 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);
}

StartListening和SendFile

    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());
        }
    }

Message类和方法

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);

此外,由于我必须使这完全异步(我知道客户端连接的线程非常糟糕),实现这一目标的最佳方法是什么?

我非常感谢您解决问题的任何帮助,买一杯啤酒! :)

祝你好运, D6mi

1 个答案:

答案 0 :(得分:0)

我找到了这个答案。问题在于接收方使用 CopyTo(),因为 CopyTo()会尝试复制整个流,为此目的,NetworkStream是一个无限流,所以 CopyTo()永远不会达到目的,因此会阻塞整个应用程序。

唯一可行的方法是客户端在发送文件后关闭连接,然后服务器知道流已结束,但这不适用于我的使用场景。

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

Solution from the MSDN forums