cmultithreadingsocketschatposix-select

Select blocked using socket


The code below is a chatroom. First I run the server, and then the clients. The clients reach the server with sockets. Then the server accepts the connection and the client has to enter his nickname.

I use threads because this way, the fgets is not blocking and multiple clients can reach the server even if a client is slow to enter his nickname.

Until here, there is no problem and everything works, even the select.

After this, the client can introduce a message which is send to all the clients (this is the principle of a chat :P). The problem is that from here, the select doesn't react and stay blocked. Maybe it's because of the FD_set or the threads but actually I don't know and that is the problem.

server.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <pthread.h>


#define MYPORT 5555
#define PSEUDO_MAX 30
#define MESSAGE_MAX 1000

#define BACKLOG 10



struct User {
  char pseudo[PSEUDO_MAX];
  int socket;
  struct User *next;
};

struct Thread {
  struct User* lst;
  fd_set* readfds;
  int* max;
  int socket;
  pthread_mutex_t* mutex;
};

void add(struct User** pp, const char *t, const int sock)
{
    struct User *p = malloc(sizeof(*p));
    strcpy(p->pseudo, t);
    p->socket = sock;
    p->next = NULL;

    while (*pp)
        pp = &(*pp)->next;
    *pp = p;
}

void print(const struct User* n) 
{
    for ( ; n; n = n->next ){
        printf("LISTE : %s\n", n->pseudo);
        printf("LISTE : %d\n", n->socket);
    }
    printf("\n");
}

void *thread_pseudo(void *arg)
{
  struct Thread* ps_thread = (struct Thread*)arg;
  int nsock = ps_thread->socket;
  struct User* lst = ps_thread->lst;
  int* max = ps_thread->max;
  pthread_mutex_t* mutex = ps_thread->mutex;
  pthread_mutex_lock(mutex);
  fd_set* readfds = ps_thread->readfds;
  pthread_mutex_unlock(mutex);

    char pseudo[PSEUDO_MAX];
    if (send(nsock, "Bienvenue sur le chat !\n",24,0)==-1){
      perror("Serveur: send");
    }

    if (recv(nsock, pseudo, PSEUDO_MAX, 0) == -1) {
      perror("Serveur: recv 2: ");
    }

    pthread_mutex_lock(mutex);

    FD_SET(nsock, readfds); // Ajout du nouveau socket au select
    *max = (nsock < *max ? *max : nsock);

    pthread_mutex_unlock(mutex);

    add(&lst, pseudo, nsock);
    print(lst);


    
    pthread_exit(NULL);
}

int findSender(fd_set* readfds, const struct User* n){
  int socket = 0;
  for ( ; n; n = n->next ){
    if(FD_ISSET(n->socket, readfds)){
      socket = n->socket;
    }
  }
  return socket;
}

int main()
{
  pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;


  int sockfd2, new_fd;  // listen on sock_fd, new connection on new_fd
  int max;
  struct sockaddr_in my_addr;  // my address information
  struct sockaddr_in their_addr; // connector's address information
  unsigned int sin_size;
  int yes=1;
  struct User *lst = NULL; // Initialisation de la liste des Users
  struct Thread *ps_thread = malloc(sizeof(*ps_thread)); // Initialisation du struct qui contient les paramètres pour le thread
  fd_set readfds;
  
  
  if ((sockfd2 = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
    perror("Serveur: socket 2");
    return EXIT_FAILURE;
  }
  
  if (setsockopt(sockfd2,SOL_SOCKET,SO_REUSEADDR, &yes,sizeof(int)) == -1) {
    perror("Serveur: setsockopt 2");
    return EXIT_FAILURE;
  }
        
  my_addr.sin_family = AF_INET;        
  my_addr.sin_port = htons(MYPORT);   
  my_addr.sin_addr.s_addr = INADDR_ANY;
  memset(&(my_addr.sin_zero), '\0', 8);
 
  if (bind(sockfd2, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
    perror("Serveur: bind 2");
    return EXIT_FAILURE;
  }
  
  if (listen(sockfd2, BACKLOG) == -1) {
    perror("Serveur: listen 2");
    return EXIT_FAILURE;
  }

  //Initialisation du fd_set et du max    
  FD_ZERO(&readfds);
  FD_SET(sockfd2, &readfds);
  max = sockfd2;

 
     
  while(1){
    if (select(max+1, &readfds, NULL, NULL, NULL) < 0){
      printf("error select\n");
    }

    int sock = findSender(&readfds, lst);

    if(FD_ISSET(sockfd2, &readfds)){
      new_fd = accept(sockfd2, (struct sockaddr *)&their_addr, &sin_size);

      if (new_fd == -1) { 
      perror("Serveur: accept");
      }

      pthread_t thread; 
      ps_thread->lst = lst;
      ps_thread->readfds = &readfds;
      ps_thread->max = &max;
      ps_thread->socket = new_fd;
      ps_thread->mutex = &mutex;

      if (pthread_create(&thread, NULL, thread_pseudo, ps_thread)) {
      perror("pthread_create");
      return EXIT_FAILURE;
      }
    }

    else if(FD_ISSET(sock, &readfds)) {
      char* message;
      int sock = findSender(&readfds, lst);
      if (recv(sock, message, MESSAGE_MAX, 0) == -1) {
        perror("Serveur: recv 2: ");
      }
    }

    else{
      printf("ERROR\n");
    }
  }

  return EXIT_SUCCESS;
}

client.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <pthread.h>

#define PORT 5555
#define PSEUDO_MAX 30
#define MESSAGE_MAX 1000


void getPseudo(char* pseudo) {
  size_t ln = -1;
  while (ln <= 0 || ln > PSEUDO_MAX-1) {
    printf("Entrez votre pseudo : ");
    fgets(pseudo, PSEUDO_MAX, stdin);
    ln = strlen(pseudo) - 1;
  }
  
  if (pseudo[ln] == '\n')
    pseudo[ln] = '\0';
}

void getMessage(char* message) {
  size_t ln = -1;
  while (ln <= 0 || ln > MESSAGE_MAX-1) {
    printf(">");
    fgets(message, MESSAGE_MAX, stdin);
    ln = strlen(message) - 1;
  }
  
  if (message[ln] == '\n')
    message[ln] = '\0';
}

void *thread_message(void *arg)
{
  printf("Bonjour\n");
  char* message;
  message = malloc (sizeof(char) * MESSAGE_MAX);
  int* sockfd = (int*)arg;
  printf("%d\n", *sockfd);
  
  while (1) {
    getMessage(message);
    if (send(*sockfd, message, strlen(message), 0) == -1){
      perror("Client: send message");
    }
  }
  pthread_exit(NULL);
}

int main(int argc, char *argv[])
{
  int sockfd, numbytes;  
  struct sockaddr_in their_addr;
         // connector's address information 
  struct hostent *he;
  char buf[MESSAGE_MAX];
  long int term1, term2, res;
  int demande_pseudo = 0;
  char* pseudo;
  pseudo = malloc (sizeof(char) * PSEUDO_MAX);

  if (argc != 2) {
    fprintf(stderr, "Donner le nom du serveur.");
    return EXIT_FAILURE;
  }
      
  if ((he=gethostbyname(argv[1])) == NULL) {
    perror("Client: gethostbyname");
    return EXIT_FAILURE;
  }

  if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
    perror("Client: socket");
    return EXIT_FAILURE;
  }
  




  their_addr.sin_family = AF_INET;    
  their_addr.sin_port = htons(PORT);
  their_addr.sin_addr = *((struct in_addr*)he->h_addr);
  memset(&(their_addr.sin_zero), '\0', 8); 
   
  if (connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1) {
    perror("Client: connect");
    return EXIT_FAILURE;
  }
  
  while (1) { 
    if ((numbytes=recv(sockfd, buf, MESSAGE_MAX-1, 0)) == -1) {
      perror("Client: recv");
      return EXIT_FAILURE;
    }

    if (demande_pseudo == 0) {
      buf[numbytes] = '\0';
      printf("%s",buf);
      getPseudo(pseudo);
      printf("Tu as choisi comme pseudo : %s\n", pseudo);
      demande_pseudo++;
      printf("%d\n", sockfd);
      if (send(sockfd, pseudo, strlen(pseudo), 0) == -1){
        perror("Client: send pseudo");
        return EXIT_FAILURE;
      }

      pthread_t thread; 
      
      if (pthread_create(&thread, NULL, thread_message, &sockfd)) {
        perror("pthread_create");
      return EXIT_FAILURE;
      }
    }
    else
    {
    }
  }
   
  close(sockfd);
  return EXIT_SUCCESS;
} 

Solution

  • First, your question is confusing, try to be more specific giving some examples. If I understood your question, your problem is that your select is configured to operate on blocking mode. This is an example on how to set your socket flags to non-blocking mode.

    flags = fcntl(sock, F_GETFL, 0);
    if(flags < 0)
        err(1, "%s: fcntl", __FUNCTION__);
    
    ret = fcntl(sock, F_SETFL, flags | O_NONBLOCK);
    if(ret < 0)
        err(1, "%s: fcntl:", __FUNCTION__); 
    

    Answer Update

    Well, one of your problems is that your main function doesn't wait your thread update readfds and max, thus getting blocked on select with the wrong values for those variables. You could solve that by either adding pthread_join(thread, NULL); after you create your thread OR , as I said first, setting your select as non-blocking mode.

    Try to develop those ideas and you'll make that code work! (: