自学内容网 自学内容网

【Linux C | 网络编程】进程池小文件传输的实现详解(二)

上一篇实现了一个最基本的进程池:客户端读取标准输入,发送给服务端,服务端回复一个相同的内容。

【Linux C | 网络编程】简易进程池的实现详解(一)

这篇内容在上篇进程池的基础上实现小文件的传输。

文件传输的本质实现上和 cp 命令的原理是一样:应用程序需要打开源文件并且进行读取,然后将读取得到的内容写入到目标文件当中。如果是远程上传/ 下载文件,则需要将前述流程分解成两个应用程序,应用程序之间使用网络传输数据。

1.文件传输流程

服务端的处理流程
  1. 监听客户端请求

    • 服务端创建监听套接字,等待客户端连接请求。
    • 当有客户端请求连接时,服务端通过accept函数接收连接,得到一个用于通信的套接字文件描述符(peerfd)。
  2. 分配任务给子进程

    • 父进程将客户端的连接分配给一个空闲的子进程,并通过进程间通信的管道传递文件描述符。
  3. 读取文件内容

    • 子进程接收到任务后,从管道中获取文件描述符(peerfd)。
    • 子进程打开需要传输的文件,并读取文件内容,将其存入发送缓冲区(sendBuf)。
  4. 发送文件名和文件内容

    • 子进程首先通过网络套接字发送文件名到客户端。
    • 然后,子进程将文件内容通过网络套接字发送到客户端。
  5. 任务完成通知

    • 文件传输完成后,子进程关闭客户端的文件描述符,并通过管道通知父进程自己已完成任务,可以接受新的任务。
客户端的处理流程
  1. 发送请求

    • 客户端向服务端发送连接请求,请求下载特定的文件。
  2. 接收文件名和文件内容

    • 客户端接收到服务端的响应后,首先读取传输过来的文件名。
    • 然后,客户端接收文件内容,并将内容存入接收缓冲区(receiveBuf)。
  3. 写入本地文件

    • 客户端将接收缓冲区中的文件内容写入本地文件系统,完成文件下载。

2.小文件的传输

所谓的小文件,就是指单次 send recv 就能发送 / 接收完成的文件。如果一端要把文件发送给另一端,要发送两个部分的数据:其一是文件名,用于对端创建文件;另一个部分是文件内容。
假设是客户端将文件上传到服务端, 一种简单的实现方法是这样的:
//客户端
//...
send(sockFd,filename,strlen(filename),0);
ret = read(fd,buf,sizeof(buf))
send(sockFd,buf,ret,0);
//...
//服务端
//...
recv(netFd,filename,sizeof(filename),0);
int fd = open(filename,O_RDONLY|O_CREAT,0666);
ret = recv(netFd,buf,sizeof(buf),0);
write(fd,buf,ret);
//...
但是这种写法会引入一个非常严重的问题,服务端在接收文件名,实际上并不知道有多长,所以它会试图把网络缓冲区的所有内容都读取出来,但是 send 底层基于的协议是 TCP 协议 —— 这是一种流式协议。 这样的情况下,服务端没办法区分到底是哪些部分是文件名而哪些部分是文件内容。完全可能会出现服务端把文件名和文件内容混杂在一起的情况,这种就是江湖中所谓的" 粘包 " 问题。
TCP的“粘包”问题详解可以看我这篇内容:
所以接下要我们要做的事情是在应用层上构建一个私有协议,这个协议的目的是规定 TCP 发送和接收的实际长度从而确定单个消息的边界。目前这个协议非常简单,可以把它看成是一个小火车,包括一个火车头和一个火车车厢。火车头里面存储一个整型数字,描述了火车车厢的长度,而火车车厢才是真正承载数据的部分。
一个完整的文件表示:
typedef struct train_s{
    int size;                //火车头:记录数据长度
    char buf[1000];          //车厢:记录数据本身
} train_t;

 示例代码:

//transfer.c
#include "process_pool.h"

#define FILENAME "small_file.txt"

int transferFile(int peerfd)
{
    //读取本地文件
    int fd = open(FILENAME, O_RDONLY);
    ERROR_CHECK(fd, -1, "open");
    char buff[100] = {0};
    int filelength = read(fd, buff, sizeof(buff));
    ERROR_CHECK(filelength, -1, "read");

    //进行发送操作
    //1. 发送文件名
    train_t t;
    memset(&t, 0, sizeof(t));
    t.len = strlen(FILENAME);
    strcpy(t.buf, FILENAME);
    send(peerfd, &t, 4 + t.len, 0);
    
    //2. 再发送文件内容
    memset(&t, 0, sizeof(t));
    t.len = filelength;
    strncpy(t.buf, buff, t.len);
    send(peerfd, &t, 4 + t.len, 0);

    return 0;
}

//客户端代码: client-smallfile.c
#include <func.h>

int main()
{
    //创建客户端的套接字
    int clientfd = socket(AF_INET, SOCK_STREAM, 0);
    ERROR_CHECK(clientfd, -1, "socket");

    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    //指定使用的是IPv4的地址类型 AF_INET
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(8080);
    serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    //连接服务器
    int ret = connect(clientfd, (struct sockaddr*)&serveraddr, 
                      sizeof(serveraddr));
    ERROR_CHECK(ret, -1, "connect");
    printf("connect success.\n");

    //进行文件的接收
    //1. 先接收文件的名字
    //1.1 先接收文件名的长度
    int length = 0;
    ret = recv(clientfd, &length, sizeof(length), 0);
    printf("filename length: %d\n", length);
    //1.2 再接收文件名本身
    char buff[100] = {0};
    ret = recv(clientfd, buff, length, 0);
    printf("1 recv ret: %d\n", ret);
    int fd = open(buff, O_CREAT|O_RDWR, 0644);
    ERROR_CHECK(fd, -1, "open");

    //2. 再接收文件的内容
    //2.1 先接收文件内容的长度
    ret = recv(clientfd, &length, sizeof(length), 0);
    printf("fileconent length: %d\n", length);

    //2.2 再接收文件内容本身
    memset(buff, 0, sizeof(buff));
    ret = recv(clientfd, buff, length, 0);
    printf("2 recv ret: %d\n", ret);
    write(fd, buff, ret);

    close(fd);
    close(clientfd);

    return 0;
}

在头文件增加函数声明,子进程执行任务函数加入发送文件操作


原文地址:https://blog.csdn.net/qq_42037383/article/details/140725797

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!