How to use select() in UDP Sockets to read and write from client to server

Damian picture Damian · May 10, 2014 · Viewed 7.9k times · Source

I'm trying to make a chat application using sockets, and I want them both to be able to write and read simultaneously. So I did some research and found that I/O multiplexing like select() will fix this problem. But I still have one problem. After I compile it and run it, one of the two programs doesn't receive the message 'till after one responds. So for example, the server sends a message to the client right? Then the client won't see the message until AFTER the client responds. Below you can find an example of this as well as the source code.

Client: hi

Server: hello

Client said "hi"

And the same for client etc

//SERVER
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/select.h>//use select() for multiplexing
#include <sys/fcntl.h> // for non-blocking

#define MAX_LENGTH 1024

/* Select() params
 * int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
 * FD_SET(int fd, fd_set *set);
 * FD_CLR(int fd, fd_set *set);
 * FD_ISSET(int fd, fd_set *set);
 * FD_ZERO(fd_set *set);
*/

void error(char *message)
{
    perror(message);
    exit(1);
}

int main()
{
  fd_set original_socket;
  fd_set original_stdin;
  fd_set readfds;
  fd_set writefds;
  struct timeval tv;
  int numfd;

  int socket_fd, bytes_read;
  unsigned int address_length;
  char recieve_data[MAX_LENGTH],send_data[MAX_LENGTH];
  struct sockaddr_in server_address , client_address;

  if ((socket_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) 
  {
      error("socket()");
  }

  int flags = fcntl(socket_fd, F_GETFL);
  flags |= O_NONBLOCK;
  fcntl(socket_fd, F_SETFL, flags);
  //fcntl(socket_fd, F_SETFL, O_NONBLOCK); //set socket to non-blocking

  // clear the set ahead of time
  FD_ZERO(&original_socket);
  FD_ZERO(&original_stdin);
  FD_ZERO(&readfds);
  FD_ZERO(&writefds);

  // add our descriptors to the set (0 - stands for STDIN)
  FD_SET(socket_fd, &original_socket);//instead of 0 put socket_fd
  FD_SET(socket_fd, &readfds);
  FD_SET(0,&original_stdin);
  FD_SET(0, &writefds);

  // since we got s2 second, it's the "greater", so we use that for
  // the n param in select()
  numfd = socket_fd + 1;

  // wait until either socket has data ready to be recv()d (timeout 10.5 secs)
  tv.tv_sec = 10;
  tv.tv_usec = 500000;

  server_address.sin_family = AF_INET;
  server_address.sin_port = htons(5000);
  server_address.sin_addr.s_addr = INADDR_ANY;
  bzero(&(server_address.sin_zero),8);


  if (bind(socket_fd,(struct sockaddr *)&server_address, sizeof(struct sockaddr)) == -1)
  {
      error("bind()");
  }

  address_length = sizeof(struct sockaddr);

  printf("\nUDP_Server Waiting for client to respond...\n");
  fflush(stdout);
  printf("Type (q or Q) at anytime to quit\n");

  while (1)
  {
    readfds = original_socket;
    writefds = original_stdin;//problem
    int recieve = select(numfd, &readfds, &writefds,/*NULL,*/ NULL, &tv);

    if (recieve == -1) 
    {
      perror("select"); // error occurred in select()
    } 
    else if (recieve == 0) 
    {
      printf("Timeout occurred!  No data after 10.5 seconds.\n");
    } 
    else 
    {
        // one or both of the descriptors have data
        if (FD_ISSET(socket_fd, &readfds)) //if set to read
        { 
          FD_CLR(socket_fd, &readfds);
          bytes_read = recvfrom(socket_fd,recieve_data,MAX_LENGTH,0,(struct sockaddr *)&client_address, &address_length); //block call, will wait till client enters something, before proceeding
          recieve_data[bytes_read] = '\0'; //add null to the end of the buffer

          if((strcmp(recieve_data , "q") == 0) || (strcmp(recieve_data , "Q") == 0)) { //if client quit, then quit also
            printf("\nClient has exited the chat.\n");
            break;
          }

          printf("\n(%s , %d) said: %s\n",inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port),recieve_data);
        }
        else if (FD_ISSET(/*socket_fd*/0, &writefds)) //if set to write
        //else
        {
          FD_CLR(0, &writefds);
          printf("SERVER: ");
          fgets (send_data, MAX_LENGTH, stdin); //input the name with a size limit of MAX_LENGTH

          if ((strlen(send_data)>0) && (send_data[strlen (send_data) - 1] == '\n')) //if there is a trailing \n, remove it
          {
            send_data[strlen (send_data) - 1] = '\0';
          }

          if ((strcmp(send_data , "q") == 0) || (strcmp(send_data , "Q") == 0)) { //if user enters q, then quit
            sendto(socket_fd,send_data,strlen(send_data),0,(struct sockaddr *)&client_address, sizeof(struct sockaddr));
            break;
          }
          sendto(socket_fd,send_data,strlen(send_data),0,(struct sockaddr *)&client_address, sizeof(struct sockaddr));
          fflush(stdout);
        }
        else printf("\nOOPS! What happened? SERVER");
    } //end else
  }//end while

  close (socket_fd);
  return 0;
}

At first I thought it was a trailing newline, but I took care of that possibility already by changing the '/n' to '/0' if it exists. I really don't understand why this is happening!

//CLIENT
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h> //host struct
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/select.h>//use select() for multiplexing
#include <sys/fcntl.h> // for non-blocking

#define MAX_LENGTH 1024
#define HOST "localhost" //127.0.0.1

void error(char *message)
{
    perror(message);
    exit(1);
}

int main()
{
  fd_set original_socket;
  fd_set original_stdin;
  fd_set readfds;
  fd_set writefds;
  struct timeval tv;
  int numfd;

  int socket_fd,bytes_recieved;
  unsigned int address_length;
  struct sockaddr_in server_address;
  struct hostent *host;
  char send_data[MAX_LENGTH],recieve_data[MAX_LENGTH];

  host = (struct hostent *) gethostbyname((char *)HOST);//127.0.0.1

  if ((socket_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
  {
    error("socket()");
  }

  int flags = fcntl(socket_fd, F_GETFL);
  flags |= O_NONBLOCK;
  fcntl(socket_fd, F_SETFL, flags);
  //fcntl(socket_fd, F_SETFL, O_NONBLOCK); //set socket to non-blocking

  // clear the set ahead of time
  FD_ZERO(&original_socket);
  FD_ZERO(&original_stdin);
  FD_ZERO(&readfds);
  FD_ZERO(&writefds);

  // add our descriptors to the set (0 - stands for STDIN)
  FD_SET(socket_fd, &original_socket);//instead of 0 put socket_fd
  FD_SET(socket_fd, &readfds);
  FD_SET(0,&original_stdin);
  FD_SET(0, &writefds);

  // since we got s2 second, it's the "greater", so we use that for
  // the n param in select()
  numfd = socket_fd + 1;

  // wait until either socket has data ready to be recv()d (timeout 10.5 secs)
  tv.tv_sec = 10;
  tv.tv_usec = 500000;

  server_address.sin_family = AF_INET;
  server_address.sin_port = htons(5000);
  server_address.sin_addr = *((struct in_addr *)host->h_addr);
  bzero(&(server_address.sin_zero),8);

  address_length = sizeof(struct sockaddr);
  printf("Type (q or Q) at anytime to quit\n");

  while (1)
  {
    readfds = original_socket;
    writefds = original_stdin;//problem

    int recieve = select(numfd, &readfds, /*NULL*/&writefds, NULL, &tv);
    if (recieve == -1) 
    {
      perror("select"); // error occurred in select()
    } 
    else if (recieve == 0) 
    {
      printf("Timeout occurred!  No data after 10.5 seconds.\n");
    } 
    else 
    {
      // one or both of the descriptors have data
      if (FD_ISSET(socket_fd, &readfds)) //if set to read
      {
        FD_CLR(socket_fd, &readfds);//clear the set
        bytes_recieved = recvfrom(socket_fd,recieve_data, sizeof(recieve_data),0,(struct sockaddr *)&server_address,&address_length);
        recieve_data[bytes_recieved]= '\0';

        if((strcmp(recieve_data , "q") == 0) || (strcmp(recieve_data , "Q") == 0)) //if client quit, then quit also
        { 
          printf("\nServer has exited the chat.\n");
          break;
        }

        printf("\n(%s , %d) said: %s\n",inet_ntoa(server_address.sin_addr),ntohs(server_address.sin_port),recieve_data);
        //inet_ntoa returns an ip address ipv4 style, ex: 127.0.0.1, and ntohs returns the port in the converted byte ordering
      }

      else if (FD_ISSET(0/*socket_fd*/, &writefds)) //if set to write
      //else
      {
        FD_CLR(0, &writefds);
        printf("ClIENT: ");
        fgets (send_data, MAX_LENGTH, stdin);

        if ((strlen(send_data)>0) && (send_data[strlen (send_data) - 1] == '\n')) //remove trailing newline, if exists
        { 
           send_data[strlen (send_data) - 1] = '\0';
        }

        if ((strcmp(send_data , "q") == 0) || strcmp(send_data , "Q") == 0) //if user quits, then send an invisible message to server to quit also
        { 
          sendto(socket_fd, send_data, strlen(send_data), 0, (struct sockaddr *)&server_address, sizeof(struct sockaddr));
          break;
        }

        else 
        {
          sendto(socket_fd, send_data, strlen(send_data), 0, (struct sockaddr *)&server_address, sizeof(struct sockaddr));
        }

      }
       else printf("\nOOPS! What happened? CLIENT");
    } //end else
  } // end while

  close (socket_fd);
}

Answer

Armali picture Armali · Mar 5, 2015

Your mistake is that with select() you wait for the socket to be readable (which is right) and STDIN to be writable (which is wrong, since the terminal is writable most of the time). You have to wait for both socket or STDIN to be readable, i. e. both file descriptors are to be set in readfds, and NULL can be passed instead of &writefds.