Neil's Digital Garden

Mysql实战——二进制数据读写

· Neil

Mysql提供了许多C API函数,借此我们可以使用实现一个客户端(位于NS)用于对mysql进行增删查改,最终实现图片数据的读写访问功能

一、增删查改

几个常见SQL语句

  • SELECT
  • INSERT
  • DELETE 这些可以通过#define宏定义的方式定义出来,避免每次都需要将SQL命令重新输入,也有更好的可读性。

增删查改的API函数 实现select:int neil_mysql_select(MYSQL *handle) 实现insert:int mysql_write(MYSQL *handle, char *buffer, int length)

值得注意的是,以上三个函数都拥有一个叫做handle的参数,名字叫做MYSQL连接句柄。有了它,我们才可以从NS连接到数据库,并且进行我们想要的操作,并且当操作不成功的时候,他可以通过mysql_error(handle)返回错误值。

1.1 select查询函数

函数mysql_real_query()可用于执行SQL语句,其返回值是int(只是为了方便错误处理),真正的内容传递依赖于MYSQL连接句柄,在mysql_real_query中我们想要的查询语句直接作为一个参数传入此函数即可,配合mysql_store_result(handle),我们可以将服务器的内容存储到客户端的内存当中,其载体是MYSQL_RES,不过我们并不需要了解它的细节。

当我们希望按照行和列打印所返回内容的时候,使用mysql_fetch_row和mysql_fetch_fields,把MYSQl_RES这个结构体的实例作为参数传递进去即可。

int neil_mysql_select(MYSQL *handle) {
  if (mysql_real_query(handle, SQL_SELECT_TBL_USER, strlen(SQL_SELECT_TBL_USER))) {
    printf("mysql_real_query: %s\n", mysql_error(handle));
  }

  MYSQL_RES *res = mysql_store_result(handle);
  if (res == NULL) {
    printf("mysql_real_query: %s\n", mysql_error(handle));
    return -2;
  }

  int rows = mysql_num_rows(res);
  printf("rows: %d\n", rows);

  int fields = mysql_num_fields(res);
  printf("fields: %d\n",fields);

  //取出并且显示结果
  MYSQL_ROW row;
  while ((row = mysql_fetch_row(res))) {

    int i = 0;
    for (i = 0; i < fields; i++) {
      printf("%s\t", row[i]);
    }
    printf("\n");
  }
  
  
  mysql_free_result(res);

  return 0;
}

1.2 二进制数据写入和读取函数

读和写的实现是镜像地,所以我们只要搞懂其中一个就能知道另一个该如何去编程。与之前select函数所使用的mysql_real_query不同,现在我们尝试采用预处理语句的方式。 预处理语句句柄:MYSQL_STMT,预处理语句好处:

  • 安全性:有效防止注入攻击
  • 高性能:重复的查询,亦只需要一次编译
  • 二进制数据:更好支持大二进制数据类型BLOB

使用的时候先使用mysql_stmt_init来初始化预处理语句句柄MYSQL_STMT,然后使用mysql_stmt_prepare将句柄关联到我们的目标sql语句,这里还有一个特别的步骤,使用mysql_stmt_bind_param进行参数绑定,这是为了提前告诉服务器所需要传递参数的类型。

MYSQL_BIND参数说明:

  • buffer_type = MYSQL_TYPE_LONG_BLOB - 指定参数为二进制大对象类型
  • buffer = NULL - 不直接提供数据缓冲区,使用分段发送
  • is_null = 0 - 明确表示该参数不是NULL值
  • length = NULL - 长度信息将在mysql_stmt_send_long_data中提供

之后的代码就是用mysql_stmt_execut执行预处理语句,即分段发送二进制数据,完成后关闭即可。

int mysql_write(MYSQL *handle, char *buffer, int length) {

  if(handle == NULL || buffer == NULL || length <= 0) return -1;
  //statement
  MYSQL_STMT *stmt = mysql_stmt_init(handle);
  int ret = mysql_stmt_prepare(stmt, SQL_INSERT_IMG_USER, strlen(SQL_INSERT_IMG_USER));
  if (ret) {
    printf("mysql_stmt_prepar: %s\n", mysql_error(handle));
    return -2;
  }

  MYSQL_BIND param = {0};
  param.buffer_type = MYSQL_TYPE_LONG_BLOB;
  param.buffer = NULL;
  param.is_null = 0;
  param.length = NULL;

  ret = mysql_stmt_bind_param(stmt, &param);
  if (ret) {
    printf("mysql_stmt_bind_param: %s\n", mysql_error(handle));
    return -3;
  }
  //分段将数据发到服务器
  ret = mysql_stmt_send_long_data(stmt, 0, buffer, length);
    if (ret) {      
      printf("mysql_stmt_send_long_data: %s\n", mysql_error(handle));
      return -4;  
    }
  //执行:插入到服务器里
  ret = mysql_stmt_execute(stmt);
  if (ret) {
    printf("mysql_stmt_execute: %s\n", mysql_error(handle));
    return -5;  
  }
  //关闭
  ret = mysql_stmt_close(stmt);
  if (ret) {
    printf("mysql_stmt_close: %s\n", mysql_error(handle));
    return -6;  
  }

  return ret;
}

二、图片读写

我们的目标是提供一个接口,用于在主函数main当中调用,可以直接操作我们想要的图片文件。

2.1 图片写入

int read_image(char *filename, char *buffer) {
  if (filename == NULL || buffer == NULL) return -1;
  
    FILE *fp = fopen(filename, "rb"); //fp指向文件的开头
    if (fp == NULL) {
      printf("fopen failed\n");
      return -2;
    }

    //file size
    //下面这个函数将fp指向末尾
    fseek(fp, 0, SEEK_END);
    int length = ftell(fp);//偏移量
    fseek(fp, 0, SEEK_SET);

    int size = fread(buffer, 1, length, fp);
    if (size != length) {
      printf("fread failed:%d\n", size);
      return -3;
    }

    fclose(fp);

    return size;
}

技术阐释:以上函数的作用就是从filename这个路径中获取我们想要的文件,并将其导入到buffer当中,然后,我们之前实现的二进制数据处理函数就可以顺利地从buffer当中找到需要发送到Mysql服务器的二进制图片数据。

你可能注意到了,从图片格式到二进制数据流这个转换的过程究竟是如何完成的?—答案是fopen,因为我们指定了一个fopen中的第二个参数为“rb”:

  • r代表read only,只读
  • b代表二进制

如果不指定b这个参数,则默认使用文本模式导入,一定要注意的是,图片格式一定不可以用文本模式导入,这会导致一些严重的格式问题,比如说换行符的转换。

文件模式说明:

  • "rb" - 二进制只读模式(图片读取)
  • "wb" - 二进制只写模式,会清空原文件
  • "wb+" - 二进制读写模式,会清空原文件 对于图片保存,使用"wb"更为合适

2.2 图片读取

几乎和图片写入对称,不多赘述。

int write_image(char *filename, char *buffer, int length) {
  if (filename == NULL || buffer == NULL || length <= 0) return -1;

  FILE *fp = fopen(filename, "wb+"); //fp指向文件的开头
  if (fp == NULL) {
    printf("fopen failed\n");
    return -2;
  }

  int size = fwrite(buffer, 1, length, fp);
  if (size != length) {
    printf("fwrite failed: %d\n", size);
    return -3;
  }

  fclose(fp);
  
  return size;
  
}