Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I unbind a socket in C#?

I'm having some problems reusing a server socket in a test application I've made. Basically, I have a program that implements both the client side and the server side. I run two instances of this program for testing purposes, one instance starts to host and the other connects. This is the listening code:

private void Listen_Click(object sender, EventArgs e)
{
    try
    {
        server = new ConnectionWrapper();
        HideControls();
        alreadyReset = false;

        int port = int.Parse(PortHostEdit.Text);
        IPEndPoint iep = new IPEndPoint(IPAddress.Any, port);

        server.connection.Bind(iep); // bellow explanations refer to this line in particular
        server.connection.Listen(1);
        server.connection.BeginAccept(new AsyncCallback(OnClientConnected), null);
        GameStatus.Text = "Waiting for connections on port " + port.ToString();
    }
    catch (Exception ex)
    {
        DispatchError(ex);
    }
}
private void OnClientConnected(IAsyncResult iar)
{
    try
    {
        me = Player.XPlayer;
        myTurn = true;
        server.connection = server.connection.EndAccept(iar); // I will only have one client, so I don't care for the original listening socket.
        GameStatus.Text = server.connection.RemoteEndPoint.ToString() + " connected";
        StartServerReceive();
    }
    catch (Exception ex)
    {
        DispatchError(ex);
    }
}

This works fine the first time. However, after a while (when my little game ends), I call Dispose() on the server object, implemented like this:

public void Dispose()
{
    connection.Close(); // connection is the actual socket
    commandBuff.Clear(); // this is just a StringBuilder
}

I also have this in the object constructor:

public ConnectionWrapper()
{
    commandBuff = new StringBuilder();
    connection = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

    connection.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
}

I get no error when I click the Listen button a second time. The client side connects just fine, however my server side does not detect the client connection a second time, which basically renders the server useless anyway. I'm guessing it's connecting to the old, lingering socket, but I have no idea why this is happening to be honest. Here's the client connection code:

private void Connect_Click(object sender, EventArgs e)
{
    try
    {
        client = new ConnectionWrapper();
        HideControls();
        alreadyReset = false;

        IPAddress ip = IPAddress.Parse(IPEdit.Text);
        int port = int.Parse(PortConnEdit.Text);
        IPEndPoint ipe = new IPEndPoint(ip, port);
        client.connection.BeginConnect(ipe, new AsyncCallback(OnConnectedToServer), null); 
    }
    catch (Exception ex)
    {
        DispatchError(ex);
    }
}

If I do netstat -a in CMD, I see that the port I use is still bound and its state is LISTENING, even after calling Dispose(). I read that this is normal, and that there's a timeout for that port to be "unbound".

Is there a way I can force that port to unbind or set a very short timeout until it automatically gets unbound? Right now, it only gets unbound when I exit the program. Maybe I'm doing something wrong in my server? If so, what could that be? Why does the client connect fine, yet the server side doesn't detect it a second time?

I could make the socket always listen, not dispose it, and use a separate socket to handle the server connection, which would probably fix it, but I want other programs to be able to use the port between successive play sessions.

I remember seeing another question asking this, but there was no satisfactory answer for my case there.

like image 411
IVlad Avatar asked Dec 29 '22 17:12

IVlad


2 Answers

There may be a couple of reasons why the port would stay open, but I think you should be able to resolve your issue by using an explicit LingerOption on the socket:

LingerOption lo = new LingerOption(false, 0);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger, lo);

This basically turns the socket shutdown into an abortive shutdown instead of a graceful shutdown. If you want it to be graceful but just not wait as long, then use true in the constructor and specify a small but nonzero value for the timeout.

I just noticed this line, which is almost undoubtedly part of your problem:

server.connection = server.connection.EndAccept(iar); // I will only have one client, so I don't care for the original listening socket.

The comment you've written here is, well, wrong. Your wrapper class really shouldn't allow connection to be written to at all. But you cannot simply replace the listening socket with the client socket - they're two different sockets!

What's going to happen here is that (a) the listening socket goes out of scope and therefore never gets explicitly closed/disposed - this will happen at a random time, possibly at a nasty time. And (b) the socket that you do close is just the client socket, it will not close the listening socket, and so it's no wonder that you're having trouble rebinding another listening socket.

What you're actually witnessing isn't a socket timeout, it's the time it takes for the garbage collector to realize that the listening socket is dead and free/finalize it. To fix this, you need to stop overwriting the listening socket; the Dispose method of your wrapper class should dispose the original listening socket, and the client socket should be tracked separately and disposed whenever you are actually done with it.

In fact, you should really never need to rebind another listening socket at all. The listening socket stays alive the whole time. The actual connection is represented by just the client socket. You should only need to dispose the listening socket when you finally shut down the server.

like image 55
Aaronaught Avatar answered Dec 31 '22 07:12

Aaronaught


I agree with the previous answer, you should also "shutdown" to allow any existing activity to complete and then close the socket flagging it for reuse...

socket.Shutdown(SocketShutdown.Both);
socket.Disconnect(true);
like image 21
JoeGeeky Avatar answered Dec 31 '22 06:12

JoeGeeky