Повторно использовать сокеты или каждый раз создавать новый?

У меня есть 2 приложения, работающие на одном компьютере, которым нужно общаться. Один сделан с Unreal Engine, а другой — с настольным приложением C#.

Взяв это (Unreal Engine) и this (C#) в качестве справки, мне удалось успешно отправить сообщение с C# на UE.

Проблема в том, что я могу отправить только одно сообщение; во второй раз я не получаю ошибок, но сообщение не получено.

Правильным подходом к исправлению этого было бы закрытие сокета и создание нового для каждого сообщения или повторное использование одного и того же сокета?

Если возможны оба варианта, есть ли какие-либо существенные преимущества/недостатки в каждом из них?

Просто для справки: в моем конкретном сценарии связь осуществляется только в одну сторону (C # в UE), а отправляемые сообщения составляют всего несколько байтов каждые 10–60 секунд.

Любая помощь или комментарии приветствуются.


person jmcorallo    schedule 20.02.2016    source источник


Ответы (1)


В примере кода на C# это показано, что сокет должен использоваться для подключения, отправки и получения только один раз (и все это делается синхронно, то есть вы не можете перейти к следующей строке, пока не завершится вызов метода) перед его закрытием:

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();

Таким образом, если ваш прослушиватель TCP-сокетов в нереальном движке не закроет текущее соединение, вы потеряете конкретное соединение сокета, которое было к нему подключено, и, следовательно, не сможете продолжить работу.

Кроме того, имейте в виду, что пример кода, использующий синхронное соединение, отправку и получение, почти наверняка заблокирует ваш код и не идеален для большинства случаев.

Таким образом, не просто используйте код примера, вам необходимо значительно изменить его.

Я рекомендую вам взглянуть на этот пост , содержащий код для создания сервера и клиента для связи TCP/IP с использованием класса Socket. В посте уже есть рабочее решение для тестирования с помощью консольного приложения C#. И вы также можете увидеть, как Async (вместо Sync) используется для Connect и Receive.

Вот выдержка из кода (просто измените SERVER_IP и PORT_NO, чтобы они соответствовали вашим, и, вероятно, вам нужно будет использовать только клиент, поскольку Unreal Engine в вашем случае является сервером):

Код для вашего теста:

Сервер

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());
            }
        }

    }
}

Клиент

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());
            }
        }
    }
}
person Ian    schedule 20.02.2016
comment
Я изменил код клиента на отправлять и получать в цикле (синхронно), но я каждый раз создавал и уничтожал сокет. Socket Listener не закрывал соединения в Unreal, поэтому я терял соединение, поэтому прошло только первое сообщение. Спасибо за Ваш ответ - person jmcorallo; 20.02.2016
comment
@jmcorallo Понятно ... рад, что мой ответ может чем-то помочь. И удачи вашему проекту! ;) - person Ian; 20.02.2016