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
아, 그렇군요..
우선 새로운 지식에 감사드리고..
희망을 가지고 고쳐보았습니다만 역시 죽는군요..
얼핏 보기엔 문제 없는 것 같은데..
아, 정말 미치겠네요. 후덜덜덜..
댓글 달기