Как создать TCP-сервер, который будет принимать только одно соединение за раз?

Я пишу пару клиент-сервер на C++, используя сокеты Linux. Я хочу, чтобы сервер прослушивал соединение, и пока один клиент подключен, сервер должен отклонять любые другие клиенты, которые пытаются подключиться.

Я попытался реализовать это, установив параметр backlog в слушать функция до 0 и до 1, и ни одно из этих значений, похоже, не работает. Первый клиент подключается, как ожидалось, но все последующие клиенты просто блокируют, пока первый клиент заканчивает. Что меня действительно смущает, так это то, что они не блокируют при подключении к серверу, они блокируют при первом чтении.

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

9 ответов


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

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

есть ли чистая разница, которую вы ищете по сравнению с закрытием нового принятого сокета сразу после принятия? Клиент будет знать, как только он пытается использовать свой сокет (или сразу, если он уже ждет на сервере с вызовом чтения) с последней ошибкой: сервер активно закрыл соединение.


только не fork() после accept().

этот псевдо-C-код будет принимать только одного клиента одновременно.

while(1) {
    listen()
    accept()
    *do something with the connection*
    close()
}

вы можете закрыть исходный сокет, который прослушивает соединения после принятия первого соединения. Я не знаю, позволит ли класс сокета, который вы используете, сделать это.


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


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

Я думаю, что это должен быть Listen сокет, который должен быть закрыт. Когда первое соединение установлено, вы закрываете исходный сокет прослушивания. И после этого больше не может быть никаких связей.

после завершения первого соединения можно создать новый сокет снова прислушаться.


Если у вас есть контроль над своими клиентами, вы можете сделать сокеты неблокирующие. В этом случае они вернут сообщение об ошибке EINPROGRESS.

Я все еще ищу как изменить разъем для неблокирующих. Если кто-нибудь знает, как навскидку, не стесняйтесь редактировать ответ.


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


у вас может быть опция сокета TCP_DEFER_ACCEPT установите на гнездо прослушивания:

TCP_DEFER_ACCEPT (since Linux 2.4)
    Allows  a  listener to be awakened only when data arrives on the socket.
    Takes an integer value (seconds), this can bound the maximum  number  of
    attempts  TCP  will make to complete the connection.  This option should
    not be used in code intended to be portable.

Я бы предположил, что это приведет к описанному вами эффекту, что подключающийся клиент не блокирует connect, но на последующем read. Я не совсем уверен, какие параметры по умолчанию и к чему он должен быть установлен, чтобы отключить это поведение, но, вероятно, значение нуля стоит попробовать:

int opt = 0;
setsockopt(sock, IPPROTO_TCP, TCP_DEFER_ACCEPT, &opt, sizeof(opt));

насколько я вижу, невозможно прослушивать ровно одно соединение.

Tcp включает трехстороннее рукопожатие. После первого syn получен пакет ядро помещает это "соединение" в очередь ожидания, отвечает syn/ack и ждет финала ack. После этого он перемещает соединение из очереди ожидания в очередь приема, где его может забрать приложение с помощью accept() звонок. (Для деталей посмотрите здесь.)

в linux аргумент backlog ограничивает только размер очереди принятия. но ядро все равно будет делать 3-х проходного магии. Клиент получает syn / ack и отвечает с окончательным ack и вызывает установленное соединение.

ваши единственные варианты, либо закрытие слушающего сокета, как только вы приняли первое подключение. (Однако это может привести в другую связь уже доступна.) Или вы активно принимаете другие соединения и немедленно закрыть их, чтобы уведомить клиента.

последний вариант у вас есть, который вы уже используете: сервер очереди подключений и обрабатывать их один за другим. Ваши клиенты будут блокировать.