嵌入式Linux网络编程实战:基于libcurl实现Gitee文件上传


嵌入式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 核心模块架构

读取本地文件
Base64编码
构造JSON请求
libcurl发送POST
处理服务器响应

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访问令牌

  1. 登录Gitee → 个人设置 → 私人令牌
  2. 生成新令牌(需勾选projects权限)
  3. 修改代码中的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等平台实现多平台同步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

银河码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值