tcpclient 및 tcplistener를 사용한 후 뒤죽박죽 바이트 어레이
-
18-09-2019 - |
문제
TCPCLIENT 및 TCPLISTERE를 사용하여 네트워크를 통해 MP3 파일을 보내고 싶습니다. 소켓을 사용 하여이 솔루션을 구현했지만 몇 가지 문제가 있었으므로 파일을 보내는 새로운/더 나은 방법을 조사하고 있습니다.
다음과 같이 보이는 바이트 배열을 만듭니다 : longth_of_filename | filename | 파일
그런 다음 위에서 언급 한 클래스를 사용하여 전송해야하지만 서버 측에서 읽은 바이트 배열은 완전히 엉망이되어 이유가 확실하지 않습니다.
내가 보내는 데 사용하는 방법 :
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
대신 파일이 이미 존재하는 경우 덮어 씁니다.