сокет recv() не получает данные

У меня есть серверный демон, прослушивающий домен TCP unix/локальный сокет. К нему подключаются несколько клиентов, работающих на одном компьютере. Демон также привязан к интернет-сокету UDP. Всякий раз, когда демон получает какие-либо данные от одного из локальных клиентов, он отправляет те же данные всем подключенным клиентам, кроме клиента-отправителя. Если демон получает данные через интернет-сокет UDP, он должен отправить эти данные всем локальным подключенным клиентам. Отправка/получение данных работает отлично, когда демон получает данные через локальный сокет. Однако клиенты не получают никаких данных, когда сервер отправляет им данные, полученные через интернет-сокет UDP. Клиенты получают эти интернет-данные либо после выхода из демона сервера и закрытия соединения, либо когда любой из клиентов отправляет данные локально на сервер. Интернет-данные принимаются клиентами вместе с локальными данными. Я установил как локальные, так и инет-сокеты как блокирующие, используя fcntl(). Вот код демона, который у меня есть (я удалил весь ненужный код):

while(1)
{
  FD_SET(sockfd, &read_fds);
  FD_SET(inet_sock, &read_fds);
  for (i = 0; i < nclients; i++)
  {
    FD_SET(clients[i], &read_fds);
  }

  select(maxfd + 1, &read_fds, &write_fds, &except_fds, NULL);

  /* Check for events on inet sock */
  if (FD_ISSET(inet_sock, &read_fds))
  {
    /* Read from inet sock */
    socklen = sizeof(dest_sin);
    rval = recvfrom(inet_sock, buf, BUFLEN-1, MSG_DONTWAIT,
                    (struct sockaddr *) &dest_sin, &socklen);        
    buf[rval]=0;
    fprintf(stderr, "Received: %d (%d) bytes containing %s", rval, strlen(buf), buf);

    /* Send the message to every other client */
    for(j=0; j < nclients; j++)
    {
      send(clients[j], buf, strlen(buf), MSG_DONTWAIT);
    }
  }

  /* A read event on the local socket is a new connection */
  if (FD_ISSET(sockfd, &read_fds))
  {
    socklen = sizeof(dest_sun);
    /* Accept the new connection */
    rval = accept(sockfd, (struct sockaddr *) &dest_sun, &socklen);

    /* Add client to list of clients */
    clients[nclients++] = rval;
    if (rval > maxfd) maxfd = rval;
    snprintf(s, BUFLEN, "You are client %d [%d]. You are now connected.\n\0",
        nclients, rval);
    send(rval, s, strnlen(s, BUFLEN), MSG_DONTWAIT);
  }

  /* Check for events from each client */
  for (i = 0; i < nclients; i++)
  {
    fprintf(stderr,"Checking client %d [%d] for read indicator.\n",i, clients[i]);

    /* Client read events */
    if (FD_ISSET(clients[i], &read_fds))
    {
      fprintf(stderr, "Client %d [%d] marked for read.\n", i, clients[i]);

      /* Read from client */
      rval=recv(clients[i], buf, BUFLEN-1, MSG_DONTWAIT);

      buf[rval]=0;
      fprintf(stderr, "Received: %d (%d) bytes containing %s", rval, strlen(buf), buf);

      /* Send the message to every other client */
      for(j=0; j < nclients; j++)
      {
        /* Skip the sender */
        if (j == i) continue;
        /* Send the message */
        send(clients[j], s, strlen(s, BUFLEN), MSG_DONTWAIT);
      }
    }
  } 
}

Вот код клиента, который у меня есть:

while(1)
{
  FD_SET(fileno(stdin), &read_fds);
  FD_SET(sockfd, &read_fds);
  select(fileno(stdin) > sockfd ? fileno(stdin)+1 : sockfd+1,
&read_fds, &write_fds, &except_fds, NULL);

  if (FD_ISSET(sockfd, &read_fds))
  {
    /* Read from socket and display to user */
    mlen = recv(sockfd, (void *)buf, BUFLEN-1, MSG_DONTWAIT);
    buf[mlen]=0;
    printf("Received %d bytes: %s", mlen, buf);
  }

  if (FD_ISSET(fileno(stdin), &read_fds))
  {
    fgets(buf, BUFLEN, stdin);
    fprintf(stderr, "Sent %d octets to server.", 
    send(sockfd, (void *)buf, (size_t) strnlen(buf, BUFLEN), 0));
  }
}

Цель состоит в том, чтобы клиенты немедленно получали данные, которые демон отправляет им (данные, которые демон получает через свой инет-сокет).

РЕДАКТИРОВАТЬ: я понял, что когда демон отправляет данные, select() на стороне клиента возвращает, что сокет доступен для чтения, но recv() блокируется, поэтому я не получаю данные на стороне клиента. Любые предложения о том, как это исправить?


person ddd    schedule 11.04.2012    source источник
comment
Пока ничего не выскакивает, пожалуйста, не делайте, просто для формы, вызовите send() внутри оператора printf(). Кроме того, вы сказали, что убрали код. Проверяет ли фактический рабочий пример коды возврата/errno?   -  person Duck    schedule 11.04.2012
comment
Да, реальная программа выполняет проверку ошибок.   -  person ddd    schedule 11.04.2012
comment
Мне бросается в глаза то, что вы никогда не проверяете возвращаемые значения вызовов send и recv. Кроме того, как вы создаете сокет UDP как на сервере, так и на клиенте?   -  person Some programmer dude    schedule 11.04.2012
comment
Другой вопрос, как вы инициализируете maxfd?   -  person Some programmer dude    schedule 11.04.2012
comment
Я проверяю возвращаемые значения send и recv, я просто удалил их отсюда, чтобы сделать пост кратким. Сервер и клиенты взаимодействуют через локальные/доменные сокеты TCP. Сокет UDP открыт на сервере для приема пакетов из Интернета. maxfd инициализируется как max локального сокета sockfd и сокета inet inet_sock. Позже, когда клиенты подключаются к серверу, я пересчитываю maxfd с учетом даже fds клиентских подключений.   -  person ddd    schedule 11.04.2012


Ответы (1)


Вот вызовы send() из вашего кода, извлеченные и выровненные:

send(clients[j], buf, strlen(buf),        MSG_DONTWAIT);
send(rval,       s,   strnlen(s, BUFLEN), MSG_DONTWAIT);
send(clients[j], s,   strlen(s, BUFLEN),  MSG_DONTWAIT);

Я вижу здесь некоторые несоответствия. Иногда вы вызываете strlen(), иногда strnlen(), а иногда strlen() с двумя аргументами (я даже не знаю, что это будет делать).

Проблема, которую вы видите, может быть связана с тем фактом, что вы не отправляете в сокет никакой информации, показывающей, где проходят границы между сообщениями. В потоковом сокете границы сообщений не сохраняются, и вы должны позаботиться о том, чтобы включить в протокол соответствующую информацию о кадрировании, чтобы получатель мог извлечь отдельные сообщения. Вы не можете полагаться на точно такое же количество байтов, поступающих через вызов recv(), как и при вызове send(). Вы получите такое же общее количество байтов в том же порядке (в этом смысл потокового сокета), но сообщения могут объединяться или разделяться, и вы не можете это контролировать.

person Greg Hewgill    schedule 11.04.2012