Search code examples
ctcppthreadsrecvposix-select

tcp server with multiple clients sending message back to all connected clients


I have a tcp chat program: server.c and client.c.

The server is in a while(1) loop and uses select to detect clients wanting to connect on it's socket. A new thread is then created for the accepted client and its socket descriptor is given as an argument for thread: pthread_create (&thread,NULL, do_something, (void *) &socket_descriptor);

When receiving a message from a client, the server should send this message to all connected clients. (not implemented this yet).

Now I'm wondering how to do this. I absolutely need each accepted connection to be in a thread.

I was thinking of using a select inside the do_something as well; will select detect if data is incoming on the socket descriptor? Or would you do it another way?

edit: added code my code:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include "tcp_comm.h"
#include <sys/time.h>
#include <sys/types.h>

#define BUFSIZE 1024
#define PORT 1234

void *do_something(void *a);

int main (void){
    Socket server = tcp_passive_open( PORT );
    MySocket *s = (MySocket *)server;
    printf("Server socked_id (main): %i", s->sd);

    pthread_t thread;

    fd_set active_socketDescriptors,read_socketDescriptors;

    FD_ZERO(&active_socketDescriptors);         
    FD_SET(s->sd,&active_socketDescriptors);    

    while (1){
        read_socketDescriptors = active_socketDescriptors;
        if (select (FD_SETSIZE, &read_socketDescriptors, NULL, NULL, NULL) < 0){
            perror ("select"); 
            exit (EXIT_FAILURE);
        }

        int i;
        for (i = 0; i < FD_SETSIZE; ++i){
            if (FD_ISSET (i, &read_socketDescriptors)){
                if (i == s->sd){
                    Socket client = tcp_wait_for_connection( server ); 
                    pthread_create (&thread,NULL, do_something, (void *)client); 
                    FD_SET (s->sd, &active_socketDescriptors);          
                } else {
                }
            }
        }
    }

    tcp_close( server );
    return 0;

}
void *do_something(void *client){
    unsigned char input[BUFFER_SIZE]; 
    pthread_detach(pthread_self());

    MySocket *s = (MySocket *)client;
    printf("Client socked_id (thread): %i", s->sd);
    int j;
    while (1){
        int nbytes = tcp_receive(client, input, BUFSIZE );
        if (nbytes <= 0){
                if (nbytes ==0){
                    /* connection closed by client*/    
                    printf("Client closed connection");         
                } else {
                    /* other error*/
                    perror("tcp_receive");              
                }
                tcp_close(&client);
                /*remove the socket descriptor from set in the main BRAINSTORM ABOUT THIS */
        } else {
            /*data incoming */
            printf("\nMessage from client: %s",input);
        }
    }
    return 0;
}

edit 2: reformulation of problem I have to use threads (it not because of the system; linux) but because it's mandatory in the assignment to have a thread for each client.

The problem i have specifically is that only the main thread can send the data recieved in each thread from each client to all clients because only the main thread has access to the set which contains the socket descriptors.

edit3: what I need to add in each thread but I can't because of the s.thread and s.main being in different places & the thread not knowing the set of the main.

for (j=0; j<=FD_SETSIZE;j++){
    if(FD_ISSET(j,&active_socketDescriptors)){
        if (j != s.thead && j!=s.main){
            tcp_send(j, (void*)input,nbytes);
        }       
    }   
}

edit 4: I solved it this way: i have a dynamic array list where i put a list of connected clients with there socket descriptor. Inside the thread of the server (do something) I have the recieve blocking until it gets input then this input is send to all connected clients using there socket descriptor from the list which it loops trough. Inside the clients there is a thread listening and a thread sending.


Solution

  • If the client connection sockets are non-blocking, then using e.g. select to wait for the socket receive data is a possible way. However, since you already have the connected sockets in threads, you can keep them blocking, and just do a read call on them. The call to read will block until you receive data, which can then be spread to the other threads.

    Edit

    After better understanding your requirements, you should probably have the sockets non-blocking, and use a loop with select with a short timeout. When select timeouts (i.e. returns 0) then you check if there is data to send. If there is, then send the data, and go back to the select call.