3

I have made a chat server which uses multi-threading for dealing with multiple clients. I have a while loop which runs infinitely and waits for new clients. I want to come out of it after I press ctrl+c. So, I am trying to catch the SIGINT signal as has been mentioned here. But I am not able to exit from the program. I am working in terminal on Linux.

server.c

//for running type ./a.out anyportnumber
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
int s2;
int arr[100];
int tc = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
volatile sig_atomic_t flag = 1;
void handler(int signal)
{
    flag = 0;
}
void sendtoall(char *msg,int s1)
{
    int i;
    pthread_mutex_lock(&mutex);
    for(i = 0; i < tc; i++) {
        if(arr[i] != s1) 
            write(arr[i],msg,strlen(msg));
    }
    pthread_mutex_unlock(&mutex);
}
void *function(void *s)
{
    int s1;
    int n;
    char rmsg[500];
    s1 = *(int *)s;
    while((n = read(s1,rmsg,500)) > 0) {
        rmsg[n] = '\0';
        sendtoall(rmsg,s1);
        bzero(rmsg,500);
    }
    pthread_exit(NULL);
}
int main(int arrc,char *argv[])
{
    struct sockaddr_in server,client;
    int s1,len;
    int n;
    int port;
    pthread_t t1;
    char message[500];
    port = atoi(argv[1]);
    bzero((char *)&server,sizeof(server));
    server.sin_port = htons(port);
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_family = AF_INET;
    s1 = socket(AF_INET,SOCK_STREAM,0);
    if(s1 == -1) {
        perror("socket not created\n");
        exit(1);
    }
    if(bind(s1,(struct sockaddr *)&server,sizeof(struct sockaddr)) == -1) {
        perror("socket not binded\n");
        exit(1);
    }
    if(listen(s1,5) == -1) {
        perror("unable to listen");
        exit(1);
    }
    len = sizeof(struct sockaddr_in);
    signal(SIGINT, handler);
    while(flag) {
        s2 = accept(s1,(struct sockaddr *)&client,&len);
        pthread_create(&t1,NULL,function,(void *)&s2);
        arr[tc] = s2;
        tc++;
    }
    close(s1);
    close(s2);
    return 0;

}
Community
  • 1
  • 1
Shivam Mitra
  • 1,040
  • 3
  • 17
  • 33

1 Answers1

4

Catching signal via flag set by interrupt handler is not suitable for cases when signal needs to reliably interrupt blocking system call (accept in your case). The problem is that signal may arrive just before blocking system is entered: after checking the flag but before the state when signal interrupts given system call. So, even flag is set, the system call blocks program's execution.

Also, when signal is allowed by several threads, only one of the threads catch the signal, and it is unspecified which of them. In your case it is possible that signal is catched no by the main thread, so accept is not interrupted at all.

While the second problem (related to multithreaded programs) is easily overcomed by blocking the signal in all threads except the main one, the first problem requires special approaches. Possible ones:

signalfd() in conjunction with poll() / select()

The most complex approach, but works in almost all cases. Signal is "transformed" into file descriptor, which is combined with file descriptor on which the system call waits. Resulted set of file descritors is used for polling:

// Preparations
sigset_t s;
sigemptyset(&s);
sigaddset(&s, SIGINT);
sigprocmask(SIGBLOCK, &s, NULL); // For multithreaded program *pthread_sigmask* should be used.
int fd_int = signalfd(0, &s, 0); // When signal arises, this file becomes readable

// Usage
fd_set fds;
FD_ZERO(&fds);
FD_SET(fd_int, &fds);
FD_SET(s1, &fds);
int nfds = MAX(fd_int, s1) + 1;
select(nfds, &fds, NULL, NULL, NULL);
if(FD_ISSET(fd_int, &fds)) {
    // Signal is arrived
    ...
}
else {
    // New connection request is received
    accept(s1, ...);
    ...
}

Note, that signal is blocked for all threads, included the main one.

Finalization inside signal handler and exit

The most simple approach but with limited usage. If all finalization actions are signal-safe ones (see man singnal(7) for complete list of functions which call is allowed within signal handler), that actions may be performed by the signal handler itself, which then exits from the program:

void handler(int signal)
{
    close(s1);
    close(s2);
    _exit(0); // This function is thread-safe, unlike to *exit*.
}

But in case of multithreaded program this approach normally is not suitable, as function thread_join is not signal-safe.

Changing state of syscall's parameters in the signal handler

so system call will return immediately, without blocking. The simplest state changing is closing file descriptor with which system call works:

void handler(int signal)
{
    flag = 0;
    close(s1); // Close file descriptor which is used by the system call.
}

while(flag)
{
    s2 = accept(s1, ...);
    if(s2 == -1 && !flag) {
        // Signal is catched
        break;
    }
    ...
}

Note, that in case of multithreaded program the signal should be explicitely blocked for all threads except main one. Otherwise, closing file in one thread while other thread reads it isn't required to interrupt reading thread.

Also, in case of multithreaded program one should take into account, that if some other thread creates(opens) file descriptor, it can reuse one, closed in the signal handler, just before it is used in the syscall.

Tsyvarev
  • 60,011
  • 17
  • 110
  • 153
  • Calling `close()` in a signal handler like this introduces a race: the very next allocation of a file descriptor will use the just-closed file descriptor number. Your `accept()` might be called on someone else's socket, pipe, file, message queue descriptor, etc. – pilcrow Jan 01 '16 at 21:27
  • @pilcrow: Good note about possible reusing closed file descriptor, but in the given case it can hardly be a problem. Firtst, in the given example thread doesn't create any file descriptor actually. Second, even if it does, it should create *connection-based, listening socket* for `accept()` could do any harm: for any other type of file descriptor `accept()` does nothing and returns error (EINVAL, ENOSOCK or EOPNOTSUPP). – Tsyvarev Jan 02 '16 at 01:59
  • 1
    For completely eliminate raced file descriptor allocation between `close` and `accept`, instead of `close(s1)` signal handler can use `dup2(oldfd, s1)`, where `oldfd` is any already-opened file descriptor, but not a socket suitable for `accept()`. So `s1` will *atomically* be "relinked" to the same object as `oldfd`, and `accept(s1)` will return appropriate error without possibility of harm. – Tsyvarev Jan 02 '16 at 02:15