주제 : 다중접속 서버 기반 네트워크 프로그램
기간 : 2014.11.14~2014.12.05
3학년 2학기에 수강한 과목인 네트워크 프로그래밍 과목에서 개인작품을 내는 과제로 진행한 프로젝트 입니다.
네트워크 프로그래밍 과목에서는 TCP/IP 소켓 프로그래밍을 배우게 되었는데, 한 학기동안 수강했던 내용을 바탕으로 작품을 만들어야 해서 Hangman Game을 구현하게 되었습니다.
-작품소개 : 서버가 게임을 중재 및 진행하고 클라이언트가 게임에 참가하는 방식이다. 한 게임에 최대 두 명이 참가 할 수 있으며, 게임 최초 실행 시 서버가 두 개의 클라이언트 중 ‘출제자’를 랜덤으로 선택한다. 출제자는 서버에게 출제할 단어를 입력하고, 그 단어를 바탕으로 게임을 시작한다. 행맨 게임의 룰은 기본적으로 알파벳을 하나씩 선택하여 단어를 맞추는 게임인데, 못 맞출시 행맨이라는 사람모형의 신체의 일부가 하나씩 그려진다. (정확히는 걸려있는 사람, Hangman이 만들어 진다) 그리고 행맨이 완성되어 떨어지게 되면(죽으면) ‘도전자’의 패배, 즉 출제자의 승리가 되는 것이다. 따라서 서버는 출제자가 제출한 단어를 바탕으로 도전자에게 알파벳 입력을 받고, 틀릴시 행맨의 상태를 보여주거나, 맞출 시 해당 알파벳의 위치 (예로 apple의 경우 p를 맞췄을 때. _pp__ )를 알려준다. 기회를 다 소진할 때까지 도전자가 맞추지 못하면 출제자의 승리, 다 맞추게 되면 도전자의 승리가 되며 게임을 진행하는 동안 도전자는 3번의 힌트를 얻을 수 있다. 도전자가 서버로부터 힌트를 요청하면 서버는 출제자에게 힌트를 입력받고, 그 힌트를 서버가 다시 도전자에게 전달해준다. 힌트 횟수가 초과되면 더 이상 힌트를 요청해도 서버에서 제어한다. 그리고 한 게임이 종료되면 게임을 계속할지에 대한 여부를 물은 뒤, 첫 번째의 포지션과 반대로 게임이 진행된다. (출제자와 도전자의 역할이 바뀜)
-주요기능 : 클라이언트 간의 온라인 게임 기능, 메시지 전달 기능. 서버의 게임 중재 및 제어 기능.
-설계명세(구조도)
(개인작품 기획안)
또한 이 과제는 각각 epoll방식과 select방식 모두로 구현을 해야했습니다.
(epoll과 select는 I/O Multiplexing-하나의 프로세스로 여러개의 소켓을 핸들링하는 방법- 기법의 종류로, select의 단점을 보완한 것이 epoll방식이다. 하지만 epoll은 리눅스 환경에서 사용가능하다.)
강의시간에 배운 내용을 기반으로 게임에 대한 알고리즘만 짜면 되는 것이라서 제작하는데 크게 어려움은 없었던 것 같습니다.
그래도 만드는 과정에서 오류와 디버깅을 반복하며 제작하였습니다.
(개인작품 보고서 np_개인작품보고서.pptx )
(실행화면)
-epoll 방식
hangman_client.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <pthread.h> #define BUF_SIZE 1024 void error_handling(char *message); void *send_msg(void* arg); void *recv_msg(void* arg); int main(int argc, char *argv[]) { int sock; char message[BUF_SIZE]; int str_len, recv_len, recv_cnt; struct sockaddr_in serv_addr; pthread_t send_thread, recv_thread; void *thread_return; if(argc!=3) { printf("Usage : %s <IP> <port>\n", argv[0]); exit(1); } //socket(int domain, int type, int protocol) sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if(sock==-1) error_handling("socket() error!"); //bind() //구조체 초기화 (gabage값 비우기) memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family=AF_INET; serv_addr.sin_addr.s_addr=inet_addr(argv[1]); serv_addr.sin_port=htons(atoi(argv[2])); if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1) error_handling("connect() error!"); else puts("Connected..........."); //pthread_create() send와 recv용 둘다만들어줌 pthread_create(&send_thread, NULL, send_msg, (void *)&sock); pthread_create(&recv_thread, NULL, recv_msg, (void *)&sock); //두 쓰레드가 종료될때까지 기다림 pthread_join(send_thread, &thread_return); //send가 먼저 종료됨 pthread_join(recv_thread, &thread_return); close(sock); return 0; } void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } void *send_msg(void* arg) { int sock = *((int*)arg); int str_len; char message[BUF_SIZE]; while(1) { fgets(message, BUF_SIZE, stdin); str_len=write(sock, message, strlen(message)); } } void *recv_msg(void* arg) { int sock = *((int*)arg); int recv_cnt; char message[BUF_SIZE]; while(1) { recv_cnt=read(sock, &message, BUF_SIZE-1); if(recv_cnt==-1) error_handling("read() error!"); message[recv_cnt]=0; if(!strcmp(message, "fail")) //게임인원이 다찼거나 게임진행시 종료 (server에서 fail메세지전송) error_handling("Connect Fail-game already start"); printf("%s", message); } } | cs |
hangman_server.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 | #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/time.h> #include <sys/epoll.h> #define BUF_SIZE 1024 #define HINT 3 void error_handling(char *message); char* drawHangman(int num); int main(int argc, char *argv[]) { int serv_sock, clnt_sock; char message[BUF_SIZE]; int str_len; struct sockaddr_in serv_addr; struct sockaddr_in clnt_addr; socklen_t clnt_addr_size; struct epoll_event event; struct epoll_event *events; int epfd; int event_cnt, i; int user_cnt=0; //user count int user[2]={0}; //user정보=[소켓] int game_flag=0; //게임진행여부 int challenger[2]={0}; //도전자 [0]=소켓 [1]=플래그 int examiner[2]={0}; //출제자 [0]=소켓 [1]=플래그 char word[BUF_SIZE]; //출제답안 char question[BUF_SIZE]; //맞추고있는단어 int hint_cnt, hang_cnt, j, iscorrect, iscontinue; if(argc!=2) { printf("Usage : %s <port>\n", argv[0]); exit(1); } //메모리확보 events = malloc(sizeof(struct epoll_event)*50); //socket(int domain, int type, int protocol) serv_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if(serv_sock==-1) error_handling("socket() error!"); //bind() //구조체 초기화 (gabage값 비우기) memset(&serv_addr, 0, sizeof(serv_addr)); //구조체 serv_addr 값채우기 serv_addr.sin_family=AF_INET; serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); serv_addr.sin_port=htons(atoi(argv[1])); if(bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1) error_handling("bind() error!"); if(listen(serv_sock, 5)==-1) error_handling("listen() error!"); //accept() clnt_addr_size=sizeof(clnt_addr); epfd=epoll_create(50); //이폴만듬(50개까지이벤트등록) event.events=EPOLLIN; //이벤트의종류 event.data.fd=serv_sock; epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event); //이벤트등록 while(1) { printf("%d\n", serv_sock); event_cnt=epoll_wait(epfd, events, 50, -1); //목록의 갯수 리턴 if(event_cnt==-1) //오류시 종료 error_handling("epoll_wait() error!\n"); //break; printf("%d\n", event_cnt); for(i=0; i<event_cnt; i++) { if(events[i].data.fd==serv_sock) //listening소켓인지확인 { //accept clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size); if(clnt_sock==-1) error_handling("accept() error!"); else printf("connected client: %d\n", clnt_sock); event.events=EPOLLIN; event.data.fd=clnt_sock; epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event); user_cnt++; if(user_cnt<=2){ user[user_cnt-1]=clnt_sock; strcpy(message, "행맨게임에 오신 것을 환영합니다. -Player"); if(user_cnt==1){ strcat(message, "1\n상대방이 접속하면 게임이 시작됩니다.\n"); } else if(user_cnt==2){ strcat(message, "2\n잠시 후 게임이 시작됩니다.\n"); send(user[0], "잠시 후 게임이 시작됩니다.\n", strlen("잠시 후 게임이 시작됩니다.\n"), 0); game_flag=1; } send(clnt_sock, message, strlen(message), 0); } else{ //2명정원이 다차면 send(clnt_sock, "fail", strlen("fail"), 0); } if(game_flag==1&&user_cnt==2){ //게임시작 hint_cnt=HINT; hang_cnt=0; strcpy(message, "이번 게임의 출제자는~?\n"); send(user[0], message, strlen(message), 0); //난수생성 srand(time(NULL)); examiner[0]=user[rand()%2]; if(examiner[0]==user[0]) challenger[0]=user[1]; else challenger[0]=user[0]; strcpy(message, "상대방입니다! (참고로 저는 사형집행인입니다 ^-^)\n"); send(challenger[0], message, strlen(message), 0); strcpy(message, "출제자가 문제를 낼동안 잠시만 기다려주세요!\n"); send(challenger[0], message, strlen(message), 0); strcpy(message, "당신입니다! (참고로 저는 사형집행인입니다 ^-^)\n"); send(examiner[0], message, strlen(message), 0); strcpy(message, "출제할 영어단어를 소문자 알파벳으로 입력하세요 : "); send(examiner[0], message, strlen(message), 0); examiner[1]=1; } } else { //client소켓이므로 receive str_len=recv(events[i].data.fd, message, BUF_SIZE, 0); if(str_len==0) { //client가 종료한경우 epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL); close(events[i].data.fd); printf("closed socket: %d\n", events[i].data.fd); user_cnt--; if(user_cnt==1 && game_flag==1){ //한명만남았을시 game_flag변경, 게임이종료되었음을알림 strcpy(message, "\n상대방이 게임을 종료하여 게임이 중지되었습니다.\n"); challenger[1]=0; examiner[1]=0; if(user[0]==events[i].data.fd){ strcat(message, "Player1이 되었습니다.\n"); strcat(message, "상대방이 접속하면 게임이 시작됩니다.\n"); user[0]=user[1]; send(user[1], message, strlen(message), 0); } else{ send(user[0], message, strlen(message), 0); } game_flag=0; } } else if(events[i].data.fd==challenger[0]&&game_flag==1) { message[str_len-1]=0; if(strcmp(message, "")) switch(challenger[1]){ case 1: if(!strcmp(message, "hint")){ //힌트요청시 if(hint_cnt==0){ //사용가능힌트가없으면 strcpy(message, "힌트를 모두 소진하였습니다.\n"); strcat(message, "소문자 알파벳을 입력하세요 : "); send(events[i].data.fd, message, strlen(message), 0); } else{ strcpy(message, "도전자가 힌트를 요청하였습니다.\n"); strcat(message, "힌트를 입력하세요 : "); send(examiner[0], message, strlen(message), 0); examiner[1]=3; challenger[1]=0; } break; } iscontinue=0; iscorrect=0; if(strlen(message)==1){//알파벳 답확인 for(j=0; j<strlen(word); j++){ if(word[j]==message[0]){ iscorrect=1; question[j]=word[j]; } else if(question[j]=='*') iscontinue=1; } strcat(message, "\n"); send(examiner[0], "도전자의 답 입력 : ", strlen("도전자의 답 입력 : "), 0); send(examiner[0], message, strlen(message), 0); if(iscorrect==1) //맞췄을때 strcpy(message, "맞췄습니다!\n"); else{ //틀렸을때 strcpy(message, "틀렸습니다!\n"); hang_cnt++; } //결과알림 strcat(message, "- 문제 : "); strcat(message, question); strcat(message, "\n"); strcat(message, drawHangman(hang_cnt)); send(examiner[0], message, strlen(message), 0); send(challenger[0], message, strlen(message), 0); if(hang_cnt==6){ //마지막기회일때 strcpy(message, "마지막 기회입니다.\n"); send(examiner[0], message, strlen(message), 0); send(challenger[0], message, strlen(message), 0); } if(iscontinue==0){ //다맞췄을때 strcpy(message, "단어완성!\n도전자의 승리!\n"); } else if(hang_cnt==7){ //기회를모두소진했을때 strcpy(message, "꾸엑 행맨이쥬거슴다..ㅠㅠ\n출제자의 승리!\n"); } else{ //그렇지않았을때->다시답을입력받음 strcpy(message, "도전자가 답을 입력중입니다.\n"); send(examiner[0], message, strlen(message), 0); strcpy(message, "* 'hint'입력시 힌트사용가능.\n소문자 알파벳을 입력하세요 : "); send(challenger[0], message, strlen(message), 0); break; } //게임의종료 (클라이언트가 나가지않으면 게임은 계속됨) strcat(message, "게임이 종료되었습니다.\n"); strcat(message, "다음 게임에서는 역할이 바뀝니다.\n이번 게임의 출제자는~?\n"); send(examiner[0], message, strlen(message), 0); send(challenger[0], message, strlen(message), 0); //게임재시작 hint_cnt=HINT; hang_cnt=0; challenger[0]=examiner[0]; examiner[0]=events[i].data.fd; strcpy(message, "상대방입니다! (참고로 저는 사형집행인입니다 ^-^)\n"); strcat(message, "출제자가 문제를 낼동안 잠시만 기다려주세요!\n"); send(challenger[0], message, strlen(message), 0); strcpy(message, "당신입니다! (참고로 저는 사형집행인입니다 ^-^)\n"); strcat(message, "출제할 영어단어를 소문자 알파벳으로 입력하세요 : "); send(examiner[0], message, strlen(message), 0); examiner[1]=1; challenger[1]=0; break; } default: break; } } else if(events[i].data.fd==examiner[0]&&game_flag==1) { message[str_len-1]=0; if(strcmp(message, "")) switch(examiner[1]){ case 1: strcpy(word, message); printf("%s\n", word); strcpy(message, "**출제단어 : "); strcat(message, word); strcat(message, "\n맞으면 'y' / 틀리면 'n' 입력 : "); send(events[i].data.fd, message, strlen(message), 0); examiner[1]=2; break; case 2: //출제단어확인작업 if(!strcmp(message, "y")){ examiner[1]=0; challenger[1]=1; for(j=0; j<strlen(word); j++) question[j]='*'; question[j]='\0'; strcpy(message, "- 문제 : "); strcat(message, question); strcat(message, "\n"); send(examiner[0], message, strlen(message), 0); send(challenger[0], message, strlen(message), 0); strcpy(message, drawHangman(0)); send(examiner[0], message, strlen(message), 0); send(challenger[0], message, strlen(message), 0); strcpy(message, "도전자가 답을 입력중입니다.\n"); send(examiner[0], message, strlen(message), 0); strcpy(message, "소문자 알파벳을 입력하세요 : "); send(challenger[0], message, strlen(message), 0); } else if(!strcmp(message, "n")){ strcpy(message, "출제할 영어단어를 소문자 알파벳으로 입력하세요 : "); send(examiner[0], message, strlen(message), 0); examiner[1]=1; } else{ strcpy(message, "다시입력하세요.\n맞으면 'y' / 틀리면 'n' 입력 : "); send(examiner[0], message, strlen(message), 0); } break; case 3: //힌트요청시 strcat(message, "\n"); send(challenger[0], message, strlen(message), 0); hint_cnt--; strcpy(message, "도전자가 답을 입력중입니다.\n"); send(examiner[0], message, strlen(message), 0); strcpy(message, "소문자 알파벳을 입력하세요 : "); send(challenger[0], message, strlen(message), 0); examiner[1]=0; challenger[1]=1; break; default: break; } } } } } close(serv_sock); return 0; } void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } char* drawHangman(int num){ char* hangman={ 0 }; switch (num) { case 0: hangman = "┌───┐\n│\n│\n│\n│\n└──────\n"; break; case 1: hangman = "┌───┐\n│ ○\n│\n│\n│\n└──────\n"; break; case 2: hangman = "┌───┐\n│ ○\n│ |\n│\n│\n└──────\n"; break; case 3: hangman = "┌───┐\n│ ○\n│ /|\n│\n│\n└──────\n"; break; case 4: hangman = "┌───┐\n│ ○\n│ /|\\n│ \n│\n└──────\n"; break; case 5: hangman = "┌───┐\n│ ○\n│ /|\\n│ /\n│\n└──────\n"; break; case 6: hangman = "┌───┐\n│ ○\n│ /|\\n│ /\\n│\n└──────\n"; break; case 7: hangman = "┌───┐\n│ ○\n│ X\n│ /|\\n│ /\\n└──────\n"; break; default: hangman = "drawing error\n"; break; } return hangman; } | cs |
-select 방식
hangman_client.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <pthread.h> #define BUF_SIZE 1024 void error_handling(char *message); void *send_msg(void* arg); void *recv_msg(void* arg); int main(int argc, char *argv[]) { int sock; char message[BUF_SIZE]; int str_len, recv_len, recv_cnt; struct sockaddr_in serv_addr; pthread_t send_thread, recv_thread; void *thread_return; if(argc!=3) { printf("Usage : %s <IP> <port>\n", argv[0]); exit(1); } //socket(int domain, int type, int protocol) sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if(sock==-1) error_handling("socket() error!"); //bind() //구조체 초기화 (gabage값 비우기) memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family=AF_INET; serv_addr.sin_addr.s_addr=inet_addr(argv[1]); serv_addr.sin_port=htons(atoi(argv[2])); if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1) error_handling("connect() error!"); else puts("Connected..........."); //pthread_create() send와 recv용 둘다만들어줌 pthread_create(&send_thread, NULL, send_msg, (void *)&sock); pthread_create(&recv_thread, NULL, recv_msg, (void *)&sock); //두 쓰레드가 종료될때까지 기다림 pthread_join(send_thread, &thread_return); //send가 먼저 종료됨 pthread_join(recv_thread, &thread_return); close(sock); return 0; } void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } void *send_msg(void* arg) { int sock = *((int*)arg); int str_len; char message[BUF_SIZE]; while(1) { fgets(message, BUF_SIZE, stdin); str_len=write(sock, message, strlen(message)); } } void *recv_msg(void* arg) { int sock = *((int*)arg); int recv_cnt; char message[BUF_SIZE]; while(1) { recv_cnt=read(sock, &message, BUF_SIZE-1); if(recv_cnt==-1) error_handling("read() error!"); message[recv_cnt]=0; if(!strcmp(message, "fail")) //게임인원이 다찼거나 게임진행시 종료 (server에서 fail메세지전송) error_handling("Connect Fail-game already start"); printf("%s", message); } } | cs |
hangman_server.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 | #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/time.h> #include <sys/select.h> #define BUF_SIZE 1024 #define HINT 3 void error_handling(char *message); char* drawHangman(int num); int main(int argc, char *argv[]) { int serv_sock, clnt_sock; char message[BUF_SIZE]; int str_len; struct sockaddr_in serv_addr; struct sockaddr_in clnt_addr; socklen_t clnt_addr_size; fd_set reads, copy_reads; struct timeval timeout; int fd_max, fd_num, i; int user_cnt=0; //user count int user[2]={0}; //user정보=[소켓] int game_flag=0; //게임진행여부 int challenger[2]={0}; //도전자 [0]=소켓 [1]=플래그 int examiner[2]={0}; //출제자 [0]=소켓 [1]=플래그 char word[BUF_SIZE]; //출제답안 char question[BUF_SIZE]; //맞추고있는단어 int hint_cnt, hang_cnt, j, iscorrect, iscontinue; if(argc!=2) { printf("Usage : %s <port>\n", argv[0]); exit(1); } //socket(int domain, int type, int protocol) serv_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if(serv_sock==-1) error_handling("socket() error!"); //bind() //구조체 초기화 (gabage값 비우기) memset(&serv_addr, 0, sizeof(serv_addr)); //구조체 serv_addr 값채우기 serv_addr.sin_family=AF_INET; serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); serv_addr.sin_port=htons(atoi(argv[1])); if(bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1) error_handling("bind() error!"); if(listen(serv_sock, 5)==-1) error_handling("listen() error!"); clnt_addr_size=sizeof(clnt_addr); FD_ZERO(&reads); FD_SET(serv_sock, &reads); fd_max=serv_sock; while(1) { copy_reads=reads; //원본훼손 방지, copy해서넘김 timeout.tv_sec=5; timeout.tv_usec=5000; //microsec fd_num=select(fd_max+1, ©_reads, 0, 0, &timeout); //0은 리턴받지않겟단것 if(fd_num==-1) break; //오류시종료 if(fd_num==0) continue; //소켓이 없을경우 for(i=0; i<fd_max+1; i++) { if(FD_ISSET(i, ©_reads)) //1인지아닌지확인-1이면처리 { if(i==serv_sock) //listening소켓인지확인 { //accept clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size); if(clnt_sock==-1) error_handling("accept() error!"); FD_SET(clnt_sock, &reads); if(clnt_sock > fd_max) fd_max=clnt_sock; printf("connected client: %d\n", clnt_sock); user_cnt++; if(user_cnt<=2){ user[user_cnt-1]=clnt_sock; strcpy(message, "행맨게임에 오신 것을 환영합니다. -Player"); if(user_cnt==1){ strcat(message, "1\n상대방이 접속하면 게임이 시작됩니다.\n"); } else if(FD_ISSET(user[0], &reads)&&user_cnt==2){ strcat(message, "2\n잠시 후 게임이 시작됩니다.\n"); send(user[0], "잠시 후 게임이 시작됩니다.\n", strlen("잠시 후 게임이 시작됩니다.\n"), 0); game_flag=1; } send(clnt_sock, message, strlen(message), 0); } else{ //2명정원이 다차면 send(clnt_sock, "fail", strlen("fail"), 0); } if(FD_ISSET(user[0], &reads)&&FD_ISSET(user[1], &reads)&&game_flag==1&&user_cnt==2){ //게임시작 hint_cnt=HINT; hang_cnt=0; strcpy(message, "이번 게임의 출제자는~?\n"); send(user[0], message, strlen(message), 0); //난수생성 srand(time(NULL)); examiner[0]=user[rand()%2]; if(examiner[0]==user[0]) challenger[0]=user[1]; else challenger[0]=user[0]; strcpy(message, "상대방입니다! (참고로 저는 사형집행인입니다 ^-^)\n"); send(challenger[0], message, strlen(message), 0); strcpy(message, "출제자가 문제를 낼동안 잠시만 기다려주세요!\n"); send(challenger[0], message, strlen(message), 0); strcpy(message, "당신입니다! (참고로 저는 사형집행인입니다 ^-^)\n"); send(examiner[0], message, strlen(message), 0); strcpy(message, "출제할 영어단어를 소문자 알파벳으로 입력하세요 : "); send(examiner[0], message, strlen(message), 0); examiner[1]=1; } } else { //client소켓이므로 receive str_len=recv(i, message, BUF_SIZE, 0); if(str_len==0) { //client가 종료한경우 FD_CLR(i, &reads); close(i); printf("closed socket: %d\n", i); user_cnt--; if(user_cnt==1 && game_flag==1){ //한명만남았을시 game_flag변경, 게임이종료되었음을알림 strcpy(message, "\n상대방이 게임을 종료하여 게임이 중지되었습니다.\n"); challenger[1]=0; examiner[1]=0; if(user[0]==i){ strcat(message, "Player1이 되었습니다.\n"); strcat(message, "상대방이 접속하면 게임이 시작됩니다.\n"); user[0]=user[1]; send(user[1], message, strlen(message), 0); } else if(FD_ISSET(user[0], &reads)){ send(user[0], message, strlen(message), 0); } game_flag=0; } } else if(i==challenger[0]&&FD_ISSET(examiner[0], &reads)&&game_flag==1) { message[str_len-1]=0; if(strcmp(message, "")) switch(challenger[1]){ case 1: if(!strcmp(message, "hint")){ //힌트요청시 if(hint_cnt==0){ //사용가능힌트가없으면 strcpy(message, "힌트를 모두 소진하였습니다.\n"); strcat(message, "소문자 알파벳을 입력하세요 : "); send(i, message, strlen(message), 0); } else{ strcpy(message, "도전자가 힌트를 요청하였습니다.\n"); strcat(message, "힌트를 입력하세요 : "); send(examiner[0], message, strlen(message), 0); examiner[1]=3; challenger[1]=0; } break; } iscontinue=0; iscorrect=0; if(strlen(message)==1){//알파벳 답확인 for(j=0; j<strlen(word); j++){ if(word[j]==message[0]){ iscorrect=1; question[j]=word[j]; } else if(question[j]=='*') iscontinue=1; } strcat(message, "\n"); send(examiner[0], "도전자의 답 입력 : ", strlen("도전자의 답 입력 : "), 0); send(examiner[0], message, strlen(message), 0); if(iscorrect==1) //맞췄을때 strcpy(message, "맞췄습니다!\n"); else{ //틀렸을때 strcpy(message, "틀렸습니다!\n"); hang_cnt++; } //결과알림 strcat(message, "- 문제 : "); strcat(message, question); strcat(message, "\n"); strcat(message, drawHangman(hang_cnt)); send(examiner[0], message, strlen(message), 0); send(challenger[0], message, strlen(message), 0); if(hang_cnt==6){ //마지막기회일때 strcpy(message, "마지막 기회입니다.\n"); send(examiner[0], message, strlen(message), 0); send(challenger[0], message, strlen(message), 0); } if(iscontinue==0){ //다맞췄을때 strcpy(message, "단어완성!\n도전자의 승리!\n"); } else if(hang_cnt==7){ //기회를모두소진했을때 strcpy(message, "꾸엑 행맨이쥬거슴다..ㅠㅠ\n출제자의 승리!\n"); } else{ //그렇지않았을때->다시답을입력받음 strcpy(message, "도전자가 답을 입력중입니다.\n"); send(examiner[0], message, strlen(message), 0); strcpy(message, "* 'hint'입력시 힌트사용가능.\n소문자 알파벳을 입력하세요 : "); send(challenger[0], message, strlen(message), 0); break; } //게임의종료 (클라이언트가 나가지않으면 게임은 계속됨) strcat(message, "게임이 종료되었습니다.\n"); strcat(message, "다음 게임에서는 역할이 바뀝니다.\n이번 게임의 출제자는~?\n"); send(examiner[0], message, strlen(message), 0); send(challenger[0], message, strlen(message), 0); //게임재시작 hint_cnt=HINT; hang_cnt=0; challenger[0]=examiner[0]; examiner[0]=i; strcpy(message, "상대방입니다! (참고로 저는 사형집행인입니다 ^-^)\n"); strcat(message, "출제자가 문제를 낼동안 잠시만 기다려주세요!\n"); send(challenger[0], message, strlen(message), 0); strcpy(message, "당신입니다! (참고로 저는 사형집행인입니다 ^-^)\n"); strcat(message, "출제할 영어단어를 소문자 알파벳으로 입력하세요 : "); send(examiner[0], message, strlen(message), 0); examiner[1]=1; challenger[1]=0; break; } default: break; } } else if(i==examiner[0]&&FD_ISSET(challenger[0], &reads)&&game_flag==1) { message[str_len-1]=0; if(strcmp(message, "")) switch(examiner[1]){ case 1: strcpy(word, message); printf("%s\n", word); strcpy(message, "**출제단어 : "); strcat(message, word); strcat(message, "\n맞으면 'y' / 틀리면 'n' 입력 : "); send(i, message, strlen(message), 0); examiner[1]=2; break; case 2: //출제단어확인작업 if(!strcmp(message, "y")){ examiner[1]=0; challenger[1]=1; for(j=0; j<strlen(word); j++) question[j]='*'; question[j]='\0'; strcpy(message, "- 문제 : "); strcat(message, question); strcat(message, "\n"); send(examiner[0], message, strlen(message), 0); send(challenger[0], message, strlen(message), 0); strcpy(message, drawHangman(0)); send(examiner[0], message, strlen(message), 0); send(challenger[0], message, strlen(message), 0); strcpy(message, "도전자가 답을 입력중입니다.\n"); send(examiner[0], message, strlen(message), 0); strcpy(message, "소문자 알파벳을 입력하세요 : "); send(challenger[0], message, strlen(message), 0); } else if(!strcmp(message, "n")){ strcpy(message, "출제할 영어단어를 소문자 알파벳으로 입력하세요 : "); send(examiner[0], message, strlen(message), 0); examiner[1]=1; } else{ strcpy(message, "다시입력하세요.\n맞으면 'y' / 틀리면 'n' 입력 : "); send(examiner[0], message, strlen(message), 0); } break; case 3: //힌트요청시 strcat(message, "\n"); send(challenger[0], message, strlen(message), 0); hint_cnt--; strcpy(message, "도전자가 답을 입력중입니다.\n"); send(examiner[0], message, strlen(message), 0); strcpy(message, "소문자 알파벳을 입력하세요 : "); send(challenger[0], message, strlen(message), 0); examiner[1]=0; challenger[1]=1; break; default: break; } } } } } } close(serv_sock); return 0; } void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } char* drawHangman(int num){ char* hangman={ 0 }; switch (num) { case 0: hangman = "┌───┐\n│\n│\n│\n│\n└──────\n"; break; case 1: hangman = "┌───┐\n│ ○\n│\n│\n│\n└──────\n"; break; case 2: hangman = "┌───┐\n│ ○\n│ |\n│\n│\n└──────\n"; break; case 3: hangman = "┌───┐\n│ ○\n│ /|\n│\n│\n└──────\n"; break; case 4: hangman = "┌───┐\n│ ○\n│ /|\\n│ \n│\n└──────\n"; break; case 5: hangman = "┌───┐\n│ ○\n│ /|\\n│ /\n│\n└──────\n"; break; case 6: hangman = "┌───┐\n│ ○\n│ /|\\n│ /\\n│\n└──────\n"; break; case 7: hangman = "┌───┐\n│ ○\n│ X\n│ /|\\n│ /\\n└──────\n"; break; default: hangman = "drawing error\n"; break; } return hangman; } | cs |
(실제 코드)
사실 서버라고하면 아파치 서버 같은 이미 만들어져 있는 것을 설치해서 쓰는 경우가 대부분이었습니다. 하지만 이 강의를 통해서 서버와 클라이언트의 기능을 직접 구현해보고, 그 원리를 이해할 수 있었습니다. 또한 이 프로그램을 만들어보는 과정에서 서버와 클라이언트의 수행 분배가 중요하고, 다중 접속 서버는 특히 클라이언트의 관리가 중요하다는 사실을 알게 되었습니다.
조금 낯설기도 한 개념들이라 처음에는 어려움을 느끼기도 하였지만, 이렇게 게임을 구현해보는 과정에서 재미도 있었고 오류없이 완성되어 돌아가는 것을 보니 뿌듯하기도 했던 프로젝트 입니다.
+) 물론, A+이라는 성적을 수확할 수 있었습니다.ㅎㅎ