Опция SO_REUSEADDR не работает, когда приложение пытается привязать порт

У меня есть приложение, которое сбрасывается и снова начинает прослушивание, если сокет закрыт удаленно. Поведение, которое я наблюдаю, заключается в том, что при первой загрузке приложение может прослушивать, но когда я пытаюсь очистить и снова начать прослушивание, я получаю ошибку при привязке порта. Для контекста ниже приведены соответствующие методы моего серверного класса.

void Server::listen() {
    std::cout<<"Server::listen\n";
    m_listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (m_listenSocket < 0) {
        std::cout<<"Failed to open listen socket.\n";
        return;
    }
        
    int optval = 0;
    if (setsockopt(m_listenSocket, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) {
        std::cout<<"Failed to set SO_REUSEADDR option.\n";
        close(m_listenSocket);
        return;
    }
        
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(m_port);
        
    if (bind(m_listenSocket, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        std::cout<<"Failed to bind to port.\n";
        std::cout<<strerror(errno)<<"\n";
        close(m_listenSocket);
        return;
    }
        
    int backlog = 1;  // We are only interested to pool one client.
    if (::listen(m_listenSocket, backlog) < 0) {
        std::cout<<" Failed to listen on the listen socket.\n";
        close(m_listenSocket);
        return;
    }
        
    std::cout<<"Server is listening."<<":port = "<<m_port<<"\n";
        
    m_acceptThread = std::thread(&SocketServer::acceptThread, this);
}

void Server::cleanup(){
    std::cout<<"Server::cleanup.\n";
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        m_messageQueue.clear();
        m_sendThreadWait.notify_one();
    }
    ::shutdown(m_listenSocket, SHUT_RD); // socket that is used for listening
    ::shutdown(m_socketId, SHUT_RDWR);// socket that was returned by ::accept
    joinThreads(); // ensures recv/send/accept thread joins and completes
}
    
void Server::resetThread() {
    std::cout<<"Server::resetThread started.\n";
    cleanup();
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        m_startDisconnect = false;
    }
    //sleep(5);<- had no effect
    listen();
    
    std::cout<<"Server::resetThread completed.\n";
}

Результат, который я получаю:

Server:listen
Server:listen::SocketServer is listening.:port=11008
...
Server:recvThread::connection closed by remote.
Server:recvThread::completed
Server:resetThread::started.
Server:cleanup
Server:joinThreads
Server:listen
Server:listen:: Failed to bind to port.
Address already in use
Server:resetThread::completed.

Теперь, как только происходит отключение, мое приложение выполняет сброс и вызывает listen(), который снова пытается подключиться к тому же порту, но я читал, что SO_REUSEADDR сообщает ОС, что приложение может немедленно снова выполнить привязку, это правильное понимание? Если да, то что мне здесь не хватает?

Я также пробовал поставить сон на 5 секунд, но это тоже не помогло. Обратите внимание: если я подожду некоторое время и снова запущу приложение, оно сможет снова привязаться без каких-либо проблем.

Я использую операционную систему Linux.


ОБНОВЛЯТЬ:

Показываю acceptThread и присоединяюсь к теме:

void Server::acceptThread() {
    try {
        std::cout<<"Server::acceptThread::started.\n";
        socklen_t sockaddrLength = sizeof(struct sockaddr_in);
        int incomingSocketId = accept(m_listenSocket, NULL, sockaddrLength);
        if (incomingSocketId < 0) {
            std::cout<<"Failed to accept the client connection\n";
            return;
        }
    
        m_socketId = incomingSocketId;
    
        // Set socket to non-blocking mode
        int flags = fcntl(m_socketId, F_GETFL, 0);
        fcntl(m_socketId, F_SETFL, flags | O_NONBLOCK);
             
        // Start send and recv thread
        m_sendThread = std::thread(&SocketServer::sendThread, this);
        m_recvThread = std::thread(&SocketServer::recvThread, this);
    
    } catch (const std::exception& e) {
        std::cout<<"socket closed by remote."<< e.what()<<"\n";
        return;
    }
    std::cout<<"Server::acceptThread completed.\n";
}

void Server::joinThreads() {
    std::cout<<"Server::joinThreads.\n";
    if (m_acceptThread.joinable()) {
        m_acceptThread.join();
    }
    
    if (m_recvThread.joinable()) {
        m_recvThread.join();
    }
    
    if (m_sendThread.joinable()) {
        m_sendThread.join();
    }
}

Вывод консоли, зависает на accept():

Server:listen
Server:listen::SocketServer is listening.:port=49152
Server:acceptThread::started.
Server:shutdown
Server:joinThreads

За Failed to bind to port должно следовать значение errno или сообщение perror().

3CxEZiVlQ 06.09.2024 07:25

я напечатал ошибку, и это Address already in use

bourne 06.09.2024 07:28

Кроме того, порт 1100 находится в диапазоне, который вам, вероятно, не следует использовать. Посмотрите здесь: en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers. Для собственных приложений чаще всего используются порты в этом диапазоне: от 49152 до 65535.

Pepijn Kramer 06.09.2024 07:56

Почему вы заново создаете сокет сервера, когда один из клиентов отключился? Если это одноклиентский сервер, вы можете закрыть сокет сервера сразу после подключения клиента. Если это многоклиентский сервер, вам не нужно заново создавать сокет прослушивания. Ошибка возникает из-за того, что вы пытаетесь привязаться к адресу, который все еще используется предыдущим сокетом.

dimich 06.09.2024 09:01
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
4
54
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я почти уверен, errno вернусь EADDRINUSE.

shutdown() не закрывает порт. Вы получаете протекший сокет, привязанный к порту. Вам следует вызвать close() после shutdown() или после joinThreads().

На самом деле раньше я закрывал там вместо ::shutdown, но если бы я вызывал выключение() моего класса Socket из основного потока, блокировка принятия не возвращалась, и только после того, как я перешел на ::shutdown, блокировка ::accept вышла .Позвольте мне в этом случае также вызвать close.

bourne 06.09.2024 07:32

Судя по другим потокам здесь, у меня сложилось впечатление, что закрытие не работает, если вы вызываете его из другого потока, и вместо этого вам нужно вызвать завершение работы.

bourne 06.09.2024 07:34

Ваше впечатление ошибочно. Вы развиваетесь через испытания, это неправильный путь движения вперед. Приведите минимально воспроизводимый пример.

3CxEZiVlQ 06.09.2024 08:31

если я поменяю ::shutdown(m_listenSocket, SHUT_RD) и ::shutdown(m_socketId, SHUT_RDWR); на close(m_listenSocket) и close(m_socketId), я смогу воспроизвести раздачу в ::accept. Я обновил сообщение и добавил код для принятия и соединения.

bourne 06.09.2024 08:49

Кроме того, чтобы предоставить больше контекста, раньше я использовал блокирующий сокет, и единственная причина, по которой я переключился на неблокирующий сокет, - это другая проблема, о которой я опубликовал проблема. в основном блокировка приема не возвращается после закрытия/выключения сокета. Если у вас есть какие-то идеи, пожалуйста, предоставьте.

bourne 06.09.2024 08:53

Что касается вызова принятия блокировки, я думаю, что столкнулся с https://stackoverflow.com/questions/9365282/c-linux-accept-b‌​locking-after-socket‌​-closed и вызов завершения работы - правильная вещь.

bourne 06.09.2024 09:23

@bourne Если вы используете recv() для блокировки сокета, вы можете прервать его по сигналу (но удалите флаг SO_RESTART из sigaction). Если вы используете неблокирующий сокет, вам следует ожидать событий на нескольких дескрипторах с помощью poll/epoll/wait и т. д. Один из этих дескрипторов должен указывать на прерывание. Это может быть eventfd, signalfd, Pipe и т. д. Также вы можете вызывать неблокирующую функцию Recv() в цикле, но это очень неэффективно и ужасно. Старайтесь не смешивать многопоточность и неблокирующий ввод-вывод.

dimich 06.09.2024 09:29

Другие вопросы по теме