-1

This is my client side code. The package is sent and received only once. In this code, I use two TCP and UDP protocols simultaneously on one port. My UDP protocol works very well but my TCP protocol has a problem and only sends and receives packets once. Thanks in advance for your guidance.

This is my code:

private void TryClientConnectToServerCallback(IAsyncResult result)
        {
             m_TcpClient.EndConnect(result);
            if (!m_TcpClient.Connected)
            {
                return;
            }
            else
            {
                m_MyStream = m_TcpClient.GetStream();
                m_MyStream.BeginRead(m_ReceiveBuffer, 0, 4096 * 2,new AsyncCallback(ReceiveDataCallBackTcp), null);
                //m_TcpClient.Client.BeginSend(m_ReceiveBuffer, 0, 4096 * 2, SocketFlags.None, ReceiveDataCallBackUdp, null);
                m_UdpClient.BeginReceive(ReceiveDataCallBackUdp, null);
                print(m_UdpClient.Client.Connected);
            }
        }
    
    private void ReceiveDataCallBackTcp(IAsyncResult result)
            {
                
                try
                {
                    print("Data Received");
                    int m_ReadBytes = m_MyStream.EndRead(result);
                    if (m_ReadBytes <= 0)
                    {
                        return; //no take any data from client
                    }
    
                    jsonObject = new JsonObject();
                    byte[] m_NewByte = new byte[m_ReadBytes];
                    Buffer.BlockCopy(m_ReceiveBuffer, 0, m_NewByte, 0, m_ReadBytes);
    
                    string jsonData = DataConverter.ConvertToString(m_ReceiveBuffer);
    
                    SendAndReceiveSizeDataLogger(false, m_NewByte, "TCP");
                    //Console.WriteLine("Data receive form client {0}", jsonData);
                    jsonObject = JsonConvert.DeserializeObject<JsonObject>(jsonData);
                    Debug.Log(jsonObject.FunctionName);
    
                    if (m_Events.ContainsKey(jsonObject.FunctionName))
                    {
                        for (int i = 0; i < m_Events[jsonObject.FunctionName].Count; i++)
                        {
                            m_Events[jsonObject.FunctionName][i](jsonObject);
                        }
                    }
                    m_MyStream.BeginRead(m_ReceiveBuffer, 0, 4096 * 2, new AsyncCallback(ReceiveDataCallBackTcp), null);
                    //m_TcpClient.Client.BeginSend(m_ReceiveBuffer, 0, 4096 * 2, SocketFlags.None, ReceiveDataCallBackUdp, null);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                }
            }

this code send data for client

public void SendTCP(JsonObject jsonObject)
        {
            if (!m_Socket.Connected)
            {
                Console.WriteLine("Failed to send data to server, you are not connected to the server");
                return;
            }

            //convert jsonObject class to jsonObject for send to server
            string jsonData = JsonConvert.SerializeObject(jsonObject);
            //Console.WriteLine("Data Serialized " + jsonData);
            byte[] data = new byte[4096];
            //convert data to byte array for send to server
            data = DataConverter.ConvertToByteArray(jsonData);

            Console.WriteLine("Sent Method " + m_Socket.Client.RemoteEndPoint.ToString());
            m_MyStream.BeginWrite(data, 0, jsonData.Length, SendTCPCallBack, null);

            #region Data Property
            int dataSendSize = 0;
            for (int i = 0; i < data.Length; i++)
            {
                if (data[i] > 0)
                {
                    dataSendSize++;
                }
            }
            //Console.WriteLine("Data size send to client : " + dataSendSize);
            #endregion
        }  
  • `TcpClient` has no `BeginRead`, that's a `Stream` method. It works just fine, although since 2012 there's **no** reason to use it instead of `ReadAsync`. Your code calls `BeginRead` just once, so where's the code that fails to work twice? – Panagiotis Kanavos Jul 14 '21 at 10:10
  • Have you checked if it's reading 0 bytes and returning without calling beginread again? Agree with PK that generally swapping to task async pattern makes life easier.. See something like https://stackoverflow.com/questions/24111640/async-await-usage-with-tcpclient for some example pattern of how using the Async way makes things easier – Caius Jard Jul 14 '21 at 10:10
  • Your code would be a **lot** simpler and cleaner if you used asynchronous methods and *didn't* try to use global fields for local variables like `m_MyStream` – Panagiotis Kanavos Jul 14 '21 at 10:11
  • does the code reach the second BeginRead? Is an exception thrown? Does it just not invoke the callback the second time? what happens? – Marc Gravell Jul 14 '21 at 10:11
  • btw: a bigger problem here is that the TCP code doesn't appear to deal with "framing"; TCP is strictly a stream protocol, not a packet protocol, which means that there is no natural boundary between data, and any "reads" **do not** necessarily map 1:1 with "writes"; this means that you might "read" half a message, three and a half messages, a single byte, or anything else - not exactly one message. Worse: since you're dealing with text data, unless you're dealing with ASCII, you might receive *partial characters* (i.e. 1 byte of a 3 byte encoded char); is it possible that the real problem... – Marc Gravell Jul 14 '21 at 10:16
  • @PanagiotisKanavos Your suggestion is to use `ReadAsync`? – Mahdi Mashmool Jul 14 '21 at 10:16
  • ...is the above, and it is failing in the `DataConverter.ConvertToString` step precisely because you don't have exactly one message (and/or partial characters)? Normally, TCP handling means "buffer until you have at least one message, by detecting frame boundaries; now handle any buffered frames, remembering to retain any partial frame data at the end, i.e. the incomplete start of the next message"; detecting frame boundaries is protocol-specific; in the case of text, it usually means looking for a sentinel character combination – Marc Gravell Jul 14 '21 at 10:16
  • @MarcGravell Not just once `BiginRead` is called – Mahdi Mashmool Jul 14 '21 at 10:19
  • *Both* the TCP and UDP client use the *same* global stream, `m_MyStream`. This *guarantees* failures. Both Json.NET and System.Text.Json can deserialize directly from a stream, so the entire `ReceiveDataCallBackTcp` method could be replaced eg with `using var sr=new StreamReader(stream); var jr=new JsonTextReader(sr); var data =serializer.Deserialize(jr);` – Panagiotis Kanavos Jul 14 '21 at 10:20
  • @PanagiotisKanavos that *will* block the thread on data, though; whether that is desirable is complex and contextual, and some serializers may have async handlers. There's also the question of whether this is a unary data stream, vs multiple payloads - in the latter case, OP will need some kind of framing protocol which will need handling separately to the deserialization step – Marc Gravell Jul 14 '21 at 10:25
  • @MahdiMashmool my suggestion is to explain what you want to do. Is the *real* question how deserialize asynchronously with JSON.NET? You can't do that, as the deserializer is synchronous. You can run it with `Task.Run` though. The rest of the code can be replaced with just a couple of lines using `async/await`. *BUT* the greatest issue is that `TCP` has no messages, so reading from the stream means you have to wait for it to close before you can process the entire data. You can use a *reader*, eg a `JsonTextReader` to process elements as they arrive – Panagiotis Kanavos Jul 14 '21 at 10:26
  • @MarcGravell thee are too many problems with the code, even if only UDP was used. We need to know what the *real* problem is - misunderstanding TCP? Or does the server really send a stream of JSON? Could a reader be used? – Panagiotis Kanavos Jul 14 '21 at 10:28
  • @PanagiotisKanavos Everything is working fine now I can send data from the client to the server with the TCP protocol, but I have trouble receiving it because the client does not notice the data at all. – Mahdi Mashmool Jul 14 '21 at 10:30
  • @PanagiotisKanavos The server sends the data, but it is read only once in the client, and if it is resended by the server, the client will not notice it. – Mahdi Mashmool Jul 14 '21 at 10:31
  • @MahdiMashmool it's not, when you use 30 lines to the work of 3 and use a global stream. You still haven't explained what you want to do. If you use TCP, you either read everything until the stream closes, or you need a way to know when each "message" terminates. So what are you trying to do? People can't write an answer if they don't know what the actual issue is. Do you receive one or multiple JSON documents from that stream? If multiple, how are they separated? By a newline or other character? Or is there no separator? – Panagiotis Kanavos Jul 14 '21 at 10:33
  • @PanagiotisKanavos The `m_MySreamBeginRead` method only works once this my problem – Mahdi Mashmool Jul 14 '21 at 10:40
  • @MahdiMashmool no, that's just one of the problems of the attempted solution. What does that TCP client receive? One JSON document per connection or multiple? – Panagiotis Kanavos Jul 14 '21 at 10:44
  • @MarcGravell He does not reach the second `BeginRead` and does not throw the exception And after that, nothing is received anymore – Mahdi Mashmool Jul 14 '21 at 10:47
  • @MahdiMashmool if it doesn't reach the second BeginRead, the reason it only reads once is precisely because *you only read once*; as for *why* that is: what happens when you step through the code from the first read? We can't tell you that: we don't have your data etc. Does it throw? Does it receive an EOF? *we don't know*, but: you can find out by stepping through – Marc Gravell Jul 14 '21 at 10:49
  • @PanagiotisKanavos Yes One JSON document with a size of about 200 bytes – Mahdi Mashmool Jul 14 '21 at 10:49
  • @MarcGravell No, nothing will be received anymore – Mahdi Mashmool Jul 14 '21 at 10:50
  • @MarcGravell The first time I get the full json document but the second time nothing is read – Mahdi Mashmool Jul 14 '21 at 10:52
  • @MahdiMashmool well... what did you expect to get? you already said you got the full JSON document, and you're expecting just one document, so... you should be expecting an EOF; are you sure it isn't correctly detecting that EOF condition? i.e. `m_ReadBytes <= 0`? – Marc Gravell Jul 14 '21 at 10:54
  • @MarcGravell I need to receive the document Jason several times, but in this code I only receive the document Jason once, on the first download to the client. – Mahdi Mashmool Jul 14 '21 at 10:58
  • @MahdiMashmool so once or several? First you say `one JSON document` then you say `receive several times` – Panagiotis Kanavos Jul 14 '21 at 11:04
  • @MahdiMashmool what does it *mean* to receive a single document several times? is the other end of the socket sending one document? or multiple documents? if it is sending one: you're done; if it is sending multiple, you'll need either multiple separate socket connections (one per document), or you'll need to implement a framing protocol, as already said – Marc Gravell Jul 14 '21 at 11:06
  • @MahdiMashmool A JSON string can only have one root and all serializers expect this. There's no standard way to have multiple independent JSON documents in a file or stream. Libraries that write multiple entries to a stream, eg logs, use custom delimiters, like newlines. – Panagiotis Kanavos Jul 14 '21 at 11:08
  • @MahdiMashmool I posted an answer for all possible options, using `await` and asynchronous methods to replace the code with just 3-6 lines per case. Which one are you talking about? – Panagiotis Kanavos Jul 14 '21 at 11:15
  • @PanagiotisKanavos I am writing this silence for a multiplayer game The game requires both TCP and UDP protocols – Mahdi Mashmool Jul 14 '21 at 11:36
  • This explains nothing. You can listen for UDP messages on one port and TCP on another. But what do you expect to retrieve from the TCP connection? One document per connection? Multiple? Besides, if you control all the code, why use TcpClient instead of HTTP or gRPC ? gRPC allows streaming messages, and ASP.NET Core fully supports this. – Panagiotis Kanavos Jul 14 '21 at 11:36
  • @PanagiotisKanavos What exactly do you want me to explain? Tell me so I can explain – Mahdi Mashmool Jul 14 '21 at 11:39
  • @PanagiotisKanavos I want the client to always be waiting for Jason documents Maybe every minute a Jason document was sent by the server Every second! But I want the client to always be listening to the server In my client code, after receiving the first Jason document, the client no longer listens to the server – Mahdi Mashmool Jul 14 '21 at 11:46
  • @MahdiMashmool and as everyone has commented already, you can't do that without a framing protocol, ie a way to identify messages. TCP has no messages. And once again, *why aren't you using ASP.NET Core?* You're trying to solve an already solved problem, in an inefficient way. You can start just a single HttpListener. Or create a [lightweight gRPC server](https://learn.microsoft.com/en-us/aspnet/core/grpc/?view=aspnetcore-5.0). Both will be faster than the current inefficient code – Panagiotis Kanavos Jul 14 '21 at 11:58
  • You can stream messages from servers to clients with both HTTP and gRPC, although gRPC is faster. You can use WebSockets in HTTP. You can use [server streaming](https://www.stevejgordon.co.uk/server-streaming-with-grpc-in-asp-dotnet-core) in gRPC. The implementations take care of efficient buffering and async operations, so both cases will be more efficient than the current code – Panagiotis Kanavos Jul 14 '21 at 12:01
  • @PanagiotisKanavos I have not worked with WebSocket and I have no information about it Is it suitable for multiplayer play? Does it support both UDP and TCP protocols simultaneously? – Mahdi Mashmool Jul 14 '21 at 12:09
  • Both websockets and gRPC are TCP-based (although I wonder whether gRPC will gain UDP for later HTTP transports like QUIC), but that's fine - you could use naked UDP *which already works*, and gRPC for the TCP part. Both are scalable - you're clearly using Stack Overflow, and I can tell you that Stack Overflow has 601082 open web-socket connections currently; however, I'd default to gRPC for your scenario (not web-sockets, which is HTTP/1.1 based) – Marc Gravell Jul 14 '21 at 12:40

1 Answers1

0

It's unclear what this code is trying to do, but it does have several problems. It's clear it's trying to asynchronously receive and deserialize data from a UDP or TCP client, but it's doing this in a very complicated way.

The only clear thing is that it's using Json.NET, which can't deserialize objects asynchronously.

Simply reading and deserializing a UPD message needs just a few lines :

var result=await udpClient.ReceiveAsync();
var jsonData=Encoding.UTF8.GetString(result.Buffer);
var jsonObject = JsonConvert.DeserializeObject<JsonObject>(jsonData);

TCP doesn't have messages though, it's a continuous stream of bytes. If a connection is only meant to retrieve a single document, we could just read until the stream closes and deserialize the data :

await tcpClient.ConnectAsync(...);
using var stream=tcpClient.GetStream();
//Uses UTF8 by default
using var sr=new StreamReader(stream);
var serializer = new JsonSerializer();
var jsonObject=serializer.Deserialize<JsonObject>(jsonData);

If the server sends multiple JSON documents, we'd need a way to identify them, and deserialize them one by one. A very common way to send multiple documents is to use unindented, single line documents and separate them with a newline, eg:

{"EventId":123,"Category":"Business","Level":"Information"}
{"EventId":123,"Category":"Business","Level":"Information"}
{"EventId":123,"Category":"Business","Level":"Information"}

In this case we can use a StreamReader to read the strings one line at a time and deserialize them:

await tcpClient.ConnectAsync(...);
using var stream=tcpClient.GetStream();
//Uses UTF8 by default
using var sr=new StreamReader(stream);
while(true)
{
    var line=await sr.ReadLineAsync();
    var jsonObject=JsonConvert.DeserializeObject<JsonObject>(jsonData);
    ...
}

Full Async with System.Text.Json

.NET Core 3.1 introduced System.Text.Json which is fully asynchronous. The TCP code can be rewritten to use JsonSerializer.DeserializeAsync :

await tcpClient.ConnectAsync(...);
using var utf8Stream=tcpClient.GetStream();
var jsonObject=await JsonSerializer.DeserializeAsync<JsonObject>(utf8Stream);

System.Text.Json works only with UTF-8, which is after all the de-facto standard encoding for all web applications.

No DeserializeAsync overload accepts a string, because there's no IO involved when the string is already in memory. The UDP or streaming JSON code would look similar to JSON.NET :

await tcpClient.ConnectAsync(...);
using var stream=tcpClient.GetStream();
//Uses UTF8 by default
using var sr=new StreamReader(stream);
while(true)
{
    var line=await sr.ReadLineAsync();
    var jsonObject=JsonSerializer.Deserialize<JsonObject>(jsonData);
    ...
}
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236