Search code examples
c#tcpfilestreamnetworkstream

C# TCP NetworkStream with FileStream losing data


I'm programing an application where I need to make file transfers.

Most of the communication in my application is TCP and works just fine. But when I try to do a file transfer, I seem to lose some bytes at the start and/or end of the file.

Here is the piece of code that is supposed to do the file transfer:

Thread sendFile = new Thread(new ThreadStart(() =>
{
    TcpClient tcpClient = new TcpClient(ip, 3);
    tcpClient.Client.DontFragment = true;
    FileStream fileStream = new FileInfo(FilePath).OpenRead();
    Thread.Sleep(1000);
    fileStream.CopyTo(tcpClient.GetStream());
    fileStream.Close();
    tcpClient.Close();
}));
sendFile.SetApartmentState(ApartmentState.STA);
sendFile.Start();
sendFile.Join();

I have searched and tried a bunch of methodes of sending a filestream through a networkstream (WriteAsync, byte[] buffer, flushing the buffers,...) but all had similar results: some bytes at the start of the file and about every 128kb disappear.

I got the best results when running the transfer in a STA thread with some delay before starting.

Client code:

FileStream fileStream = File.Create(path);
Thread receiveFile = new Thread(new ThreadStart(() =>
{
    tcpClient.GetStream().CopyTo(fileStream);
}));
receiveFile.SetApartmentState(ApartmentState.STA);
receiveFile.Start();
receiveFile.Join();
fileStream.Close();

I tried it on different computers and routers connected with LAN cables to make sure those weren't the problems.

I'm using .Net Core 5.0

Update

I've tried a few things and it made it better, but still not perfect.

Server code:

Thread sendFile = new Thread(new ThreadStart(() =>
{
    TcpClient tcpClient = new TcpClient(ip, 3);
    FileStream fileStream = new FileInfo(FilePath).OpenRead();
    NetworkStream networkStream = tcpClient.GetStream();
    Thread.Sleep(1000);
    byte[] bytes = new byte[1024];
    int read = -1;
    while (read != 0)
    {
        read = fileStream.Read(bytes);
        networkStream.Write(bytes, 0, read);
    }
    filestream.Flush();
    fileStream.Close();
    tcpClient.Close();
}));
sendFile.SetApartmentState(ApartmentState.STA);
sendFile.Start();
sendFile.Join();

Client code:

FileStream fileStream = File.Create(path);
BufferedStream networkStream = new BufferedStream(client.GetStream());
Thread receiveFile = new Thread(new ThreadStart(() =>
{
   byte[] bytes = new byte[2048];
   int read = -1;
   while (read != 0)
   {
       read = networkStream.Read(bytes, 0, bytes.Length);
       using (MemoryStream memoryStream = new MemoryStream(bytes))
       {
           using (BinaryReader binaryReader = new BinaryReader(memoryStream))
           {
               fileStream.Write(binaryReader.ReadBytes(read));
           }
       }
   }
fileStream.Flush();
}));
receiveFile.SetApartmentState(ApartmentState.STA);
receiveFile.Start();
receiveFile.Join();
fileStream.Close();

Solution

  • As I had assumed the problem lies on the TcpClient. When using normal sockets everything works as it should: no data loss.

    Client code:

    Thread receiveFile = new Thread(new ThreadStart(() =>
    {
        Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
        socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);
        socket.Bind(new IPEndPoint(IPAddress.Any, 33));
        socket.Listen();
        socket = socket.Accept();
        FileStream fileStream = File.Create(path);
        NetworkStream networkStream = new NetworkStream(socket);
        networkStream.CopyTo(fileStream);
        fileStream.Flush();
        fileStream.Close();
        socket.Close();
        socket.Dispose();
    }));
    receiveFile.SetApartmentState(ApartmentState.STA);
    receiveFile.Start();
    receiveFile.Join();
    GC.Collect();
    

    Server code:

    Thread sendFile = new Thread(new ThreadStart(() =>
    {
        Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
        socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);
        socket.Connect(new IPEndPoint(IPAddress.Parse(client.Host), 33));
        FileStream fileStream = new FileInfo(FilePath).OpenRead();
        NetworkStream networkStream = new NetworkStream(socket);
        Thread.Sleep(1000);
        fileStream.CopyTo(networkStream);
        fileStream.Flush();
        fileStream.Close();
        socket.Close();
        socket.Dispose();
    }));
    sendFile.SetApartmentState(ApartmentState.STA);
    sendFile.Start();
    sendFile.Join();
    GC.Collect();
    

    I forced the collector to make sure the sockets are disposed, so they can be used again.