2010-01-14 4 views
11

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

Сервер просто сидит в блокирующей петле 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, чтобы получить его для опроса, то он никогда не сигнализирует о том, что есть данные для чтения, и мое приложение замерзает, думая, что нет сетевого подключения (несмотря на то, что все остальные вызовы функций преуспели)

Любые указания на то, что я могу делать неправильно? Невозможно выполнить чтение-запись в одном сокете (я не могу поверить, что это правда).

(EDIT: Правильный ответ, и то, что я запомнил на сервере, но не на клиенте, чтобы иметь второй fd_set и скопировать master_list перед каждым вызовом, чтобы выбрать():

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

)

+0

извините, что если (FD_SET (... должно быть, если (FD_ISSET (.... – MarcWan

ответ

12

Все выглядит нормально, кроме линии, где вы делаете 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. Однако я не помню, почему.

+0

) Не должно быть никаких проблем с установкой тайм-аута на '0': в этом случае' select' будет просто проверять, любые дескрипторы в настоящее время готовы и сразу возвращаются. – JaakkoK

+1

Re your Edit2: 'select' будет изменять наборы, поэтому вам нужно сбросить их, прежде чем вызывать его снова. – JaakkoK

+0

@jk спасибо, я добавлю это как комментарий к исходному файлу , это будет полезно в будущем. – laura

7

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

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

+0

Мне очень нравится эта идея много. Несмотря на то, что она не решает мою проблему, я уже использую трубку, чтобы сообщить сетевому потоку о выходе, и не сделал скачок для использования поток, который есть данные для записи. Я люблю это !! – MarcWan

1

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

+0

Ну, код вверху был действительно в основном правильным (за исключением FD_SET/FD_ISSET). Неправильное было то, чего не хватало, и что @jk указало выше - вам нужно сбросить fd_set перед каждым вызовом, который нужно выбрать. Я создал новый, называемый read_fds, и перед каждым вызовом выберите «read_fds = master_list», а затем все последующие вызовы FD_ISSET выглядят в read_fds, а не в master_list. Спасибо за это. Сокеты чтения/записи снова работают, wooO! – MarcWan

+0

На самом деле @laura указал на это, я просто объяснил причину. Я бы принял ее ответ, так как именно он решил проблему. – JaakkoK

+0

Хорошо, сделано. Каждому нравится честный плакат :) – MarcWan

0

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