여기서 사용하는 소스코드는 아래의 블로그에서 가져왔으며 공개를 원치 않으시는 경우 이 글이 삭제 될 수 있음을 알려드립니다.
출처 : https://crazythink.github.io/2018/05/25/socketchat/
하나의 c파일로 이루어진 소스를 공유라이브러리를 사용하기 위해 하나씩 분리시켜 보겠습니다.
- thread_function.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 |
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <fcntl.h>
#include <sys/file.h>
#include <unistd.h>
#include <pthread.h>
#define MAXLINE 511
#define MAX_SOCK 1024 // 솔라리스의 경우 64
int num_user = 0;
int num_chat = 0;
char ip_list[MAX_SOCK][20];
void *thread_function(void *arg) { //명령어를 처리할 스레드
int i;
printf("명령어 목록 : help, num_user, num_chat, ip_list\n");
while (1) {
char bufmsg[MAXLINE + 1];
fprintf(stderr, "\033[1;32m"); //글자색을 녹색으로 변경
printf("server>"); //커서 출력
fgets(bufmsg, MAXLINE, stdin); //명령어 입력
if (!strcmp(bufmsg, "\n")) continue; //엔터 무시
else if (!strcmp(bufmsg, "help\n")) //명령어 처리
printf("help, num_user, num_chat, ip_list\n");
else if (!strcmp(bufmsg, "num_user\n"))//명령어 처리
printf("현재 참가자 수 = %d\n", num_user);
else if (!strcmp(bufmsg, "num_chat\n"))//명령어 처리
printf("지금까지 오간 대화의 수 = %d\n", num_chat);
else if (!strcmp(bufmsg, "ip_list\n")) //명령어 처리
for (i = 0; i < num_user; i++)
printf("%s\n", ip_list[i]);
else //예외 처리
printf("해당 명령어가 없습니다.help를 참조하세요.\n");
}
} |
cs |
- addClient.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 |
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define MAX_SOCK 1024 // 솔라리스의 경우 64
int extern num_user; // 채팅 참가자 수
int extern clisock_list[MAX_SOCK]; // 채팅에 참가자 소켓번호 목록
char extern ip_list[MAX_SOCK][20];
// 새로운 채팅 참가자 처리
void addClient(int s, struct sockaddr_in *newcliaddr) {
char buf[20];
inet_ntop(AF_INET, &newcliaddr->sin_addr, buf, sizeof(buf));
write(1, "\033[0G", 4); //커서의 X좌표를 0으로 이동
fprintf(stderr, "\033[33m"); //글자색을 노란색으로 변경
printf("new client: %s\n", buf);//ip출력
// 채팅 클라이언트 목록에 추가
clisock_list[num_user] = s;
strcpy(ip_list[num_user], buf);
num_user++; //유저 수 증가
} |
cs |
- removeClient.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 |
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#define MAX_SOCK 1024 // 솔라리스의 경우 64
int extern num_user; // 채팅 참가자 수
int extern clisock_list[MAX_SOCK]; // 채팅에 참가자 소켓번호 목록
char extern ip_list[MAX_SOCK][20]; //접속한 ip목록
time_t extern ct;
struct tm extern tm;
// 채팅 탈퇴 처리
void removeClient(int s) {
close(clisock_list[s]);
if (s != num_user - 1) { //저장된 리스트 재배열
clisock_list[s] = clisock_list[num_user - 1];
strcpy(ip_list[s], ip_list[num_user - 1]);
}
num_user--; //유저 수 감소
ct = time(NULL); //현재 시간을 받아옴
tm = *localtime(&ct);
write(1, "\033[0G", 4); //커서의 X좌표를 0으로 이동
fprintf(stderr, "\033[33m");//글자색을 노란색으로 변경
printf("[%02d:%02d:%02d]", tm.tm_hour, tm.tm_min, tm.tm_sec);
printf("채팅 참가자 1명 탈퇴. 현재 참가자 수 = %d\n", num_user);
fprintf(stderr, "\033[32m");//글자색을 녹색으로 변경
fprintf(stderr, "server>"); //커서 출력
} |
cs |
- getmax.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 |
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <fcntl.h>
#include <unistd.h>
#define MAX_SOCK 1024 // 솔라리스의 경우 64
int extern listen_sock; // 서버의 리슨 소켓
int extern num_user;
int extern clisock_list[MAX_SOCK]; // 채팅에 참가자 소켓번호 목록
// 최대 소켓번호 찾기
int getmax() {
// Minimum 소켓번호는 가정 먼저 생성된 listen_sock
int max = listen_sock;
int i;
for (i = 0; i < num_user; i++)
if (clisock_list[i] > max)
max = clisock_list[i];
return max;
} |
cs |
- tcp_listen.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 |
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
// listen 소켓 생성 및 listen
int tcp_listen(int host, int port, int backlog) {
int sd;
struct sockaddr_in servaddr;
sd = socket(AF_INET, SOCK_STREAM, 0);
if (sd == -1) {
perror("socket fail");
exit(1);
}
// servaddr 구조체의 내용 세팅
bzero((char *)&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(host);
servaddr.sin_port = htons(port);
if (bind(sd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
perror("bind fail"); exit(1);
}
// 클라이언트로부터 연결요청을 기다림
listen(sd, backlog);
return sd;
} |
cs |
이번에는 위 함수들을 선언한 header 파일들을 만들겠습니다.
- thread_function.h
1
2
3
4
5
6 |
#ifndef FUNCS1_H
#define FUNCS1_H
void *thread_function(void *arg);
#endif |
cs |
- addclient.h
1
2
3
4
5
6 |
#ifndef FUNCS1_H
#define FUNCS1_H
void addClient(int s,struct sockaddr_in,*newcliaddr);
#endif |
cs |
- removeclient.h
1
2
3
4
5
6 |
#ifndef FUNCS1_H
#define FUNCS1_H
void removeClient(int s);
#endif |
cs |
- getmax.h
1
2
3
4
5
6 |
#ifndef FUNCS1_H
#define FUNCS1_H
int getmax();
#endif |
cs |
- tcp_listen.h
1
2
3
4
5
6 |
#ifndef FUNCS1_H
#define FUNCS1_H
int tcp_listen(int host, int port, int backlog);
#endif |
cs |
이렇게 만든 헤더파일들을 각 기능별 c파일에 선언해 주시면 됩니다.
- ex)include "tcp_listen.h" 이런식으로 모든 코드에 모든 헤더를 선언하셔야 됩니다.
모든 lib파일에 추가 했다면 마지막으로 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 |
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#include <pthread.h>
#include "thread.h"
#include "addclient.h"
#include "removeclient.h"
#include "getmax.h"
#include "tcp_listen.h"
//this is server C source file
#define MAXLINE 511
#define MAX_SOCK 1024 // 솔라리스의 경우 64
char *EXIT_STRING = "exit"; // 클라이언트의 종료요청 문자열
char *START_STRING = "Connected to chat_server \n";
// 클라이언트 환영 메시지
int maxfdp1; // 최대 소켓번호 +1
int num_user = 0; // 채팅 참가자 수
int num_chat = 0; // 지금까지 오간 대화의 수
int clisock_list[MAX_SOCK]; // 채팅에 참가자 소켓번호 목록
char ip_list[MAX_SOCK][20]; //접속한 ip목록
int listen_sock; // 서버의 리슨 소켓
// 새로운 채팅 참가자 처리
void addClient(int s, struct sockaddr_in *newcliaddr);
int getmax(); // 최대 소켓 번호 찾기
void removeClient(int s); // 채팅 탈퇴 처리 함수
int tcp_listen(int host, int port, int backlog); // 소켓 생성 및 listen
void errquit(char *mesg) { perror(mesg); exit(1); }
time_t ct;
struct tm tm;
int main(int argc, char *argv[]) {
struct sockaddr_in cliaddr;
char buf[MAXLINE + 1]; //클라이언트에서 받은 메시지
int i, j, nbyte, accp_sock, addrlen = sizeof(struct
sockaddr_in);
fd_set read_fds; //읽기를 감지할 fd_set 구조체
pthread_t a_thread;
if (argc != 2) {
printf("사용법 :%s port\n", argv[0]);
exit(0);
}
// tcp_listen(host, port, backlog) 함수 호출
listen_sock = tcp_listen(INADDR_ANY, atoi(argv[1]), 5);
//스레드 생성
pthread_create(&a_thread, NULL, thread_function, (void *)NULL);
while (1) {
FD_ZERO(&read_fds);
FD_SET(listen_sock, &read_fds);
for (i = 0; i < num_user; i++)
FD_SET(clisock_list[i], &read_fds);
maxfdp1 = getmax() + 1; // maxfdp1 재 계산
if (select(maxfdp1, &read_fds, NULL, NULL, NULL) < 0)
errquit("select fail");
if (FD_ISSET(listen_sock, &read_fds)) {
accp_sock = accept(listen_sock,
(struct sockaddr*)&cliaddr, &addrlen);
if (accp_sock == -1) errquit("accept fail");
addClient(accp_sock, &cliaddr);
send(accp_sock, START_STRING, strlen(START_STRING), 0);
ct = time(NULL); //현재 시간을 받아옴
tm = *localtime(&ct);
write(1, "\033[0G", 4); //커서의 X좌표를 0으로 이동
printf("[%02d:%02d:%02d]", tm.tm_hour, tm.tm_min, tm.tm_sec);
fprintf(stderr, "\033[33m");//글자색을 노란색으로 변경
printf("사용자 1명 추가. 현재 참가자 수 = %d\n", num_user);
fprintf(stderr, "\033[32m");//글자색을 녹색으로 변경
fprintf(stderr, "server>"); //커서 출력
}
// 클라이언트가 보낸 메시지를 모든 클라이언트에게 방송
for (i = 0; i < num_user; i++) {
if (FD_ISSET(clisock_list[i], &read_fds)) {
num_chat++; //총 대화 수 증가
nbyte = recv(clisock_list[i], buf, MAXLINE, 0);
if (nbyte <= 0) {
removeClient(i); // 클라이언트의 종료
continue;
}
buf[nbyte] = 0;
// 종료문자 처리
if (strstr(buf, EXIT_STRING) != NULL) {
removeClient(i); // 클라이언트의 종료
continue;
}
// 모든 채팅 참가자에게 메시지 방송
for (j = 0; j < num_user; j++)
send(clisock_list[j], buf, nbyte, 0);
printf("\033[0G"); //커서의 X좌표를 0으로 이동
fprintf(stderr, "\033[97m");//글자색을 흰색으로 변경
printf("%s", buf); //메시지 출력
fprintf(stderr, "\033[32m");//글자색을 녹색으로 변경
fprintf(stderr, "server>"); //커서 출력
}
}
} // end of while
return 0;
} |
cs |
컴파일하는 방식에 따라서 makefile을 수정하겠습니다.
- player/Makefile
1
2
3
4
5
6
7
8
9
10
11
12 |
EXEC=server
CC=gcc
CFLAGS=-Wall
$(EXEC): server.o
$(CC) -o server server.o -L../lib -lserverfuncs -lpthread
%.o: %.c
$(CC) -c -I../include $< -o $@ -lpthread
clean:
rm server.o server |
cs |
- lib/Makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 |
CC=gcc
LIB=libserverfuncs.so.1.0
OBJS=thread.o addclient.o removeclient.o getmax.o tcp_listen.o
CFLAGS=-Wall
POINTER=libserverfuncs.so libserverfuncs.so.1
$(LIB): $(OBJS)
$(CC) -fPIC -shared -Wl,-soname=libserverfuncs.so.1 $(OBJS) -o $@ -lc
ln -s libserverfuncs.so.1.0 libserverfuncs.so
ln -s libserverfuncs.so.1.0 libserverfuncs.so.1
%.o: %.c
$(CC) -fPIC -c $(CFLAGS) -I../include $< -o $@
clean:
rm -f $(OBJS) $(LIB) $(POINTER) |
cs |
결과
- make 명령어 사용
- server 실행파일 실행 - 실행하기 전에 공유라이브러리의 PATH설정 부분을 잘 보고 따라해야 실행이 됩니다.
여기서 사용하는 소스코드는 아래의 블로그에서 가져왔으며 공개를 원치 않으시는 경우 이 글이 삭제 될 수 있음을 알려드립니다.
출처 : https://crazythink.github.io/2018/05/25/socketchat/ - 습관적 프로그래밍
'채팅 프로그램 제작' 카테고리의 다른 글
최종완성 및 프로젝트 분석 (0) | 2018.12.11 |
---|---|
[Client]코드와 설명 (0) | 2018.12.11 |
Makefile만들기 (0) | 2018.11.24 |
[계획서]socket 통신을 이용한 채팅프로그램 (0) | 2018.11.19 |