RTS와 Thread Pool을 사용한 서버인데, 클라이언트에서 연결을 끊으면 죽어버리는군요.

square07의 이미지

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입니다.

jick의 이미지

이 문제 때문이 아닐 것 같기는 합니다만, 그래도 혹시 모르니 노파심에...

시그널 핸들러에서 printf는 못씁니다.

http://kldp.org/node/122432#comment-550312

square07의 이미지

우선 새로운 지식에 감사드리고..
희망을 가지고 고쳐보았습니다만 역시 죽는군요..

얼핏 보기엔 문제 없는 것 같은데..
아, 정말 미치겠네요. 후덜덜덜..

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.