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

기간 : 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+이라는 성적을 수확할 수 있었습니다.ㅎㅎ

프로젝트 진행 기간 : 2015.03.01 ~ 2015.06.08

개발 환경 :

  - Android 4.4 Kitkat 기준 (Min Version : Android 4.0.3 IceCreamSandWich)

  - 개발 도구 : 안드로이드 스튜디오 1.3.2 (Gradle), mySQL, Eclipse EE(PHP)

  - 사용 언어 : 자바(Android), PHP (백엔드)

  - 서버 정보 : Ubuntu Linux 환경 Apache Server


4학년 1학기에 들은 필수과목 종합설계 프로젝트라는 캡스톤 디자인 과목에서 진행한 프로젝트 입니다.


01234567891011121314


(기획발표 프레젠테이션)


경성대의 공대는 학교 가장 꼭대기에 위치하여 쉬이 무언가를 먹으려 내려가기 힘든 환경입니다. 

그래서 연구실 생활을 하는 동안 무언가를 시켜먹는 일이 잦았습니다.

항상 비슷한 것들만 먹게 되다보니 좀 더 맛있는 집을 찾게 되었고, 여러 유명한 배달업체를 찾아봤지만 대연동이라는 검색범주 안에서는 제외되는 업체도 많고, 숨겨진 맛집을 찾기에는 힘이 들었습니다.

또한, 배달을 올때마다 뿌리고 가는 전단지들 때문에 교내 환경이 전단지로 몸살을 앓기 시작했습니다.

이러한 점들을 개선하고자 경성대만의 배달업체 정보제공 안드로이드 어플리케이션, 경성대 배달학과를 만들자는 생각을 하게 되었습니다.



종합설계 프로젝트는 강의이니 만큼 교수님의 지도 하에 프로젝트가 진행되었는데, 소프트웨어 공학에 대해 공부하면서

DFD(Data Flow Diagram), HIFO(Hierachy Plus Input Process Output) Diagram을 작성하며 프로젝트의 상세 설계를 해나갔습니다.

이러한 Diagram은 처음 그려보는 것이라 그리면서도 이게 맞나..하며 그렸지만, 교수님의 질타를 예상했던 것보단 좋은 반응을 받을 수 있었습니다.


종설 DFD.hwp종설 HIPO.hwp

(작성한 Diagram)



이때 프로젝트를 진행했던 인원은 본인 포함 2명이었는데, 다른 팀원 한명이 DB를 포함한 백엔드 서비스를 담당하고 저는 안드로이드 프로그래밍을 맡았습니다. 당시 사용한 서버는 연구실에서 사용하는 서버로, 이전의 프로젝트들로 서버 환경은 모두 구축되어 있었기에 DB와 PHP만 만들면 되는 상황이었습니다.

안드로이드에서 제가 구현을 맡은 것은 UI구성과 UI에 들어갈 이미지들 작성, SNS연동 기능, 서버와 http 통신을 통해 데이터를 교환하는 기능, 그 데이터들을 바탕으로 안드로이드 상에 출력해 주는 일 등 이었습니다.

따라서 여태 프로젝트를 진행할 때 그래왔듯이, 먼저 화면 구성을 프로토타입을 통해 짜게 되었습니다.


https://ovenapp.io/view/wFN7m1FXelKmy4mhgAtno1WGmAxH4m0y/T3RVd

(프로토타입 툴 oven을 사용해 만든 경성대 배달학과 프로토타입)



그리고 두번째로는 앱 내부에 들어갈 이미지들을 제작하였습니다. 

버튼 이미지 또한 나인패치로 제작하였습니다.



-App Icon



-Main Logo



-Categories



-Buttons(Nine Patch)


    



(제작 및 편집한 이미지들)



마지막으로 안드로이드 내부에서는 SNS연동과 백엔드와의 통신 등의 어플리케이션을 기능을 구현하였습니다.


SNS 연동 같은 경우 페이스북과 카카오 연동 두가지 방식을 사용했는데, 결과 발표 당시에는 페이스북만 구현했지만 완성 이후에 카카오톡 연동 또한 구현하였습니다.


구현 시에는 페이스북 로그인 SDK와 카카오 로그인 SDK를 사용하여 사용자의 고유 카카오/페이스북 id와 이름, 프로필 사진 url 등을 받아 최초 로그인 시에 서버 DB에 저장하고, 어플리케이션 내에서는 세션을 유지시켜서 로그인을 유지할 수 있게 하였습니다.


세션을 유지하는 기능은 이전 프로젝트에서 구현했던 기능을 활용하여서 쉽게 구현할 수 있었습니다.




-LoginActivity.java

 :  Splash Activity 겸 세션 체크 후 로그인 상태가 아닐 때 로그인 기능이 나타나는 액티비티. 

페이스북/카카오 연동 로그인 기능 및 InsertUser 객체를 통해 서버의 DB유저정보를 삽입하거나 수정한다. 

연동 과정에서 액티비티가 사라지는 현상이 있어서 BaseActivity를 밑에 깔아 주었다.


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
package com.miclab.ksbaedal.login;
 
import android.content.Context;
import android.content.Intent;
 
import android.support.v4.app.FragmentActivity;
import android.os.Bundle;
import android.os.Handler;
 
import android.util.Log;
 
import android.view.KeyEvent;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.widget.Button;
import android.widget.Toast;
 
import com.facebook.*;
import com.facebook.login.LoginManager;
import com.facebook.login.LoginResult;
 
import com.kakao.auth.ApiResponseCallback;
import com.kakao.auth.AuthType;
import com.kakao.auth.ErrorResult;
import com.kakao.auth.ISessionCallback;
import com.kakao.auth.KakaoSDK;
import com.kakao.auth.Session;
import com.kakao.usermgmt.UserManagement;
import com.kakao.usermgmt.callback.MeResponseCallback;
 
import com.kakao.usermgmt.response.model.UserProfile;
import com.kakao.util.exception.KakaoException;
import com.kakao.util.helper.log.Logger;
import com.miclab.ksbaedal.R;
import com.miclab.ksbaedal.kakao.KakaoSDKAdapter;
import com.miclab.ksbaedal.main.MainActivity;
 
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
 
 
public class LoginActivity extends FragmentActivity {
    CallbackManager callbackManager;
    ProfileTracker profileTracker;
    AccessTokenTracker accessTokenTracker;
 
    SessionCallback callback;
 
    Context con, basecon;
    Intent i, intent;
    Boolean isFirst;
    SessionManager session;
 
    Button fbloginButton, kakaologinButton;
    int delaySec;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        FacebookSdk.sdkInitialize(getApplicationContext());
        try{
            KakaoSDK.init(new KakaoSDKAdapter(this));
        }
        catch (KakaoSDK.AlreadyInitializedException ex){;}
        setContentView(com.miclab.ksbaedal.R.layout.activity_login);
        final Handler handler = new Handler();
        con=this;
 
        i = new Intent(LoginActivity.this, MainActivity.class);
        intent = getIntent();
 
        delaySec = 1500;
        Boolean isDelay = intent.getBooleanExtra("startAnimation"true);
        if(!isDelay) {
            delaySec=0;
        }
        isFirst = intent.getBooleanExtra("ExistBase",false);
 
        session = new SessionManager(this.getApplicationContext());
 
        //facebook sdk 설정
        callbackManager = CallbackManager.Factory.create();
        accessTokenTracker = new AccessTokenTracker() {
            @Override
            protected void onCurrentAccessTokenChanged(
                    AccessToken oldAccessToken,
                    AccessToken currentAccessToken) {
                // On AccessToken changes fetch the new profile which fires the event on
                // the ProfileTracker if the profile is different
                //Profile.fetchProfileForCurrentAccessToken();
            }
        };
        profileTracker = new ProfileTracker() {
            @Override
            protected void onCurrentProfileChanged(Profile oldProfile, Profile newProfile) {
                setProfile(newProfile.getId(), newProfile.getName(), "f""");
            }
        };
 
        accessTokenTracker.startTracking();
        profileTracker.startTracking();
 
        //kakao sdk 설정
        callback = new SessionCallback();
        Session.getCurrentSession().addCallback(callback);
        if(session.isLogin()) Session.getCurrentSession().checkAndImplicitOpen();
 
        fbloginButton = (Button) findViewById(R.id.fb_Login_button);
        kakaologinButton = (Button) findViewById(R.id.kakao_Login_button);
 
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        //세션체크
                        if (session.isLogin()) {
                            System.out.println("login");
                            startActivity(i);
                            overridePendingTransition(R.animator.in, R.animator.out);
                            finish();
                            if(isFirst) BaseActivity.base.finish();
                        } else {
                            Animation ani = new AlphaAnimation(01);
                            ani.setDuration(400);
                            fbloginButton.setAnimation(ani);
                            kakaologinButton.setAnimation(ani);
                            fbloginButton.setVisibility(View.VISIBLE);
                            kakaologinButton.setVisibility(View.VISIBLE);
                        }
                    }
                }, delaySec);
            }
        });
 
        //페이스북 로그인
        //fbloginButton.registerCallback(callbackManager, new FacebookCallback<LoginResult>() {
        LoginManager.getInstance().registerCallback(callbackManager, new FacebookCallback<LoginResult>() {
            @Override
            public void onSuccess(LoginResult loginResult) {
                AccessToken accessToken = loginResult.getAccessToken();
                Profile profile = Profile.getCurrentProfile();
                //Profile.fetchProfileForCurrentAccessToken();
                //setProfile(Profile.getCurrentProfile());
                setProfile(profile.getId(), profile.getName(), "f""");
                startActivity(i);
                overridePendingTransition(R.animator.in, R.animator.out);
                if(isFirst) BaseActivity.base.finish();
                finish();
                // }
            }
 
            @Override
            public void onCancel() {
                Log.d("FacebookLogin""Canceled");
            }
 
            @Override
            public void onError(FacebookException error) {
                Log.d("FacebookLogin"String.format("Error: %s", error.toString()));
                String title = "Facebook 로그인 에러";
                String alertMessage = error.getMessage();
                Toast.makeText(con, title+" : "+alertMessage, Toast.LENGTH_SHORT).show();
            }
        });
 
        fbloginButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                fbButtonOnClick();
            }
        });
 
        //카톡 로그인 부분
        kakaologinButton.setOnClickListener(new Button.OnClickListener() {
            public void onClick(View view) {
                kakaoButtonOnClick();
            }
        });
    }
 
    void fbButtonOnClick(){
        LoginManager.getInstance().logInWithReadPermissions(this, Arrays.asList("public_profile"));
    }
 
    void kakaoButtonOnClick(){
        Session.getCurrentSession().open(AuthType.KAKAO_TALK_EXCLUDE_NATIVE_LOGIN, this);
    }
 
    void kakaoLogin(){
        UserManagement.requestMe(new MeResponseCallback() {
            @Override
            public void onFailure(ErrorResult errorResult) {
                String message = "failed to get user info. msg=" + errorResult;
                Logger.d(message);
            }
 
            @Override
            public void onSessionClosed(ErrorResult errorResult) {
                String message = "failed to get user info. msg=" + errorResult;
                Logger.d(message);
            }
 
            @Override
            public void onSuccess(UserProfile userProfile) {
                if (userProfile != null) {
                    userProfile.saveUserToCache();
                    Logger.d("UserProfile : " + userProfile);
                    setProfile(String.valueOf(userProfile.getId()), userProfile.getNickname(), "k", userProfile.getThumbnailImagePath());
                    startActivity(i);
                    overridePendingTransition(R.animator.in, R.animator.out);
                    if (isFirst) BaseActivity.base.finish();
                    finish();
                }
            }
 
            @Override
            public void onNotSignedUp() {
                session.logout();
            }
        });
    }
 
    void kakaoUpdate(){
        final Map<StringString> properties = new HashMap<StringString>();
        properties.put("nickname", session.getValue("userName"));
        properties.put("profile_image", session.getValue("profilePicture"));
 
        UserManagement.requestUpdateProfile(new ApiResponseCallback<Long>() {
            @Override
            public void onSuccess(Long userId) {
                UserProfile profile = UserProfile.loadFromCache();
                profile.updateUserProfile(properties).saveUserToCache();
                Logger.d("succeeded to update user profile" + profile);
                setProfile(String.valueOf(session.getLoginID()), profile.getNickname(), "k", profile.getThumbnailImagePath());
            }
 
            @Override
            public void onNotSignedUp() {
                session.logout();
            }
 
            @Override
            public void onFailure(ErrorResult errorResult) {
                String message = "failed to get user info. msg=" + errorResult;
                Logger.e(message);
            }
 
            @Override
            public void onSessionClosed(ErrorResult errorResult) {
                String message = "failed to get user info. msg=" + errorResult;
                Logger.e(message);
            }
        }, properties);
    }
 
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        Handler handler = new Handler();
        if(event.getAction()==KeyEvent.ACTION_DOWN){
            if(keyCode==KeyEvent.KEYCODE_BACK){
                if (isFirst) BaseActivity.base.finish();
                return super.onKeyDown(keyCode, event);
            }
        }
        return true;
    }
 
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
 
        if (Session.getCurrentSession().handleActivityResult(requestCode, resultCode, data)) {
            return;
        }
        callbackManager.onActivityResult(requestCode, resultCode, data);
        super.onActivityResult(requestCode, resultCode, data);
    }
 
    @Override
    public void onDestroy() {
        Session.getCurrentSession().removeCallback(callback);
        profileTracker.stopTracking();
        accessTokenTracker.stopTracking();
        super.onDestroy();
    }
 
 
 
    private void setProfile(String id, String name, String type, String picture) {
        Log.i("login info""id : " + id + " name : " + name + " type : " + type);
        if(type=="f") {
            session.login(id, type, name, "https://graph.facebook.com/" + id + "/picture?type=large");
        }
        else if(type=="k"){
            session.login(id, type, name, picture);
        }
 
        InsertUser iu = new InsertUser(LoginActivity.this, session.getLoginID(), session.getLoginType(), session.getValue("userName"), session.getValue("profilePicture"), session);
        try {
            iu.execute();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    private class SessionCallback implements ISessionCallback {
        @Override
        public void onSessionOpened() {
            if(session.isLogin()){if(session.getLoginType()=="k")
                LoginActivity.this.kakaoUpdate();
            }
            else {
                LoginActivity.this.kakaoLogin();
            }
        }
 
        @Override
        public void onSessionOpenFailed(KakaoException exception) {
            if(exception != null) {
                Logger.e(exception);
            }
        }
    }
}
 
cs



-SessionManager.java

 : 어플리케이션 내의 앱 데이터, 세션을 설정하는 부분. 

SharedPreferences를 사용해서 현재 로그인 된 정보를 저장하고 호출시 정보를 로드한다.

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
package com.miclab.ksbaedal.login;
 
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
 
public class SessionManager {
    SharedPreferences pref;
    Editor edit;
 
    public static final String IS_LOGIN = "isLogin";
    public static final String USER_ID = "userID";
    public static final String USER_TYPE = "userType";
    public static final String USER_NAME = "userName";
    public static final String USER_NICKNAME = "nickName";
    public static final String PROFILE_PICTURE = "profilePicture";
 
    public SessionManager(Context con){
        pref=con.getSharedPreferences("LoginSession", Activity.MODE_PRIVATE);
        edit=pref.edit();
    }
 
    public void login(String id, String type, String name, String picture){
        edit.putBoolean(IS_LOGIN, true);
        edit.putString(USER_ID, id);
        edit.putString(USER_TYPE, type);
        edit.putString(USER_NAME, name);
        edit.putString(PROFILE_PICTURE, picture);
        edit.commit();
    }
 
    public void logout(){
        if(isLogin()){
            edit.putBoolean(IS_LOGIN, false);
            edit.putString(USER_ID, null);
            edit.putString(USER_TYPE, null);
            edit.putString(USER_NAME, null);
            edit.putString(USER_NICKNAME, null);
            edit.putString(PROFILE_PICTURE, null);
            edit.commit();
        }
    }
 
    public String getLoginID(){
        if(isLogin()){
            return pref.getString(USER_ID, null);
        }
        return null;
    }
 
    public String getLoginType(){
        if(isLogin()){
            return pref.getString(USER_TYPE, null);
        }
        return null;
    }
 
    public String getValue(String key){
        if(isLogin()){
            return pref.getString(key, "");
        }
        return null;
    }
 
    public boolean isLogin(){
        return pref.getBoolean(IS_LOGIN, false);
    }
 
 
 
}
 
cs



(SNS 연동 로그인 기능)



이외에 나머지 기능은 대부분 커스텀 리스트뷰와 http 통신을 하는 것으로 생략하겠습니다.


수정한 실제 액티비티의 구성은 이렇습니다.




-kakao : kakaoSDK 관련 파일

-login : splash 포함 로그인 기능 액티비티, 세션관리 매니저

-main : http 통신을 통해 json 데이터를 파싱하는 기능, 메인 액티비티를 중심으로 가게 카테고리 리스트, 메뉴 추천 액티비티 포함 (FavoriteAcitivity 파일은 개인의 즐겨찾기 가게를 보여주는 액티비티로 미구현 기능)

-pasing : 교내 식당 페이지 데이터 파싱 기능, 파싱한 데이터(학식 메뉴)를 표시해주는 액티비티

-store : 가게 상세 페이지 관련 액티비티/어댑터. 리뷰 작성 기능

-CTextView.java : 글자가 잘리지 않고 줄넘김이 되는 텍스트뷰 사용을 위한 파일




다음은 백엔드 서비스 구조입니다.

서버 내에 php 언어를 사용해서 json 데이터를 송출하고, 안드로이드에서는 json데이터가 있는 해당 페이지에서 파싱을 통해 데이터를 가져옵니다.




리뷰 삽입/로드

-insert_review.php

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
<?php
header("Content-Type: application/json; charset=UTF-8");
header("Accept: application/json");
header("Cache-control: No-Cache");
header("Pragma: No-Cache");
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept");
 
$mysql_hostname="";
$mysql_user="";
$mysql_password="";
$mysql_database="";
 
$dbc = mysql_connect($mysql_hostname,$mysql_user,$mysql_password)
or die("db connect error: ".mysql_error());
mysql_select_db($mysql_database,$dbc)
or die("db connect error: ".mysql_error());
 
mysql_query("SET NAMES UTF8");
 
$idx = $_POST["idx"]; 
$score = $_POST["score"];
$userid = $_POST["userid"];
$usertype = $_POST["usertype"];
$storeid = $_POST["storeid"];
$content = $_POST["content"];
$image = $_POST["image"];
 
if($userid!=null&&$usertype!=null&&$storeid!=null){
    //index - null : insert new review
    if($idx==null){
        $result = mysql_query("INSERT INTO baedal.review (score, userid, usertype, storeid, content, image) values (".$score.",'".$userid."','".$usertype."','".$storeid."','".$content."','".$image."')");
        if($result)
            echo "review insert success. ";
        else
            die("review insert fail:".mysql_error());
    }
    
    //index - not null : update review
    else{
        //review check
        $result = mysql_query("SELECT * FROM review WHERE idx = ".$idx);
        if(!$result)
            die("review query fail:".mysql_error());
        $numrow = mysql_num_rows($result);
        
        if($numrow!=null){
            $result = mysql_query("UPDATE review SET score=".$score.", userid='".$userid."',usertype='".$usertype."',storeid='".$storeid."',content='".$content."',image='".$image."' WHERE idx=".$idx);
            if($result)
                echo "review update success. ";
            else
                die("review update fail:".mysql_error());
        }
        else die("not exist review index");
    }    
}
 
//exception
else{
    echo "error - foreign key null";
}
 
//score update
$result = mysql_query("SELECT sum(score), count(score) FROM review WHERE storeid='".$storeid."'");
if($result)
    echo "review select success. ";
else
    die("review select fail".mysql_error());
    
$row = mysql_fetch_row($result);
$avg_score = $row[0]/$row[1];
$result = mysql_query("UPDATE store SET score=".$avg_score." WHERE storeid='".$storeid."'");
 
if($result)
    echo "score update success";
else
    die("score update fail".mysql_error());
 
 
mysql_close($dbc);
?>
 
cs


-load_review.php

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
<?php
header("Content-Type: application/json; charset=UTF-8");
header("Accept: application/json");
header("Cache-control: No-Cache");
header("Pragma: No-Cache");
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept");
 
$mysql_hostname="";
$mysql_user="";
$mysql_password="";
$mysql_database="";
 
$dbc = mysql_connect($mysql_hostname,$mysql_user,$mysql_password)
or die("db connect error: ".mysql_error());
mysql_select_db($mysql_database,$dbc)
or die("db connect error: ".mysql_error());
 
mysql_query("SET NAMES UTF8");
 
$type = $_POST["type"];
$userid = $_POST["userid"];
$usertype = $_POST["usertype"];
$storeid = $_POST["storeid"];
 
if($type=='u')
    $selectquery = "SELECT * FROM review WHERE userid='".$userid."' AND usertype='".$usertype."'";
 
else if($type='s')
    $selectquery = "SELECT * FROM review WHERE storeid='".$storeid."'";
 
else
    die("type is null or incorrect (only u or s)");
 
$result = mysql_query($selectquery);
if(!$result)
    die("review select fail:".mysql_error());
 
if(mysql_num_rows($result)>0){
 
    $response = array();
echo "{\"android\":";
    while($row = mysql_fetch_array($result)){
        $main["idx"= $row[0];
        $main["score"= $row[1];
        $main["username"= mysql_fetch_array(mysql_query("SELECT name FROM user WHERE userid='".$row[2]."' AND type='".$row[3]."'"))[0];
        $main["storeid"= $row[4];
        $main["content"= $row[5];
        $main["datetime"= $row[6];
        $main["image"= $row[7];
 
        array_push($response$main);
    }
    echo json_encode($response, JSON_UNESCAPED_UNICODE);
    echo "}";
}
 
else {
    //not found
    $response["success"= 0;
    $response["message"= "Can't load or is not any review";
 
    //echo no user JSON
    echo json_encode($response, JSON_UNESCAPED_UNICODE);
}
 
mysql_close($dbc);
?>
 
cs



데이터베이스의 경우, 함께 프로젝트를 진행했던 팀원이 설계를 담당했었는데 가게마다 각각의 메뉴 테이블을 가지고 있는 아주 비효율적인 구조를 가지고 있었습니다. 그래서 최종발표 이후 데이터베이스를 갈아엎는 수정을 거치게 됩니다.


데이터베이스는 User 테이블, Review 테이블, Store 테이블로 구성되어 있으며 

store테이블안에는 가게정보와 메뉴정보가 함께 들어있습니다.

메뉴같은 경우 parent id라는 컬럼을 추가한 메뉴와 가게 테이블을 join한 형태인데 parent id에 값이 있냐 없냐에 따라 가게항목과 메뉴항목을 구분하였습니다. 메뉴의 경우 해당 가게의 id값을 parent id로 조회하여 쿼리 결과로 나타내었는데, 데이터베이스에 설계가 미숙할 때 수정했던 것이라 지금에 와서는 메뉴만을 모아놓은 테이블과 가게테이블을 1대다 관계로 설정하면 되지 않을까 하는 생각이 듭니다. 

수정한 데이터베이스는 서버가 열려있을 때 (지금은 닫혀있는 상태) DB 구성을 캡쳐하지 못하여 관련 자료가 없습니다.


이렇게 만들어진 베타 버전의 경성대 배달학과는 야침찬 부가기능 개발계획을 가지고 있었지만, 이 후의 일정에 밀려 더 이상 발전시키지 못해서 아쉬움이 많은 프로젝트입니다.

구글 플레이스토어에 등록까지 했지만, 언젠가 완성시켜서 이미 졸업한 학교이지만 후배들에게 편의를 제공해 주는 것이 목표입니다.


마지막으로 실제 실행 화면이 포함된 어플리케이션 사용설명서를 첨부합니다.

사용설명서.hwp