c语言实现http下载功能,显示进度条和下载速率

#include <stdio.h>//printf
#include <string.h>//字符串处理
#include <sys/socket.h>//套接字
#include <arpa/inet.h>//ip地址处理
#include <fcntl.h>//open系统调用
#include <unistd.h>//write系统调用
#include <netdb.h>//查询DNS
#include <stdlib.h>//exit函数
#include <sys/stat.h>//stat系统调用获取文件大小
#include <sys/time.h>//获取下载时间
//下载地址   http://dldir1.qq.com/qqfile/qq/QQ7.9/16638/QQ7.9.exe
struct HTTP_RES_HEADER//保持相应头信息
{
    int status_code;//HTTP/1.1 '200' OK
    char content_type[128];//Content-Type: application/gzip
    long content_length;//Content-Length: 11683079
};

void parse_url(const char *url, char *host, int *port, char *file_name)
{
    /*通过url解析出域名, 端口, 以及文件名*/
    int j = 0;
    int start = 0;
    *port = 80;
    char *patterns[] = {"http://", "https://", NULL};

    for (int i = 0; patterns[i]; i++)//分离下载地址中的http协议
        if (strncmp(url, patterns[i], strlen(patterns[i])) == 0)
            start = strlen(patterns[i]);

    //解析域名, 这里处理时域名后面的端口号会保留
    for (int i = start; url[i] != '/' && url[i] != '\0'; i++, j++)
        host[j] = url[i];
    host[j] = '\0';

    //解析端口号, 如果没有, 那么设置端口为80
    char *pos = strstr(host, ":");
    if (pos)
        sscanf(pos, ":%d", port);

    //删除域名端口号
    for (int i = 0; i < (int)strlen(host); i++)
    {
        if (host[i] == ':')
        {
            host[i] = '\0';
            break;
        }
    }

    //获取下载文件名
    j = 0;
    for (int i = start; url[i] != '\0'; i++)
    {
        if (url[i] == '/')
        {
            if (i !=  strlen(url) - 1)
                j = 0;
            continue;
        }
        else
            file_name[j++] = url[i];
    }
    file_name[j] = '\0';
}

struct HTTP_RES_HEADER parse_header(const char *response)
{
    /*获取响应头的信息*/
    struct HTTP_RES_HEADER resp;

    char *pos = strstr(response, "HTTP/");
    if (pos)//获取返回代码
        sscanf(pos, "%*s %d", &resp.status_code);

    pos = strstr(response, "Content-Type:");
    if (pos)//获取返回文档类型
        sscanf(pos, "%*s %s", resp.content_type);

    pos = strstr(response, "Content-Length:");
    if (pos)//获取返回文档长度
        sscanf(pos, "%*s %ld", &resp.content_length);

    return resp;
}

void get_ip_addr(char *host_name, char *ip_addr)
{
    /*通过域名得到相应的ip地址*/
    struct hostent *host = gethostbyname(host_name);//此函数将会访问DNS服务器
    if (!host)
    {
        ip_addr = NULL;
        return;
    }

    for (int i = 0; host->h_addr_list[i]; i++)
    {
        strcpy(ip_addr, inet_ntoa( * (struct in_addr*) host->h_addr_list[i]));
        break;
    }
}


void progress_bar(long cur_size, long total_size, double speed)
{
    /*用于显示下载进度条*/
    float percent = (float) cur_size / total_size;
    const int numTotal = 50;
    int numShow = (int)(numTotal * percent);

    if (numShow == 0)
        numShow = 1;

    if (numShow > numTotal)
        numShow = numTotal;

    char sign[51] = {0};
    memset(sign, '=', numTotal);

    printf("\r%.2f%%[%-*.*s] %.2f/%.2fMB %4.0fkb/s", percent * 100, numTotal, numShow, sign, cur_size / 1024.0 / 1024.0, total_size / 1024.0 / 1024.0, speed);
    fflush(stdout);

    if (numShow == numTotal)
        printf("\n");
}

unsigned long get_file_size(const char *filename)
{
    //通过系统调用直接得到文件的大小
    struct stat buf;
    if (stat(filename, &buf) < 0)
        return 0;
    return (unsigned long) buf.st_size;
}

void download(int client_socket, char *file_name, long content_length)
{
    /*下载文件函数*/
    long hasrecieve = 0;//记录已经下载的长度
    struct timeval t_start, t_end;//记录一次读取的时间起点和终点, 计算速度
    int mem_size = 8192;//缓冲区大小8K
    int buf_len = mem_size;//理想状态每次读取8K大小的字节流
    int len;

    //创建文件描述符
    int fd = open(file_name, O_CREAT | O_WRONLY, S_IRWXG | S_IRWXO | S_IRWXU);
    if (fd < 0)
    {
        printf("文件创建失败!\n");
        exit(0);
    }

    char *buf = (char *) malloc(mem_size * sizeof(char));

    //从套接字流中读取文件流
    long diff = 0;
    int prelen = 0;
    double speed;

    while (hasrecieve < content_length)
    {
        gettimeofday(&t_start, NULL ); //获取开始时间
        len = read(client_socket, buf, buf_len);
        write(fd, buf, len);
        gettimeofday(&t_end, NULL ); //获取结束时间

        hasrecieve += len;//更新已经下载的长度

        //计算速度
        if (t_end.tv_usec - t_start.tv_usec >= 0 &&  t_end.tv_sec - t_start.tv_sec >= 0)
            diff += 1000000 * ( t_end.tv_sec - t_start.tv_sec ) + (t_end.tv_usec - t_start.tv_usec);//us

        if (diff >= 1000000)//当一个时间段大于1s=1000000us时, 计算一次速度
        {
            speed = (double)(hasrecieve - prelen) / (double)diff * (1000000.0 / 1024.0);
            prelen = hasrecieve;//清零下载量
            diff = 0;//清零时间段长度
        }

        progress_bar(hasrecieve, content_length, speed);

        if (hasrecieve == content_length)
            break;
    }
}

int main(int argc, char const *argv[])
{
    /* 命令行参数: 接收两个参数, 第一个是下载地址, 第二个是文件的保存位置和名字, 下载地址是必须的, 默认下载到当前目录
     * 示例: ./download http://www.baidu.com baidu.html
     */
    char url[2048] = "127.0.0.1";//设置默认地址为本机,
    char host[64] = {0};//远程主机地址
    char ip_addr[16] = {0};//远程主机IP地址
    int port = 80;//远程主机端口, http默认80端口
    char file_name[256] = {0};//下载文件名

    if (argc == 1)
    {
        printf("您必须给定一个http地址才能开始工作\n");
        exit(0);
    }
    else
        strcpy(url, argv[1]);

    puts("1: 正在解析下载地址...");
    parse_url(url, host, &port, file_name);//从url中分析出主机名, 端口号, 文件名

    if (argc == 3)
    {
        printf("\t您已经将下载文件名指定为: %s\n", argv[2]);
        strcpy(file_name, argv[2]);
    }

    puts("2: 正在获取远程服务器IP地址...");
    get_ip_addr(host, ip_addr);//调用函数同访问DNS服务器获取远程主机的IP
    if (strlen(ip_addr) == 0)
    {
        printf("错误: 无法获取到远程服务器的IP地址, 请检查下载地址的有效性\n");
        return 0;
    }

    puts("\n>>>>下载地址解析成功<<<<");
    printf("\t下载地址: %s\n", url);
    printf("\t远程主机: %s\n", host);
    printf("\tIP 地 址: %s\n", ip_addr);
    printf("\t主机PORT: %d\n", port);
    printf("\t 文件名 : %s\n\n", file_name);

    //设置http请求头信息
    char header[2048] = {0};
    sprintf(header, \
            "GET %s HTTP/1.1\r\n"\
            "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n"\
            "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537(KHTML, like Gecko) Chrome/47.0.2526Safari/537.36\r\n"\
            "Host: %s\r\n"\
            "Connection: keep-alive\r\n"\
            "\r\n"\
        ,url, host);

    puts("3: 创建网络套接字...");
    int client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (client_socket < 0)
    {
        printf("套接字创建失败: %d\n", client_socket);
        exit(-1);
    }

    //创建IP地址结构体
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(ip_addr);
    addr.sin_port = htons(port);

    //连接远程主机
    puts("4: 正在连接远程主机...");
    int res = connect(client_socket, (struct sockaddr *) &addr, sizeof(addr));
    if (res == -1)
    {
        printf("连接远程主机失败, error: %d\n", res);
        exit(-1);
    }

    puts("5: 正在发送http下载请求...");
    write(client_socket, header, strlen(header));//write系统调用, 将请求header发送给服务器


    int mem_size = 4096;
    int length = 0;
    int len;
    char *buf = (char *) malloc(mem_size * sizeof(char));
    char *response = (char *) malloc(mem_size * sizeof(char));

    //每次单个字符读取响应头信息
    puts("6: 正在解析http响应头...");
    while ((len = read(client_socket, buf, 1)) != 0)
    {
        if (length + len > mem_size)
        {
            //动态内存申请, 因为无法确定响应头内容长度
            mem_size *= 2;
            char * temp = (char *) realloc(response, sizeof(char) * mem_size);
            if (temp == NULL)
            {
                printf("动态内存申请失败\n");
                exit(-1);
            }
            response = temp;
        }

        buf[len] = '\0';
        strcat(response, buf);

        //找到响应头的头部信息
        int flag = 0;
        for (int i = strlen(response) - 1; response[i] == '\n' || response[i] == '\r'; i--, flag++);
        if (flag == 4)//连续两个换行和回车表示已经到达响应头的头尾, 即将出现的就是需要下载的内容
            break;

        length += len;
    }

    struct HTTP_RES_HEADER resp = parse_header(response);

    printf("\n>>>>http响应头解析成功:<<<<\n");

    printf("\tHTTP响应代码: %d\n", resp.status_code);
    if (resp.status_code != 200)
    {
        printf("文件无法下载, 远程主机返回: %d\n", resp.status_code);
        return 0;
    }
    printf("\tHTTP文档类型: %s\n", resp.content_type);
    printf("\tHTTP主体长度: %ld字节\n\n", resp.content_length);


    printf("7: 开始文件下载...\n");
    download(client_socket, file_name, resp.content_length);
    printf("8: 关闭套接字\n");

    if (resp.content_length == get_file_size(file_name))
        printf("\n文件%s下载成功! ^_^\n\n", file_name);
    else
    {
        remove(file_name);
        printf("\n文件下载中有字节缺失, 下载失败, 请重试!\n\n");
    }
    shutdown(client_socket, 2);//关闭套接字的接收和发送
    return 0;
}

运行效果:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/119119.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java版小程序商城免费搭建-直播商城平台规划及常见的营销模式有哪些?电商源码/小程序/三级分销

1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis 3. 前端框架…

【机器学习2】模型评估

模型评估主要分为离线评估和在线评估两个阶段。 针对分类、 排序、 回归、序列预测等不同类型的机器学习问题&#xff0c; 评估指标的选择也有所不同。 1 评估指标 1.1准确率 准确率是指分类正确的样本占总样本个数的比例 但是准确率存在明显的问题&#xff0c;比如当负样本…

深度学习(CNN+RNN)笔记2

文章目录 第五课&#xff1a;序列模型(Sequence Models)第一周&#xff1a;循环神经网络&#xff08;Recurrent Neural Networks&#xff09;【序列模型、语言模型序列生成、对新序列采样。RNN、GRU、LSTM、双向RNN、深度RNN】第二周&#xff1a;自然语言处理与词嵌入&#xff…

easyConnect虚拟网卡未安装,导致连接失败(虚拟网卡安装失败)

前言 使用easyConnect&#xff0c;但是一直连接失败&#xff0c;看到提示错误 虚拟网卡未安装&#xff0c;请确保虚拟网卡安装成功 我的错误原因是因为我自己装过VM虚拟机&#xff0c;用过虚拟网卡然后产生的虚拟网卡冲突 解决方式 1.打开网络设置2.选择你的网络&#xff08…

OpenAI首席科学家:ChatGPT已经出现意识,人类未来将与AI融合

OpenAI首席科学家在最近的专访中抛出了很多惊人言论。在他看来&#xff0c;ChatGPT背后的神经网络已经产生了意识&#xff0c;而且未来人类会与人工智能融合起来&#xff0c;出现新的形态。而他现在工作的重点&#xff0c;已经不是去创建那个必然会出现的通用人工智能&#xff…

从零开始搭建React+TypeScript+webpack开发环境-使用iconfont构建图标库

创建iconfont项目 进入iconfont官网&#xff0c;完成注册流程&#xff0c;即可创建项目。 无法访问iconfont可尝试将电脑dns改为阿里云镜像223.5.5.5和223.6.6.6 添加图标 在图标库里选择图标&#xff0c;加入购物车 将图标添加到之前创建的项目中 生成代码 将代码配置到项目…

Modbus转Profinet网关在暖通空调系统中应用案例

在过去&#xff0c;空调系统一般采用传统的控制方式&#xff0c;通常需要使用独立的控制模块和传感器来监测和控制温度、湿度等参数。这种传统的控制方式不仅复杂&#xff0c;而且容易出现故障和误差&#xff0c;给用户的使用和维护带来了一定的困扰。 然而&#xff0c;通过P…

Linux 实现原理 — NUMA 多核架构中的多线程调度开销与性能优化

前言 NOTE&#xff1a;本文中所指 “线程” 均为可执行调度单元 Kernel Thread。 NUMA 体系结构 NUMA&#xff08;Non-Uniform Memory Access&#xff0c;非一致性存储器访问&#xff09;的设计理念是将 CPU 和 Main Memory 进行分区自治&#xff08;Local NUMA node&#x…

立冬将至,别忘记吃饺子!Don‘t forget to eat dumplings

立冬通常是每年的11月7日或8日。每年这个时候&#xff0c;河水就开始结冰。“Winter Begins” arrives on November 7 or November 8 each year. At this time of the year, some rivers in China start to freeze. 立冬是冬季的第一个节气&#xff0c;进入这一时节&#xff0…

电机应用-直流有刷电机

目录 直流有刷电机 工作原理 直流有刷减速电机的重要参数 电路原理与分析 驱动芯片分析 L298N驱动芯片 直流有刷减速电机控制实现 控制速度原理 硬件设计 L298N 野火直流有刷电机驱动板-MOS管搭建板 软件设计1&#xff1a;两个直流有刷减速电机按键控制 开发设计 …

Android Studio(项目打包成APK)

打包流程 直接上图即可 按照上面操作后&#xff0c;即可以开始打包&#xff0c;一般第一次打包都需要几分钟&#xff08;我第一次打包花了七八分钟&#xff09;&#xff0c;如果打包错误了也别担心&#xff0c;可以查看错误分析一下原因&#xff0c;实在不行可以把错误放到网站…

ElasticSearch 实现 全文检索 支持(PDF、TXT、Word、HTML等文件)通过 ingest-attachment 插件实现 文档的检索

一、Attachment 介绍 Attachment 插件是 Elasticsearch 中的一种插件&#xff0c;允许将各种二进制文件&#xff08;如PDF、Word文档等&#xff09;以及它们的内容索引到 Elasticsearch 中。插件使用 Apache Tika 库来解析和提取二进制文件的内容。通过使用 Attachment 插件&a…

Qt全局定义

一、QtGlobal头文件 头文件中包含了Qt类库的一些全局定义&#xff0c;包括&#xff1a; 基本数据类型全局函数宏定义 二、基本数据类型 三、全局函数 四、宏定义 1.Qt版本相关的宏 1.1 QT_VERSION 这个宏展开为数值形式 0xMMNNPP (MM major, NN minor, PP patch) 表示…

Hadoop知识点全面总结

文章目录 什么是HadoopHadoop发行版介绍Hadoop版本演变历史Hadoop3.x的细节优化Hadoop三大核心组件介绍HDFS体系结构NameNode介绍总结 SecondaryNameNode介绍DataNode介绍DataNode总结 MapReduce介绍分布式计算介绍MapReduce原理剖析MapReduce之Map阶段MapReduce之Reduce阶段 实…

Verilog HDL语言基础知识

目录 Verilog HDL语言基础知识 6.1.2 Verilog HDL模块的结构 6.1.3 逻辑功能定义 6.2.1 常量 6.3 运算符及表达式 6.4.2 条件语句 Verilog HDL语言基础知识 先来看两个Verilog HDL程序。 例6.1 一个8位全加器的 Verilog HDL源代码 module adder8(cout,sum,ina,…

Si4010 一款带有MCU SoC RF发射机芯片 无线遥控器

Si4010是一款完全集成的SoC RF发射机&#xff0c;带有嵌入式CIP-51 8051 MCU&#xff0c;专为1GHz以下ISM频带设计。该芯片针对电池供电的应用进行了优化&#xff0c;工作电压为1.8至3.6 V&#xff0c;待机电流小于10 nA的超低电流消耗。高功率放大器可提供高达10 dBm的输出功率…

pytorch复现_UNet

什么是UNet U-Net由收缩路径和扩张路径组成。收缩路径是一系列卷积层和汇集层&#xff0c;其中要素地图的分辨率逐渐降低。扩展路径是一系列上采样层和卷积层&#xff0c;其中特征地图的分辨率逐渐增加。 在扩展路径中的每一步&#xff0c;来自收缩路径的对应特征地图与当前特征…

前端框架Vue学习 ——(四)Axios

文章目录 Axios 介绍Axios 入门Vue项目中使用 Axios Axios 介绍 介绍: Axios 对原生的 Ajax 进行了封装&#xff0c;简化书写&#xff0c;快速开发。&#xff08;异步请求&#xff09; 官网: https://www.axios-http.cn/ 官网介绍&#xff1a;Axios 是一个基于 promise 网络请…

@Tag和@Operation标签失效问题。SpringDoc 2.2.0(OpenApi 3)和Spring Boot 3.1.1集成

问题 Tag和Operation标签失效 但是Schema标签有效 pom依赖 <!-- 接口文档--><!--引入openapi支持--><dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-ui</artifactId><vers…

多测师肖sir_高级金牌讲师_jenkins搭建

jenkins操作手册 一、jenkins介绍 1、持续集成&#xff08;CI&#xff09; Continuous integration 持续集成 团队开发成员每天都有集成他们的工作&#xff0c;通过每个成员每天至少集成一次&#xff0c;也就意味着一天有可 能多次集成。在工作中我们引入持续集成&#xff0c;通…