чтение и запись в один и тот же сокет (TCP) с помощью select - PullRequest
11 голосов
/ 14 января 2010

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

Сервер просто сидит в блокирующем цикле select в ожидании трафика и, когда он приходит, отправляет данные другим клиентам. Это, кажется, работает просто отлично.

Проблема в клиенте. В ответ на чтение иногда требуется выполнить запись.

Однако я обнаружил, что если я использую:

 rv = select(fdmax + 1, &master_list, NULL, NULL, NULL);

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

if (select(....) != -1)
{
  if (FD_SET(sockfd, &master_list))
     // handle data or disconnect
  else
     // look for data to write and write() / send() those.
}

Я попытался установить режим выбора в опрос (или смехотворно короткие таймауты) с помощью:

// 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
}

)

Ответы [ 4 ]

12 голосов
/ 14 января 2010

Все выглядит хорошо, кроме строки, где вы делаете if (FD_SET(sockfd, &master_list)). У меня очень похожая структура кода, и я использовал FD_ISSET. Вы должны проверить, установлен ли список, а не устанавливать его снова. Кроме этого, я не вижу ничего другого.

Edit. Кроме того, у меня есть следующее время ожидания:

timeval listening_timeout;
listening_timeout.tv_sec = timeout_in_seconds;
listening_timeout.tv_usec = 0;

возможно, есть проблема, если вы установите его на 0 (как вы, похоже, делаете?)

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

FD_ZERO(&sockfd);
FD_SET(sockfd, &rd);

до того, как я вошел select. Хотя я не могу вспомнить, почему.

7 голосов
/ 14 января 2010

Кажется, я вспомнил трюк о создании и совместном использовании файлового дескриптора чтения / записи между сетевым потоком и основным потоком, который добавляется к дескрипторам в вызове select. Этот fd имеет один байт, записанный ему основным потоком, когда ему есть, что отправить. Запись активирует сетевой поток из вызова select, и сетевой поток затем захватывает данные из общего буфера и записывает их в сеть, а затем снова переключается в режим ожидания.

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

1 голос
/ 14 января 2010

Я не вижу ничего плохого в вашем коде, поэтому он должен работать. Если вы не можете заставить его работать, один из способов обойти это - создать канал, который будет использоваться вашим потоком чтения и потоком, который готовит вещи для записи, и добавить конец чтения канала в ваш набор select. Затем, когда другой поток подготовил данные для записи, он просто отправляет что-то по каналу, ваш поток чтения пробуждается из select, и он может затем выполнить запись. В зависимости от того, как часто есть данные для чтения или записи, это также может быть более эффективным.

0 голосов
/ 09 марта 2010

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

...