Jumbled byte dopo usando TcpClient e TcpListener
-
18-09-2019 - |
Domanda
Voglio usare il TcpClient e TcpListener per inviare un file mp3 su una rete. Ho implementato una soluzione di questo utilizzando i socket, ma ci sono stati alcuni problemi in modo da sto indagando su un nuovo modo / meglio per inviare un file.
creo un array di byte che assomiglia a questo: length_of_filename | filename | file
Questo dovrebbe quindi essere trasmessa utilizzando le classi di cui sopra, ma sul lato server l'array di byte che ho letto è completamente incasinato e non sono sicuro perché.
Il metodo che uso per l'invio:
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);
}
}
}
Poi sul lato server appare come segue:
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();
}
}
Chiunque può vedere qualcosa che mi manca / facendo male?
Soluzione
La corruzione è causato dal seguente codice sul server:
// Loop to receive all the data sent by the client.
while ((stream.Read(bytes, 0, bytes.Length)) != 0)
{
data.AddRange(bytes);
}
stream.Read
non sarà sempre riempire il buffer bytes
. Non sarà riempita se il socket TCP non ha più dati disponibili, o durante la lettura l'ultimo pezzo del messaggio (a meno che non sia un multiplo esatto della dimensione del buffer).
La chiamata data.AddRange
aggiunge tutto da bytes
(facendo l'ipotesi che sia sempre pieno). Come risultato, questo a volte finire l'aggiunta di dati dalla precedente chiamata a stream.Read
. Per ovviare a tale situazione, è necessario memorizzare il numero di byte restituiti da Read
e solo aggiungere questo numero di byte:
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);
}
Si noti che si potrebbe desiderare di ristrutturare il codice per migliorare le prestazioni e l'utilizzo della memoria (e, probabilmente, rendere più facile da leggere come un risultato). Invece di leggere tutti i dati nella memoria sul client prima di inviare si può semplicemente scrivere direttamente al NetworkStream
. Sul server non è necessario copiare tutto dal flusso in memoria. È possibile leggere la lunghezza 4 byte di nome di file e decodificarlo, poi leggere e decodificare il nome del file e, infine, copiare il resto del flusso direttamente ad un FileStream
(la BinaryWriter
non è necessaria).
E 'anche interessante notare che si sta creando il file di output con FileMode.Append
. Questo significa che ogni file inviato verrà aggiunto alla copia precedente con lo stesso nome. Si consiglia di utilizzare FileMode.Create
invece, che sovrascriverà se il file esiste già.