'소켓프로그래밍'에 해당되는 글 1건

  1. [소켓프로그래밍] Hangman Game 2

주제 : 다중접속 서버 기반 네트워크 프로그램

기간 : 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은 리눅스 환경에서 사용가능하다.)

강의시간에 배운 내용을 기반으로 게임에 대한 알고리즘만 짜면 되는 것이라서 제작하는데 크게 어려움은 없었던 것 같습니다.

그래도 만드는 과정에서 오류와 디버깅을 반복하며 제작하였습니다.



012345678910111213141516


(개인작품 보고서 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, 0sizeof(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, 0sizeof(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, 0sizeof(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, 0sizeof(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&copy_reads, 00&timeout); //0은 리턴받지않겟단것
 
        if(fd_num==-1break;    //오류시종료
        if(fd_num==0continue;    //소켓이 없을경우
 
        for(i=0; i<fd_max+1; i++)
        {
            if(FD_ISSET(i, &copy_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+이라는 성적을 수확할 수 있었습니다.ㅎㅎ