Search code examples
c#socketsunreal-engine4

Reuse sockets or create new one every time?


I have 2 applications running on the same machine that need to communicate. One is made with Unreal Engine and the other one a C# desktop app.

Taking this (Unreal Engine) and this (C#) as reference, I managed to successfully send a message from C# to UE.

The problem is that I can only send one message; the second time I get no errors, but the message is not received.

The correct approach to fix this would be to close the socket and create a new one for each message, or to reuse the same socket?

If both options are possible, is there any significant advantage/disadvantage on any?

Just as a reference, in my particular scenario communication is one way only (C# to UE) and the messages sent are just a few bytes every 10-60 seconds.

Any help or comments appreciated.


Solution

  • In the code example on C#, it is shown that the socket is to be used to connect, send, and receive only once (and all are done synchronously - that is, you cannot proceed to the next line unless the method call is completed) before it is closed:

    sender.Connect(remoteEP);
    
    Console.WriteLine("Socket connected to {0}",
        sender.RemoteEndPoint.ToString());
    
    // Encode the data string into a byte array.
    byte[] msg = Encoding.ASCII.GetBytes("This is a test<EOF>");
    
    // Send the data through the socket.
    int bytesSent = sender.Send(msg);
    
    // Receive the response from the remote device.
    int bytesRec = sender.Receive(bytes);
    Console.WriteLine("Echoed test = {0}",
        Encoding.ASCII.GetString(bytes,0,bytesRec));
    
    // Release the socket.
    sender.Shutdown(SocketShutdown.Both);
    sender.Close();
    

    Thus, if your TCP Socket listener in the unreal engine does not close the current connection, you will lose the particular socket connection which was connected to it and thus unable to proceed further.

    Further more, beware that the sample code using synchronous connect, send, and receive, which would almost definitely block your code and is not ideal for most of the cases.

    So, don't just simply use the example code, you need to significantly modify it.

    I recommend you to take a look at this post, containing the code for making both Server and Client for TCP/IP communication using Socket class. In the post, there is already a working solution for testing using C# Console application. And you can also see how the Async (instead of Sync) is used for Connect and Receive.

    Here is the excerpt of the code (just change the SERVER_IP and the PORT_NO to fit yours, and likely you would only need to use the client as the Unreal Engine is the server in your case):

    Code for your test:

    Server

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace TcpListenerConsoleApplication {
        class Program {
            const int PORT_NO = 2201;
            const string SERVER_IP = "127.0.0.1";
            static Socket serverSocket;
            static void Main(string[] args) {
                //---listen at the specified IP and port no.---
                Console.WriteLine("Listening...");
                serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                serverSocket.Bind(new IPEndPoint(IPAddress.Any, PORT_NO));
                serverSocket.Listen(4); //the maximum pending client, define as you wish
                serverSocket.BeginAccept(new AsyncCallback(acceptCallback), null);      
                string result = "";
                do {
                    result = Console.ReadLine();
                } while (result.ToLower().Trim() != "exit");
            }
    
            private const int BUFFER_SIZE = 4096;
            private static byte[] buffer = new byte[BUFFER_SIZE]; //buffer size is limited to BUFFER_SIZE per message
            private static void acceptCallback(IAsyncResult result) { //if the buffer is old, then there might already be something there...
                Socket socket = null;
                try {
                    socket = serverSocket.EndAccept(result); // The objectDisposedException will come here... thus, it is to be expected!
                    //Do something as you see it needs on client acceptance
                    socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
                    serverSocket.BeginAccept(new AsyncCallback(acceptCallback), null); //to receive another client
                } catch (Exception e) { // this exception will happen when "this" is be disposed...        
                    //Do something here             
                    Console.WriteLine(e.ToString());
                }
            }
    
            const int MAX_RECEIVE_ATTEMPT = 10;
            static int receiveAttempt = 0; //this is not fool proof, obviously, since actually you must have multiple of this for multiple clients, but for the sake of simplicity I put this
            private static void receiveCallback(IAsyncResult result) {
                Socket socket = null;
                try {
                    socket = (Socket)result.AsyncState; //this is to get the sender
                    if (socket.Connected) { //simple checking
                        int received = socket.EndReceive(result);
                        if (received > 0) {
                            byte[] data = new byte[received]; //the data is in the byte[] format, not string!
                            Buffer.BlockCopy(buffer, 0, data, 0, data.Length); //There are several way to do this according to http://stackoverflow.com/questions/5099604/any-faster-way-of-copying-arrays-in-c in general, System.Buffer.memcpyimpl is the fastest
                            //DO SOMETHING ON THE DATA int byte[]!! Yihaa!!
                            Console.WriteLine(Encoding.UTF8.GetString(data)); //Here I just print it, but you need to do something else                     
    
                            //Message retrieval part
                            //Suppose you only want to declare that you receive data from a client to that client
                            string msg = "I receive your message on: " + DateTime.Now;                      
                            socket.Send(Encoding.ASCII.GetBytes(msg)); //Note that you actually send data in byte[]
                            Console.WriteLine("I sent this message to the client: " + msg);
    
                            receiveAttempt = 0; //reset receive attempt
                            socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket); //repeat beginReceive
                        } else if (receiveAttempt < MAX_RECEIVE_ATTEMPT) { //fail but not exceeding max attempt, repeats
                            ++receiveAttempt; //increase receive attempt;
                            socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket); //repeat beginReceive
                        } else { //completely fails!
                            Console.WriteLine("receiveCallback fails!"); //don't repeat beginReceive
                            receiveAttempt = 0; //reset this for the next connection
                        }
                    }
                } catch (Exception e) { // this exception will happen when "this" is be disposed...
                    Console.WriteLine("receiveCallback fails with exception! " + e.ToString());
                }
            }
    
        }
    }
    

    Client

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace TcpClientConsoleApplication {
        class Program {
            const int PORT_NO = 2201;
            const string SERVER_IP = "127.0.0.1";
            static Socket clientSocket; //put here
            static void Main(string[] args) {
                //Similarly, start defining your client socket as soon as you start. 
                clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                loopConnect(3, 3); //for failure handling
                string result = "";
                do {
                    result = Console.ReadLine(); //you need to change this part
                    if (result.ToLower().Trim() != "exit") {
                        byte[] bytes = Encoding.ASCII.GetBytes(result); //Again, note that your data is actually of byte[], not string
                        //do something on bytes by using the reference such that you can type in HEX STRING but sending thing in bytes
                        clientSocket.Send(bytes);
                    }
                } while (result.ToLower().Trim() != "exit");
            }
    
            static void loopConnect(int noOfRetry, int attemptPeriodInSeconds) {
                int attempts = 0;
                while (!clientSocket.Connected && attempts < noOfRetry) {
                    try {
                        ++attempts;
                        IAsyncResult result = clientSocket.BeginConnect(IPAddress.Parse(SERVER_IP), PORT_NO, endConnectCallback, null);
                        result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(attemptPeriodInSeconds));
                        System.Threading.Thread.Sleep(attemptPeriodInSeconds * 1000);
                    } catch (Exception e) {
                        Console.WriteLine("Error: " + e.ToString());
                    }
                }
                if (!clientSocket.Connected) {
                    Console.WriteLine("Connection attempt is unsuccessful!");
                    return;
                }
            }
    
            private const int BUFFER_SIZE = 4096;
            private static byte[] buffer = new byte[BUFFER_SIZE]; //buffer size is limited to BUFFER_SIZE per message
            private static void endConnectCallback(IAsyncResult ar) {
                try {
                    clientSocket.EndConnect(ar);
                    if (clientSocket.Connected) {
                        clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), clientSocket);
                    } else {
                        Console.WriteLine("End of connection attempt, fail to connect...");
                    }
                } catch (Exception e) {
                    Console.WriteLine("End-connection attempt is unsuccessful! " + e.ToString());
                }
            }
    
            const int MAX_RECEIVE_ATTEMPT = 10;
            static int receiveAttempt = 0;
            private static void receiveCallback(IAsyncResult result) {
                System.Net.Sockets.Socket socket = null;
                try {
                    socket = (System.Net.Sockets.Socket)result.AsyncState;
                    if (socket.Connected) {
                        int received = socket.EndReceive(result);
                        if (received > 0) {
                            receiveAttempt = 0;
                            byte[] data = new byte[received];
                            Buffer.BlockCopy(buffer, 0, data, 0, data.Length); //There are several way to do this according to http://stackoverflow.com/questions/5099604/any-faster-way-of-copying-arrays-in-c in general, System.Buffer.memcpyimpl is the fastest
                            //DO ANYTHING THAT YOU WANT WITH data, IT IS THE RECEIVED PACKET!
                            //Notice that your data is not string! It is actually byte[]
                            //For now I will just print it out
                            Console.WriteLine("Server: " + Encoding.UTF8.GetString(data));
                            socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
                        } else if (receiveAttempt < MAX_RECEIVE_ATTEMPT) { //not exceeding the max attempt, try again
                            ++receiveAttempt;
                            socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(receiveCallback), socket);
                        } else { //completely fails!
                            Console.WriteLine("receiveCallback is failed!");
                            receiveAttempt = 0;
                            clientSocket.Close();
                        }
                    }
                } catch (Exception e) { // this exception will happen when "this" is be disposed...
                    Console.WriteLine("receiveCallback is failed! " + e.ToString());
                }
            }
        }
    }