c socket server close 시 클라이언트 종료 감지 문의

전병욱@Facebook의 이미지

안녕하세요.

c socket server close 시 클라이언트 종료 감지 문의드립니다.

server가 종료되면 client도 종료가 안 되어 종료되는 방법 문의드립니다.

source 올려드리니 봐주시면 감사하겠습니다.

echoserver.c

#include "libDefine.h"
 
void signalHandlerServer(int nSignal);
 
int g_nServerSocket = -1;								/* server socket file descriptor */
int g_nClientSocket = -1;								/* client socket file descriptor */
 
int main(int argc, char *argv[])
{
	int nPort = -1;
	char szMsg[SIZEBUFFER + 1] = {0,};			/* 수신용 버퍼 */
	int nReadMessage = 0;								/* 받은 메시지 길이 저장 변수 */
	struct sockaddr_in stClientAddr = {0,};	/* 클라이언트 주소 저장할 구조체 */
	unsigned int uClientAddrSize = sizeof(stClientAddr);			/* 클라이언트 주소 구조체의 크기 */
	int nWriteMessage = -1;
 
	/* signal 처리 */
	signal(SIGINT, signalHandlerServer);
 
	if (argc != 2)
	{
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}
 
	nPort = atoi(argv[1]);
 
	printf("서버 연결 중입니다.\n");
	g_nServerSocket = serverSocket(nPort);
 
	if(g_nServerSocket < 0)
	{
		errHandle(errno, "socket() error \n");
		exit(1);
	}
 
	while(1)
	{
		g_nClientSocket = accept(g_nServerSocket, (struct sockaddr*) &stClientAddr, &uClientAddrSize);
 
		if(g_nClientSocket < 0)
		{
			errHandle(errno, "accept() 클라이언트로의 접속 요청이 수락되지 않았습니다.\n");
			g_nClientSocket = -1;
			sleep(1);
		}
		else
		{
			printf("클라이언트 접속 IP : %s\n", inet_ntoa(stClientAddr.sin_addr));
			printf("accept() 클라이언트로 접속 요청 수락\n");
		}
 
		while((nReadMessage = read(g_nClientSocket, szMsg, SIZEBUFFER)) > 0)
		{
			printf("클라이언트로 받은 메시지 %s\n", szMsg);
 
			nWriteMessage = write(g_nClientSocket, szMsg, nReadMessage);
 
			if(nWriteMessage < 0)
			{
				errHandle(errno, "write error \n");
				break;
			}
			memset(szMsg, 0, sizeof(szMsg));
		}
 
		close(g_nClientSocket);
		g_nClientSocket = -1;
		printf("연결이 끊어졌습니다. \n");
		sleep(1);
	}
	close(g_nServerSocket);
	g_nServerSocket = -1;
	return 0;
}
 
void signalHandlerServer(int nSignal)
{
	char szMessage[SIZEBUFFER] = {0,};
 
	if (nSignal == SIGINT)
	{
		fputs("종료하시겠습니까?(y 입력) : ", stdout);
		fgets(szMessage, SIZEBUFFER, stdin);
		if(!strcasecmp(szMessage, "y\n"))
		{
			if(g_nClientSocket >= 0)
			{
				close(g_nClientSocket);
				g_nClientSocket = -1;
				printf("client socket 종료되었습니다.\n");
				exit(0);
			}
 
			if(g_nServerSocket >= 0)
			{
				close(g_nServerSocket);
				g_nServerSocket = -1;
				printf("server socket 종료되었습니다.\n");
				exit(0);
			}
		}
	}
}

echoclient

#include "libDefine.h"
 
void signalHandlerClient(int nSignal);
 
int g_nClientSocket = -1;
 
int main(int argc, char *argv[])
{
	int nPort = -1;
	char *pszAddr = NULL;					/* ip 문자 입력받을 포인터 변수 */
	char szMsg[SIZEBUFFER + 1] = {0,};	/* 서버에 보낼 메시지를 저장할 문자열 버퍼 */
	int nReadMessage = 0;					/* 송수신 메시지의 문자열 길이 */
	int nWriteMessage = 0;
 
	signal(SIGINT, signalHandlerClient);
 
	/* port */
	if (argc != 3)
	{
		printf("usage : %s <IP> <port> \n", argv[0]);
		exit(1);
	}
 
	pszAddr = argv[1];
	nPort = atoi(argv[2]);
 
	g_nClientSocket = clientSocket(nPort, pszAddr);
 
	if(g_nClientSocket < 0)
	{
		errHandle(errno, "socket() error \n");
		exit(1);
	}
 
	while (1)
	{
		memset(szMsg, 0, sizeof(szMsg));
 
		fputs("입력 메시지(Q는 종료, 버퍼 사이즈까지 입력할 수 있습니다.) : ", stdout);
		fgets(szMsg, SIZEBUFFER, stdin);
 
		if(!strcasecmp(szMsg, "q\n"))
		{
			close(g_nClientSocket);
			g_nClientSocket = -1;
			break;
		}
 
		if((nWriteMessage = write(g_nClientSocket, szMsg, strlen(szMsg))) < 0)
		{
			errHandle(errno, "write error \n");
			close(g_nClientSocket);
			g_nClientSocket = -1;
			exit(1);
		}
 
		if((nReadMessage = read(g_nClientSocket, szMsg, SIZEBUFFER)) < 0)
		{
			errHandle(errno, "read error \n");
			close(g_nClientSocket);
			g_nClientSocket = -1;
			exit(1);
		}
 
		szMsg[nReadMessage] = 0;
		printf("서버로 받은 메시지 : %s\n", szMsg);
	}
	return 0;
}
 
void signalHandlerClient(int nSignal)
{
	char szMessage[SIZEBUFFER] = {0,};
 
	if (nSignal == SIGINT)
	{
		fputs("종료하시겠습니까?(y 입력) : ", stdout);
		fgets(szMessage, SIZEBUFFER, stdin);
		if(!strcasecmp(szMessage, "y\n"))
		{
			if(g_nClientSocket >= 0)
			{
				close(g_nClientSocket);
				g_nClientSocket = -1;
				printf("연결 종료되었습니다.\n");
				exit(0);
			}
		}
	}
}

libDefine.c

#include "libDefine.h"
 
int serverSocket(int nPort)
{
	int nServerSocket = -1;
	struct sockaddr_in stServerAddr = {0,};	/* 서버 주소 저장할 구조체  */
 
	if(nPort < 0)
	{
		errHandle(errno, "port error");
		exit(1);
	}
 
	if(nPort)
 
	/* TCP 통신용 서버 소켓 생성 */
	nServerSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
 
	if (nServerSocket < 0)
	{
		errHandle(errno, "socket() error");
		exit(1);
	}
	else
	{
		printf("socket() TCP 통신용 서버 소켓이 생성되었습니다.\n");
	}
 
	memset(&stServerAddr, 0, sizeof(stServerAddr));
	stServerAddr.sin_family = AF_INET;
	stServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	stServerAddr.sin_port = htons(nPort);
 
	/* bind */
	if (bind(nServerSocket, (struct sockaddr*) &stServerAddr, sizeof(stServerAddr)) < 0)
	{
		errHandle(errno, "bind() error %d\n", nPort);
		close(nServerSocket);
		nServerSocket = -1;
		exit(1);
	}
	else
	{
		printf("bind() - socket에 주소와 IP 할당되었습니다.\n");
	}
 
	/* 소켓을 서버용으로 사용할 수가 있게 한다. */
	if (listen(nServerSocket, LISTENQ) < 0)
	{
		errHandle(errno, "listen() error %d\n", nPort);
		close(nServerSocket);
		nServerSocket = -1;
		exit(1);
	}
	else
	{
		printf("listen() 연결 요청 대기하는 중입니다.\n");
	}
 
	return nServerSocket;
}
 
int clientSocket(int nPort, char *pszAddr)
{
	int nClientSocket = -1;
	struct sockaddr_in stServerAddr = {0,};			/* 접속할 서버의 주소 저장할 구조체 */
 
	if(nPort < 0)
	{
		errHandle(errno, "port error");
		exit(1);
	}
 
	if(pszAddr == NULL)
	{
		errHandle(errno, "address error");
		exit(1);
	}
 
	nClientSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
 
	if(nClientSocket < 0)
	{
		errHandle(errno, "socket() error");
		exit(1);
	}
 
	/* 서버 쪽 주소 구조체 0 초기화 */
	memset(&stServerAddr, 0, sizeof(stServerAddr));
	stServerAddr.sin_family = AF_INET;								/* 인터넷 주소체계 사용 */
	stServerAddr.sin_addr.s_addr = inet_addr(pszAddr);			/* 서버 IP 구조체에 저장 */
	stServerAddr.sin_port = htons(nPort);							/* 서버 Port 구조체에 저장 */
 
	/* connect */
	if (connect(nClientSocket, (struct sockaddr*) &stServerAddr, sizeof(stServerAddr)) < 0)
	{
		errHandle(errno, "connect() error %s\n", pszAddr);
		close(nClientSocket);
		nClientSocket = -1;
		exit(1);
	}
	else
	{
		printf("연결되었습니다.\n");
	}
	return nClientSocket;
}
 
/* error 처리 handler */
void errHandle(int nErrorCode, char *pszMsg, ...)
{
	va_list argptr;
	char szBuf[MESSAGE] = {0,};
 
	if(nErrorCode)
	{
		printf("error = %s\n", strerror(errno));
	}
 
	if(pszMsg == NULL)
	{
		printf("message error = %s\n", strerror(errno));
		exit(1);
	}
 
	va_start(argptr, pszMsg);
	vsnprintf(szBuf, MESSAGE, pszMsg, argptr);		/* 버퍼에 문자열 쓰고 버퍼 길이 지정하며 va_list 얻는다. */
	va_end(argptr);
	puts(szBuf);
}

익명 사용자의 이미지

여러 문제가 보이지만,

클라이언트도 read를 다했으면 loop에서 빠저나와 close() 해줘야죠.

익명 사용자의 이미지

추가로 클라이언트 read에서 버퍼사이즈 많큼 못 받아서 무한정 대기중에 빠진건지 확인 해 보세요.

전병욱@Facebook의 이미지

감사합니다.
클라이언트 시그널 ctrl c 누르면 y 입력하면 close되게 했습니다.
근데 서버에서 ctrl + c 시그널 프로세스가 되면 클라이언트에서 종료되지 않습니다.
클라이언트 read에서는 버퍼만큼 충분히 받고 있는 데 어떻게 수정하면 될까요?

익명 사용자의 이미지

이 참...어디서부터 설명을 해야할지....

코드 수정하는 부분은 능력이 안되 가르쳐 드릴 순 없지만,

서버에서 ctrl + c 했다고 클라이언트에 SIGINT 전달 됩니까??

그리고 read는 non-blockg설정을 하지 않는 이상, 버퍼 사이즈 많큼 read되어야 다음 작업을 수행합니다.

전병욱@Facebook의 이미지

서버에서는 ctrl c 하면 사실 시그널 서버, client를 각각 만들었습니다.
ctrl + c를 하면 서버에 g_nClientSocket이 accept 접속 요청을 수락해서 받는 소켓인 데 이 부분이 서버에서 ctrl + c를 하면 close 되게 했습니다.
지금 하는 부분은 다중 접근이 아닌 단일 통신입니다.

ymir의 이미지

client 가 서버의 종료를 감지할 수 있는 순간은, socket 에 write 또는 read 를 하다 실패하는 경우입니다.
그런데, 현재 client 는 fgets() 에서 block 해 있기 때문에..
아무거라도 입력해서 다음 루틴으로 넘어가지 않는 한, socket 에러를 감지할 수 없습니다.

위와 같은 로직에서는 자동으로 종료하게 하는 것은 불가하고..
stdin 과 socket fd 를 select() 로 묶어서 감시하면, 어케 될 수 있을 것 같네요.

되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』

전병욱@Facebook의 이미지

그럼 저기서 어떻게 추가하면 될까요?
그리고 특정 port는 접속하지 못하게 어떻게 하면 되는지 궁금합니다.

ymir의 이미지

select 를 보고 느낌이 없으시다면, 어디서부터 설명해야 할지 막막해 집니다.
적어도 man select 해보셨다면, stdin 에서 입력받는 예제가 있었을 테고..
구글에 select stdin socket 정도만 넣어봤어도, 참고할 만한 코드들이 나오니까요.

대충 아래 글 보시고, 어떻게 쓰는건지 한 번 로직을 살펴 보시고..
아래 글의 '문제를 수정해서' client 코드에 반영해 보세요.

https://stackoverflow.com/questions/39402886/select-on-stdin-and-incoming-socket

되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』

전병욱@Facebook의 이미지

감사합니다.

그리고 서버 포트에서 1024~49151 port가 주로 많이 사용하는 데 65535 이상은 서버 포트로 사용하지 않게 하려면 예외 처리로 서버에서 bind() 전에 하면 되지 않을까요?

전병욱@Facebook의 이미지

아마 select나 epoll을 사용해도 무관하지 않나요?
감사합니다.

댓글 달기

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
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.