socket 배열
글쓴이: 익명 사용자 / 작성시간: 월, 2019/10/07 - 10:43오전
server에서 epoll 기반 다중 접속으로 되어 있는 데 시그널 처리하는 함수에서 y 입력하면 소켓 종료되게 하는 부분에서 여러 클라이언트라 배열로 변경하려고 합니다.
server에서 client 소켓 배열로 만들고 시그널 함수에서 수정할 부분이 있는지 알려주시면 감사하겠습니다.
server
#include "libDefine.h"
void signalHandlerServer(int nSignal);
int setNonblockSocket(int nFileDescriptor);
int g_nServerSocket = -1; /* server socket file descriptor */
int g_nClientSocket[MAXCLIENT] = {0, };
int nClientCount = 0;
//int g_nClientSocket = -1; /* client socket file descriptor */
int main(int argc, char *argv[])
{
int nPort = -1;
char szMsg[SIZEBUFFER + 1] = {0,}; /* 수신용 버퍼 */
int nReadLength = 0; /* 받은 메시지 길이 저장 변수 */
struct sockaddr_in stClientAddr = {0,}; /* 클라이언트 주소 저장할 구조체 */
unsigned int uClientAddrSize = sizeof(stClientAddr); /* 클라이언트 주소 구조체의 크기 */
int nWriteLength = 0;
struct epoll_event stEvent = {0, };
struct epoll_event *pstEpollEvent[MAXCLIENT] = &stEvent;
int nEpollFileDescriptor = -1;
int nEventCnt = 0;
int nIndex = 0;
/* signal 처리 */
signal(SIGINT, signalHandlerServer);
if (argc != 2)
{
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}
/* port 예외 처리 port 범위 */
if(atoi(argv[1]) < 1024 || atoi(argv[1]) > 49151)
{
errHandle(errno, "port 사용하지 않는 포트입니다.\n");
exit(1);
}
else
{
nPort = atoi(argv[1]);
}
printf("서버 연결 중입니다.\n");
g_nServerSocket = serverSocket(nPort);
/* server socket 예외 처리 */
if(g_nServerSocket < 0)
{
errHandle(errno, "socket() error \n");
exit(1);
}
nEpollFileDescriptor = epoll_create(EPOLLSIZE);
if(nEpollFileDescriptor < 0)
{
errHandle(errno, "epoll file error \n");
close(g_nServerSocket);
g_nServerSocket = -1;
exit(1);
}
/* calloc() 메모리 할당 및 초기화 */
if((pstEpollEvent = calloc(EPOLLSIZE, sizeof(struct epoll_event))) == NULL)
{
errHandle(errno, "할당되지 않았습니다. \n");
close(nEpollFileDescriptor);
nEpollFileDescriptor = -1;
close(g_nServerSocket);
g_nServerSocket = -1;
exit(1);
}
setNonblockSocket(g_nServerSocket); /* server socket 에서 비동기 IO 설정 */
stEvent.events = EPOLLIN | EPOLLRDHUP | EPOLLET; /* event 발생 시 알림 */
stEvent.data.fd = g_nServerSocket; /* server socket 추가 */
/* server socket 상태 변화를 nEpollFileDescriptor 통해 알 수 있다. */
/* 예외 처리 추가 */
if(epoll_ctl(nEpollFileDescriptor, EPOLL_CTL_ADD, g_nServerSocket, &stEvent) < 0)
{
errHandle(errno, "server socket 상태 변화를 nEpollFileDescriptor 통해 관찰할 수 없습니다.");
epoll_ctl(nEpollFileDescriptor, EPOLL_CTL_DEL, g_nServerSocket, &stEvent);
close(nEpollFileDescriptor);
nEpollFileDescriptor = -1;
close(g_nServerSocket);
g_nServerSocket = -1;
exit(1);
}
while(1)
{
/* nEpollFileDescriptor 의 사건 발생 시 pstEpollEvent 에 fd 채운다. */
nEventCnt = epoll_wait(nEpollFileDescriptor, pstEpollEvent, EPOLLSIZE, -1);
/* neventCnt 예외 처리 */
if(nEventCnt < 0)
{
errHandle(errno, "epoll wait error");
break;
}
else if(nEventCnt == 0)
{
break;
}
/* event 발생 횟수 출력 */
printf("return epoll 발생 \n");
for(nIndex = 0; nIndex < nEventCnt; nIndex++)
{
if(pstEpollEvent[nIndex].data.fd == g_nServerSocket)
{
g_nClientSocket = accept(g_nServerSocket, (struct sockaddr*) &stClientAddr, &uClientAddrSize);
g_nClientSocket[nClientCount++] = g_nClientSocket;
/* client socket 예외 처리 */
if(g_nClientSocket < 0)
{
errHandle(errno, "accept error");
continue;
}
else
{
printf("클라이언트 접속 IP : %s\n", inet_ntoa(stClientAddr.sin_addr));
printf("accept() client 접속 요청 수락");
}
setNonblockSocket(g_nClientSocket); /* client 소켓 넌블로킹으로 */
stEvent.events = EPOLLIN | EPOLLRDHUP | EPOLLET; /* event */
//stEvent.events = EPOLLIN | EPOLLET;
stEvent.data.fd = g_nClientSocket; /* client socket */
/* epoll_ctl() 로 pstEpollEvent 상태 변화를 nEpollFileDescriptor로 확인 */
if(epoll_ctl(nEpollFileDescriptor, EPOLL_CTL_ADD, g_nClientSocket, &stEvent) < 0)
{
errHandle(errno, "client socket 상태 변화가 되지 않았습니다.");
epoll_ctl(nEpollFileDescriptor, EPOLL_CTL_DEL, g_nClientSocket, &stEvent);
close(nEpollFileDescriptor);
nEpollFileDescriptor = -1;
close(g_nClientSocket);
g_nClientSocket = -1;
break;
}
printf("연결된 클라이언트 : %d\n", g_nClientSocket);
}
else
{
nReadLength = read(pstEpollEvent[nIndex].data.fd, szMsg, SIZEBUFFER);
printf("받은 메시지 %s\n", szMsg);
if(nReadLength == 0)
{
//epoll_ctl(nEpollFileDescriptor, EPOLL_CTL_DEL, pstEpollEvent[nIndex].data.fd, &stEvent);
epoll_ctl(nEpollFileDescriptor, EPOLL_CTL_DEL, pstEpollEvent[nIndex].data.fd, &stEvent);
//close(nEpollFileDescriptor);
//nEpollFileDescriptor = -1;
close(pstEpollEvent[nIndex].data.fd);
printf("연결 종료된 클라이언트 %d\n", pstEpollEvent[nIndex].data.fd);
break;
}
else if(nReadLength < 0)
{
errHandle(errno, "read error \n");
/* 에러 예외 처리 추가 error */
if(errno == EAGAIN || errno == EINVAL || errno == EBADF || errno == EEXIST || errno == ELOOP ||
errno == ENOENT || errno == ENOMEM || errno == ENOSPC || errno == EPERM) /* error 예외 처리 */
{
errHandle(errno, "error \n");
epoll_ctl(nEpollFileDescriptor, EPOLL_CTL_DEL, pstEpollEvent[nIndex].data.fd, &stEvent);
//epoll_ctl(nEpollFileDescriptor, EPOLL_CTL_DEL, pstEpollEvent[nIndex].data.fd, &stEvent);
close(nEpollFileDescriptor);
nEpollFileDescriptor = -1;
close(pstEpollEvent[nIndex].data.fd);
break;
}
}
else
{
for(nIndex=0; nIndex < nClientCount; nIndex++)//클라이언트 개수만큼
{
nWriteLength = write(pstEpollEvent[nIndex].data.fd, szMsg, nReadLength); //클라이언트들에게 메시지를 전달한다.
}
if(nWriteLength < 0)
{
errHandle(errno, "write error \n");
epoll_ctl(nEpollFileDescriptor, EPOLL_CTL_DEL, pstEpollEvent[nIndex].data.fd, &stEvent);
close(nEpollFileDescriptor);
nEpollFileDescriptor = -1;
close(pstEpollEvent[nIndex].data.fd);
break;
}
memset(szMsg, 0, sizeof(szMsg));
}
}
for(nIndex = 0; nIndex < nClientCount; nIndex++) //배열의 갯수만큼
{
if(g_nClientSocket == g_nClientSocket[nIndex]) //만약 현재 clientSock값이 배열의 값과 같다면
{
while(nIndex++ < nClientCount - 1) //클라이언트 개수 만큼
{
g_nClientSocket[nIndex] = g_nClientSocket[nIndex + 1]; //앞으로 땡긴다.
}
break;
}
}
}
}
free(pstEpollEvent);
pstEpollEvent = NULL;
close(nEpollFileDescriptor);
nEpollFileDescriptor = -1;
nClientCount--; //클라이언트 개수 하나 감소
close(g_nClientSocket);
g_nClientSocket = -1;
close(g_nServerSocket);
g_nServerSocket = -1;
printf("연결이 끊어졌습니다. \n");
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);
close(g_nClientSocket[MAXCLIENT]);
g_nClientSocket = -1;
printf("client socket 종료되었습니다.\n");
}
if(g_nServerSocket >= 0)
{
close(g_nServerSocket);
g_nServerSocket = -1;
printf("server socket 종료되었습니다.\n");
}
exit(0);
}
}
}
int setNonblockSocket(int nFileDescriptor)
{
int nFlag = fcntl(nFileDescriptor, F_GETFL, 0);
/* nFlag 예외 처리 */
if(nFlag < 0)
{
errHandle(errno, "flag error");
exit(1);
}
fcntl(nFileDescriptor, F_SETFL, nFlag | O_NONBLOCK);
return 0;
}client
#include "libDefine.h"
void signalHandlerClient(int nSignal);
int g_nClientSocket = -1;
int g_nClientSocket[EPOLLSIZE] = {0, };
int main(int argc, char *argv[])
{
int nPort = -1;
char *pszAddr = NULL; /* ip 문자 입력받을 포인터 변수 */
char szMsg[SIZEBUFFER + 1] = {0,}; /* 서버에 보낼 메시지를 저장할 문자열 버퍼 */
int nReadLength = 0; /* 송수신 메시지의 문자열 길이 */
int nWriteLength = 0;
signal(SIGINT, signalHandlerClient);
/* port */
if (argc != 3)
{
printf("usage : %s <IP> <port> \n", argv[0]);
exit(1);
}
pszAddr = argv[1];
/* port 사용 범위 예외 처리 */
if(atoi(argv[2]) < 1024 || atoi(argv[2]) > 49151)
{
errHandle(errno, "port incorrect \n");
exit(1);
}
else
{
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는 종료, 버퍼(1024)까지 입력할 수 있습니다.) : ", stdout);
fgets(szMsg, sizeof(szMsg), stdin);
//szMsg[strlen(szMsg) - 1] = '\0';
/*
if(fgets(szMsg, sizeof(szMsg), stdin) == NULL)
{
fputs("입력 메시지 ", stdout);
fgets(szMsg, sizeof(szMsg), stdin);
fputs("입력 ", stdout);
}
*/
if(!strcasecmp(szMsg, "q\n"))
{
close(g_nClientSocket);
g_nClientSocket = -1;
break;
}
if((nWriteLength = write(g_nClientSocket, szMsg, strlen(szMsg))) < 0)
{
errHandle(errno, "write error \n");
close(g_nClientSocket);
g_nClientSocket = -1;
exit(1);
}
memset(szMsg, 0, sizeof(szMsg));
if((nReadLength = read(g_nClientSocket, szMsg, nWriteLength)) < 0)
{
errHandle(errno, "read error \n");
close(g_nClientSocket);
g_nClientSocket = -1;
exit(1);
}
szMsg[nReadLength] = 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,}; /* 서버 주소 저장할 구조체 */
int nOptval = 0;
if(nPort < 0)
{
errHandle(errno, "port error");
return -1;
}
/* TCP 통신용 서버 소켓 생성 */
nServerSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (nServerSocket < 0)
{
errHandle(errno, "socket() error");
return -1;
}
else
{
printf("socket() TCP 통신용 서버 소켓이 생성되었습니다.\n");
}
if(setsockopt(nServerSocket, SOL_SOCKET, SO_REUSEADDR, (const void *)&nOptval, sizeof(int)) < 0)
{
errHandle(errno, "socket option error");
close(nServerSocket);
nServerSocket = -1;
return -1;
}
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;
return -1;
}
else
{
printf("bind() - socket에 주소와 IP 할당되었습니다.\n");
}
/* 소켓을 서버용으로 사용할 수가 있게 한다. */
if (listen(nServerSocket, LISTENQ) < 0)
{
errHandle(errno, "listen() error %d\n", nPort);
close(nServerSocket);
nServerSocket = -1;
return -1;
}
else
{
printf("listen() 연결 요청 대기하는 중입니다.\n");
}
return nServerSocket;
}
int clientSocket(int nPort, char *pszAddr)
{
int nClientSocket = -1;
struct sockaddr_in stServerAddr = {0,}; /* 접속할 서버의 주소 저장할 구조체 */
struct epoll_event stEvent[EPOLLSIZE] = {0,};
int nEpollFileDescriptor = -1;
if(nPort < 0)
{
errHandle(errno, "port error");
return -1;
}
if(pszAddr == NULL)
{
errHandle(errno, "address error");
return -1;
}
nClientSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if(nClientSocket < 0)
{
errHandle(errno, "socket() error");
return -1;
}
nEpollFileDescriptor = epoll_create(EPOLLSIZE);
/* 서버 쪽 주소 구조체 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;
return -1;
}
else
{
printf("연결되었습니다.\n");
}
if(nClientSocket < 0)
{
errHandle(errno, "connect error \n");
close(nClientSocket);
nClientSocket = -1;
}
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));
}
va_start(argptr, pszMsg);
vsnprintf(szBuf, MESSAGE, pszMsg, argptr); /* 버퍼에 문자열 쓰고 버퍼 길이 지정하며 va_list 얻는다. */
va_end(argptr);
puts(szBuf);
}libDefine.h
#ifndef LIBDEFINE_H_ #define LIBDEFINE_H_ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <arpa/inet.h> #include <errno.h> #include <signal.h> #include <stdarg.h> #include <sys/epoll.h> #include <sys/types.h> #include <sys/time.h> #include <fcntl.h> #include <netinet/in.h> #include <stdbool.h> #define SIZEBUFFER 1024 #define LISTENQ 10 #define MESSAGE 1000 #define EPOLLSIZE 100 #define MAXCLIENT 100 /* 서버 socket */ int serverSocket(int nPort); /* 클라이언트 */ int clientSocket(int nPort, char *pszAddr); /* 에러 예외 처리 */ void errHandle(int nErrorCode, char *pszMsg, ...); #endif
Forums:

...
지금 하시려고 하는 게 보기보다 꽤 까다로운 일입니다. Signal handler 안에서 쓸 수 있는 함수는 *대단히* 제한되어 있습니다.
http://man7.org/linux/man-pages/man7/signal-safety.7.html
예컨대 fputs, fget, printf, exit 같은 함수는 다 못씁니다.
그럼에도 불구하고 SIGINT를 잡아서 처리를 구현하고 싶다면, 제 생각에 가장 쉬운 방법은 프로그램이 시작할 때 pipe를 하나 엽니다. 그리고 그 pipe의 읽는 쪽 fd를 epoll의 감시 목록에 추가합니다. 시그널 핸들러에서는 다른 거 할 필요 없고 pipe에 한 바이트를 write하면 됩니다.
그리고 메인 함수에서 pipe에 뭔가 읽을 게 있는지를 검사하면 중간에 놓치는 일 없이 signal 발생 유무를 검사할 수 있습니다.
* 이 이야기가 잘 이해가 안되신다면... 정상입니다. -_- 시그널을 제대로 처리하는 건 대단히 어렵고, 대충 짜면 반드시 빈틈이 생깁니다. 웬만하면 Stevens의 Advanced Programming in the Unix Environment 관련 챕터를 정독하는 걸 권합니다.
감사합니다.
그러면 signal에서 처리하려면 pipe 시그널을 추가해야 되나요?
원래 클라이언트 소켓을 배열 형태로 만들어서 y 입력하면 소켓 종료되게 하는 것입니다.
여러 클라이언트가 있으면 A가 종료하면 A만 종료되게 하는 것입니다.
감사합니다.
그리고 client에서 버퍼보다 넘게 보내면 서버에서 나눠서 받는 데 이를 클라이언트에서 버퍼보다 넘으면 못 보내게 하거나 에러 처리로 하려고 합니다.
클라이언트는 fgets() 함수로 버퍼까지 받아서 버퍼보다 넘는 예외 처리를 추가를 어떻게 하면 될까요?
감사합니다.
그것이 2개의 client 접속 후 차례로 접속 종료하였을때 소켓이 정리되지 않는 버그인 데 그래서 클라이언트 배열로 만들고 처리하려고 합니다.
댓글 달기