У меня есть приложение, которое сбрасывается и снова начинает прослушивание, если сокет закрыт удаленно. Поведение, которое я наблюдаю, заключается в том, что при первой загрузке приложение может прослушивать, но когда я пытаюсь очистить и снова начать прослушивание, я получаю ошибку при привязке порта. Для контекста ниже приведены соответствующие методы моего серверного класса.
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
я напечатал ошибку, и это Address already in use
Кроме того, порт 1100 находится в диапазоне, который вам, вероятно, не следует использовать. Посмотрите здесь: en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers. Для собственных приложений чаще всего используются порты в этом диапазоне: от 49152 до 65535.
Почему вы заново создаете сокет сервера, когда один из клиентов отключился? Если это одноклиентский сервер, вы можете закрыть сокет сервера сразу после подключения клиента. Если это многоклиентский сервер, вам не нужно заново создавать сокет прослушивания. Ошибка возникает из-за того, что вы пытаетесь привязаться к адресу, который все еще используется предыдущим сокетом.





Я почти уверен, errno вернусь EADDRINUSE.
shutdown() не закрывает порт. Вы получаете протекший сокет, привязанный к порту. Вам следует вызвать close() после shutdown() или после joinThreads().
На самом деле раньше я закрывал там вместо ::shutdown, но если бы я вызывал выключение() моего класса Socket из основного потока, блокировка принятия не возвращалась, и только после того, как я перешел на ::shutdown, блокировка ::accept вышла .Позвольте мне в этом случае также вызвать close.
Судя по другим потокам здесь, у меня сложилось впечатление, что закрытие не работает, если вы вызываете его из другого потока, и вместо этого вам нужно вызвать завершение работы.
Ваше впечатление ошибочно. Вы развиваетесь через испытания, это неправильный путь движения вперед. Приведите минимально воспроизводимый пример.
если я поменяю ::shutdown(m_listenSocket, SHUT_RD) и ::shutdown(m_socketId, SHUT_RDWR); на close(m_listenSocket) и close(m_socketId), я смогу воспроизвести раздачу в ::accept. Я обновил сообщение и добавил код для принятия и соединения.
Кроме того, чтобы предоставить больше контекста, раньше я использовал блокирующий сокет, и единственная причина, по которой я переключился на неблокирующий сокет, - это другая проблема, о которой я опубликовал проблема. в основном блокировка приема не возвращается после закрытия/выключения сокета. Если у вас есть какие-то идеи, пожалуйста, предоставьте.
Что касается вызова принятия блокировки, я думаю, что столкнулся с https://stackoverflow.com/questions/9365282/c-linux-accept-blocking-after-socket-closed и вызов завершения работы - правильная вещь.
@bourne Если вы используете recv() для блокировки сокета, вы можете прервать его по сигналу (но удалите флаг SO_RESTART из sigaction). Если вы используете неблокирующий сокет, вам следует ожидать событий на нескольких дескрипторах с помощью poll/epoll/wait и т. д. Один из этих дескрипторов должен указывать на прерывание. Это может быть eventfd, signalfd, Pipe и т. д. Также вы можете вызывать неблокирующую функцию Recv() в цикле, но это очень неэффективно и ужасно. Старайтесь не смешивать многопоточность и неблокирующий ввод-вывод.
За
Failed to bind to portдолжно следовать значениеerrnoили сообщениеperror().