Linux环境:C编程实战——实现文件下载

本文介绍了一个使用C语言在Linux环境下构建的文件下载服务,采用C/S模式,服务端通过进程池处理客户端请求,确保了服务的稳定性。文件传输过程中,通过TCP连接并发送文件名、大小及每次传输的字节数,保证数据准确性。客户端能实时显示下载进度,并通过动态进度条提供可视化反馈。服务端有序退出时,子进程能优雅地关闭。
摘要由CSDN通过智能技术生成

设计目标

  • 采用C/S模式,服务端发送文件,多个客户端可以同时下载文件
  • 服务端的进程不会因为客户端的操作(意外退出)崩溃
  • 关闭服务端时各个子进程可以有序退出
  • 需要设计协议保证文件传输不出差错
  • 客户端可以动态查看文件下载进度

设计思路

服务端请求响应
  • 服务端采用进程池模式,父进程监听服务端口,创建多个子进程负责传输文件,有客户端请求时,建立连接,然后唤醒一个阻塞的子进程处理。
  • 父进程通过维护一个子进程状态队列来实时更新各个子进程的状态。
  • 采用传递socket描述符的方式使子进程可以直接与客户端通信。
  • 父子进程之间通过一对匿名套接字通信,有新请求时父进程写套接字,通知子进程开始工作,子进程传输完毕后,写套接字通知父进程已经工作完毕进入阻塞状态
  • 父进程采用epoll模型监控各个子进程的通信套接字和监听端口的套接字:
    • 监听端口套接字可读,代表有新的下载请求,查找空闲子进程交付任务,更新子进程状态队列
    • 子进程套接字可读,代表有子进程完成工作,更新子进程状态队列
服务端进程退出
  • 注册信号处理函数实现有序退出,收到信号后查找子进程状态队列,杀死第一个空闲子进程,标记该进程已注销,将其监控事件从epoll监控实例中删除,关闭套接字,打印提示。同时处理epoll_wait函数由于监控对象变化而返回的errno == 4错误,直接忽略即可。
文件传输
  • 通过tcp连接进行文件传输,每次传输数据之前应先发送本次传输的字节数,使得客户端不至于多读或少读数据。
  • 正式传输之前需要传输文件名和文件大小,方便客户端创建文件和计算下载进度。
  • 服务端子进程每次send后需要检查返回值来确定客户端的状态,如果返回-1则打印提示客户端断开连接,重新阻塞。
  • 文件传输结束后发送一个0代表传输结束
  • 客户端每次先读一个int型的控制数据代表接下来要读的字节数,然后再读数据。如果控制数据为0代表传输结束。
  • 需要注意的是,recv的第四个参数要设置为阻塞,MSG_WAITALL,即不满足所读长度时就一直阻塞;
  • 客户端每次读完数据都要和本次数据的控制数据对比,查看长度是否一致,不一致证明传输出差错,打印提示信息,退出客户端,重新进行下载。
客户端信息显示
  • 客户端每成功接收一组数据,显示当前的下载进度百分比,通过\r回到行首来覆盖重写。
  • 客户端根据下载进度打印若个标志字符来显示动态进度条。
  • 可以 通过获取当前窗口的winsize结构体来确定需要打印的进度条长度,从而适应不同大小的窗口。
  • 参考链接:linux获取终端窗口的大小方法

代码实现

头文件
#pragma once
#include <fun.h> //包含各种需要用到的标准库和Linux库和一个检查返回值的宏定义,省略

#define FILENAME "test.txt"
//子进程状态信息数据结构

typedef struct{

    int pid;//进程号	

    int fd;	//通信套接字
 
    int busy;//进程状态,0代表阻塞,1代表运行,-1代表死亡

}pro_data;

// 用来传输文件的数据结构
typedef struct{

    int len;//控制信息,提示接下来的字节长度

    char p[1024];//真正的数据信息

}Train_t;

//创建num子进程,传出参数p保存各个子进程的状态信息队列
int fork_child(int num,pro_data * p);

//向传入的套接字传输文件
int transp(int);

//子进程工作函数,传入参数是其与父进程通信的套接字接口
int work(int);

//传送文件描述符,第一个参数是目的描述符,第二个参数是要传送的文件描述符
int sendFD(int,int);

//接收文件描述符,第一个参数是接收信息的套接字描述符,第二个参数是传出参数,传出接收的文件描述符
int recvFD(int,int *);

客户端
#include <fun.h>
#include "pool.h"
int main(int args,char *argv[])
{
   
    struct winsize wsize;
    ioctl(STDOUT_FILENO, TIOCGWINSZ, &wsize);
    
    ARG_CHECK(args,3);
    int sfd = tcp_connect(argv[1],atoi(argv[2]));
    RET_CHECK(sfd,-1,"client tcp connetc");

    printf("successfully connetc server.\n");

    Train_t train;
    int ret;
    //get filename and create file
    int len;
    ret =  recv(sfd,&len,4,0);
    RET_CHECK(ret,-1,"服务器已满负荷,请稍候再试");
    ret = recv(sfd,train.p,len,0);
    RET_CHECK(ret,-1,"getfilename");
    train.p[ret]='\0';
    int fd = open(train.p,O_RDWR|O_CREAT,0666);
    if(fd==-1)
    {
   
        printf("文件创建失败,可能存在同名文件!client exit.\n");
        close(sfd);
        return 0;
    }
    //get size of file 
    int size;
    ret = recv(sfd,&size,sizeof(int),0);
    RET_CHECK(fd,-1,"recv");
    printf("the size of file is %d\n",size);
    printf("start downloading file %s\n",train.p);
    int getsize = 0;
    while(1)
    {
   
        ret = recv(sfd,&len,4,MSG_WAITALL);
        RET_CHECK(ret,-1,"getlen");
        if(len == 0 && ret == 4)
        {
   
            printf("\r");
            printf(">进度:%5.2f%s 已下载:%d",(double)getsize/size*100,"%",getsize);
            for(int i = 0;i<(getsize/size)*(wsize.ws_row-35);i++)
            {
   
                printf("-");
            }
            printf(">\ndownload is finished\n");
            close(fd);
            close(sfd);
            return 
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值