Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to know if the client has terminated in sockets

Tags:

c

sockets

Suppose, I have a connected socket after writing this code..

if ((sd = accept(socket_d, (struct sockaddr *)&client_addr, &alen)) < 0)
{
    perror("accept failed\n");
    exit(1);
}

How can I know at the server side that client has exited.

My whole program actually does the following..

  • Accepts a connection from client
  • Starts a new thread that reads messages from that particular client and then broadcast this message to all the connected clients.

If you want to see the whole code... In this whole code. I am also struggling with one more problem that whenever I kill a client with Ctrl+C, my server terminates abruptly.. It would be nice if anyone could suggest what the problem is..

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

/*CONSTANTS*/
#define DEFAULT_PORT 10000 
#define LISTEN_QUEUE_LIMIT 6
#define TOTAL_CLIENTS 10
#define CHAR_BUFFER 256

/*GLOBAL VARIABLE*/
int current_client = 0;
int connected_clients[TOTAL_CLIENTS];
extern int errno;

void *client_handler(void * socket_d);

int main(int argc, char *argv[])
{
    struct sockaddr_in server_addr;/* structure to hold server's address*/
    int    socket_d;             /* listening socket descriptor       */
    int    port;           /* protocol port number              */
    int    option_value;   /* needed for setsockopt             */
    pthread_t tid[TOTAL_CLIENTS];
    port = (argc > 1)?atoi(argv[1]):DEFAULT_PORT;

    /* Socket Server address structure */
    memset((char *)&server_addr, 0, sizeof(server_addr)); 
    server_addr.sin_family = AF_INET;               /* set family to Internet */
    server_addr.sin_addr.s_addr = INADDR_ANY;       /* set the local IP address */
    server_addr.sin_port = htons((u_short)port);    /* Set port */

    /* Create socket */
    if ( (socket_d = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
        fprintf(stderr, "socket creation failed\n");
        exit(1);
    }

    /* Make listening socket's port reusable */
    if (setsockopt(socket_d, SOL_SOCKET, SO_REUSEADDR, (char *)&option_value, 
                sizeof(option_value)) < 0) {
        fprintf(stderr, "setsockopt failure\n");
        exit(1);
    }

    /* Bind a local address to the socket */
    if (bind(socket_d, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        fprintf(stderr, "bind failed\n");
        exit(1);
    }

    /* Specify size of request queue */
    if (listen(socket_d, LISTEN_QUEUE_LIMIT) < 0) {
        fprintf(stderr, "listen failed\n");
        exit(1);
    }

    memset(connected_clients,0,sizeof(int)*TOTAL_CLIENTS);

    for (;;)
    {
        struct sockaddr_in client_addr;    /* structure to hold client's address*/
        int    alen = sizeof(client_addr); /* length of address                 */
        int    sd;                /* connected socket descriptor */

        if ((sd = accept(socket_d, (struct sockaddr *)&client_addr, &alen)) < 0)
        {
            perror("accept failed\n");
            exit(1);
        }
        else printf("\n I got a connection from (%s , %d)\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));

        if (pthread_create(&tid[current_client],NULL,(void *)client_handler,(void *)sd) != 0)
        {
            perror("pthread_create error");
            continue;
        }
        connected_clients[current_client]=sd;
        current_client++; /*Incrementing Client number*/
    }

    return 0;
}

void *client_handler(void *connected_socket)
{
    int sd;
    sd = (int)connected_socket;
    for ( ; ; ) 
    {
        ssize_t n;
        char buffer[CHAR_BUFFER];
        for ( ; ; )
        {
            if (n = read(sd, buffer, sizeof(char)*CHAR_BUFFER) == -1)
            {
                perror("Error reading from client");
                pthread_exit(1);
            }
            int i=0;
            for (i=0;i<current_client;i++)
            {
                if (write(connected_clients[i],buffer,sizeof(char)*CHAR_BUFFER) == -1)
                    perror("Error sending messages to a client while multicasting");
            }
        }
    }
}

My client side is this (Maye be irrelevant while answering my question)

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

void error(char *msg)
{
    perror(msg);
    exit(0);
}

void *listen_for_message(void * fd)
{
    int sockfd = (int)fd;
    int n;
    char buffer[256];
    bzero(buffer,256);
    printf("YOUR MESSAGE: ");
    fflush(stdout);
    while (1)
    {
        n = read(sockfd,buffer,256);
        if (n < 0) 
            error("ERROR reading from socket");
        if (n == 0) pthread_exit(1);
        printf("\nMESSAGE BROADCAST: %sYOUR MESSAGE: ",buffer);
        fflush(stdout);
    }
}

int main(int argc, char *argv[])
{
    int sockfd, portno, n;
    struct sockaddr_in serv_addr;
    struct hostent *server;
    pthread_t read_message;
    char buffer[256];
    if (argc < 3) {
        fprintf(stderr,"usage %s hostname port\n", argv[0]);
        exit(0);
    }
    portno = atoi(argv[2]);
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) 
        error("ERROR opening socket");
    server = gethostbyname(argv[1]);
    if (server == NULL) {
        fprintf(stderr,"ERROR, no such host\n");
        exit(0);
    }
    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    bcopy((char *)server->h_addr, 
            (char *)&serv_addr.sin_addr.s_addr,
            server->h_length);
    serv_addr.sin_port = htons(portno);
    if (connect(sockfd,&serv_addr,sizeof(serv_addr)) < 0) 
        error("ERROR connecting");
    bzero(buffer,256);
    if (pthread_create(&read_message,NULL,(void *)listen_for_message,(void *)sockfd) !=0 )
    {
        perror("error creating thread");
    }
    while (1)
    {
        fgets(buffer,255,stdin);
        n = write(sockfd,buffer,256);
        if (n < 0) 
            error("ERROR writing to socket");
        bzero(buffer,256);
    }
    return 0;
}
like image 761
Abhijeet Rastogi Avatar asked Apr 07 '10 14:04

Abhijeet Rastogi


2 Answers

After accepting the connection, your recv() on the socket will return 0 or -1 in special cases.

Excerpt from recv(3) man page:

Upon successful completion, recv() shall return the length of the message in bytes. If no messages are available to be received and the peer has performed an orderly shutdown, recv() shall return 0. Otherwise, -1 shall be returned and errno set to indicate the error.

So, if your client exited gracefully, you will get 0 from recv() at some point. If the connection was somehow lost, you may also get -1 and checking for appropriate errno would tell you if the connection was lost of some other error occured. See more details at recv(3) man page.

Edit:

I see that you are using read(). Still, the same rules as with recv() apply.

Your server can also fail when trying to write() to your clients. If your client disconnects write() will return -1 and the errno would probably be set to EPIPE. Also, SIGPIPE signal will be send to you process and kill him if you do not block/ignore this signal. And you don't as I see and this is why your server terminates when client presses Ctrl-C. Ctrl-C terminates client, therefore closes client socket and makes your server's write() fail.

See mark4o's answer for nice detailed explanation of what else might go wrong.

like image 180
pajton Avatar answered Sep 29 '22 06:09

pajton


If the client program exits, then the OS on the client will close its end of the socket. When you call recv() it will return 0, or -1 with errno ECONNRESET if a TCP RST has been received (e.g. because you attempted to send data after the client had closed). If the whole client machine goes down, or the network becomes disconnected, then in that case you may not receive anything if the server is not trying to send anything; if that is important to detect, you can either send some data periodically, or set the SO_KEEPALIVE socket option using setsockopt() to force it to send a packet with no data after long periods (hours) of inactivity. When no acknowledgment is received, recv() will then return -1 with errno ETIMEDOUT or another error if more specific information is available.

In addition, if you attempt to send data on a socket that has been disconnected, by default the SIGPIPE signal will terminate your program. This can be avoided by setting the SIGPIPE signal action to SIG_IGN (ignore), or by using send() with the MSG_NOSIGNAL flag on systems that support it (Linux).

like image 28
mark4o Avatar answered Sep 29 '22 06:09

mark4o