0
votes

There are two socket servers, one is the primary server which is not always on, and one is a backup server.

My program will try to connect the primary server using non-block connection (so that a timeout val can be applied), and if failed, it will connect the backup server using blocking connection.

However the second connect function will return "Invalid argument" error code in most timeļ¼š

#define SERVER_URL "example.com"
#define SERVER_PORT_PRIMARY "1234"
#define SERVER_PORT_BACKUP "5678"

struct addrinfo *result = NULL;
struct addrinfo hints;

ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;

if (getaddrinfo(SERVER_URL , SERVER_PORT_PRIMARY , &hints, &result) != 0) {
    WSACleanup();
    return;
}

SOCKET socketClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socketClient == SOCKET_ERROR){
    WSACleanup();
    return;
}

//set the socket in non-blocking
unsigned long iMode = 1;
iResult = ioctlsocket(socketClient, FIONBIO, &iMode);
if (iResult != NO_ERROR){
    closesocket(socketClient);
    WSACleanup();
    return;
}

if (connect(socketClient, result->ai_addr, (int)result->ai_addrlen) == SOCKET_ERROR){
    if (WSAGetLastError() != WSAEWOULDBLOCK){
        closesocket(socketClient);
        WSACleanup();
        return;
    }
}

//switch it back to blocking socket
iMode = 0;
iResult = ioctlsocket(socketClient, FIONBIO, &iMode);
if (iResult != NO_ERROR){
    closesocket(socketClient);
    WSACleanup();
    return;
}
fd_set Write, Err;
FD_ZERO(&Write);
FD_ZERO(&Err);
FD_SET(socketClient, &Write);
FD_SET(socketClient, &Err);
TIMEVAL Timeout;
Timeout.tv_sec = 10;
Timeout.tv_usec = 0;
select(0, NULL, &Write, &Err, &Timeout);
if (FD_ISSET(socketClient, &Write) == false){
    //unable to connect to primary server within 10s, try to connect backup server
    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    if (getaddrinfo(SERVER_URL , SERVER_PORT_BACKUP, &hints, &result) != 0) {
        closesocket(socketClient);
        WSACleanup();
        return;
    }
    iResult = connect(socketClient, result->ai_addr, (int)result->ai_addrlen);
    if (iResult == SOCKET_ERROR){
        int a = WSAGetLastError(); ///<----Problem here, a == WSAEINVAL (Invalid argument)
        closesocket(socketClient);
        WSACleanup();
        return;
    }
}

As I commented on the code above, the second "connect" call will return SOCKET_ERROR in most time and WSAGetLastError() returns WSAEINVAL (Invalid argument).

If I remove the non-blocking codes, it will connect with no error. So what's wrong with my code?

1

1 Answers

1
votes

You have to leave the socket in non-blocking mode while connect() is still busy connecting. Call select() only if connect() returns WSAEWOULDBLOCK, and you need to check the return value of select() as well. You are also leaking the memory returned by getaddrinfo().

Try something more like this instead:

int connectTo(SOCKET s, const char *host, const char *port)
{
    struct addrinfo *result = NULL;
    struct addrinfo hints;

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    int ret = getaddrinfo(host, port, &hints, &result);
    if (ret != 0) {
        return ret;
    }

    if (connect(socketClient, result->ai_addr, result->ai_addrlen) != SOCKET_ERROR) {
        freeaddrinfo(result);
        return 0;
    }

    ret = WSAGetLastError();
    freeaddrinfo(result);

    if (ret != WSAEWOULDBLOCK) {
        return ret;
    }

    fd_set Write, Err;
    FD_ZERO(&Write);
    FD_ZERO(&Err);
    FD_SET(s, &Write);
    FD_SET(s, &Err);

    TIMEVAL Timeout;
    Timeout.tv_sec = 10;
    Timeout.tv_usec = 0;

    ret = select(0, NULL, &Write, &Err, &Timeout);
    if (ret == SOCKET_ERROR) {
        return WSAGetLastError();
    }

    if (ret == 0) {
        return WSAETIMEDOUT;
    }

    if (FD_ISSET(s, &Err)) {
        u_long err;
        if (getsockopt(s, SOL_SOCKET, SO_ERROR, (char*)&err, sizeof(err)) == SOCKET_ERROR) {
            return WSAGetLastError();
        }
        return (int) err;
    }

    return 0;
}

Then you can do this:

#define SERVER_URL "example.com"
#define SERVER_PORT_PRIMARY "1234"
#define SERVER_PORT_BACKUP "5678"

SOCKET socketClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socketClient == SOCKET_ERROR) {
    WSACleanup();
    return;
}

//set the socket in non-blocking
u_long iMode = 1;
iResult = ioctlsocket(socketClient, FIONBIO, &iMode);
if (iResult == SOCKET_ERROR) {
    closesocket(socketClient);
    WSACleanup();
    return;
}

if (connectTo(socketClient, SERVER_URL, SERVER_PORT_PRIMARY) != 0) {
    if (connectTo(socketClient, SERVER_URL, SERVER_PORT_BACKUP) != 0) {
        closesocket(socketClient);
        WSACleanup();
        return;
    }
}

//switch it back to blocking socket
iMode = 0;
iResult = ioctlsocket(socketClient, FIONBIO, &iMode);
if (iResult == SOCKET_ERROR) {
    closesocket(socketClient);
    WSACleanup();
    return;
}

// communicate with the server as needed ...