繁体   English   中英

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

[英]Copying a stream to a stream - Hang (TCP)

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

使用CopyTo()通过TCP传输文件

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

客户端通过发送包含发送命令的消息,接着文件名的长度,其次是实际文件名的转移。 服务器解析该消息,并让用户决定他/她想要接受还是拒绝该文件。 然后将适当的消息发送回客户端 如果Client读取了Accept命令,则初始化文件传输。 使用Stream.CopyTo()方法或通过我的自定义解决方案可以成功完成此部分。

这也是发生问题的地方。 Server不会移动到该行代码(下面显示的代码),即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

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

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

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

来自MSDN论坛的解决方案

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM