我创建了一个使用多线程处理多个客户端的聊天服务器。我有一个无限运行的while循环,等待新客户端。按ctrl + c后我想退出。所以,我正试图捕捉SIGINT信号,正如前面提到的here。但我无法退出该计划。我在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;
}
答案 0 :(得分:4)
通过中断处理程序设置的标记捕获信号不适用于信号需要可靠地中断阻塞系统调用(在您的情况下为accept
)的情况。问题是信号可能在阻塞系统进入之前到达:检查标志之后但在信号中断给定系统调用之前的状态之前。因此,即使设置了标志,系统调用也会阻止程序的执行。
此外,当多个线程允许信号时,只有一个线程捕获信号,并且未指定哪个线程。在您的情况下,主线程可能无法捕获信号,因此accept
根本不会中断。
虽然第二个问题(与多线程程序有关)很容易通过阻塞除主程序之外的所有线程中的信号来解决,但第一个问题需要采用特殊方法。可能的:
最复杂的方法,但几乎适用于所有情况。信号被“转换”为文件描述符,该文件描述符与系统调用等待的文件描述符组合。结果集文件descritor用于轮询:
// 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, ...);
...
}
注意,所有线程都会阻止该信号,包括主线程。
最简单的方法,但使用有限。如果所有终结操作都是信号安全的那些(请参阅man singnal(7)以获取在信号处理程序中允许调用的函数的完整列表),那些操作可以由信号处理程序本身执行,然后信号处理程序本身执行退出该计划:
void handler(int signal)
{
close(s1);
close(s2);
_exit(0); // This function is thread-safe, unlike to *exit*.
}
但是在多线程程序的情况下,这种方法通常不合适,因为函数thread_join
不是信号安全的。
因此系统调用将立即返回,不会阻塞。最简单的状态更改是关闭系统调用的文件描述符:
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;
}
...
}
注意,在多线程程序的情况下,除了主线程之外的所有线程都应明确阻止信号。否则,在一个线程中关闭文件而其他线程读取它不需要中断读取线程。
此外,在多线程程序的情况下,应该考虑到,如果某些其他线程创建(打开)文件描述符,它可以重用一个,在信号处理程序中关闭,就在它之前在系统调用中使用。