2011년7월26일_채팅 서버 & 클라이언트(선생님꺼) 소스코드 타이핑하고 흐름분석, 프로젝트 방향정하기
서버는 다수의 클라이언트가 보내오는 메세지를 받아 접속한 모든 클라이언트에게 방송(유니캐스트)한다.
네이트온 메신저와 같이 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:
<실행결과>
● 프로그램의 흐름
상기의 사진을 보면 프로그램의 흐름이 굉장히 복잡한 것 같지만...실제 복잡하다...-0-
//시간나면 자세히 설명하겠다. ㅎㅎ
//내일 쓸게 없으니 오늘은 이만하겠다. ㅎㅎ
● 채팅프로그램 방향정하기
1. 입력 창과 로그창을 분리하여 보기 좋게함. 2. 한 번에 여러 줄을 입력할 수 있게 입력처리부분을 추가 3. TCP서버외에 UDP프로토콜을 사용하면 서버없이 클라이언트들 끼리 통신이 가능하지 않을까? 아니면... 하나의 프로그램이 서버와 클라이언트 프로세스 둘 다 돌리면 될 것 같음. 4. 채팅만 하면 지루하니 게임적인 요소를 추가. (몬스터를 잡으면 레벨업~!) 5. 채팅서버의 IP를 입력하지 않으면 디폴트 채팅서버에 접속. |