4.05.2014

Problem z Socket.Connect

Robiąc jakiś przykład systemu klient-serwer napotkałem problem w metodzie Connect dla socketa. Problem pojawia się w chwili gdy np. restartując usługę na serwerze, klient .Net Micro Framework próbuje się do niego połączyć. Cały klient się po prostu zawiesza właśnie na metodzie Connect. Takie zachowanie można wywołać np. takim kotem:
IPAddress serverIp = Dns.GetHostEntry("www.google.pl").AddressList[0];
EndPoint endPoint = new IPEndPoint(serverIp, 10000);

using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
    try
    {
        socket.Connect(endPoint);
    }
    catch (SocketException ex)
    {
        Debug.Print(ex.Message + ": " + ex.ErrorCode);
    }
}
Próbowałem to jakoś obejść. Zacząłem kombinować tak, aby uruchomić Connect w osobnym wątku, a jeśli się zawiesi to go ubić. Wyszło mi coś takiego:

IPAddress serverIp = Dns.GetHostEntry("www.google.pl").AddressList[0];
EndPoint endPoint = new IPEndPoint(serverIp, 10000);

retryConnect:
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
    bool connected = false;
    var t = new Thread(() =>
                            {
                                try
                                {
                                    socket.Connect(endPoint);
                                    connected = true;
                                }
                                catch (SocketException ex)
                                {
                                    Debug.Print("SocketException: " + ex.ErrorCode);
                                }
                            });

    t.Start();
    if (!t.Join(1000))
    {
        t.Abort();
        Debug.Print("Timeout");
        goto retryConnect;
    }

    if (!connected)
        goto retryConnect;


    Debug.Print("Connected!!!");
}
Moje rozwiązanie działa poprawnie, ale przypomniałem sobie o pewnej klasie: Microsoft.SPOT.ExecutionConstraint. ExecutionConstraint pilnuje, aby program zakończył się w założonym czasie. Jeśli wykonywanie programu będzie trwało dłużej, zostanie on przerwany i zostanie wygenerowany wyjątek ConstraintException. Poniżej przykład jak tego używać:
const int runMilisec = 10000;
ExecutionConstraint.Install(runMilisec, 0);
try
{
    //operation
    //code here
}
catch (ConstraintException)
{
    //timeout
}
finally
{
    ExecutionConstraint.Install(Timeout.Infinite, 0);
}
Tak więc na bazie tej klasy można zbudować rozszerzenie klasy Socket - nową funkcję Connect, która będzie próbować się połączyć w zadanym czasie, i zwróci informację czy połączenie przebiegło poprawnie czy nie:
public static class SocketExtension
{
    public static bool Connect(this Socket @this, EndPoint endPoint, int timeoutMiliseconds)
    {
        ExecutionConstraint.Install(timeoutMiliseconds, 0);
        try
        {
            @this.Connect(endPoint);
            return true;
        }
        catch (ConstraintException)
        {
            return false;
        }
        finally
        {
            ExecutionConstraint.Install(Timeout.Infinite, 0);
        }
    }
}