5

N.B. refer to this link.

I wrote this class to be used as a proxy in the server-end and as a client at the client-end.

My current source code is the following:

public class ClientClass : IDisposable
{
    private string Host { get; set; }
    private int Port { get; set; }

    public bool IsConnected { private set;  get; }

    public TcpClient Tcp { get; private set; }

    private System.Net.Sockets.NetworkStream stream;

    public ClientClass()
    {
        IsConnected = false;
    }

    //constructor for server program.
    public ClientClass(TcpListener listener)
    {
        Tcp = listener.AcceptTcpClient();

        Host = ((IPEndPoint)Tcp.Client.RemoteEndPoint).Address.ToString();
        Port = ((IPEndPoint)Tcp.Client.LocalEndPoint).Port;

        IsConnected = true;

        stream = Tcp.GetStream();
    }

    //constructor for client.
    public ClientClass(string host, int port)
    {
        Host = host;
        Port = port;
    }

    public string Read()
    {
        if (IsConnected)
        {
            byte[] buffer = new byte[Tcp.ReceiveBufferSize];//create a byte array
            int bytesRead = stream.Read(buffer, 0, Tcp.ReceiveBufferSize);//read count
            string str = Encoding.ASCII.GetString(buffer, 0, bytesRead);//convert to string
            return str.TrimEnd(new char[] {'\r', '\n'});//remove CR and LF
        }
        else
        {
            throw new Exception("Client " + ID + " is not connected!");
        }
    }

    public void Write(string str)
    {
        if (IsConnected)
        {
            str = str + Constants.CRLF;// add CR and LF
            byte[] bytesToSend = ASCIIEncoding.ASCII.GetBytes(str);
            stream.Write(bytesToSend, 0, bytesToSend.Length);
            stream.Flush();
        }
        else
        {
            throw new Exception("Client " + ID + " is not connected!");
        }
    }

    public bool Connect()
    {
        if (IsConnected == false)
        {
            IsConnected = true;

            Tcp = new TcpClient(Host, Port);

            stream = Tcp.GetStream();

            return true;
        }

        return false;
    }

    public bool Disconnect()
    {
        if (IsConnected)
        {
            if (Tcp != null)
            {
                //stream.Flush();
                stream.Close();
                //Tcp.GetStream().Flush();
                //Tcp.GetStream().Close();
                Tcp.Close();

                return true;
            }
        }

        return false;
    }

    #region dispose pattern
    // Flag: Has Dispose already been called?
    bool disposed = false;

    // Public implementation of Dispose pattern callable by consumers.
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    // Protected implementation of Dispose pattern.
    protected virtual void Dispose(bool disposing)
    {
        if (disposed)
            return;

        if (disposing)
        {
            // Free any other managed objects here
            if (stream != null)
            {
                stream.Flush();
                stream.Close();
                stream.Dispose();
                stream = null;
            }
            if (Tcp != null)
            {
                if (Tcp.Connected)
                {
                    Tcp.Client.Disconnect(false);
                    Tcp.Client.Close();
                    Tcp.Client.Dispose();
                    Tcp.Client = null;

                    //Tcp.GetStream().Flush();
                    //Tcp.GetStream().Close();
                    Tcp.Close();                        
                    Tcp = null;
                }
            }
        }

        // Free any unmanaged objects here.
        // ...
        disposed = true;
    }

    ~ClientClass()
    {
        Dispose(false);
    }
    #endregion
}

The problem I am facing with this code is:

ClientClass client = new ClientClass(...);
client.Write("1");
client.Write("2");
client.Write("hello");

are going to the other end as only one input:

"12hello"

, rather than three separate inputs {"1", "2", and "hello"}.

How can I fix this?


user366312
  • 16,949
  • 65
  • 235
  • 452

1 Answers1

7

TCP is a stream protocol, not a packet protocol. All that you are guaranteed is the same bytes in the same order (or a socket failure), not the composition of groups. So: anything beyond that you need to add yourself. With text-based protocols, a common approach might be to put a line feed (\n) after each logical payload, and then look for the same when decoding. With binary protocols, a length prefix is more common.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • how can I split data at the other end? – user366312 Mar 01 '20 at 20:51
  • @user366312 that really depends on how serious a server this is; for casual systems, you could just use `StreamReader` over the `NetworkStream`, and use `ReadLine()` / `ReadLineAsync()` in a loop (you could use `StreamWriter` and `WriteLine()` at the other end, too). For "serious" servers, I'd need about 4 hours to cover most of it ... – Marc Gravell Mar 01 '20 at 20:53
  • But, one question remains: my old source code was also using TCP protocol, but I didn't face this problem then. **why?** – user366312 Mar 01 '20 at 21:08
  • 1
    @user366312 probably fluke of timing combined with "nagle" (i.e. TCP delay heuristics); you might find that adding `socket.NoDelay = true` makes it behave a lot like it used to, but it is still fundamentally broken; it just usually worked "well enough"; again, TCP *needs* framing in your code - you can't rely on delivery details – Marc Gravell Mar 01 '20 at 21:18