RTS와 Thread Pool을 사용한 서버인데, 클라이언트에서 연결을 끊으면 죽어버리는군요.
글쓴이: square07 / 작성시간: 금, 2011/04/15 - 12:03오전
using namespace std; #define MAX_SENDBUF_SIZE 1024 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; int gpid; // RTS 값과 thread id를 가지는 구조체 typedef struct _fd_sig { int signum; int pid; } fd_sig; /* 쓰레드 하나 당 처리 중인 소켓의 수를 저장하고 있는 multimap * key : 처리하고 있는 소켓 수 * value : 쓰레드와 RTS 정보 */ multimap<int, fd_sig> pool_list; multimap<int, fd_sig>::iterator mi; void do_sigio(int signo) { printf("signal : SIGIO....................\n"); } void do_sigpipe(int signo) { printf("signal : SIGPIPE.................\n"); } void init_signal_handler() { // SIGIO 나 SIGPIPE 시그널이 발생하면 // 프로그램이 강제 종료되므로 // 종료되지 않도록 핸들러 지정 struct sigaction sigact, pipe_sig; sigemptyset(&sigact.sa_mask); sigact.sa_flags = SA_SIGINFO; sigact.sa_restorer = NULL; sigact.sa_handler = do_sigio; sigemptyset(&pipe_sig.sa_mask); pipe_sig.sa_handler = do_sigpipe; pipe_sig.sa_flags = 0; // SIGIO 핸들러 등록 if( sigaction(SIGIO, &sigact, NULL) < 0 ) { printf("sigio handler error\n"); exit(0); } // SIGPIPE 핸들러 등록 if( sigaction(SIGPIPE, &pipe_sig, NULL) < 0 ) { printf("sigpipe handler error\n"); exit(0); } } int setup_sigio(int fd, int signum, int pid) { if( fcntl(fd, F_SETFL, O_RDWR | O_NONBLOCK | O_ASYNC) < 0 ) { printf("fcntl : Nonblocking error\n"); return -1; } if( fcntl(fd, F_SETSIG, SIGRTMIN + signum) < 0) { printf("fcntl : Couldn't set signal %d on %d\n", SIGRTMIN + signum, fd); return -1; } if( fcntl(fd, F_SETOWN, pid) < 0 ) { printf("fcntl : Couldn't set owner %d on %d\n", pid, fd); return -1; } return 0; } void disconnect(int fd, int signum) { fd_sig lfd_sig; int count; close(fd); mi = pool_list.begin(); while( mi != pool_list.end() ) { if( mi->second.signum == signum ) { lfd_sig = mi->second; count = mi->first - 1; pool_list.erase(mi); pool_list.insert(pair<int, fd_sig>(count, lfd_sig)); break; } mi++; } } void sendPacket(int fd, int code, char *data) { char sendbuf[MAX_SENDBUF_SIZE]; int size; size = strlen(data); memset(sendbuf, 0, MAX_SENDBUF_SIZE); memcpy(&sendbuf[0], &code, sizeof(code)); memcpy(&sendbuf[2], &size, sizeof(size)); memcpy(&sendbuf[4], data, size); send(fd, sendbuf, size+4, 0); } int procData(int fd, int signum) { char buf[MAX_SENDBUF_SIZE] = { 0x00, }; int code = 0; int size = 0; if( recv(fd, buf, 2, 0) <= 0 ) { printf("recv code error\n"); disconnect(fd, signum); } else { memcpy(&code, &buf[0], sizeof(code)); memset(&buf, 0, sizeof(code)); if( recv(fd, buf, 2, 0) <= 0 ) { printf("recv size error\n"); disconnect(fd, signum); } memcpy(&size, &buf[0], sizeof(size)); memset(&buf, 0, sizeof(size)); if( recv(fd, buf, size, 0) <= 0 ) { printf("recv data error\n"); disconnect(fd, signum); } printf("code = %d, size = %d, data = %s\n", code, size, buf); switch( code ) { case C2S_REQ_LOGIN: pthread_mutex_lock(&mutex); if( procLogin(fd, buf) < 0 ) { disconnect(fd, signum); } pthread_mutex_unlock(&mutex); break; case C2S_REQ_REGISTER: procRegister(fd, buf); disconnect(fd, signum); break; } } return 0; } void *workthread(void *rts_num) { int ret; sigset_t set; struct siginfo si; int signum = *((int *)rts_num); // 쓰레드의 PID를 가져온다. gpid = syscall(SYS_gettid); sigemptyset(&set); sigaddset(&set, SIGRTMIN + signum); pthread_sigmask(SIG_BLOCK, &set, NULL); // 메인 쓰레드에 조건변수 signal 알림 pthread_mutex_lock(&mutex); pthread_cond_signal(&cond); pthread_mutex_unlock(&mutex); while(1) { ret = sigwaitinfo(&set, &si); ///////////////////////////////////// 이 부분에서 죽습니다. if( ret == SIGRTMIN + signum ) { // 클라이언트와 통신 procData(si.si_fd, signum); } } return NULL; } int main(int argc, char** argv) { int i, k; int server_sockfd, client_sockfd; socklen_t clilen; int count; int ret; int optvalue = 1; struct sockaddr_in serveraddr, clientaddr; int threadnum = 4; struct siginfo si; fd_sig lfd_sig; sigset_t set; vector<void *(*) (void *)> thread_list; vector<pthread_t> tiden(threadnum); // RTS Setting init_signal_handler(); sigemptyset(&set); sigaddset(&set, SIGRTMIN); sigprocmask(SIG_BLOCK, &set, NULL); for( i=0; i<threadnum; i++ ) { thread_list.push_back(workthread); } // client와의 통신 전용 thread들 생성 // 각 쓰레드는 SIGRTMIN + k 값의 시그널을 기다린다. for( i=0, k=1; i<threadnum; i++, k++ ) { // 메인 쓰레드와 생성되는 쓰레드 간 데이터 전달이 // 확실해야 하므로 뮤텍스와 조건변수를 이용해서 동기화한다. pthread_mutex_lock(&mutex); pthread_create(&tiden[i], NULL, thread_list[i], (void *)&k); // 각 쓰레드에서 처리하는 RTS 번호와 처리중인 소켓 수를 유지한다. lfd_sig.signum = k; pthread_cond_wait(&cond, &mutex); lfd_sig.pid = gpid; pool_list.insert(pair<int, fd_sig>(0, lfd_sig)); pthread_mutex_unlock(&mutex); } printf("create thread pool success\n"); if( ( server_sockfd = socket(PF_INET, SOCK_STREAM, 0) ) < 0 ) { perror("Socket Error\n"); exit(0); } setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &optvalue, sizeof(optvalue)); memset(&serveraddr, 0, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); serveraddr.sin_port = htons(7000); if( bind(server_sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0 ) { perror("bind error\n"); exit(0); } if( listen(server_sockfd, 5) < 0) { perror("listen error\n"); exit(0); } if( setup_sigio(server_sockfd, 0, getpid()) < 0 ) { printf("setup_sigio error\n"); exit(0); } clilen = sizeof(clientaddr); while( 1 ) { ret = sigwaitinfo(&set, &si); if( ret == SIGRTMIN ) { if( si.si_fd == server_sockfd ) { client_sockfd = accept(server_sockfd, (struct sockaddr *)&clientaddr, &clilen); if( client_sockfd < 0 ) { printf("accept error\n"); continue; } mi = pool_list.begin(); lfd_sig = mi->second; count = mi->first + 1; setup_sigio(client_sockfd, lfd_sig.signum, lfd_sig.pid); pool_list.erase(mi); pool_list.insert(pair<int, fd_sig>(count, lfd_sig)); sendPacket(client_sockfd, S2C_SND_CONNECT_OK, ""); } else { } } } for( i=0; i<threadnum; i++ ) { pthread_join(tiden[i], NULL); } pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond); return 0; }
////////////////////////////////////////////////////////////////////
주제와 상관없는 서버 기능 코드와 헤더 파일은 생략했습니다.
정확한 원인은 Visual Studio에서 MFC로 작성한 클라이언트의
AsyncSocket을 상속받은 소켓 객체가 Close() 혹은 ShutDown() 함수를 호출하여
연결을 끊으면 발생하는 것으로 100% 판명되었습니다.
클라이언트 코드에서 서버로의 연결 후의 어떤 코드에서든 연결을 끊는 함수를 호출하면
서버의 sigwaitinfo() 함수에서 죽는 것으로 보이며,
터미널에서 "실제 시간 시그널 1번" 이라는 메시지를 띄우면서 죽습니다.
(1번은 SIGRTMIN + 1 시그널에서의 1 이네요.)
그 외 서버와 클라이언트 간의 통신기능은 모두 정상입니다.
서버에서 close() 함수를 이용한 연결 끊기는 정상적으로 작동하며,
이 문제로 인해 개인 프로젝트가 이틀간 잠정 중단.......
기능 코드에는 전혀 문제가 없고, 위에 있는 코드에서 문제가 있거나
설정을 빼먹은 것이 분명한 것 같은데.. 명쾌한 해결 가능했으면 좋겠네요.
우분투 10.10 커널 버전 2.6.35입니다.
Forums:
...
이 문제 때문이 아닐 것 같기는 합니다만, 그래도 혹시 모르니 노파심에...
시그널 핸들러에서 printf는 못씁니다.
http://kldp.org/node/122432#comment-550312
아, 그렇군요..
우선 새로운 지식에 감사드리고..
희망을 가지고 고쳐보았습니다만 역시 죽는군요..
얼핏 보기엔 문제 없는 것 같은데..
아, 정말 미치겠네요. 후덜덜덜..
댓글 달기