嵌入式Linux网络编程实战:基于libcurl实现Gitee文件上传
【本文代码已在 立创·泰山派 平台验证通过,可直接用于物联网设备数据上报场景】
一、功能概述与实现效果
1.1 核心功能
- 📁 本地文件读取:支持任意二进制/文本文件
- 🔒 Base64编码转换:符合RFC 4648标准
- 📡 HTTP传输:通过libcurl实现,也可以使用HTTPS加密通信
- 🚀 Gitee API对接:自动创建/更新仓库文件
1.2 运行效果演示
# 上传本地文件到Gitee仓库
$ ./gitee test.txt test.txt
文件大小: 15 字节, 读取内容: Hello Gitee!
base64编码结果: SGVsbG8gR2l0ZWUh
文件: test.txt 上传成功
# 浏览器查看你的Gitee仓库
https://gitee.com/
二、开发环境配置
2.1 使用的硬件
设备(泰山派) | 配置 |
---|---|
主控芯片 | RK3566 |
CPU | 四核Cortex-A55 |
架构 | arm64 |
系统镜像 | Linux |
空间 | 2+16G版本 |
网络 | 支持HTTP、HTTPS通信 |
2.2 软件依赖
buildroot环境:
在编译好的SDK目录下找到buildroot目录,执行 make menuconfig
去设置开启。具体操作:立创·泰山派:Buildroot开启libcurl+OpenSSL教程。
ubuntu环境下:
# 安装libcurl开发库
sudo apt update
sudo apt install libcurl4-openssl-dev
# 验证安装
curl-config --version # 应显示7.64.0或更高
# 查找头文件路径,一般都在/c/include/路径下能找到
sudo find /usr -name "*curl.h"
三、代码深度解析
3.1 核心模块架构
3.2 关键技术实现
🔑 Gitee API认证
// 构造携带access_token的POST数据
snprintf(post_fields, sizeof(post_fields),
"{\"access_token\": \"%s\", \"message\": \"libcurl上传\", \"content\": \"%s\"}",
ACCESS_TOKEN, base64_content);
📤 文件Base64编码
char *base64_encode(unsigned char *src, size_t len) {
// RFC 4648标准实现
unsigned int combined = (byte0 << 16) | (byte1 << 8) | byte2;
encodedata[j++] = base64_table[(combined >> 18) & 0x3f];
// ...(完整编码逻辑见代码)
}
🔗 libcurl HTTPS配置
curl_easy_setopt(curl, CURLOPT_URL, url); // 设置API地址
curl_easy_setopt(curl, CURLOPT_POST, 1L); // 启用POST方法
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); // 添加JSON头
四、快速使用指南
4.1 获取Gitee访问令牌
- 登录Gitee → 个人设置 → 私人令牌
- 生成新令牌(需勾选
projects
权限) - 修改代码中的
ACCESS_TOKEN
值
4.2 编译与运行
嵌入式环境下(给出makefile)
# makefile文件
# 编译器设置
CROSS_COMPILE := aarch64-linux-gnu-
CC := $(CROSS_COMPILE)gcc
# 编译和链接选项
CFLAGS = -Wall -g
LDFLAGS = -lcurl # curl 网络库
# 系统根路径和库路径设置
SYSROOT_PATH = --sysroot=/你的SDK路径/buildroot/output/rockchip_rk3566/host/aarch64-buildroot-linux-gnu/sysroot
INCLUDE_PATH = -I/你的SDK路径/buildroot/output/rockchip_rk3566/host/aarch64-buildroot-linux-gnu/sysroot/usr/include
LIBRARY_PATH = -L/你的SDK路径/buildroot/output/rockchip_rk3566/host/aarch64-buildroot-linux-gnu/sysroot/usr/lib
# 目标文件
TARGETS = gitee
# 默认目标
all: $(TARGETS)
gitee: gitee_client.c
$(CC) $(CFLAGS) $(SYSROOT_PATH) $(INCLUDE_PATH) $(LIBRARY_PATH) -o $@ $^ $(LDFLAGS)
clean:
rm -f $(TARGETS)
.PHONY: all clean
# 1. 编译
make gitee
# 2.把可执行文件gitee传递到开发板上,并给其其执行权限
# 连接之后在电脑终端使用adb push,在开发板终端使用chmod +x gitee
# 3. 上传文件(使用默认远程文件名)
./gitee local_file.txt
# 4. 指定远程文件名
./gitee local_file.txt remote_file.txt
虚拟机环境下
# 1. 编译程序
gcc gitee_client.c -o gitee -lcurl
# 2. 上传文件(使用默认远程文件名)
./gitee local_file.txt
# 3. 指定远程文件名
./gitee local_file.txt remote_file.txt
五、环境增强建议
5.1 安全增强
// 启用SSL证书验证(需配置CA证书路径)
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(curl, CURLOPT_CAINFO, "/etc/ssl/certs/ca-certificates.crt");
5.2 性能优化
- 连接复用:保持HTTP长连接
- 异步传输:使用libcurl多线程接口
- 内存池:避免频繁malloc/free
5.3 功能扩展
// 多文件上传支持
for(int i=1; i<argc; i++) {
upload_file(argv[i]);
}
// 添加进度回调
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progress_callback);
六、常见问题排查
6.1 401 Unauthorized错误
# 检查项
1. ACCESS_TOKEN是否过期
2. 令牌权限是否包含仓库写入(repo)
3. 服务器时间是否同步(NTP校准)
6.2 数据截断问题
// 增大POST缓冲区
#define POST_BUFFER_SIZE 8192 // 原为1024
char post_fields[POST_BUFFER_SIZE] = {0};
6.3 内存泄漏检测
valgrind --tool=memcheck --leak-check=full ./gitee_uploader test.txt
七、完整代码获取
#include <stddef.h> // 标准类型定义(如size_t)
#include <stdio.h> // 标准输入输出
#include <string.h> // 字符串操作
#include <stdlib.h> // 动态内存管理
#include <curl/curl.h> // libcurl核心库
#include <curl/easy.h> // libcurl易用接口
/* 配置区 - 根据实际情况修改以下参数 */
// Gitee个人访问令牌(需repo权限)
#define ACCESS_TOKEN "你生成gitee个人访问令牌" 这里要改,形如:35cbfb00bbb1c4b4581346b......
#define OWNER "仓库所有者用户名" 这里要改
#define REPO "仓库名" 这里要改
#define REMOTE_FILE_PATH "test.txt" // 默认仓库目标文件名
/* 错误处理宏:打印错误信息并退出程序 */
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
/* Base64编码表(RFC 4648标准) */
static const char base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/**
* @brief Base64编码函数
* @param src 原始二进制数据指针
* @param len 原始数据长度(字节)
* @return 编码后的字符串指针(需调用者释放内存)
* @note 返回的字符串包含标准Base64填充字符'='
*/
static char *base64_encode(unsigned char *src, size_t len)
{
if(len == 0){
return NULL; // 空输入直接返回
}
// 计算编码后的长度(公式:4*(n/3)向上取整)
size_t outputlen = 4*((len+2)/3);
char *encodedata = (char *)malloc(outputlen+1); // +1用于终止符
if(!encodedata){
perror("malloc");
return NULL;
}
size_t i,j;
for(i=0,j=0;i < len;){
/* 每次处理3字节数据 */
unsigned int byte0 = src[i++]; // 第1字节
unsigned int byte1 = (i<len) ? src[i++] : 0; // 第2字节(不足则补0)
unsigned int byte2 = (i<len) ? src[i++] : 0; // 第3字节(不足则补0)
// 将3字节合并为24位
unsigned int combined = (byte0 << 16) | (byte1 << 8) | byte2;
/* 拆分24位为4个6位索引,查表编码 */
encodedata[j++] = base64_table[(combined >> 18) & 0x3f];
encodedata[j++] = base64_table[(combined >> 12) & 0x3f];
encodedata[j++] = base64_table[(combined >> 6) & 0x3f];
encodedata[j++] = base64_table[combined & 0x3f];
}
/* 处理尾部填充 */
switch (len % 3) {
case 1: // 剩余1字节:补两个=
encodedata[j - 2] = '=';
encodedata[j - 1] = '=';
break;
case 2: // 剩余2字节:补一个=
encodedata[j - 1] = '=';
break;
}
encodedata[j] = '\0'; // 终止符
return encodedata;
}
/**
* @brief 读取文件内容到内存
* @param filename 文件名
* @param outsize 输出参数,返回文件大小
* @return 文件内容指针(需调用者释放内存)
*/
static unsigned char *read_file(const char *filename, size_t *outsize)
{
// 以二进制模式打开,避免文本模式转换
FILE *fp = fopen(filename, "rb");
if(fp == NULL){
perror("fopen");
return NULL;
}
// 获取文件大小
fseek(fp, 0, SEEK_END);
long filesize = ftell(fp);
rewind(fp);
// 分配内存(+1可选,用于文本终止符)
unsigned char *buf = (unsigned char *)malloc(filesize);
if(!buf){
fclose(fp);
perror("malloc");
return NULL;
}
// 读取完整文件
size_t readsize = fread(buf, 1, filesize, fp);
fclose(fp); // 及时关闭文件描述符
// 校验读取完整性
if(readsize != filesize){
free(buf);
return NULL;
}
*outsize = readsize;
return buf;
}
/**
* @brief 主函数
* @param argc 参数个数(至少需要1个参数)
* @param argv 参数列表:
* argv[0] - 程序名
* argv[1] - 本地文件路径(必需)
* argv[2] - 远程文件路径(可选,默认使用REMOTE_FILE_PATH)
*/
int main(int argc, char *argv[])
{
/* 参数校验 */
if(argc < 2){
fprintf(stderr,"用法:%s <本地文件路径> [远程文件路径]\n",argv[0]);
return EXIT_FAILURE;
}
/* 初始化libcurl全局环境 */
curl_global_init(CURL_GLOBAL_ALL);
int num_files = argc - 1; // 实际文件参数个数
size_t file_size = 0;
/* 读取本地文件 */
unsigned char *file_content = read_file(argv[1], &file_size);
if(!file_content){
fprintf(stderr, "read file: %s failed\n", argv[1]);
return EXIT_FAILURE;
}
printf("文件大小: %ld 字节, 读取内容: %s\n", file_size, file_content);
/*Base64编码文件内容*/
char *base64_content = base64_encode(file_content, file_size);
if(!base64_content){
fprintf(stderr, "base64 encode file: %s failed\n", argv[1]);
return EXIT_FAILURE;
}
printf("base64 编码结果: %s\n", base64_content);
/*构造 Gitee API URL*/
char url[512];
if(num_files >= 2){ // 指定远程文件名
snprintf(url, sizeof(url), "https://gitee.com/api/v5/repos/%s/%s/contents/%s",
OWNER,REPO,argv[2]);
printf("gitee保存名: %s\n", argv[2]);
}else{ // 使用默认远程文件名
snprintf(url, sizeof(url), "https://gitee.com/api/v5/repos/%s/%s/contents/%s",
OWNER,REPO,REMOTE_FILE_PATH);
printf("gitee保存名: %s\n", REMOTE_FILE_PATH);
}
/*
构造JSON请求体(注意字段顺序), 参数说明
- access_token: Gitee 访问令牌
- message: 提交信息
- content: 文件内容的 Base64 编码
*/
char post_fields[1024] = {0};
snprintf(post_fields, sizeof(post_fields),
"{\"access_token\": \"%s\", \"message\": \"libcurl 上传文件\", \"content\": \"%s\"}",
ACCESS_TOKEN, base64_content);
free(base64_content); // 及时释放编码数据
/* 初始化CURL句柄 */
CURL *curl = curl_easy_init();
if(!curl){
handle_error("curl_easy_init");
}
/* 设置 HTTP 请求头: Content-Type 为 application/json */
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Content-Type: application/json");
/* 配置CURL选项 */
curl_easy_setopt(curl, CURLOPT_URL, url); // API地址
curl_easy_setopt(curl, CURLOPT_POST, 1L); // POST请求
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_fields); // JSON数据
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); // 请求头
// curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); // 调试可启用
/* 执行HTTP请求 */
CURLcode res = curl_easy_perform(curl);
if(res != CURLE_OK){
fprintf(stderr, "curl_easy_perform() 失败: %s\n", curl_easy_strerror(res));
}else{
printf("文件: %s 上传成功\n", argv[1]);
}
/* 清理资源 */
curl_slist_free_all(headers); // 释放请求头列表
curl_easy_cleanup(curl); // 释放CURL句柄
curl_global_cleanup(); // 清理libcurl全局环境
return EXIT_SUCCESS;
}
扩展阅读:
Gitee OpenAPI文档 | libcurl多线程编程指南
通过本实现方案,开发者可以快速构建嵌入式设备与代码托管平台的安全通信通道。建议在关键业务场景中添加断点续传和自动重试机制,后续可扩展对接GitHub/GitLab等平台实现多平台同步。