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 접속 후 차례로 접속 종료하였을때 소켓이 정리되지 않는 버그인 데 그래서 클라이언트 배열로 만들고 처리하려고 합니다.
댓글 달기