将一个流复制到另一个流——挂起(TCP)

本文关键字:另一个 挂起 TCP 复制 一个 | 更新日期: 2023-09-27 17:50:11

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

使用CopyTo()在TCP上传输文件

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

Client通过发送一条消息发起传输,该消息包含Send命令,后跟Filename的长度,后跟实际的Filename服务器解析消息并让用户决定他/她是要接受还是拒绝文件。然后将适当的消息发送回客户机。如果Accept命令被Client读取,则初始化文件传输。这部分使用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 and 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

将一个流复制到另一个流——挂起(TCP)

我找到了答案。问题在于接收端对CopyTo()的使用,因为CopyTo()试图复制整个流,对于这种目的,NetworkStream是一个无限流,因此CopyTo()将永远不会到达它的终点,从而阻塞整个应用程序。

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

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

MSDN论坛的解决方案