将一个流复制到另一个流——挂起(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
我找到了答案。问题在于接收端对CopyTo()的使用,因为CopyTo()试图复制整个流,对于这种目的,NetworkStream是一个无限流,因此CopyTo()将永远不会到达它的终点,从而阻塞整个应用程序。
唯一可以工作的情况是当客户端在发送文件后关闭连接,然后服务器知道流结束,但这不适用于我的使用场景。
我在MSDN论坛上问了同样的问题,并从那里得到了这个信息,这里是线程的链接
MSDN论坛的解决方案