【问题标题】:Unable to connect to socket after switch back to blocking socket切换回阻塞套接字后无法连接到套接字
【发布时间】:2014-11-22 22:35:40
【问题描述】:

有两台socket服务器,一台是不常开的主服务器,一台是备用服务器。

我的程序将尝试使用非阻塞连接连接主服务器(以便可以应用超时值),如果失败,它将使用阻塞连接连接备份服务器。

但是第二个connect函数大部分时候会返回“Invalid argument”错误码:

#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;
    }
}

正如我在上面的代码中评论的那样,第二次“连接”调用将在大多数情况下返回 SOCKET_ERROR,而 WSAGetLastError() 返回 WSAEINVAL(无效参数)。

如果我删除非阻塞代码,它将无错误地连接。那么我的代码有什么问题呢?

【问题讨论】:

    标签: sockets nonblocking


    【解决方案1】:

    connect() 仍在忙于连接时,您必须让套接字处于非阻塞模式。仅当connect() 返回WSAEWOULDBLOCK 时才调用select(),并且您还需要检查select() 的返回值。您还泄漏了getaddrinfo() 返回的内存。

    试试类似的方法:

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

    那么你可以这样做:

    #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 ...
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-07-20
      • 1970-01-01
      • 2010-10-31
      • 2013-10-15
      • 2017-04-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多