чтение и запись в один и тот же сокет (TCP) с помощью select
-
20-09-2019 - |
Вопрос
Мы пишем клиент и сервер для выполнения (как я думал) довольно простых сетевых взаимодействий.Несколько клиентов подключаются к серверу, который затем должен отправлять данные обратно всем остальным клиентам.
Сервер просто находится в блокировке select
цикл ожидает трафика и, когда он поступает, отправляет данные другим клиентам.Кажется, это работает просто отлично.
Проблема в клиенте.В ответ на чтение иногда возникает желание выполнить запись.
Однако я обнаружил, что если я использую:
rv = select(fdmax + 1, &master_list, NULL, NULL, NULL);
Мой код будет блокироваться до тех пор, пока не появятся новые данные для чтения.Но иногда (асинхронно, из другого потока) У меня будут новые данные для записи в поток сетевого взаимодействия.Итак, я хочу, чтобы мой select периодически просыпался и позволял мне проверять, есть ли данные для записи, например:
if (select(....) != -1)
{
if (FD_SET(sockfd, &master_list))
// handle data or disconnect
else
// look for data to write and write() / send() those.
}
Я попробовал перевести select в режим опроса (или смехотворно короткие тайм-ауты) с:
// master list contains the sockfd from the getaddrinfo/socket/connect seq
struct timeval t;
memset(&t, 0, sizeof t);
rv = select(fdmax + 1, &master_list, NULL, NULL, &t);
но обнаружили, что тогда клиент никогда не получает никаких входящих данных.
Я также попытался установить сокет fd неблокирующим, например:
fcntl(sockfd, F_SETFL, O_NONBLOCK);
но это не решает проблему:
- если мой клиент
select()
не имеет никакогоstruct timeval
, чтение данных работает, но оно никогда не разблокируется, чтобы позволить мне искать данные, доступные для записи. - если мой клиент
select()
имеетtimeval
чтобы заставить его опрашивать, он никогда не сигнализирует о том, что есть входящие данные для чтения, и мое приложение зависает, думая, что сетевое подключение не установлено (несмотря на то, что все остальные вызовы функций завершились успешно)
Есть какие-нибудь указания на то, что я, возможно, делаю неправильно?Невозможно ли выполнять чтение-запись в одном и том же сокете (я не могу поверить, что это правда).
(РЕДАКТИРОВАТЬ:Правильный ответ и то, что я запомнил на сервере, но не на клиенте, - это иметь второй fd_set и копировать master_list перед каждым вызовом select():
// declare and FD_ZERO read_fds:
// put sockfd in master_list
while (1)
{
read_fds = master_list;
select(...);
if (FD_ISSET(read_fds))
....
else
// sleep or otherwise don't hog cpu resources
}
)
Решение
Все выглядит нормально, за исключением строки, где вы делаете if (FD_SET(sockfd, &master_list))
.У меня очень похожая структура кода, и я использовал FD_ISSET
.Предполагается, что вы должны проверить, установлен ли список, а не устанавливать его снова.Кроме этого, я больше ничего не вижу.
Редактировать.Кроме того, у меня есть следующее для тайм-аута:
timeval listening_timeout;
listening_timeout.tv_sec = timeout_in_seconds;
listening_timeout.tv_usec = 0;
возможно, возникает проблема, если вы установите его равным 0 (как вы, кажется, делаете?).
Правка2.Я вспомнил, что столкнулся со странной проблемой, когда я не очищал набор чтения после завершения выбора и до того, как снова ввел его.Я должен был сделать что-то вроде:
FD_ZERO(&sockfd);
FD_SET(sockfd, &rd);
до того, как я вошел select
.Хотя я не могу вспомнить почему.
Другие советы
Кажется, я вспоминаю трюк с созданием и совместным использованием filedescriptor для чтения / записи между сетевым потоком и основным потоком, который добавляется к дескрипторам в вызове select.Этот fd имеет один байт, записанный в него основным потоком, когда ему есть что отправить.Запись пробуждает сетевой поток от вызова select, и сетевой поток затем извлекает данные из общего буфера и записывает их в сеть, а затем снова переходит в спящий режим в режиме select.
Извините , если это немного расплывчато и не хватает кода ...и моя память может быть неверной..так что другим, возможно, придется направлять вас дальше.
Я не вижу ничего плохого в вашем коде, так что он должен сработать.Если вы не можете заставить это работать, одним из способов обойти это было бы создать канал, который будет использоваться вашим потоком чтения, и поток, который готовит данные к записи, и добавить конец канала для чтения в ваш select
набор.Затем, когда другой поток подготовил данные для записи, он просто отправляет что-то по каналу, ваш поток чтения пробуждается из select
, и затем он может выполнить запись.В зависимости от того, как часто имеются данные для чтения или записи, это также может быть более эффективным.
2 потока должны иметь возможность работать с одним и тем же сокетом одновременно, поэтому ваш основной поток должен иметь возможность выполнять запись клиенту, в то время как другой находится в режиме ожидания select в ожидании входящих данных.Это, конечно, предполагает, что оба потока имеют доступ к списку клиентов.