这个问题与这个问题直接相关:
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);
}
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);
此外,由于我必须使这完全异步(我知道客户端连接的线程非常糟糕),实现这一目标的最佳方法是什么?
我非常感谢您解决问题的任何帮助,买一杯啤酒! :)
祝你好运, D6mi
答案 0 :(得分:0)
我找到了这个答案。问题在于接收方使用 CopyTo(),因为 CopyTo()会尝试复制整个流,为此目的,NetworkStream是一个无限流,所以 CopyTo()永远不会达到目的,因此会阻塞整个应用程序。
唯一可行的方法是客户端在发送文件后关闭连接,然后服务器知道流已结束,但这不适用于我的使用场景。
我在MSDN论坛上问了同样的问题并从那里得到了这个信息,这里是线程的链接