accept() не блокируется после закрытия клиента (с помощью Ctrl+C) ошибка 10093

Я разрабатываю простое клиент-серверное приложение, использующее winsock2, в котором я отправляю целочисленное значение от клиента, а сервер его получает.
Когда я отправляю одно (или несколько) целое число, а клиент корректно закрывает сокет, сервер понимает, что клиент закрыл соединение и переходит к функции accept() в ожидании другого соединения.
Однако, когда я останавливаю клиент, например, комбинацией Ctrl+C, accept() не останавливается и продолжает основной цикл сервера, возвращая ошибку 10093 каждый раз, когда он зацикливается (который связан с WSAStartup()).
Я думаю, что каким-то образом я должен управлять сигналом, который отправляется на сервер, например, SIPIPE в Linux или что-то подобное, но я не знаю, как это сделать.
Как лучше всего решить эту проблему?

Здесь я принимаю реализацию:

bool Network::Accept() {
    caddrlen = sizeof(clientAddr);
    int ret; 
    if ((ret = accept(listeningSocket, (struct sockaddr*)&clientAddr, &caddrlen) ) == INVALID_SOCKET) {
        myFormatMessage(WSAGetLastError());
        closesocket(listeningSocket);
        WSACleanup();
        return false;
    }
    else {
//save client ip address in a string
        getpeername(listeningSocket, (SOCKADDR *)&clientAddr, (int *)sizeof(clientAddr));
        char ip[20];
        inet_ntop(AF_INET, (sockaddr*)&clientAddr.sin_addr, ip, 20);
        clientIPaddr.assign(ip);
        connectedSocket = ret; 
        return true;
    }

}

person A. Wolf    schedule 27.10.2016    source источник


Ответы (1)


Ошибка Winsock 10093: WSANOTINITIALISED:

Успешный WSAStartup еще не выполнен.
Либо приложение не вызвало WSAStartup, либо WSAStartup завершился неудачно. Возможно, приложение обращается к сокету, которым не владеет текущая активная задача (то есть пытается использовать сокет совместно между задачами), или WSACleanup вызывался слишком много раз.

Ваш метод Network::Accept() вызывает WSACleanup(), когда accept() по какой-либо причине не работает. Network::Accept() вообще не должен этого делать. Он также не должен закрывать прослушивающий сокет. Удалите эти две строки из Network::Accept(), а затем сделайте так, чтобы ваш основной цикл перестал вызывать Network::Accept(), если он возвращает false, а затем при необходимости очистите свой прослушивающий сокет.

Есть и другие проблемы с вашим кодом Network::Accept():

  • accept() возвращает SOCKET, а не int.

  • когда accept() завершается успешно, вы вызываете getpeername() с неправильными значениями параметров. Вы передаете прослушиваемый сокет вместо принятого клиентского сокета, и вы передаете недопустимый указатель для его параметра namelen (вам нужно передать указатель на ваше значение caddrlen, а не возвращаемое значение типа sizeof()). В этом отношении звонить getpeername() в любом случае излишне, поскольку accept() уже дал вам тот же адрес, что и getpeername().

  • при вызове inet_ntop() вы преобразуете поле sin_addr адреса клиента в sockaddr*, что неверно. Но в этом случае компилятор принимает его, так как параметр pAddr является void*. Вам вообще не нужен тип.

  • если listeningSocket является сокетом AF_INET (IPv4), то жесткое кодирование AF_INET при вызове inet_ntop() допустимо, поскольку принятый клиент всегда будет использовать адрес IPv4 (sockaddr_in). Однако, если вы хотите/должны поддерживать IPv6 (и вы должны), вам следует проверить фактическое семейство адресов клиента, чтобы узнать, использует ли адрес sockaddr_in или sockaddr_in6, а затем передать параметры в inet_ntop() соответственно.

С учетом сказанного попробуйте что-то еще вроде этого:

Если вы поддерживаете только IPv4:

bool Network::Accept() {
    // declare clientAddr as sockaddr_in...
    caddrlen = sizeof(clientAddr);
    SOCKET ret = accept(listeningSocket, (struct sockaddr*)&clientAddr, &caddrlen);
    if (ret == INVALID_SOCKET) {
        myFormatMessage(WSAGetLastError());
        return false;
    }

    //save client ip address in a string
    char ip[INET_ADDRSTRLEN] = {0};
    inet_ntop(AF_INET, &(clientAddr.sin_addr), ip, INET_ADDRSTRLEN);
    clientIPaddr.assign(ip);
    // declare connectedSocket as SOCKET...
    connectedSocket = ret; 
    return true;
}

Если вы поддерживаете только IPv6:

bool Network::Accept() {
    // declare clientAddr as sockaddr_in6...
    caddrlen = sizeof(clientAddr);
    SOCKET ret = accept(listeningSocket, (struct sockaddr*)&clientAddr, &caddrlen);
    if (ret == INVALID_SOCKET) {
        myFormatMessage(WSAGetLastError());
        return false;
    }

    //save client ip address in a string
    char ip[INET6_ADDRSTRLEN] = {0};
    inet_ntop(AF_INET6, &(clientAddr.sin6_addr), ip, INET6_ADDRSTRLEN);
    clientIPaddr.assign(ip);
    // declare connectedSocket as SOCKET...
    connectedSocket = ret; 
    return true;
}

Если вы поддерживаете как IPv4, так и IPv6:

bool Network::Accept() {
    // declare clientAddr as SOCKADDR_STORAGE...
    caddrlen = sizeof(clientAddr);
    SOCKET ret = accept(listeningSocket, (struct sockaddr*)&clientAddr, &caddrlen);
    if (ret == INVALID_SOCKET) {
        myFormatMessage(WSAGetLastError());
        return false;
    }

    //save client ip address in a string
    char ip[INET6_ADDRSTRLEN] = {0};
    switch (clientAddr.ss_family)
    {
        case AF_INET:
            inet_ntop(AF_INET, &(((struct sockaddr_in*)&clientAddr)->sin_addr), ip, INET_ADDRSTRLEN);
            break;
        case AF_INET6:
            inet_ntop(AF_INET6, &((struct sockaddr_in6*)&clientAddr)->sin6_addr), ip, INET6_ADDRSTRLEN);
            break;
    }
    clientIPaddr.assign(ip);
    // declare connectedSocket as SOCKET...
    connectedSocket = ret; 
    return true;
}
person Remy Lebeau    schedule 27.10.2016
comment
Большое спасибо за все эти подсказки! - person A. Wolf; 28.10.2016