1

Here is how i read data from my stream now:

public List<ServerClient> clients = new List<ServerClient>();

while (true)
{
    Update();
}

    private void Update()
    {
        //Console.WriteLine("Call");
        if (!serverStarted)
        {
            return;
        }

        foreach (ServerClient c in clients.ToList())
        {
            // Is the client still connected?
            if (!IsConnected(c.tcp))
            {
                c.tcp.Close();
                disconnectList.Add(c);
                Console.WriteLine(c.connectionId + " has disconnected.");
                CharacterLogout(c.connectionId);
                continue;
                //Console.WriteLine("Check for connection?\n");
            }
            else
            {



                // Check for message from Client.
                NetworkStream s = c.tcp.GetStream();
                if (s.DataAvailable)
                {
                    string data = c.streamReader.ReadLine();

                    if (data != null)
                    {
                        OnIncomingData(c, data);
                    }

                }
                //continue;
            }
        }

        for (int i = 0; i < disconnectList.Count - 1; i++)
        {
            clients.Remove(disconnectList[i]);
            disconnectList.RemoveAt(i);
        }


    }

When data is read it is send to OnIncomingData function which is processing the data. I don't have problems there.

Here is how i send data to the stream:

public void Send(string header, Dictionary data) {

if (stream.CanRead)
{
    socketReady = true;
}

if (!socketReady)
{
    return;
}
JsonData SendData = new JsonData();
SendData.header = "1x" + header;
foreach (var item in data)
{
    SendData.data.Add(item.Key.ToString(), item.Value.ToString());
}
SendData.connectionId = connectionId;

string json = JsonConvert.SerializeObject(SendData);
var howManyBytes = json.Length * sizeof(Char);
writer.WriteLine(json);
writer.Flush();

Debug.Log("Client World:" + json);

}

Here is my:

public class ServerClient
{
    public TcpClient tcp;
    public int accountId;
    public StreamReader streamReader;
    public int connectionId;
    public ServerClient(TcpClient clientSocket)
    {
        tcp = clientSocket;
    }
}

Here is my OnConnection function:

    private void OnConnection(IAsyncResult ar)
    {
        connectionIncrementor++;
        TcpListener listener = (TcpListener)ar.AsyncState;
        NetworkStream s = clients[clients.Count - 1].tcp.GetStream();
        clients.Add(new ServerClient(listener.EndAcceptTcpClient(ar)));
        clients[clients.Count - 1].connectionId = connectionIncrementor;
        clients[clients.Count - 1].streamReader = new StreamReader(s, true);
        StartListening();


        //Send a message to everyone, say someone has connected!
        Dictionary<string, string> SendDataBroadcast = new Dictionary<string, string>();
        SendDataBroadcast.Add("connectionId", clients[clients.Count - 1].connectionId.ToString());

        Broadcast("001", SendDataBroadcast, clients, clients[clients.Count - 1].connectionId);
        Console.WriteLine(clients[clients.Count - 1].connectionId + " has connected.");
    }

Normally everything works fine. However if i try to send more request per 1 second the problem occurs. The message received is not full and complete. It just receives a portion of the message sent.

From Debug.Log("Client World:" + json); i can see that the message is full and complete but on the server i see that it is not.

This is not happening if i send less requests.

So for that reason i think i should create a MemoryStream and puts a message there and read it after. However i'm really not sure how i can do that. Can you help ?

Venelin
  • 2,905
  • 7
  • 53
  • 117
  • Too much text, try to create a [mcve]. That said, how/from where do you call `ClientWorldServer.Send`? – Tewr Nov 20 '17 at 07:48
  • You are reading one line per connection, or reading lines in a loop? – Evk Nov 20 '17 at 08:56
  • I have updated my question for you to see. And yes this function is called inside a while loop. – Venelin Nov 20 '17 at 09:03
  • StreamReader buffers data inside, so it's wrong to create new `StreamReader` every time like you are doing now. Instead create one `StreamReader` per client and then read lines from it in a loop. – Evk Nov 20 '17 at 09:05
  • can you make the changes you suggest to the code and add it as an answer ? – Venelin Nov 20 '17 at 09:35

2 Answers2

1

The whole code is not very good, but I'll concentrate on your specific problem. It's most likely related to data buffering by StreamReader. StreamReader has buffer size (which you can pass to constructor) which defaults to 1024 bytes. When you call ReadLine - it's perfectly possible for stream reader to read more than one line from the underlying stream. In your case - you have while loop in which you enumerate connected clients and in every iteration of the loop you create new StreamReader and read one line from it. When message rate is low - all looks fine, because between your loop iterations only one line arrives. Now suppose client quickly sent 2 json messages, each of which is 800 bytes, and they both arrived into your socket. Now you call StreamReader.ReadLine. Because buffer size is 1024 - it will read 1024 bytes from socket (NetworkStream) and return first 800 to you (as a line). You process that line and discard StreamReader going to the next iteration of your while loop. By doing that you also discard part of the message (224 bytes of the next message), because they were already read from the socket into StreamReader buffer. I think from that it should be clear how to solve that problem - don't create new StreamReader every time but create one per client (for example store as a member of ServerClient) and use that.

Evk
  • 98,527
  • 8
  • 141
  • 191
  • The interesting part is that the missing part of the message is not the end, but the begging. If what you say is the problem and two messages of 800 bytes are sent in one socket but the default buffer is 1024 doesn't that mean i'll get the whole first message and the beginning part of the second ? – Venelin Nov 20 '17 at 10:00
  • @TonyStark no, you will get the end of second and not beginning, because you discarded beginning (first 224 bytes in my example). You read one line (800 bytes) but did not read second. On next iteration you will read second line, starting from 225 byte, while first 224 will be missing. – Evk Nov 20 '17 at 10:03
  • Do you suggest me to store `StreamReader` for every client connected in my `ServerClient` List ? I have added the class `ServerClient` into the question so you can check it. Thank you for helping me :) – Venelin Nov 20 '17 at 10:07
  • Yes, store it as `ServerClient.Reader` and then in your loop do `c.Reader.ReadLine()`. Well that's a thing that should fix your immediate problem. There are many things that can be improved in your code, but that's outside the scope of this question. – Evk Nov 20 '17 at 10:10
  • I'll do that but should i increase the default buffer size ? I mean if one message is 1200 bytes for example isn't that going to create a trouble again ? – Venelin Nov 20 '17 at 10:31
  • @TonyStark no, buffer indicates how much data is read from underlying stream in one "operation". So if buffer is 1024, it will read 1024 bytes, then again 1024 bytes and so on. If underlying stream has more data than buffer size - it's not a problem. – Evk Nov 20 '17 at 10:37
  • I'm not really sure how i can add the `StreamReader` inside the `ServerClient`. For that reason i have added new variable `public StreamReader streamReader;` in `ServerClient` and in the question i have added the code of `OnConnection` function where i set the connection id and so on. I'm pretty sure i have to set the `StreamReader` somehow inside that `OnConnection` function but i don't know how to declare it. Can you help me out on this please ? – Venelin Nov 20 '17 at 10:58
  • I have made the changes into the question. Can you check if i'm correctly setting the `StreamReader` inside `ServerClient` and also i'm i correctly using it in `Update` function ? I'm pretty sure it's not correct how i set the `StreamReader` – Venelin Nov 20 '17 at 11:04
  • You can do that in constructor of ServerClient: `streamReader = new StreamReader(tcp.GetStream(), false)` – Evk Nov 20 '17 at 11:08
  • In that case `public TcpClient tcp;` must be static ? like `public static TcpClient tcp;` ? Because if i leave like this: https://pastebin.com/uZEZp58y i get error `A field initializer cannot reference the non-static field, method, or property of 'ServerClient.tcp'` – Venelin Nov 20 '17 at 11:11
  • No, why do you think so? – Evk Nov 20 '17 at 11:12
  • Because if i leave like this: http://pastebin.com/uZEZp58y i get error `A field initializer cannot reference the non-static field, method, or property of 'ServerClient.tcp'` – Venelin Nov 20 '17 at 11:16
  • You need to put that in constructor: `public ServerClient(TcpClient clientSocket) {tcp = clientSocket; streamReader = new StreamReader(tcp.GetStream(), false);}` – Evk Nov 20 '17 at 11:35
  • Huuuuraaay! Problem is fixed. You said that there are many bad things in my code. What do you mean ? Can you please point me a bit ? – Venelin Nov 20 '17 at 11:40
  • You usually don't run endless `while` loop checking for if data is available. Instead you process each client individually in separate thread (and ideally in asynchronous manner). Try to google for something like "c# tcplistener async" for some examples. And also, using newline as separator for json messages might not be the best idea, because json itself might contain newlines. Usually people use something like format. Or just . This also prevents sending a ton of data without newlines and fill memory. – Evk Nov 20 '17 at 11:53
  • Thank you for your help. I appreciate everything you said. But i'm not using newline as separator for JSON. I'm using newtownsoft json library. – Venelin Nov 20 '17 at 13:34
  • By separator I mean you read from socket until newline character (with `StreamReader.ReadLine`). So one json message is separated from other by newline. – Evk Nov 20 '17 at 13:35
  • You mean `ReadLine` is separating messages by `\n` code ? – Venelin Nov 20 '17 at 13:36
  • Tcp is not "message oriented" protocol, which means what you read is just stream of bytes, there is no concept of message. If you want to write and read "messages" - you need to define message boundary, that is - how many bytes to read until you get full "message". You read until newline character appears in stream of bytes (with `ReadLine`). Your client writes json followed by newline (with `WriteLine`). – Evk Nov 20 '17 at 13:44
  • I got what you mean. So if i use `writter.Write` and `reader.Read` instead of `WriteLine` and `ReadLine` defining the beginning and the end of the message this problem will be resolved? Correct ? – Venelin Nov 20 '17 at 13:55
  • If you just use `writer.Write` - then you are losing your current separator and need to do something else, because you cannot then do `reader.Read()` - you don't know _how many_ bytes to read. But if you do something like `writer.Write(BitConverter.GetBytes(totalLengthOfMessage))` followed by `writer.Write(message)` - you can then read first 4 bytes, convert to integer via `BitConverter.ToInt32(your4BytesHere)` and then read given number of bytes from stream. – Evk Nov 20 '17 at 14:01
  • Yes that is more doable. I'll take that approach to improve my code. Thank you so much for your help. – Venelin Nov 20 '17 at 14:08
  • Hello there! I need your help once again. Can you please check this out: https://stackoverflow.com/questions/47429139/c-sharp-unity-client-reading-data-from-tcp-stream-slow ? Thanks. – Venelin Nov 22 '17 at 07:37
  • Hello Again @Evk can you please take a look at this question: https://stackoverflow.com/questions/47672684/c-unity-convert-streamwriter-writeline-to-streamwriter-write-and-message-byte – Venelin Dec 06 '17 at 11:16
0

The client looks more suspicious to me than the server.

StreamWriter is not thread-safe. Are you calling it in a thread-safe manner when using ClientWorldServer.Send? Lock up or queue your calls to ClientWorldServer.Send using a lock or BlockingCollection or some other synchronisation primitive. There is also a thread-safe wrapper of streamwriter you might be able to use.

Tewr
  • 3,713
  • 1
  • 29
  • 43
  • On the test made i can confirm that the client is sending the whole message but the server is not reading it. I've made the checks on the client here: https://pastebin.com/D4S0AKHL i can see that the `json` string is full and correct. However can i be sure that the whole string was writen in the stream ? How can i check that except `Debug.Log("Client World:" + json);` ? – Venelin Nov 20 '17 at 08:08
  • Sorry, can't read pastebin from here so can't verify your test. Your `Debug.Log` *may* show a correct package event if it is sent hacked up to the server, so I repeat my question: how are you calling `ClientWorldServer.Send`? Try putting a `lock` around `writer.WriteLine(json); writer.Flush();`. If that helps, even though probably not a good solution, at least you found the source of your problem – Tewr Nov 20 '17 at 08:24
  • Is this what you mean `lock (writer) { writer.WriteLine(json); writer.Flush(); Debug.Log("Client World:" + json); }` ? – Venelin Nov 20 '17 at 08:30
  • Also i have updated my question. Can you help me create a `StreamMemory` on the server side ? – Venelin Nov 20 '17 at 08:53
  • Not sure how I help you anything more than the basics which is already in the [manpage for Memorystream](https://msdn.microsoft.com/en-us/library/system.io.memorystream(v=vs.110).aspx). If you are truly sure this is a producer/consumer problem (some kind of buffer full problem?), consider the BlockingCollection I mentioned above. – Tewr Nov 20 '17 at 09:02