멀티쓰레드 기반 채팅서버의 클라이언트 접속 로그 DB저장하기

책 18-5 의 채팅 서버에 클라이언트가 로그인 하면 주소, 포트, 시간 기록을 mysql ChatClients 테이블에 기록하자

 

앞서 생성한 db 데이터베이스에 다음 테이블을 생성한다.

1
2
3
4
5
6
7
create table ChatClients(
 id bigint(20unsigned not null auto_increment,
 client_ip varchar(255not null,
 client_port varchar(255not null,
 date varchar(255not null,
 primary key (id)
DEFAULT CHARACTER SET utf8; 
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

 

chat_serv.c 에서 클라이언트 접속시 아래 예로 든 sql 쿼리로 ChatClients 테이블에 레코드를 추가한다.

1
2
INSERT INTO ChatClients(client_ip, client_port, date)
VALUES('192.168.1.1''34246''2019-01-01');
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

 

추가한 로그는 mysql 클라이언트에서 확인해 보자,

gcc -o chat_server $(mysql_config --cflags) chat_server.c $(mysql_config --libs)

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
/* chat_server.c */
#include <time.h>
#include <my_global.h>
#include <mysql.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#define BUF_SIZE 100
#define MAX_CLNT 256
void * handle_clnt(void * arg);
void send_msg(char * msg, int len);
void error_handling(char * msg);
int clnt_cnt=0;
int clnt_socks[MAX_CLNT];
pthread_mutex_t mutx;
 
 
void finish_with_error(MYSQL *con)
{
  fprintf(stderr, "%s\n", mysql_error(con));
  mysql_close(con);
  exit(1); 
}
 
 
void stprintf_time(time_t org_time, char *time_str)
{
    struct tm *tm_ptr;
    tm_ptr = localtime(&org_time);
 
    sprintf(time_str,"%d-%d-%d %d:%d:%d",tm_ptr->tm_year+1900,tm_ptr->tm_mon+1,tm_ptr->tm_mday,tm_ptr->tm_hour,tm_ptr->tm_min,tm_ptr->tm_sec);
}
 
 
int main(int argc, char *argv[])
{
    int i;
    time_t the_time;
    char buffer[255];
    
    int temp;
    MYSQL *con = mysql_init(NULL);
    int query_stat;
    char query[255];
 
    if(con==NULL)
    {
        fprintf(stderr,"%s\n",mysql_error(con));
        exit(1);
    }
    
 
    if(mysql_real_connect(con,"localhost","kim","1q2w3e4rA!!",
                "db",0,NULL,0)==NULL)
    {
        finish_with_error(con);
    }
 
    char client_ip[255];
    char client_port[255];
 
 
 
 
 
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    int clnt_adr_sz;
    pthread_t t_id;
    if(argc!=2) {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }
 
    pthread_mutex_init(&mutx, NULL);
    serv_sock=socket(PF_INET, SOCK_STREAM, 0); 
    memset(&serv_adr, 0sizeof(serv_adr));
    serv_adr.sin_family=AF_INET;
    serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_adr.sin_port=htons(atoi(argv[1]));
    
    if(bind(serv_sock, (struct sockaddr*&serv_adr,
sizeof(serv_adr))==-1)
        error_handling("bind() error");
    if(listen(serv_sock, 5)==-1)
        error_handling("listen() error");
    
    while(1)
    {
        clnt_adr_sz=sizeof(clnt_adr);
        clnt_sock=accept(serv_sock, (struct
sockaddr*)&clnt_adr,&clnt_adr_sz);
        
        pthread_mutex_lock(&mutx);
        clnt_socks[clnt_cnt++]=clnt_sock;
        pthread_mutex_unlock(&mutx);
    
        pthread_create(&t_id, NULL, handle_clnt,
(void*)&clnt_sock);
        pthread_detach(t_id);
        printf("Connected client IP: %s \n",
inet_ntoa(clnt_adr.sin_addr));
 
 
        
       strcpy(client_ip,inet_ntoa(clnt_adr.sin_addr));
       sprintf(client_port,"%d",ntohs(clnt_adr.sin_port));
 
 
        time(&the_time);
       stprintf_time(the_time,buffer);
        printf("ip:%s\n port:%s\n time:%s\n",client_ip,client_port,buffer);
      sprintf(query,"INSERT INTO ChatClients(client_ip,client_port,date) VALUES "
                   "('%s', '%s', '%s')",  
           client_ip,client_port,buffer);
 
          query_stat=mysql_query(con,query);
       if (query_stat != 0)
    {
        finish_with_error(con);
        return 1;
 
    }
    }
    mysql_close(con);
    close(serv_sock);
    return 0;
}
    
void * handle_clnt(void * arg)
{
    int clnt_sock=*((int*)arg);
    int str_len=0, i;
    char msg[BUF_SIZE];
    
    while((str_len=read(clnt_sock, msg, sizeof(msg)))!=0)
        send_msg(msg, str_len);
    
    pthread_mutex_lock(&mutx);
    for(i=0; i<clnt_cnt; i++// remove disconnected client
    {
        if(clnt_sock==clnt_socks[i])
        {
            while(i++<clnt_cnt-1)
                clnt_socks[i]=clnt_socks[i+1];
            break;
        }
    }
    clnt_cnt--;
    pthread_mutex_unlock(&mutx);
    close(clnt_sock);
    return NULL;
}
void send_msg(char * msg, int len) // send to all
{
    int i;
    pthread_mutex_lock(&mutx);
    for(i=0; i<clnt_cnt; i++)
        write(clnt_socks[i], msg, len);
    pthread_mutex_unlock(&mutx);
}
void error_handling(char * msg)
{
    fputs(msg, stderr);
    fputc('\n', stderr);
    exit(1);
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

 

 

 

 

 

1. json 파일 다루기:

json 파싱 코딩도장 샘플

1. json 구조체: https://dojang.io/mod/page/view.php?id=721

2. json 파일읽기: https://dojang.io/mod/page/view.php?id=722

3. json 문자열 파싱: https://dojang.io/mod/page/view.php?id=724

4. json 문자열 배열 파싱: https://dojang.io/mod/page/view.php?id=725

 

1) 1,2,3 까지 하고

2) jsonParser를 4번으로 배열있는 example2.json 파싱

2.2) example1.json 사용해보기

 

C 언어 코딩 도장: 83.3 JSON에서 문자열 파싱하기

먼저 문자열부터 파싱해보겠습니다. 그림 83‑1은 JSON에서 문자열을 파싱하는 과정입니다. ▼ 그림 83‑1 JSON에서 문자열을 파싱하는 과정 파싱할 JSON 문서는 다음과 같으며 .c 파일이 있는 폴더에 example.json으로 저장합니다(파일은 GitHub 저장소의 Unit 83/83.3/json/json 폴더에 들어있습니다). example.json { "Title": "Inception", "Genre": "Sci-Fi", "Director"

dojang.io

 

example.json

{
  "Title": "Inception",
  "Genre": "Sci-Fi",
  "Director": "Christopher Nolan"
}

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
#define _CRT_SECURE_NO_WARNINGS    // fopen 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>     // 파일 처리 함수가 선언된 헤더 파일
#include <stdlib.h>    // malloc, free 함수가 선언된 헤더 파일
#include <stdbool.h>   // bool, true, false가 정의된 헤더 파일
#include <string.h>    // strchr, memset, memcpy 함수가 선언된 헤더 파일
 
// 토큰 종류 열거형
typedef enum _TOKEN_TYPE {
    TOKEN_STRING,    // 문자열 토큰
    TOKEN_NUMBER,    // 숫자 토큰
} TOKEN_TYPE;
 
// 토큰 구조체
typedef struct _TOKEN {
    TOKEN_TYPE type;   // 토큰 종류
    union {            // 두 종류 중 한 종류만 저장할 것이므로 공용체로 만듦
        char *string;     // 문자열 포인터
        double number;    // 실수형 숫자
    };
    bool isArray;      // 현재 토큰이 배열인지 표시
} TOKEN;
 
#define TOKEN_COUNT 20    // 토큰의 최대 개수
 
// JSON 구조체
typedef struct _JSON {
    TOKEN tokens[TOKEN_COUNT]; // 토큰 배열
} JSON;
 
char *readFile(char *filename, int *readSize)    // 파일을 읽어서 내용을 반환하는 함수
{
    FILE *fp = fopen(filename, "rb");
    if (fp == NULL)
        return NULL;
 
    int size;
    char *buffer;
 
    // 파일 크기 구하기
    fseek(fp, 0, SEEK_END);
    size = ftell(fp);
    fseek(fp, 0, SEEK_SET);
 
    // 파일 크기 + NULL 공간만큼 메모리를 할당하고 0으로 초기화
    buffer = malloc(size + 1);
    memset(buffer, 0size + 1);
 
    // 파일 내용 읽기
    if (fread(buffer, size1, fp) < 1)
    {
        *readSize = 0;
        free(buffer);
        fclose(fp);
        return NULL;
    }
 
    // 파일 크기를 넘겨줌
    *readSize = size;
 
    fclose(fp);    // 파일 포인터 닫기
 
    return buffer;
}
 
void parseJSON(char *doc, int size, JSON *json)    // JSON 파싱 함수
{
    int tokenIndex = 0;    // 토큰 인덱스
    int pos = 0;           // 문자 검색 위치를 저장하는 변수
 
    if (doc[pos] != '{')   // 문서의 시작이 {인지 검사
        return;
 
    pos++;    // 다음 문자로
 
    while (pos < size)       // 문서 크기만큼 반복
    {
        switch (doc[pos])    // 문자의 종류에 따라 분기
        {
        case '"':            // 문자가 "이면 문자열
        {
            // 문자열의 시작 위치를 구함. 맨 앞의 "를 제외하기 위해 + 1
            char *begin = doc + pos + 1;
 
            // 문자열의 끝 위치를 구함. 다음 "의 위치
            char *end = strchr(begin'"');
            if (end == NULL)    // "가 없으면 잘못된 문법이므로 
                break;          // 반복을 종료
 
            int stringLength = end - begin;    // 문자열의 실제 길이는 끝 위치 - 시작 위치
 
            // 토큰 배열에 문자열 저장
            // 토큰 종류는 문자열
            json->tokens[tokenIndex].type = TOKEN_STRING;
            // 문자열 길이 + NULL 공간만큼 메모리 할당
            json->tokens[tokenIndex].string = malloc(stringLength + 1);
            // 할당한 메모리를 0으로 초기화
            memset(json->tokens[tokenIndex].string0, stringLength + 1);
 
            // 문서에서 문자열을 토큰에 저장
            // 문자열 시작 위치에서 문자열 길이만큼만 복사
            memcpy(json->tokens[tokenIndex].stringbegin, stringLength);
 
            tokenIndex++// 토큰 인덱스 증가
 
            pos = pos + stringLength + 1;    // 현재 위치 + 문자열 길이 + "(+ 1)
        }
        break;
        }
 
        pos++// 다음 문자로
    }
}
 
void freeJSON(JSON *json)    // JSON 해제 함수
{
    for (int i = 0; i < TOKEN_COUNT; i++)            // 토큰 개수만큼 반복
    {
        if (json->tokens[i].type == TOKEN_STRING)    // 토큰 종류가 문자열이면
            free(json->tokens[i].string);            // 동적 메모리 해제
    }
}
 
int main()
{
    int size;    // 문서 크기
    
    // 파일에서 JSON 문서를 읽음, 문서 크기를 구함
    char *doc = readFile("example.json"&size);
    if (doc == NULL)
        return -1;
 
    JSON json = { 0, };    // JSON 구조체 변수 선언 및 초기화
 
    parseJSON(doc, size&json);    // JSON 문서 파싱
 
    printf("Title: %s\n"json.tokens[1].string);       // 토큰에 저장된 문자열 출력(Title)
    printf("Genre: %s\n"json.tokens[3].string);       // 토큰에 저장된 문자열 출력(Genre)
    printf("Director: %s\n"json.tokens[5].string);    // 토큰에 저장된 문자열 출력(Director)
 
    freeJSON(&json);    // json 안에 할당된 동적 메모리 해제
 
    free(doc);    // 문서 동적 메모리 해제
 
    return 0;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

 

 

2. json 라이브러리 살펴보기

https://github.com/json-c/json-c

 

json-c/json-c

https://github.com/json-c/json-c is the official code repository for json-c. See the wiki for release tarballs for download. API docs at http://json-c.github.io/json-c/ - json-c/json-c

github.com

git 유틸리티 설치

$ sudo apt install git

$ sudo apt-get install autoconf

$ sudo apt-get install libtool

$ git clone https://github.com/json-c/json-c.git Cloning into ‘json-c'...

$ cd json-c

$ sh autogen.sh

$ ./configure

$ make

$ sudo make install

설치된 위치는 사용자 컴파일 설치는 ./configure로 기본 위치가 /usr/ local/include/json, 라이브러리는 /usr/local/lib에 설치된다.

보통 헤더를 #include <json/json.h>

처럼 인클루드 하므로 다음 같이 json 헤더 폴더를 수동으로 작업해 주자

$ cd /usr/local/include/json-c

$ sudo mkdir json

$ mv *.h json

 

jsonc1.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
#include <stdio.h>
#include <json/json.h>
 
int main(int argc, char **argv)
{
 json_object *myobj, *dataobj;
 
 // 메모리 할당
 myobj = json_object_new_object();
 dataobj = json_object_new_object();
 
 json_object_object_add(dataobj, "test1",
json_object_new_int(1));
 json_object_object_add(dataobj, "test2",
json_object_new_string("TEST2"));
 json_object_object_add(myobj, "testData", dataobj);
 
 printf("myobj.to_string()=%s\n",
json_object_to_json_string(myobj));
 
 // 메모리 해제
 json_object_put(dataobj);
 json_object_put(myobj); 
 
 return(0);
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

 

 

컴파일 기본 옵션:
$ gcc -o jsonc jsonc1.c -I/usr/local/include/json-c -L/usr/local/lib -ljson-c
$
$ ./jsonc
./jsonc: error while loading shared libraries: libjson-c.so.
4: cannot open shared object file: No such file or directory

 

단!!!기본설치시공유라이브러리로설치되므로바로사용하려면 ldconfig로캐시를갱신해야한다.

$ sudo ldconfig

[sudo] password for qkboo:

다시 실행해 본다.

 

$ ./jsonc

myobj.to_string()={ "testData": { "test1": 1, "test2": "TEST2" } }

 

day8/실습2

공유한 nlp_day8/학과정보.json 을 다운받는다.

학과정보.json
0.05MB

json-c 라이브러리를 이용해 파싱을 시도해 보자

코딩도장 json 파싱 설명에 있는 char *readFile(char *filename, int *readSize) 함수를 이용해 json 파일을 읽어 들인다.

 

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
/* json_file.c */
#include <stdio.h>
#include <json/json.h>
 
char *readFile(char *filename, int *readSize);
int main(int argc, char **argv)
{
 int size// 문서 크기
 char *doc = readFile("classesinfo.json"&size);
 json_object *myobj, *rootobj, *contentobj;
 json_object *dobj, *dval;
 int i;
 
 /* JSON type의 데이터를 읽는다. */
 myobj = json_tokener_parse(doc);
 
 rootobj = json_object_object_get(myobj, "dataSearch");
 printf("%s\n", json_object_to_json_string(rootobj));
 return(0);
}
/*
 * 파일을 읽어서 내용을 반환
 */
char *readFile(char *filename, int *readSize)
 FILE *fp = fopen(filename, "rb");
 if (fp == NULL)
 return NULL;
 int size;
 char *buffer;
 // 파일 크기 구하기
 fseek(fp, 0, SEEK_END);
 size = ftell(fp);
 fseek(fp, 0, SEEK_SET);
 buffer = malloc(size + 1); // 파일크기+NULL 공간
 memset(buffer, 0size + 1);
 if (fread(buffer, size1, fp) < 1)
 {
 *readSize = 0;
 free(buffer);
 fclose(fp);
 return NULL;
 }
 // 파일 크기를 넘겨줌
 *readSize = size;
 fclose(fp); // 파일 포인터 닫기
 return buffer;
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

 

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
/* json_file.c
 * - json_object_object_get() 으로 객체를 읽어 들인다.
 *
 * gcc -o jsoncfile jsonc_file.c -I/usr/local/include/json-c -
ljson-c
 */
#include <stdio.h>
#include <json/json.h>
 
char *readFile(char *filename, int *readSize);
int main(int argc, char **argv)
{
 int size// 문서 크기
 char *doc = readFile("classesinfo.json"&size);
 json_object *myobj, *rootobj, *contentobj;
 json_object *dobj, *dval;
 int i;
 
 /* JSON type의 데이터를 읽는다. */
 myobj = json_tokener_parse(doc);
 
 rootobj = json_object_object_get(myobj, "dataSearch");
 contentobj = json_object_object_get(rootobj, "content");
 printf("----------------- contentobj
--------------------------\n");
 printf("%s\n", json_object_to_json_string(contentobj));
 
 return(0);
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

 

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
/* json_file.c
 * - json_object_object_get() 으로 배열 내부의 객체를 읽어 들인다.
 *
 * gcc -o jsoncfile jsonc_file.c -I/usr/local/include/json-c -
ljson-c
 */
int main(int argc, char **argv)
{
 int size// 문서 크기
 char *doc = readFile("classesinfo.json"&size);
 json_object *myobj, *rootobj, *contentobj;
 json_object *dobj, *dval;
 int i;
 
 /* JSON type의 데이터를 읽는다. */
 myobj = json_tokener_parse(doc);
 
 rootobj = json_object_object_get(myobj, "dataSearch");
 contentobj = json_object_object_get(rootobj, "content");
 // printf("%s\n", json_object_to_json_string(contentobj));
 
 for (i = 0; i < json_object_array_length(contentobj); i++)
 {
 //인덱스 위치
 dobj = json_object_array_get_idx(contentobj, i);
 // mClass 추출
 dval = json_object_object_get(dobj, "mClass");
 printf(" %s\n", json_object_get_string(dval));
 }
 return(0);
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

+ Recent posts