Перемешанный массив байтов после использования TcpClient и TcpListener
-
18-09-2019 - |
Вопрос
Я хочу использовать TcpClient и TcpListener для отправки mp3-файла по сети.Я реализовал решение этой проблемы с помощью сокетов, но возникли некоторые проблемы, поэтому я изучаю новый / лучший способ отправки файла.
Я создаю массив байтов, который выглядит следующим образом:length_of_filename|имя файла|file
Затем это должно быть передано с использованием вышеупомянутых классов, однако на стороне сервера массив байтов, который я прочитал, полностью перепутан, и я не уверен, почему.
Метод, который я использую для отправки:
public static void Send(String filePath)
{
try
{
IPEndPoint endPoint = new IPEndPoint(Settings.IpAddress, Settings.Port + 1);
Byte[] fileData = File.ReadAllBytes(filePath);
FileInfo fi = new FileInfo(filePath);
List<byte> dataToSend = new List<byte>();
dataToSend.AddRange(BitConverter.GetBytes(Encoding.Unicode.GetByteCount(fi.Name))); // length of filename
dataToSend.AddRange(Encoding.Unicode.GetBytes(fi.Name)); // filename
dataToSend.AddRange(fileData); // file binary data
using (TcpClient client = new TcpClient())
{
client.Connect(Settings.IpAddress, Settings.Port + 1);
// Get a client stream for reading and writing.
using (NetworkStream stream = client.GetStream())
{
// server is ready
stream.Write(dataToSend.ToArray(), 0, dataToSend.ToArray().Length);
}
}
}
catch (ArgumentNullException e)
{
Debug.WriteLine(e);
}
catch (SocketException e)
{
Debug.WriteLine(e);
}
}
}
Тогда на стороне сервера это выглядит следующим образом:
private void Listen()
{
TcpListener server = null;
try
{
// Setup the TcpListener
Int32 port = Settings.Port + 1;
IPAddress localAddr = IPAddress.Parse("127.0.0.1");
// TcpListener server = new TcpListener(port);
server = new TcpListener(localAddr, port);
// Start listening for client requests.
server.Start();
// Buffer for reading data
Byte[] bytes = new Byte[1024];
List<byte> data;
// Enter the listening loop.
while (true)
{
Debug.WriteLine("Waiting for a connection... ");
string filePath = string.Empty;
// Perform a blocking call to accept requests.
// You could also user server.AcceptSocket() here.
using (TcpClient client = server.AcceptTcpClient())
{
Debug.WriteLine("Connected to client!");
data = new List<byte>();
// Get a stream object for reading and writing
using (NetworkStream stream = client.GetStream())
{
// Loop to receive all the data sent by the client.
while ((stream.Read(bytes, 0, bytes.Length)) != 0)
{
data.AddRange(bytes);
}
}
}
int fileNameLength = BitConverter.ToInt32(data.ToArray(), 0);
filePath = Encoding.Unicode.GetString(data.ToArray(), 4, fileNameLength);
var binary = data.GetRange(4 + fileNameLength, data.Count - 4 - fileNameLength);
Debug.WriteLine("File successfully downloaded!");
// write it to disk
using (BinaryWriter writer = new BinaryWriter(File.Open(filePath, FileMode.Append)))
{
writer.Write(binary.ToArray(), 0, binary.Count);
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
// Stop listening for new clients.
server.Stop();
}
}
Кто-нибудь может увидеть что-то, чего я не хватает / делаю неправильно?
Решение
Повреждение вызвано следующим кодом на сервере:
// Loop to receive all the data sent by the client.
while ((stream.Read(bytes, 0, bytes.Length)) != 0)
{
data.AddRange(bytes);
}
stream.Read
не всегда будет заполнять bytes
буфер.Он не будет заполнен, если в TCP-сокете больше нет доступных данных или при чтении последнего фрагмента сообщения (если только он не кратен размеру буфера).
В data.AddRange
вызов добавляет все из bytes
(исходя из предположения, что он всегда полон).В результате это иногда приводит к добавлению данных из предыдущего вызова в stream.Read
.Чтобы исправить это, вам нужно сохранить количество байт, возвращаемых Read
и добавьте только это количество байт:
int length;
while ((length = stream.Read(bytes, 0, bytes.Length)) != 0)
{
var copy = new byte[length];
Array.Copy(bytes, 0, copy, 0, length);
data.AddRange(copy);
}
Обратите внимание, что вы, возможно, захотите реструктурировать свой код, чтобы повысить производительность и использование памяти (и, возможно, в результате облегчить его чтение).Вместо того чтобы считывать все данные в память клиента перед отправкой, вы можете просто записать непосредственно в NetworkStream
.На сервере вам не нужно копировать все из потока в память.Вы можете считать длину имени файла в 4 байта и декодировать его, затем прочитать и декодировать имя файла и, наконец, скопировать оставшуюся часть потока непосредственно в FileStream
(тот BinaryWriter
является ненужным).
Также стоит отметить, что вы создаете выходной файл с FileMode.Append
.Это означает, что каждый отправленный файл будет добавлен к предыдущей копии с тем же именем.Возможно, вы захотите использовать FileMode.Create
вместо этого, который перезапишет, если файл уже существует.