¿Cuál es la forma correcta de cerrar un C# Socket en .NET Core 3.1?

Jan 30 2021

La cuestión

Estoy tratando de manejar la desconexión de mi aplicación y cualquier enfoque que haya intentado hasta ahora ha fallado, traté de desconectarme del lado del servidor, traté de desconectarme del lado del cliente pero recibí problemas en ambos extremos, lo que estoy tratando de lograr aquí es para desconectar la aplicación a través de un comando SALIR sin tener que cerrarla desde el ícono de cerrar ya que arroja una excepción

cliente.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace MessengerConsole
{
class Client
{
    static string username;
    static int port = 8888;
    static IPAddress clientIP;
    static Socket serverSocket;
    static Thread processThread;
    static bool connected = false;

    static string GetIp()
    {
        Console.WriteLine("Type the server ip:");
        String clientIP = Console.ReadLine();
        Console.WriteLine("Client IP: " + clientIP);
        //return clientIP;
        //temp solution
        return "192.168.0.106";
    }

    static void printSession()
    {
        Console.Clear();
        Console.WriteLine("//=====================================================");
        Console.WriteLine("//                       Session Details               ");
        Console.WriteLine("//                    =====================");
        Console.WriteLine("//                    IP: " + clientIP + "\n//                    Time: " + DateTime.Now);
        Console.WriteLine("//=====================================================");
    }
    static void clientReceiver()
    {
        while (true)
        {
            Thread.Sleep(500);
            byte[] buffer = new byte[300];
            int rece = serverSocket.Receive(buffer, 0, buffer.Length, 0);
            Array.Resize(ref buffer, rece);
            if (connected == false)
            {
                Console.WriteLine("[" + DateTime.Now.ToString() + "] " + Encoding.Default.GetString(buffer) + " Connected!");
                connected = true;
            }

            else
            {

                if (Encoding.Default.GetString(buffer) == "QUIT")
                {
                    //Quit
                    Console.WriteLine("Server Shutdown");
                    serverSocket.Shutdown(SocketShutdown.Both);
                    serverSocket.Close();
                   
                }
                else
                {
                    Console.WriteLine("[" + DateTime.Now.ToString() + "] " + Encoding.Default.GetString(buffer));
                }

            }
        }
    }



    public static void StartClient()
    {
        processThread = new Thread(clientReceiver);
        Console.WriteLine("Please enter your name");
        username = Console.ReadLine();
        clientIP = IPAddress.Parse(GetIp());  //Returns IP from GetIP()
        Console.WriteLine("Please enter HostPort");
        //string portString = Console.ReadLine();
        //temp port
        string portString = "80";
        try
        {
            port = Convert.ToInt32(portString);
        }
        catch
        {
            port = 8888;
        }
        try
        {

            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            serverSocket.Connect(new IPEndPoint(clientIP, port));
            processThread.Start();
            byte[] name = Encoding.Default.GetBytes(username);
            
            //Send Name
            serverSocket.Send(name);
           // byte[] data = Encoding.Default.GetBytes("<" + username + "> Connected");
            //serverSocket.Send(data, 0, data.Length, 0);
            printSession();
            while (serverSocket.Connected)
            {
                //byte[] sdata = Encoding.Default.GetBytes("<" + username + ">" + Console.ReadLine());
                byte[] sdata = Encoding.Default.GetBytes(Console.ReadLine());
                if(Encoding.Default.GetString(sdata) == "QUIT")
                {
                    serverSocket.Send(sdata, sdata.Length, 0);
                    serverSocket.Shutdown(SocketShutdown.Both);
                    serverSocket.Disconnect(true);
                    serverSocket.Close();
                }

                else
                {
                    serverSocket.Send(sdata, 0, sdata.Length, 0);
                }

            }
        }
        catch (Exception e)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine(e.Message);
            Console.ForegroundColor = ConsoleColor.White;
        }

    }
}
}

servidor.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;

namespace MessengerConsole
{
class Server
{

    //Server Socket
    static Socket serverSocket;
    //Client Socket
    static Socket clientSocket;

    //Other Variables
    static int port = 8888;
    static IPAddress serverIP;
    static Thread processThread;
    static string username;
    static bool connected = false;
    //Function returns IP Address
    static string GetIp()
    {
        //Computer Name
        string hostname = Dns.GetHostName();

        /*The IPHostEntry class associates a Domain Name System (DNS) host name with an array of aliases and 
         * an array of matching IP addresses.
         */
        IPHostEntry ipentry = Dns.GetHostEntry(hostname);

        //The Address
        IPAddress[] ipAddress = ipentry.AddressList;

        return ipAddress[ipAddress.Length - 1].ToString();
    }

    static void printSession()
    {
        Console.Clear();
        Console.WriteLine("//=====================================================");
        Console.WriteLine("//                       Session Details               ");
        Console.WriteLine("//                    =====================");
        Console.WriteLine("//                    You are hosting the server");
        Console.WriteLine("//                    Time: " + DateTime.Now);
        Console.WriteLine("//=====================================================");
    }
    //Receive
    static void serverReciever()
    {
        while (true)
        {
            Thread.Sleep(500);
            byte[] buffer = new byte[300];
            int rece = clientSocket.Receive(buffer, 0, buffer.Length, 0);
            Array.Resize(ref buffer, rece);

            if (connected == false)
            {
                Console.WriteLine("[" + DateTime.Now.ToString() + "] " + Encoding.Default.GetString(buffer) + " Connected!");
                connected = true;
            }

            else
            {
                if(Encoding.Default.GetString(buffer) == "QUIT")
                {
                    //Quit
                    Console.WriteLine("Client disconnected from the chat");
                    clientSocket.Shutdown(SocketShutdown.Both);
                    clientSocket.Close();
                }
                else
                {
                    Console.WriteLine("[" + DateTime.Now.ToString() + "] " + Encoding.Default.GetString(buffer));
                }

            }
        }
    }



    public static void StartServer()
    {
        //Thread
        processThread = new Thread(serverReciever);

        //Display
        Console.WriteLine("Your Local Ip is " + GetIp());
        Console.WriteLine("Please enter your name");
        username = Console.ReadLine();
        Console.WriteLine("Please enter HostPort");
        //  string portString = Console.ReadLine();
        //temp solution
        string portString = "80";
        try
        {
            port = Convert.ToInt32(portString);
        }
        catch
        {
            port = 8888;
        }

        try
        {
            //GetIp returns string
            serverIP = IPAddress.Parse(GetIp());
            //TCP Socket
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            serverSocket.Bind(new IPEndPoint(serverIP, port));
            serverSocket.Listen(0);

            //Server Socket listening for client requests
            clientSocket = serverSocket.Accept();
            printSession();
            processThread.Start();
            byte[] name = Encoding.Default.GetBytes(username);
            clientSocket.Send(name);
            while (true)
            {
                byte[] sdata = Encoding.Default.GetBytes(Console.ReadLine());
                clientSocket.Send(sdata, 0, sdata.Length, 0);
            }
        }
        catch
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("Server already open!");
            Console.ForegroundColor = ConsoleColor.White;
        }
    }
}
}

este es el error que he recibido

Excepción no controlada. System.ObjectDisposedException: no se puede acceder a un objeto desechado. Nombre del objeto: 'System.Net.Sockets.Socket'. en System.Net.Sockets.Socket.Receive(Byte[] buffer, Int32 offset, Int32 size, SocketFlags socketFlags, SocketError& errorCode) en System.Net.Sockets.Socket.Receive(Byte[] buffer, Int32 offset, Int32 size, SocketFlags socketFlags) en MessengerConsole.Client.clientReceiver() en C:\Users\MessengerConsoleAppV2\MessengerConsole\Client.cs:línea 42 en System.Threading.ThreadHelper.ThreadStart_Context(Estado del objeto) en System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext , devolución de llamada de ContextCallback, estado del objeto) --- Fin del seguimiento de la pila desde la ubicación anterior donde se lanzó la excepción --- en System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, devolución de llamada de ContextCallback,

Intenté usar Socket.Disconnect() , Socket.Close(), Socket.Dispose() y todos dieron el mismo resultado

EDITAR:

agregando returnen el

if(Encoding.Default.GetString(sdata) == "QUIT")
            {
                serverSocket.Send(sdata, sdata.Length, 0);
                serverSocket.Shutdown(SocketShutdown.Both);
                serverSocket.Close();
                return;
            }

No da como resultado ningún error en el lado del servidor, pero el cliente aún arroja la excepción del acceso al objeto desechado

Unhandled exception. System.ObjectDisposedException: Cannot access a disposed object. Object name: 'System.Net.Sockets.Socket'. at System.Net.Sockets.Socket.Receive(Byte[] buffer, Int32 offset, Int32 size, SocketFlags socketFlags, SocketError& errorCode) at System.Net.Sockets.Socket.Receive(Byte[] buffer, Int32 offset, Int32 size, SocketFlags socketFlags) at MessengerConsole.Client.clientReceiver() in C:\Users\MessengerConsoleAppV2\MessengerConsole\Client.cs:line 44 at System.Threading.ThreadHelper.ThreadStart_Context(Object state) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) --- End of stack trace from previous location where exception was thrown --- at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart()

Respuestas

1 AlexeyRumyantsev Jan 30 2021 at 22:00

En todos los lugares del código relacionado con QUITusted, solo cierra y desecha el socket, pero no regresa del bucle infinito que sirve a este socket. Creo que esta pila se lanza en la próxima iteración cuando Connectedla propiedad se evalúa en el socket desechado, intente agregar returnuna declaración después de cerrar el socket.