2011년7월26일_채팅 서버 & 클라이언트(선생님꺼) 소스코드 타이핑하고 흐름분석, 프로젝트 방향정하기



image 

서버는 다수의 클라이언트가 보내오는 메세지를 받아 접속한 모든 클라이언트에게 방송(유니캐스트)한다.
네이트온 메신저와 같이 1:1채팅은 지원하지 않고 서버에 접속한 모든 사람이 서로 떠드는 일종 대화방이다.
매우 단순한 기본기능만 갖춘 프로그램으로 많은 개선이 필요하다.



● chat_server.c

   1: // chat_server.c
   2:  
   3: #include <stdio.h>
   4: #include <fcntl.h>
   5: #include <stdlib.h>
   6: #include <signal.h>
   7: #include <sys/socket.h>
   8: #include <sys/file.h>
   9: #include <netinet/in.h>
  10: #include <string.h>
  11:  
  12: #define    MAXLINE        512
  13: #define    MAX_SOCK    64
  14:  
  15: int getmax(int);
  16: void removeClient(int);            //채팅 탈퇴 처리함수
  17:  
  18: char *escapechar = "exit";
  19: int max_fd1;                    //최대 소켓번호 +1
  20: int num_chat = 0;                //채팅 참가자 수
  21: int client_s[MAX_SOCK];            //채팅 참가자 소켓번호 목록
  22:  
  23:  
  24: int main(int argc, char *argv[])
  25: {
  26:     char rline[MAXLINE], my_msg[MAXLINE];
  27:     char *start = "Connected to chat_server \n";
  28:     int i, j, n;
  29:     int s;
  30:     int client_fd, client_len;
  31:  
  32:     fd_set read_fds;
  33:     struct sockaddr_in client_addr, server_addr;
  34:  
  35:     if(argc != 2)
  36:     {
  37:         fprintf(stderr, "사용법: %s Port \n", argv[0]);
  38:         exit(0);
  39:     }
  40:  
  41:     if((s = socket(PF_INET, SOCK_STREAM, 0)) < 0)
  42:     {
  43:         fprintf(stderr, "Server: Can't open stream socket.\n");
  44:         exit(0);
  45:     }
  46:  
  47:     bzero((char *)&server_addr, sizeof(server_addr));
  48:     server_addr.sin_family = AF_INET;
  49:     server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  50:     server_addr.sin_port = htons(atoi(argv[1]));
  51:  
  52:     if(bind(s, (struct sockaddr *)&server_addr,sizeof(server_addr)) < 0)
  53:     {
  54:         fprintf(stderr, "Server: Can't bind local address.\n");
  55:         exit(0);
  56:     }
  57:  
  58:     listen(s, 5);
  59:  
  60:     max_fd1 = s + 1;            //최대 소켓번호 + 1
  61:  
  62:     while(1)
  63:     {
  64:         FD_ZERO(&read_fds);
  65:         FD_SET(s, &read_fds);
  66:  
  67:         for(i = 0 ; i < num_chat ; i++)
  68:             FD_SET(client_s[i], &read_fds);
  69:  
  70:         max_fd1 = getmax(s) + 1;        //max_fd1 재계산
  71:         if(select(max_fd1, &read_fds, (fd_set *)0, (fd_set *)0, (struct timeval *)0) < 0)
  72:         {
  73:             fprintf(stderr, "select error <= 0\n");
  74:             exit(0);
  75:         }
  76:  
  77:         if(FD_ISSET(s, &read_fds))
  78:         {
  79:             client_len = sizeof(client_addr);
  80:             client_fd = accept(s, (struct sockaddr *)&client_addr, &client_len);
  81:             if(-1 == client_fd)
  82:             {
  83:                 fprintf(stderr, "accept error\n");
  84:                 exit(0);
  85:             }
  86:  
  87:             // 채팅 클라이언트 목록에 추가
  88:             client_s[num_chat] = client_fd;
  89:             num_chat++;
  90:             send(client_fd, start, strlen(start), 0);
  91:             printf("%d번째 사용자 추가. \n", num_chat);
  92:         }
  93:  
  94:         //클라이언트가 보낸 메시지를 모든 클라이언트에게 방송
  95:         for(i = 0 ; i < num_chat ; i++)
  96:         {
  97:             if(FD_ISSET(client_s[i], &read_fds))
  98:             {
  99:                 if((n = recv(client_s[i], rline, MAXLINE, 0)) <= 0)
 100:                 {
 101:                     removeClient(i);
 102:                     continue;
 103:                 }
 104:  
 105:                 if(strstr(rline, escapechar) != NULL)    //종료문자 처리
 106:                 {
 107:                     removeClient(i);
 108:                     continue;
 109:                 }
 110:  
 111:                 //모든 채팅 참가자에게 메시지 방송
 112:                 rline[n] = '\0';
 113:                 for(j = 0 ; j < num_chat ; j++)
 114:                     send(client_s[j], rline, n, 0);
 115:  
 116:                 printf("%s \n", rline);
 117:             }
 118:         }
 119:     }
 120:         
 121:     return 0;
 122: }
 123:  
 124: //채팅 탈퇴 처리
 125: void removeClient(int i)
 126: {
 127:     close(client_s[i]);
 128:  
 129:     if(i != num_chat - 1)
 130:         client_s[i] = client_s[num_chat - 1];
 131:  
 132:     num_chat--;
 133:     printf("채팅 참가자 1명 탈퇴. 현 참가자 수 = %d \n", num_chat);
 134: }
 135:  
 136: //client_s[]내의 최대 소켓번호 얻기
 137: int getmax(int k)
 138: {
 139:     int max = k;
 140:     int r;
 141:  
 142:     for(r = 0 ; r < num_chat ; r++)
 143:     {
 144:         if(client_s[r] > max)
 145:             max = client_s[r];
 146:     }
 147:  
 148:     return max;
 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:         



● chat_client.c

   1: // chat_client.c
   2:  
   3: #include <stdio.h>
   4: #include <fcntl.h>
   5: #include <stdlib.h>
   6: #include <signal.h>
   7: #include <sys/socket.h>
   8: #include <sys/file.h>
   9: #include <netinet/in.h>
  10: #include <string.h>
  11:  
  12: #define    MAXLINE        512
  13: #define    MAX_SOCK    128
  14:  
  15: char *escapechar = "exit";
  16: char name[10];                //채팅에서 사용할 이름
  17:  
  18: int main(int argc, char *argv[])
  19: {
  20:     char line[MAXLINE], msg[MAXLINE + 1];
  21:     int n, pid;
  22:     int maxfd1;
  23:     int s;
  24:     fd_set read_fds;
  25:     struct sockaddr_in server_addr;
  26:  
  27:     if(argc != 4)
  28:     {
  29:         fprintf(stderr, "사용법: %s serverIP serverPort UserName \n", argv[0]);
  30:         exit(0);
  31:     }
  32:  
  33:     //채팅 참가자 이름 저장
  34:     sprintf(name, "[%s]", argv[3]);
  35:  
  36:     //소켓생성
  37:     if((s = socket(PF_INET, SOCK_STREAM, 0)) < 0)
  38:     {
  39:         fprintf(stderr, "Client: Can't open stream socket.\n");
  40:         exit(0);
  41:     }
  42:  
  43:     //채팅 서버의 소켓주소 저장
  44:     bzero((char *)&server_addr, sizeof(server_addr));
  45:     server_addr.sin_family = AF_INET;
  46:     server_addr.sin_addr.s_addr = inet_addr(argv[1]);
  47:     server_addr.sin_port = htons(atoi(argv[2]));
  48:  
  49:     //연결 요청
  50:     if(connect(s, (struct sockaddr *)&server_addr,sizeof(server_addr)) < 0)
  51:     {
  52:         fprintf(stderr, "Server: Can't bind local address.\n");
  53:         exit(0);
  54:     }
  55:     else
  56:     {
  57:         printf("서버에 접속되었습니다. \n");
  58:     }
  59:     
  60:  
  61:     maxfd1 = s + 1;            //최대 소켓번호 + 1
  62:     FD_ZERO(&read_fds);
  63:     
  64:     while(1)
  65:     {
  66:         FD_SET(0, &read_fds);
  67:         FD_SET(s, &read_fds);
  68:  
  69:         if(select(maxfd1, &read_fds, (fd_set *)0, (fd_set *)0, (struct timeval *)0) < 0)
  70:         {
  71:             fprintf(stderr, "select error <= 0\n");
  72:             exit(0);
  73:         }
  74:  
  75:         if(FD_ISSET(s, &read_fds))
  76:         {
  77:             int size;
  78:             if((size = recv(s, msg, MAXLINE, 0)) > 0)
  79:             {
  80:                 msg[size] = '\0';
  81:                 printf("%s \n", msg);
  82:             }
  83:         }
  84:  
  85:         if(FD_ISSET(0, &read_fds))
  86:         {
  87:             if(fgets(msg, MAXLINE, stdin))
  88:             {
  89:                 sprintf(line, "%s %s", name, msg);
  90:                 if(send(s, line, strlen(line), 0) < 0)
  91:                     printf("send() error \n");
  92:                 
  93:                 if(strstr(msg, escapechar) != NULL)    //종료문자 처리
  94:                 {
  95:                     printf("Good bye. \n");
  96:                     close(s);
  97:                     exit(0);
  98:                 }
  99:             }
 100:         }
 101:     }
 102:         
 103:     return 0;
 104: }
 105:  
 106:  
 107:  
 108:  
 109:     
 110:  
 111:         


<실행결과>

image image
                           [스크린샷] 서버                                                         [스크린샷] 클라이언트


image
                             [스크린샷] 최대 동시접속자수 6명!!!




● 프로그램의 흐름

DSCN3778 
                                       [사진] 노트필기

상기의 사진을 보면 프로그램의 흐름이 굉장히 복잡한 것 같지만...실제 복잡하다...-0-
//시간나면 자세히 설명하겠다. ㅎㅎ
//내일 쓸게 없으니 오늘은 이만하겠다. ㅎㅎ



● 채팅프로그램 방향정하기

1. 입력 창과 로그창을 분리하여 보기 좋게함.
2. 한 번에 여러 줄을 입력할 수 있게 입력처리부분을 추가
3. TCP서버외에 UDP프로토콜을 사용하면 서버없이 클라이언트들 끼리 통신이 가능하지 않을까? 아니면...
   하나의 프로그램이 서버와 클라이언트 프로세스 둘 다 돌리면 될 것 같음.
4. 채팅만 하면 지루하니 게임적인 요소를 추가. (몬스터를 잡으면 레벨업~!)
5. 채팅서버의 IP를 입력하지 않으면 디폴트 채팅서버에 접속.