文章目录
运行环境
Ubuntu20.04 虚拟机
Mysql 8.0 API libmysqlclient-dev
gcc 9.4.0
数据库中可以存储一些小型的资源文件,例如图片和声音文件。较大的资源文件应该存储在服务器中,在数据库中写文件索引。
MYSQL 中有个数据对象是 BLOB,即 Binary Large Object,顾名思义也就是二进制大型数据对象,用来记录二进制的数据,它有 4 种类型,分别是:tinyblob(255B)、blob(65KB)、mediumblob(16MB)、longblob(4GB)。
一、准备工作
在数据库中创建一张准备存储文件的表:
CREATE DATABASE C_DB;
USE C_DB;
CREATE TABLE C_IMG(
ID INT PRIMARY KEY AUTO_INCREMENT,#主键且自增
Images blob
);
二、建立与mysql的连接
1、在C文件中引入头文件
在C文件中引入头文件#inlude<mysql.h>
通过MYSQL mysql;
创建一个mysql结构体对象
MYSQL
结构体定义在mysql.h
里
#include<mysql.h>
#include<stdio.h>
#include<string.h>
int main()
{
MYSQL mysql;
}
2、初始化mysql与数据库的通道
mysql_init
函数初始化失败会返回NULL
mysql_error
是mysql API提供的标准错误
if(mysql_init(&mysql) == NULL)
{
printf("%s",mysql_error(&mysql));
return -1;
}
3、与mysql建立真实连接
先在文件最前面定义一些宏,方便后面使用
#define C_DB_SERVER_IP "192.168.186.128" //要连接的数据库IP
#define C_DB_SERVER_PORT 3306 //要连接的端口号
#define C_DB_USERNAME "admin" //mysql用户名
#define C_DB_PASSWORD "admin" //mysql密码
#define C_DB_DEFAULT_DB "C_IMG" //连接后默认使用的数据库
//参数依次为 mysql通道,
//ip,用户名,
//密码,数据库名,
//端口号,socket(设置为NULL的时候表示不适用socket),客户端标识符
//mysql_real_connect函数若返回0表示则表示连接失败
if(!mysql_real_connect(&mysql,
C_DB_SERVER_IP, C_DB_USERNAME,
C_DB_PASSWORD, C_DB_DEFAULT_DB,
C_DB_SERVER_PORT,NULL, 0))
{
printf("mysql_real_connect : %s\n",mysql_error(&mysql));
return -2;
}
此时已经通过C代码与MYSQL数据库进行了连接,下一步便可以开始进行数据库的操作
三、硬盘中文件的读写
1硬盘中读取文件
//硬盘读取文件
int read_image(char *filename, char *buffer)
{
//检测文件和文件二进制容器buffer是否存在
if(filename == NULL || buffer == NULL) return -1;
//定义文件指针 指向文件最开始 rb是以 读二进制 的方式打开
FILE *fp = fopen(filename,"rb");
if(fp == NULL)
{
printf("fopen failed\n");
return -2;
}
//检测文件大小
fseek(fp, 0, SEEK_END); //把文件指针置到文件末尾
int fileLen = ftell(fp); //获取文件偏移量
fseek(fp, 0 ,SEEK_SET); //把文件指针置到文件开头
//获取文件的二进制长度
//buffer为接收数据的地址 size为一个单元的大小 count为单元个数 stream为文件流。
//返回实际读取的单元个数。如果小于count,则可能文件结束或读取出错;
int fileSize = fread(buffer, 1, fileLen, fp);
//因为是每次读取一个单位,所以fileLen应该等于fileSize
if(fileSize != fileLen)
{
printf("fread failed : %d\n", fileSize);
return -3;
}
//如果读取到的大小超过了FILE_MAX_SIZE 也就是超过了数据库中blob的65KB则返回错误
if(fileSize >= FILE_MAX_SIZE)
{
printf("file size is too big\n");
return -4;
}
fclose(fp);
return fileSize;
}
2文件写入硬盘
//硬盘写入文件
int write_image(char *filename, char *buffer, int fileLen)
{
if(filename == NULL || buffer == NULL) return -1;
//定义文件指针 指向文件最开始 wb+是以 写二进制 的方式打开 +号表示如果没有这个文件便创建
FILE *fp = fopen(filename,"wb+");
if(fp == NULL)
{
printf("fopen failed");
return -2;
}
//由二进制的方式写入文件
//buffer为接收数据的地址 size为一个单元的大小 count为单元个数 stream为文件流。
//返回实际读取的单元个数。如果小于count,则可能文件结束或读取出错;
int length = fwrite(buffer, 1, fileLen, fp);
if(fileLen != length)
{
printf("fwite failed: %d\n",length);
return -3;
}
fclose(fp);
return length;
}
四、数据库中文件的读写
1二进制文件写入数据库
关于mysql C API的详细内容请在mysql官网查询
https://www.mysqlzh.com/doc/196/116.html
//二进制文件写入数据库
int mysql_write(MYSQL *mysql, char *buffer, int fileLen, char *sql)
{
if(mysql == NULL || buffer == NULL || fileLen <= 0) return -1;
//初始化预处理语句句柄
/*
MYSQL_BIND该结构用于语句输入(发送给服务器的数据值)和输出(从服务器返回的结果值)。
对于输入,它与mysql_stmt_bind_param()一起使用,用于将参数数据值绑定到缓冲区上,以供mysql_stmt_execute()使用。
对于输出,它与mysql_stmt_bind_result()一起使用,用于绑定结果缓冲区,以便用于with mysql_stmt_fetch()以获取行。
*/
MYSQL_STMT *stmt = mysql_stmt_init(mysql);
//mysql_stmt_prepare 成功返回0
int ret = mysql_stmt_prepare(stmt, sql, strlen(sql));
if(ret)
{
printf("mysql_stmt_prepare : %s\n", mysql_error(mysql));
return -2;
}
MYSQL_BIND param = {0};
param.buffer_type = MYSQL_TYPE_BLOB;
param.buffer = NULL;
param.length = NULL;
//mysql_stmt_bind_param()一起使用,用于将参数数据值绑定到缓冲区上,以供mysql_stmt_execute()使用
ret = mysql_stmt_bind_param(stmt, ¶m);
if(ret)
{
printf("mysql_stmt_bind_param : %s\n", mysql_error(mysql));
return -3;
}
//允许应用程序分段地(分块)将参数数据发送到服务器。
// 可以多次调用该函数,以便发送关于某一列的字符或二进制数据的不同部分,列必须是TEXT或BLOB数据类型之一。
//第二个参数指明了与数据关联的参数。参数从0开始编号。
//第三个参数是指向包含将要发送的数据的缓冲区的指针,第四个参数指明了缓冲区内的字节数。
ret = mysql_stmt_send_long_data(stmt, 0, buffer, fileLen);
if(ret)
{
printf("mysql_stmt_bind_param : %s\n", mysql_error(mysql));
return -4;
}
//执行与语句句柄相关的预处理查询。
ret = mysql_stmt_execute(stmt);
if(ret)
{
printf("mysql_stmt_execute : %s\n", mysql_error(mysql));
return -5;
}
//预处理句柄关闭
ret = mysql_stmt_close(stmt);
if(ret)
{
printf("mysql_stmt_close : %s\n", mysql_error(mysql));
return -6;
}
return ret;
}
2从数据库读取二进制文件
//从数据库读取二进制文件
int mysql_read(MYSQL *mysql, char *buffer, int fileLen, char *sql)
{
if(mysql == NULL || buffer == NULL || fileLen <= 0 || sql == NULL) return -1;
MYSQL_STMT stmt = mysql_stmt_init(mysql);
int ret = mysql_stmt_prepare(stmt, sql, strlen(sql));
if(ret)
{
printf("mysql_stmt_prepare : %s\n", mysql_error(mysql));
return -2;
}
unsigned long total_length = 0; //计数器
/*
MYSQL_BIND该结构用于语句输入(发送给服务器的数据值)和输出(从服务器返回的结果值)。
对于输入,它与mysql_stmt_bind_param()一起使用,用于将参数数据值绑定到缓冲区上,以供mysql_stmt_execute()使用。
对于输出,它与mysql_stmt_bind_result()一起使用,用于绑定结果缓冲区,以便用于with mysql_stmt_fetch()以获取行。
*/
MYSQL_BIND result = {0};
result.buffer_type = MYSQL_TYPE_BLOB;
result.length = &total_length;
//mysql_stmt_bind_result()用于将结果集中的列与数据缓冲和长度缓冲关联(绑定)起来
//当调用mysql_stmt_fetch()以获取数据时,
//MySQL客户端/服务器协议会将绑定列的数据置于指定的缓冲区内。
mysql_stmt_bind_result(stmt, &result);
if(ret)
{
printf("mysql_stmt_bind_result : %s",mysql_error(mysql));
return -3;
}
//执行 mysql_stmt_execute后结果在管道里面
mysql_stmt_execute(stmt);
if(ret)
{
printf("mysql_stmt_execute : %s\n", mysql_error(mysql));
return -4;
}
//对于成功生成结果集的所有语句(SELECT、SHOW、DESCRIBE、EXPLAIN),
//仅当打算对客户端的全部结果集进行缓冲处理时,必须调用mysql_stmt_store_result(),以便后续的mysql_stmt_fetch()调用能返回缓冲数据。
ret = mysql_stmt_store_result(stmt);
if(ret)
{
printf("mysql_stmt_store_result : %s\n", mysql_error(mysql));
return -5;
}
while (1)
{
ret = mysql_stmt_fetch(stmt);
//如果没抓取到数据,或者抓取到了数据碎片
if (ret != 0 && ret != MYSQL_DATA_TRUNCATED) break;
int start = 0;
while (start < (int)total_length)
{
result.buffer = buffer + start;
result.buffer_length = 1;
/*mysql_stmt_fetch_column从当前结果集行获取1列。
参数二提供了应将数据置于其中的缓冲。
参数三指明了将获取哪个列。第1列编号为0。
参数四是数据值内的偏移量,将从该处开始检索数据。
可将其用于获取碎片形式的数据值。值开始部分的偏移量为0。
*/
mysql_stmt_fetch_column(stmt, &result, 0, start);
start += result.buffer_length;
}
}
mysql_stmt_close(stmt);
return total_length;
}
完整代码
#include<mysql.h>
#include<stdio.h>
#include<string.h>
#define C_DB_SERVER_IP "192.168.186.128" //要连接的数据库IP
#define C_DB_SERVER_PORT 3306 //要连接的端口号
#define C_DB_USERNAME "admin" //mysql用户名
#define C_DB_PASSWORD "admin" //mysql密码
#define C_DB_DEFAULT_DB "C_DB" //连接后默认使用的数据库
#define FILE_MAX_SIZE 65*1024 //文件最大大小 65KB
//硬盘读取文件
int read_image(char *filename, char *buffer)
{
//检测文件和文件二进制容器buffer是否存在
if(filename == NULL || buffer == NULL) return -1;
//定义文件指针 指向文件最开始 rb是以 读二进制 的方式打开
FILE *fp = fopen(filename,"rb");
if(fp == NULL)
{
printf("fopen failed\n");
return -2;
}
//检测文件大小
fseek(fp, 0, SEEK_END); //把文件指针置到文件末尾
int fileLen = ftell(fp); //获取文件偏移量
fseek(fp, 0 ,SEEK_SET); //把文件指针置到文件开头
//获取文件的二进制长度
//buffer为接收数据的地址 size为一个单元的大小 count为单元个数 stream为文件流。
//返回实际读取的单元个数。如果小于count,则可能文件结束或读取出错;
int fileSize = fread(buffer, 1, fileLen, fp);
//因为是每次读取一个单位,所以fileLen应该等于fileSize
if(fileSize != fileLen)
{
printf("fread failed : %d\n", fileSize);
return -3;
}
//如果读取到的大小超过了FILE_MAX_SIZE 也就是超过了数据库中blob的65KB则返回错误
if(fileSize >= FILE_MAX_SIZE)
{
printf("file size is too big\n");
return -4;
}
fclose(fp);
return fileSize;
}
//硬盘写入文件
int write_image(char *filename, char *buffer, int fileLen)
{
if(filename == NULL || buffer == NULL) return -1;
//定义文件指针 指向文件最开始 wb+是以 写二进制 的方式打开 +号表示如果没有这个文件便创建
FILE *fp = fopen(filename,"wb+");
if(fp == NULL)
{
printf("fopen failed");
return -2;
}
//由二进制的方式写入文件
//buffer为接收数据的地址 size为一个单元的大小 count为单元个数 stream为文件流。
//返回实际读取的单元个数。如果小于count,则可能文件结束或读取出错;
int length = fwrite(buffer, 1, fileLen, fp);
if(fileLen != length)
{
printf("fwite failed: %d\n",length);
return -3;
}
fclose(fp);
return length;
}
//文件以二进制方式写入数据库
int mysql_write(MYSQL *mysql, char *buffer, int fileLen, char *sql)
{
if(mysql == NULL || buffer == NULL || fileLen <= 0 || sql == NULL) return -1;
//初始化预处理语句句柄
/*
MYSQL_BIND该结构用于语句输入(发送给服务器的数据值)和输出(从服务器返回的结果值)。
对于输入,它与mysql_stmt_bind_param()一起使用,用于将参数数据值绑定到缓冲区上,以供mysql_stmt_execute()使用。
对于输出,它与mysql_stmt_bind_result()一起使用,用于绑定结果缓冲区,以便用于with mysql_stmt_fetch()以获取行。
*/
MYSQL_STMT *stmt = mysql_stmt_init(mysql);
//mysql_stmt_prepare 成功返回0
int ret = mysql_stmt_prepare(stmt, sql, strlen(sql));
if(ret)
{
printf("mysql_stmt_prepare : %s\n", mysql_error(mysql));
return -2;
}
MYSQL_BIND param = {0};
param.buffer_type = MYSQL_TYPE_BLOB;
param.buffer = NULL;
param.length = NULL;
//mysql_stmt_bind_param()一起使用,用于将参数数据值绑定到缓冲区上,以供mysql_stmt_execute()使用
ret = mysql_stmt_bind_param(stmt, ¶m);
if(ret)
{
printf("mysql_stmt_bind_param : %s\n", mysql_error(mysql));
return -3;
}
//mysql_stmt_send_long_data允许应用程序分段地(分块)将参数数据发送到服务器。
//可以多次调用该函数,以便发送关于某一列的字符或二进制数据的不同部分,列必须是TEXT或BLOB数据类型之一。
//第二个参数指明了与数据关联的参数。参数从0开始编号。
//第三个参数是指向包含将要发送的数据的缓冲区的指针,第四个参数指明了缓冲区内的字节数。
ret = mysql_stmt_send_long_data(stmt, 0, buffer, fileLen);
if(ret)
{
printf("mysql_stmt_bind_param : %s\n", mysql_error(mysql));
return -4;
}
//执行与语句句柄相关的预处理查询。
ret = mysql_stmt_execute(stmt);
if(ret)
{
printf("mysql_stmt_execute : %s\n", mysql_error(mysql));
return -5;
}
//预处理句柄关闭
ret = mysql_stmt_close(stmt);
if(ret)
{
printf("mysql_stmt_close : %s\n", mysql_error(mysql));
return -6;
}
return ret;
}
//从数据库读取二进制文件
int mysql_read(MYSQL *mysql, char *buffer, int fileLen, char *sql)
{
if(mysql == NULL || buffer == NULL || fileLen <= 0 || sql == NULL) return -1;
MYSQL_STMT *stmt = mysql_stmt_init(mysql);
int ret = mysql_stmt_prepare(stmt, sql, strlen(sql));
if(ret)
{
printf("mysql_stmt_prepare : %s\n", mysql_error(mysql));
return -2;
}
unsigned long total_length = 0; //计数器
/*
MYSQL_BIND该结构用于语句输入(发送给服务器的数据值)和输出(从服务器返回的结果值)。
对于输入,它与mysql_stmt_bind_param()一起使用,用于将参数数据值绑定到缓冲区上,以供mysql_stmt_execute()使用。
对于输出,它与mysql_stmt_bind_result()一起使用,用于绑定结果缓冲区,以便用于with mysql_stmt_fetch()以获取行。
*/
MYSQL_BIND result = {0};
result.buffer_type = MYSQL_TYPE_BLOB;
result.length = &total_length;
//mysql_stmt_bind_result()用于将结果集中的列与数据缓冲和长度缓冲关联(绑定)起来
//当调用mysql_stmt_fetch()以获取数据时,
//MySQL客户端/服务器协议会将绑定列的数据置于指定的缓冲区内。
mysql_stmt_bind_result(stmt, &result);
if(ret)
{
printf("mysql_stmt_bind_result : %s",mysql_error(mysql));
return -3;
}
//执行 mysql_stmt_execute后结果在管道里面
mysql_stmt_execute(stmt);
if(ret)
{
printf("mysql_stmt_execute : %s\n", mysql_error(mysql));
return -4;
}
//对于成功生成结果集的所有语句(SELECT、SHOW、DESCRIBE、EXPLAIN),
//仅当打算对客户端的全部结果集进行缓冲处理时,必须调用mysql_stmt_store_result(),以便后续的mysql_stmt_fetch()调用能返回缓冲数据。
ret = mysql_stmt_store_result(stmt);
if(ret)
{
printf("mysql_stmt_store_result : %s\n", mysql_error(mysql));
return -5;
}
while (1) {
ret = mysql_stmt_fetch(stmt);
//如果没抓取到数据,或者抓取到了数据碎片
if (ret != 0 && ret != MYSQL_DATA_TRUNCATED) break;
int start = 0;
while (start < (int)total_length) {
result.buffer = buffer + start;
result.buffer_length = 1;
/*mysql_stmt_fetch_column从当前结果集行获取1列。
参数二提供了应将数据置于其中的缓冲。
参数三指明了将获取哪个列。第1列编号为0。
参数四是数据值内的偏移量,将从该处开始检索数据。
可将其用于获取碎片形式的数据值。值开始部分的偏移量为0。
*/
mysql_stmt_fetch_column(stmt, &result, 0, start);
start += result.buffer_length;
}
}
mysql_stmt_close(stmt);
return total_length;
}
int main(int argc,char *argv[])
{
MYSQL mysql;
if(mysql_init(&mysql) == NULL)
{
printf("%s",mysql_error(&mysql));
return -1;
}
//参数依次为 mysql通道,
//ip,用户名,
//密码,数据库名,
//端口号,socket(设置为NULL的时候表示不适用socket),客户端标识符
//mysql_real_connect函数若返回0表示则表示连接失败
if(!mysql_real_connect(&mysql,C_DB_SERVER_IP,
C_DB_USERNAME,C_DB_PASSWORD,
C_DB_DEFAULT_DB,C_DB_SERVER_PORT,
NULL,0))
{
printf("mysql_real_connect : %s\n",mysql_error(&mysql));
return -2;
}
char buffer[FILE_MAX_SIZE] = {0};
char sql[] = "INSERT C_IMG(ID, Images) VALUES(1,?)";
// write_image("a.jpg", buffer, read_image(argv[1], buffer));
int size = read_image(argv[1], buffer);
mysql_write(&mysql, buffer, size, sql);
char sql1[] = "SELECT Images FROM C_IMG WHERE ID='1'";
memset(buffer, 0, FILE_MAX_SIZE);
size = mysql_read(&mysql, buffer, size, sql1);
write_image("out.jpg", buffer, size);
}