11.1.7 cpp客户端上传测试和文件引用计数测试

发布于:2025-09-06 ⋅ 阅读:(19) ⋅ 点赞:(0)

1 修改数据库file_info和user_file_list表

DROP TABLE IF EXISTS `file_info`;
CREATE TABLE `file_info` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '文件序号,自动递增,主键',
    `md5` varchar(256) NOT NULL COMMENT '文件md5',
    `file_id` varchar(256) NOT NULL COMMENT '文件id:/group1/M00/00/00/xxx.png',
    `url` varchar(512) NOT NULL COMMENT '文件url 192.168.52.139:80/group1/M00/00/00/xxx.png',
    `size` bigint(20) DEFAULT '0' COMMENT '文件大小, 以字节为单位',
    `type` varchar(32) DEFAULT '' COMMENT '文件类型: png, zip, mp4……',
    `count` int(11) DEFAULT '0' COMMENT '文件引用计数,默认为1。每增加一个用户拥有此文件,此计数器+1',
    PRIMARY KEY (`id`),
    UNIQUE KEY `uq_md5` (`md5`)
    -- KEY `uq_md5` (`md5`(8)) -- 前缀索引
) ENGINE=InnoDB AUTO_INCREMENT=70 DEFAULT CHARSET=utf8 COMMENT='文件信息表';

将KEY uq_md5 ( md5 (8))改为UNIQUE KEY uq_md5 ( md5 ) 修改原因:

  • 普通的KEY uq_md5 ( md5 (8)前缀索引,可以存在md5行记录,与我们的初衷 一个相同内容的文件只存 储一份相悖。
  • 如果加上UNIQUE KEY uq_md5 ( md5 (8)),如果插入的数据的前缀与已有数据的前缀冲突,会抛出唯一 约束冲突错误,这样就导致很多不同的文件不能插入进来

基于以上原因,这个md5这个字段改为完整的唯一索引。

修改user_file_list,添加 user , md5 , file_name 唯一约束

DROP TABLE IF EXISTS `user_file_list`;
CREATE TABLE `user_file_list` (
    `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
    `user` varchar(32) NOT NULL COMMENT '文件所属用户',
    `md5` varchar(256) NOT NULL COMMENT '文件md5',
    `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '文件创建时间',
    `file_name` varchar(128) DEFAULT NULL COMMENT '文件名字',
    `shared_status` int(11) DEFAULT NULL COMMENT '共享状态, 0为没有共享, 1为共享',
    `pv` int(11) DEFAULT NULL COMMENT '文件下载量,默认值为0,下载一次加1',
    -- `type` varchar(32) DEFAULT '' COMMENT '文件类型: png, zip, mp4……',
    PRIMARY KEY (`id`),
    UNIQUE KEY `idx_user_md5_file_name` (`user`,`md5`, `file_name`)
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8 COMMENT='用户文件列表';

将KEY idx_user_md5_file_name ( user , md5 , file_name ) 改为UNIQUE KEY idx_user_md5_file_name ( user , md5 , file_name ),主要是将其改成UNIQUE 唯一索引,即是同一个 人,不允许有多个文件名同样,并且文件内容一样的的多条记录。

2 理解multi-data上传原理

multipart/form-data 是用于在 HTTP 请求中上传文件的一种编码方式。以下是其工作原理的总结:

2.1 编码格式

多部分消息体: multipart/form-data 将表单数据分成多个部分(parts),每个部分对应一个表单 字段或文件。

边界分隔符:各部分通过唯一的边界分隔符(boundary)分隔,边界符由客户端生成,确保不会与数 据内容冲突。

2.2 请求结构

请求头: Content-Type 设置为 multipart/form-data ,并包含边界符,如:

Content-Type: multipart/form-data; boundary=----
WebKitFormBoundary7MA4YWxkTrZu0gW

消息体:每个部分包含:

  • 头部信息:如 Content-Disposition 指定字段名和文件名, Content-Type 指定文件类型。
  • 数据内容:字段值或文件二进制数据。

2.3 请求体布局

multipart/form-data 请求体的布局如下:

POST /upload HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="username"

JohnDoe
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="token"

xxxxxxx
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="example.txt"
Content-Type: text/plain

[文件二进制数据]
------WebKitFormBoundary7MA4YWxkTrZu0gW--

媒体类型 multipart/form-data 相对于其他媒体类型如 application/x-www-form-urlencoded 等来说, 最明显的不同点是:

  • 请求头的 Content-Type 属性除了指定为 multipart/form-data ,还需要定义 boundary 参数
  • 请求体中的请求行数据是由多部分组成, boundary 参数的值模式 --${Boundary} 用于分隔每个独立 的分部
  • 每个部分必须存在请求头 Content-Disposition: form-data; name="${PART_NAME}"; ,这里的 ${PART_NAME} 需要进行 URL 编码,另外 filename 字段可以使用,用于表示文件的名称,但是其约束 性比 name 属性低(因为并不确认本地文件是否可用或者是否有异议)
  • 每个部分可以单独定义 Content-Type 和该部分的数据体
  • 请求体以 boundary 参数的值模式 --${Boundary}-- 作为结束标志

Boundary 参数取值规约如下:

  • Boundary 的值必须以英文中间双横杠 -- 开头,这个 -- 称为前导连字符
  • Boundary 的值除了前导连字符以外的部分不能超过 70 个字符
  • Boundary 的值不能包含 HTTP 协议或者 URL 禁用的特殊意义的字符,例如英文冒号 : 等
  • 每个 --${Boundary} 之前默认强制必须为 CRLF

2.4 服务器处理

  • 解析请求:服务器根据边界符解析各部分,提取字段和文件数据。
  • 存储文件:文件数据通常保存到磁盘或存储系统。
  • 处理字段:普通字段数据用于进一步处理。

3 文件引用计数 加减测试

1. 原子操作更新引用计数:

  • 使用数据库提供的原子操作来增加或减少 count 字段。例如,使用 UPDATE file_info SET count = count + 1 WHERE file_id = ? 来增加引用计数。
  • 这种操作通常是原子的,能避免多线程环境下的数据不一致问题。

2. 涉及file_info 的修改,删除,新增时加锁,这里封装了一个FileInfoLock的类,目的是方便后续多服务 器部署时,可以直接把FileInfoLock修改为分布式锁。

  • .handleSaveFile:转存文件时 文件引用计数+1 这里可以考虑加锁 
  • handleDeleteFile :删除文件的时候 文件引用计数-1,如果减为0删除file_info 以及删除文件
  • handleDealMd5 秒传,增加引用计数
  • storeFileinfo 增加记录,可能本来已经存在该md5的记录,但其任务又想删除该纪录,此时如果 失败,如果是因为记录已经存在则此时应该增加计数,并删除自己此次上传的文件,

冲突在于

  • 某个人要删除 文件,此时file_info -1,还有可能涉及到删除文件
  • 某个人要转存 此时file_info+1,
  • 多人同时上传同一个文件,但最终只能同时创建一个文件。

代码对应:tc_http_srv7 做了加锁的操作修改,大家可以根据client/2-upload的代码 写一个客户端多线程的 测试程序:

1. 清空0voice_tuchuang的记录,并注册用户1/用户2/用户3/用户4/用户5 五个用户。

2. 用户1/用户2/用户3/用户4/用户5 多线程上传 进行文件上传测试, 准备 10个不同文件内容的文件,文 件不用太大,比如1字节即可,但10个文件的内容不能相同,反复进行上传测试。

文件名可以为1.txt 2.txt 3.txt 4.txt 5.txt 6.txt 7.txt 8.txt 9.txt 10.txt。 文件内容分别为: 1 2 3 4 5 6 7 8 9 10,已经放到tc-src/client/3-upload/test_files 目录。

3. 用户1/用户2/用户3/用户4/用户5 进行多线程 删除文件测试(都删除 10个不同的文件),反复进行删 除测试。

4. 最后先退出文件上传测试, 然后再退出文件删除测试, 最后分析 file_info的记录是否被删除,分析 fastdfs对应的文件是否被删除。

参考链接:0voice · GitHub


网站公告

今日签到

点亮在社区的每一天
去签到