offfff

[MySQL C API] 8. 데이터베이스에 이미지 저장하기(Inserting images into MySQL database) 본문

프로그래밍

[MySQL C API] 8. 데이터베이스에 이미지 저장하기(Inserting images into MySQL database)

offfff 2016. 8. 18. 09:00

이미지는 바이너리(binary) 데이터이다.

MySQL은 바이너리 데이터를 저장할 때, BLOB 타입을 사용한다.

BLOP은 Binary Large Object의 약어이다.

BLOP 타입은 소팅이나 INDEX생성은 할 수 없다.



1. 테이블 추가


MySQL을 실행하고, 아래 명령어를 실행한다.


mysql> CREATE TABLE Images(Id INT PRIMARY KEY, Data MEDIUMBLOB);


MEDIUMBLOB 타입 데이터를 저장할 수 있는 Images 테이블을 생성한다.

BLOB 타입은 indexing을 할 수 없으므로,

INT형 Id 필드를 만들고 PRIMARY KEY로 선언한다.

MEDIUMBLOB 타입에서 이미지는 16MB까지 저장할 수 있다.

이외에도 TINYBLOB(255 Bytes), BLOB(64 KB), LONGBLOP(4GB) 타입이 있다.




2. 소스코드


그림 파일 하나를 Images 테이블에 저장하는 소스코드이다.


#include <my_global.h>

#include <mysql.h>

#include <string.h>


void finish_with_error(MYSQL *con)

{

fprintf(stderr, "%s \n", mysql_error(con));

mysql_close(con);

exit(1);

}


int main(int argc, char** argv)

{

// picture.jpg read binary(rb)모드로 파일을 연다.

// 실행파일과 같은 경로에 사진이 있어야 한다.

FILE *fp = fopen("picture.jpg", "rb");

if (fp == NULL) {

fprintf(stderr, "cannot open image file \n");

exit(1);

}


// 파일 포인터를 파일의 끝으로 옮긴다.

fseek(fp, 0, SEEK_END);


if (ferror(fp)) {

fprintf(stderr, "fseek() failed \n");

int r = fclose(fp);


if (r == EOF) {

fprintf(stderr, "cannot close file handler \n");

}

exit(1);

}


// 파일의 처음부터 파일 포인터가 가리키는 곳까지의 크기를 flen에 저장

int flen = ftell(fp);

if (flen == -1) {

perror("error occurred");

int r = fclose(fp);

if (r == EOF) {

fprintf(stderr, "cannot close file handler \n");

}

exit(1);

}


// 파일 포인터를 파일의 시작으로 옮김

fseek(fp, 0, SEEK_SET);

if (ferror(fp)) {

fprintf(stderr, "fseek() failed \n");

int r = fclose(fp);


if (r == EOF) {

fprintf(stderr, "cannot close file handler \n");

}

exit(1);

}


// 이미지 데이터를 저장할 배열 동적할당

char* data = (char *) malloc(sizeof(char) * (flen+1));


// 이미지 저장 함, 저장된 바이트 수가 size에 반환 됨

int size = fread(data, 1, flen, fp);

if (ferror(fp)) {

fprintf(stderr, "fread() failed \n");

int r = fclose(fp);

if (r == EOF) {

fprintf(stderr, "cannot close file handler \n");

}

exit(1);

}


// 파일 닫기

int r = fclose(fp);

if (r == EOF) {

fprintf(stderr, "cannot close file handler \n");

}


// MYSQL 구조체 초기화

MYSQL *con = mysql_init(NULL);

if (con == NULL) {

fprintf(stderr, "mysql_init() failed \n");

exit(1);

}


// 서버 연결

if (mysql_real_connect(con, "localhost", "user01", "1q2w3e!", "testdb",

0, NULL, 0) == NULL)

{

finish_with_error(con);

}


char* chunk = (char *)malloc(sizeof(char) * (2*size + 1));

mysql_real_escape_string(con, chunk, data, size);


char *st = "INSERT INTO Images(Id, Data) VALUES(1, '%s')";

size_t st_len = strlen(st);


char *query = (char *)malloc(sizeof(char) * (st_len + 2*size + 1));

int len = snprintf(query, st_len + 2*size + 1, st, chunk);


if (mysql_real_query(con, query, len)) {

finish_with_error(con);

}


mysql_close(con);

exit(0);

}



char* chunk = (char *)malloc(sizeof(char) * (2*size + 1));

mysql_real_escape_string(con, chunk, data, size);


binary data에서 중간에 삽입된 종결문자를 없애는 과정이다.

종결문자에 \, ', ", Ctrl+z 등이 있지만 아래에서는 \만으로 설명한다.

mysql_real_escape_string()함수는 문자열의 종료를 의미하는 '\0' 앞에

\를 붙여 '\\0'으로 만든다.


또한 이 함수를 사용함으로써 SQL injection attack도 예방할 수 있다.

(SQL injection attack 참고 : https://opentutorials.org/module/411/3962)


\를 붙이는 작업 때문에 최악의 경우 원래의 데이터의 2배짜리 버퍼가 필요하다

따라서 data버퍼 size의 2배 +1 만큼의 길이를 chunk에 할당한다.



char *st = "INSERT INTO Images(Id, Data) VALUES(1, '%s')";

size_t st_len = strlen(st);


쿼리문을 구성하고, strlen 함수를 통해 이 쿼리문의 길이를 구해 st_len에 저장.



char *query = (char *)malloc(sizeof(char) * (st_len + 2*size + 1));

int len = snprintf(query, st_len + 2*size + 1, st, chunk);


최종적으로 MySQL 서버에 보낼 쿼리문을 구성하는 과정이다.

앞서 구했던 '쿼리문의 길이'와 'chunk의 길이'를 합한

'st_len + 2*size + 1'만큼을 동적할당 한다.

그리고 snprintf를 통해서 query버퍼에 st와 chunk를 가지고

이미지 저장을 요청하는 쿼리문을 완성한다.


if (mysql_real_query(con, query, len)) {

finish_with_error(con);

}


이전까지는 mysql_query() 함수를 통해 데이터를 넣었었다.

mysql_query() 함수는 binary 데이터가 포함된 쿼리를 처리하지 못한다.

여기서는 binary 데이터를 취급하기 때문에 mysql_real_query() 함수를 사용한다.





아래 링크를 참고하여 번역 및 수정함 

http://zetcode.com/db/mysqlc/



다른 참조

http://jidolstar.tistory.com/681


http://www.mysqlkorea.com/sub.html?mcode=manual&scode=01&m_no=21872&cat1=22&cat2=596&cat3=606&lang=k