2016-04-28 3 views
1

Я новичок в программировании сокетов на C, и я хочу написать прототип сервера-клиента, где сервер будет выступать в качестве своего рода файлообменника для нескольких пользователей (используя TCP протокол). Поскольку сервер и клиент должны иметь возможность принимать команды из stdin, я использовал функцию select(). Это код для двух частей моей программы, и проблема заключается в следующем:
Поскольку сервер и клиент должны иметь возможность принимать команды из stdin и обрабатывать их, а сервер должен отправить ответ на запрос клиента, программа блокирует в определенный момент, и сервер отправляет ответ только после того, как клиент отправил другой запрос. Я думаю, что это происходит из-за использования send() и recv() в то же время в файле server.c, но я не совсем уверен.Модули сервера/клиента Мультиплексирование ввода-вывода

server.c

while(1) { 
    if (select(maxfd+1,&tmpfds,NULL,NULL,NULL) == -1) { 
    error("Err in select"); 
    } 
    for(i = 0; i < maxfd; i++) { 
    if(FD_ISSET(i,&tmpfds) { 
     if (i == listenfd) { 
     < add new client to list > 
     } 
     else if (i == 0) { /* keyboard input */ 
     < parse server commands > 
     } 
     else { 
     /* This is where I think my problem is*/ 
     recv(i,buffer,BUFLEN,0); 
     process(buffer); 
     send(i,buffer,BUFLEN,0); 
     } 
    } 
} 

client.c

while(1) { 
    if(select(fdmax+1,&tmpfds,NULL,NULL,NULL) == -1) { 
    error("Err in select"); 
    } 
    if (FD_ISSET(0,&tmpfds)) { 
    fgets(buffer,BUFLEN,stdin); 
    process_request(buffer); 
    send(serverfd,buffer,BUFLEN,0); 
    } 
    else if (FD_ISSET(serverfd,&tmpfds)) { 
    recv(serverfd,buffer,BUFLEN,0); 
    process_response(buffer); 
    } 
} 

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

+0

По умолчанию блокировка 'recv' блокируется по умолчанию. Вы можете установить таймаут с помощью 'setsockopt'. – chrisd1100

+2

Кроме того, вам необходимо управлять данными 'fd_set' в цикле, которые вы не заметили, Linux [' select' tutorial] (http://linux.die.net/man/2/select_tut) заявляет, что «Поскольку select() изменяет свои файловые дескрипторы, если вызов используется в цикле, то перед каждым вызовом набор должен быть повторно инициализирован». – Myst

+0

P.S. если вы используете linux, предпочитайте 'epoll' над' select'. Если вы используете BSD, предпочитайте 'kqueue' над' select'. Windows имеет Overlapping IO, а Solaris имеет 'evpoll' ... рассмотрите возможность использования библиотеки абстракции (то есть' libev'), если ваш код будет работать в разных системах ... Удачи! – Myst

ответ

1

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

В коде есть еще одна проблема, вы не можете просто добавить нового клиента в FD_SET в цикле, вам нужно сохранить его, а затем перестроить в начале.

Кроме того, вам не нужно проверять каждый FD в наборе, поскольку select вернет число fd, которые готовы для ввода-вывода.

Попробуйте следующие изменения:

int clients[MAX_CLIENTS] = {0}; 
int I; 
int maxfd; 
int server_sock = <the listening fd>; 
FD_SET readfds; 
int ret; 
while(1) { 
    // Setup SD_SET each time calling select 
    FD_ZERO(&readfds); 
    FD_SET(STDIN_FILENO, &readfds); 
    maxfd = STDIN_FILENO; 
    FD_SET(server_sock, &readfds); 
    maxfd = max(maxfd, server_sock); 
    for (I = 0; I < MAX_CLIENTS; I++) { 
     if (clients[I] >= 0) { 
      FD_SET(clients[I], &readfds); 
      maxfd = max(maxfd, clients[I]); 
    } 

    if ((ret = select(maxfd+1,&readfds,NULL,NULL,NULL)) == -1) { 
     error("Err in select"); 
    } 
    for(i = 0; i < maxfd && ret; i++, ret--) { 
     if(FD_ISSET(i, &readfds) { 
      if (i == listenfd) { 
       // < add new client to clients array 
      } 
      else if (i == STDIN_FILENO) { /* keyboard input */ 
       // < parse server commands > 
      } 
      else { 
        // one of the client is ready 
        int nread = recv(i,buffer,BUFLEN,0); 
        if (nread == 0) { 
         // client is closed, remove I from clients array 
         continue; 
        } 
        process(buffer); 
        send(i,buffer,BUFLEN,0); 
      } 
     } 
    } 
} 

И последнее, но не в последнюю очередь, как улучшение select, попробовать что-то вроде epoll на Linux, который поддерживает состояние для вас, так что вам не нужно перевооружить все фоны, такие как select.