чтение и запись в один и тот же сокет (TCP) с помощью select

StackOverflow https://stackoverflow.com/questions/2063366

Вопрос

Мы пишем клиент и сервер для выполнения (как я думал) довольно простых сетевых взаимодействий.Несколько клиентов подключаются к серверу, который затем должен отправлять данные обратно всем остальным клиентам.

Сервер просто находится в блокировке 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);

но это не решает проблему:

  1. если мой клиент select() не имеет никакого struct timeval, чтение данных работает, но оно никогда не разблокируется, чтобы позволить мне искать данные, доступные для записи.
  2. если мой клиент 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 в ожидании входящих данных.Это, конечно, предполагает, что оба потока имеют доступ к списку клиентов.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top