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;

}
5
  • I am on linux and using ctrl+c on terminal. Commented Dec 31, 2015 at 17:18
  • Your code runs well. I am using 3.19.0-33-generic version of linux. Commented Dec 31, 2015 at 17:25
  • I have one more while loop in the function associated with thread. Commented Dec 31, 2015 at 17:33
  • A debugger might also help (e.g. gdb). Commented Dec 31, 2015 at 17:43
  • 1
    Possible duplicate of Fixing race condition when sending signal to interrupt system call Commented Jan 1, 2016 at 18:00

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

3 Comments

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: 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).
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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.