实现TCP服务器和客户端文件操作

发布于:2023-01-18 ⋅ 阅读:(472) ⋅ 点赞:(0)

1.题型

笔者近期讲解了如下的题:

基于TCP实现服务器和客户端通信,包括但不限于如下功能:

  1. 客户端文件上传
  2. 服务器文件下载
  3. 服务器目录查看
  4. 客户端主动退出

2.思路

  1. 将四个基本功能通过客户端的指令进行选择
  2. 服务器和客户端各自匹配对应的指令
  3. 将四个基本功能封装成一个API函数
  4. 服务器和客户端代码逻辑组合
  5. 服务器使用多线程并发服务器

3.服务器核心功能实现

客户端连接线程管理函数

void *thread_client_manage(void *arg)
{
    int ret;
    char buf[BUFSIZE];
    client_info_in info = *(client_info_in *)arg;   //结构体变量整体赋值

    pthread_detach(pthread_self());         //将本线程分离 -- 在线程结束直接有系统回收空间

    while(1){
        //有客户端连接 先发送操作命令到客户端
        my_diy_send(info.s_fd, buf, BUFSIZE, "\r\n\
*********************************************\r\n\
*******欢迎访问服务器,请输入您的操作指令********\r\n\
*** 下载文件(download files):      fd    ***\r\n\
*** 上传文件(upload files):        fu    ***\r\n\
*** 查看目录(check the directory): dir   ***\r\n\
*** 退出连接(exit the connection): exit  ***\r\n\
*********************************************");

        printf("Waiting for the client to operate\r\n");
        //处理客户端事物
        ret = my_recv(info.s_fd, buf, BUFSIZE);
        if(ret == 0){
            goto exit_handle;
        }
        printf("Start processing client requests\r\n");

        if(strstr(buf, "fd") != NULL){
            //客户端下载
            printf("The client will download the file\r\n");
            ret = file_download_client( arg );
            if(ret < 0){
                goto exit_handle;
            }
        }else if(strstr(buf, "fu") != NULL){
            //客户端上传
            printf("The client will upload the file\r\n");
            ret = file_upload_client(arg);
            if(ret < 0){
                goto exit_handle;
            }
        }else if(strstr(buf, "dir") != NULL){
            printf("The client whill check the directory\r\n");
            ret = directory_check_client(arg);
            if(ret < 0){
                goto exit_handle;
            }
        }else if(strstr(buf, "exit") != NULL){
            //退出连接
            printf("The client exits the connection\r\n");
            goto exit_handle_0;
        }else{
            goto exit_handle;
        }   
    }
exit_handle:
    printf("An error has occurred and the client is about to be shut down");
exit_handle_0:
    close(info.s_fd);
    pthread_exit(NULL); //客户端断开后,销毁本线程
}

客户端下载API函数:

int file_download_client(void *arg)
{
    int ret;
    char buf[BUFSIZE];
    char path[BUFSIZE];
    struct stat file_stat;
    FILE *fp;
    client_info_in info = *(client_info_in *)arg; 
    file_transfer file_transfer_t;

    //提示客户端发送文件名
    my_diy_send(info.s_fd, buf, BUFSIZE, "Command parsing succeeded. Please send the file name you want to download");

    //获取下载的文件名
    ret = my_recv(info.s_fd, buf, BUFSIZE);
    if(ret == 0){
        goto exit_handle;
    }

    //将获取的文件名,补充路径  ./
    snprintf(path, 2+strlen(buf),"%s%s", DOWNLOAD_DIR_DEF, buf);
    printf("path = %s\r\n", path);

    //查找该文件是否存在
    fp = fopen(path, "rb");  //二进制流格式只读打开
    if(fp == NULL){
        my_diy_send(info.s_fd, buf, strlen(buf), "file is not exist");
        goto exit_handle;   //退出下载
    }

    //文件存在
    stat(path, &file_stat); //获取文件信息
    memset(buf, 0, BUFSIZE); //清除buf中的不需要的内容
    sprintf(buf, "%s size = %ld", path, file_stat.st_size);
    printf("file info:%s\r\n", buf);
    send(info.s_fd, buf, strlen(buf), 0);   //发送文件信息

    //等待客户端确认下载
    ret = my_recv(info.s_fd, buf, BUFSIZE);
    if(ret == 0){
        goto exit_handle_2;
    }
    printf("buf = %s\r\n", buf);
    if (((strstr(buf, "yes") == NULL) && (strstr(buf, "YES") == NULL)) || (strstr(buf, "no") != NULL)){
        //客户端确认失败
        printf("The client refused to download\r\n");
        my_diy_send(info.s_fd, buf, strlen(buf), "The client refused to download");
        goto exit_handle_2;
    }

    //确认成功  开始发送文件
    printf("The client confirms success and is about to start sending data\r\n");
    my_diy_send(info.s_fd, buf, BUFSIZE, "You have confirmed downloading the file, and the transfer is about to begin");

    usleep(100);    //目的 -- 防止连续两次send相互影响

    memset(&file_transfer_t, 0, sizeof(file_transfer_t));
    file_transfer_t.total_size = file_stat.st_size; //获取文件总大小
    while(1)
    {
        //读文件 -- 
        memset(buf, 0, BUFSIZE);
        file_transfer_t.cur_len = fread(buf, 1, BUFSIZE, fp);

        //发送文件
        send(info.s_fd, buf, file_transfer_t.cur_len, 0);   //发送文件 长度按照实际读取的文件长度为准
        file_transfer_t.already_len += file_transfer_t.cur_len; //传输成功的文件长度
        file_transfer_t.percent = (file_transfer_t.already_len * 100)/file_transfer_t.total_size;
        printf("file download schdule:%d%%\r\n", file_transfer_t.percent);

        if(file_transfer_t.already_len >= file_transfer_t.total_size){
            //传输完成
            printf("server transfer finish\r\n");
            //接收客户端传输完成信息
            ret = my_recv(info.s_fd, buf, BUFSIZE);
            if(ret == 0){
                goto exit_handle_2;
            }

            break;
        }
    }
    return 0;

exit_handle_2:  
    fclose(fp);
exit_handle:
    return -1;  
}

客户端上传API函数:

int file_upload_client(void *arg)
{
    char buf[BUFSIZE];
    char *p;
    FILE *fp;
    int ret;
    client_info_in info = *(client_info_in *)arg;
    file_transfer file_transfer_t;

    //向客户端发送等待上传信息
    my_diy_send(info.s_fd, buf, BUFSIZE, "After preparation, wait for the file to be uploaded!");

    //等待用户上传 文件名及文件信息  
    ret = my_recv(info.s_fd, buf, BUFSIZE);
    if(ret == 0){
        goto exit_handle;
    }

    //回显文件名  并解析文件大小
    p = strstr(buf, "size=");    //找到size位置
    p += strlen("size=");       //偏移到size后的尺寸位置
    memset(&file_transfer_t, 0, sizeof(file_transfer_t));
    file_transfer_t.total_size = atoi(p);   //转换为文件大小
    snprintf(file_transfer_t.filename, p-buf-strlen("size="), "%s", buf);   //文件名

    // printf("buf:%s\r\n", buf);  //打印输出
    my_send(info.s_fd, buf);    //回显到客户端

    //由客户端 确认 文件上传
    ret = my_recv(info.s_fd, buf, BUFSIZE);
    if(ret == 0){
        goto exit_handle;
    }
    if(((strstr(buf, "yes") == NULL) || (strstr(buf, "YES") == NULL)) 
    && ((strstr(buf, "no") != NULL) || (strstr(buf, "NO") != NULL))){
        printf("The client cancelled the upload\r\n");
        goto over;
    }

    //创建客户端上传操作文件
    sprintf(file_transfer_t.path, "%s%s", UPLOAD_DIR_DEF, file_transfer_t.filename);//合成文件路径
    fp = fopen(file_transfer_t.path, "wb"); 
    if(fp == NULL){
        perror("fopen");
        goto exit_handle_1;
    }
    printf("Waiting for the upload\r\n");

    //准备接收用户上传文件到服务器
    while(1){
        //接收客户端数据
        file_transfer_t.cur_len= my_recv_not_print(info.s_fd, buf, BUFSIZE);
        if(file_transfer_t.cur_len < 0){
            goto exit_handle_1;
        }

        //写入文件 - 写入的数据长度以实际接收的长度为准
        ret = fwrite(buf, 1, file_transfer_t.cur_len, fp);
        file_transfer_t.already_len += file_transfer_t.cur_len; //传输成功的文件长度
        file_transfer_t.percent = (file_transfer_t.already_len * 100) / file_transfer_t.total_size;
        printf("file download schdule:%d%%\r\n", file_transfer_t.percent);

        if (file_transfer_t.already_len >= file_transfer_t.total_size)
        {
            //传输完成
            printf("The file upload finish\r\n");
            fclose(fp);
            my_send(info.s_fd, "upload over");
            usleep(100);//当退出本函数,会继续发送数据,需要适当延时区分两次 send
            break;
        }
    }
over:   
    return 0;

exit_handle_1:
    fclose(fp);
exit_handle:
    return -1;
}

客户端查看服务器目录函数

int directory_check_client(void *arg)
{
    int files = 0, i = 0;
    DIR *pdir;
    char buf[BUFSIZE] = {0};
    struct dirent *dirent_t;
    char filenames[100][BUFSIZE] = {0};
    client_info_in info = *(client_info_in *)arg;

    //打开目录 - 默认路径
    pdir = opendir(UPLOAD_DIR_DEF);
    if(pdir == NULL){
        perror("opendir");
        goto exit_handle;
    }
    //获取文件名
    dirent_t = readdir(pdir);
    if(dirent_t == NULL){
        perror("readdir");
        goto exit_handle_1;
    }
    while((dirent_t != NULL) && (files < 1024)){
        sprintf(filenames[files++], "%s", dirent_t->d_name);  //保存文件名
        // printf("%02d:%s\r\n", files-1, dirent_t->d_name);   
        dirent_t = readdir(pdir);   //继续 读取目录
    }

    //关闭目录
    closedir(pdir);

    //发送目录
    printf("files = %d\r\n", files);
    memset(buf, '\0', BUFSIZE);
    for(i = 0; i < files; i++){
        //如果空间超过BUFSIZE,不再整合文件名 - 视情况优化
        if(strlen(buf) + strlen(filenames[i]) > BUFSIZE){
            break;
        }            
        strcat(buf, filenames[i]);
        strcat(buf, "\r\n");
    }   
    my_send(info.s_fd, buf);

    usleep(100);//当退出本函数,会继续发送数据,需要适当延时区分两次 send

    return 0; 

exit_handle_1:
    closedir(pdir);     
exit_handle:
    return -1;    
}

4.客户端核心功能实现

操作指令识别函数

void *pthread_communication(void *arg)
{
    char buf[BUFSIZE];
    int ret;

    int sockfd = *(int *)arg;

    while (1)
    {
        //连接上服务器后 - 服务器先发送对应的操作命令到客户端
        my_recv(sockfd, buf, BUFSIZE);

        //输入服务器操作选项
        my_diy_input_send(sockfd, buf, BUFSIZE, "Please enter action options to the server:");

        if (strstr(buf, "fd") != NULL)
        {
            //文件下载服务
            ret = file_download((void *)&sockfd);
            if(ret < 0){
                goto exit_handle;
            }
        }else if (strstr(buf, "fu") != NULL)
        {
            //文件上传服务
            ret = file_upload((void *)&sockfd);
            if(ret < 0){
                goto exit_handle;
            }
        }else if (strstr(buf, "dir") != NULL)
        {
            //文件目录服务
            ret = directory_check((void *)&sockfd);
            if(ret < 0){
                goto exit_handle;
            }
        }else if(strstr(buf, "exit") != NULL)
        {   
            //退出连接
            printf("About to disconnect\r\n");
            goto exit_handle_0;
        }else{
            goto exit_handle;
        }
    }

exit_handle:
    printf("An error has occurred and the system is about to exit");
exit_handle_0:
    close(sockfd);
    pthread_exit(NULL); //客户端断开后,销毁本线程
}

文件下载API函数

int file_download(void *arg)
{
    char buf[BUFSIZE];
    char path[BUFSIZE];
    int ret;
    char *p;
    FILE *fp;
    file_transfer file_transfer_t;

    int sockfd = *(int *)arg;

    //读取服务器提示信息
    ret = my_recv(sockfd, buf, BUFSIZE);
    if (ret == 0){
        goto exit_handle;
    }

    //终端获取文件名 并 发送到服务器
    my_diy_input_send(sockfd, buf, BUFSIZE, "Please enter the file name you want to download:");

    //文件存在 -- 存在服务器会发送文件信息 -- 文件大小
    ret = my_recv(sockfd, buf, BUFSIZE);
    if (ret == 0){
        goto exit_handle;
    }
    if (strstr(buf, "file is not exist") != NULL){
        goto exit_handle;
    }

    //保存下文件的大小
    p = strstr(buf, "size");
    p += strlen("szie = "); // S->C:./test.c size = 3480
    memset(&file_transfer_t, 0, sizeof(file_transfer_t));//清空传输文件信息结构体
    file_transfer_t.total_size = atoi(p);       //将字符串转换为整数

    //客户端下载 选择 - 保存的路径
    memset(buf, 0, BUFSIZE);
    printf("please choose your path to save the file:\r\n");
    read(STDIN_FILENO, buf, sizeof(buf));
    snprintf(path, strlen(buf)+strlen(DOWNLOAD_DIR_DEF), "%s%s", DOWNLOAD_DIR_DEF, buf); //仅提取字符串中的有效字符数据到path中
    printf("The file save path: %s\r\n", path);
    fp = fopen(path, "wb");
    if (fp == NULL){
        perror("fopen");
        goto exit_handle_2;
    }
    //客户端需要回复 - yes 进行确认下载
    my_diy_input_send(sockfd, buf, BUFSIZE, "file download, yes or no?");
    if (((strstr(buf, "yes") == NULL) && (strstr(buf, "YES") == NULL)) || (strstr(buf, "no") != NULL)){
        printf("Client cancle download\r\n");
        send(sockfd, "Client cancle download", strlen(buf), 0);
    }

    //接收服务器确认信息
    ret = my_recv(sockfd, buf, BUFSIZE);
    if (ret == 0){
        goto exit_handle;
    }

    while (1)
    {
        //接收下载文件
        file_transfer_t.cur_len = my_recv_not_print(sockfd, buf, BUFSIZE);
        if (ret < 0)
        {
            goto exit_handle_2;
        }

        //写入文件中
        ret = fwrite(buf, 1, file_transfer_t.cur_len, fp);
        file_transfer_t.already_len += file_transfer_t.cur_len; //传输成功的文件长度
        file_transfer_t.percent = (file_transfer_t.already_len * 100) / file_transfer_t.total_size;
        printf("file download schdule:%d%%\r\n", file_transfer_t.percent);

        if (file_transfer_t.already_len >= file_transfer_t.total_size)
        {
            //传输完成
            printf("file download finish\r\n");
            fclose(fp);
            send(sockfd, "download over", strlen("download over"), 0);
            break;
        }
    }

    return 0;

exit_handle_2:
    fclose(fp);

exit_handle:
    return -1;      
}

文件上传API函数

int file_upload(void *arg)
{
    int ret, pos;
    int i = 0;
    DIR *pdir;
    FILE *fp;
    struct stat fileinfo_t;
    struct dirent *dirent_t;
    file_transfer file_transfer_t;
    char filenames[100][BUFSIZE] = {0};
    char buf[BUFSIZE] = {0};
    int sockfd = *(int *)arg;

    //读取服务器提示上传信息
    ret = my_recv(sockfd, buf, BUFSIZE);
    if(ret < 0){
        goto exit_handle;
    }

    //查看本目录文件
    printf("The file list is as follows. Select a file to be uploaded");

    pdir = opendir(UPLOAD_DIR_DEF);
    dirent_t = readdir(pdir);
    while((dirent_t != NULL) && (i < 1024)){
        sprintf(filenames[i], "%s", dirent_t->d_name);  //保存文件名
        printf("%02d:%s\r\n", i++, dirent_t->d_name);   
        dirent_t = readdir(pdir);   //继续 读取目录
    }
    closedir(pdir);

    //选择一个需要上传的文件 并 附加文件信息(size)
    my_diy_input(buf, BUFSIZE, "Enter the corresponding file number to upload");
    //打开文件获取信息    
    pos = atoi(buf);
    sprintf(buf, "%s%s", UPLOAD_DIR_DEF, filenames[pos]);//按照文件名合成文件路径打开
    printf("path:%s\r\n", buf);
    fp = fopen(buf, "rb");
    if(fp == NULL){
        perror("fopen");
        goto exit_handle;
    }

    ret = stat(buf, &fileinfo_t);
    if(ret < 0){
        perror("stat");
        goto exit_handle_1;
    }

    //和服务器确认上传文件信息
    sprintf(buf, "%s size=%ld", filenames[pos], fileinfo_t.st_size);//合成上传确认信息,带上文件大小-计算下载进度用
    file_transfer_t.total_size = fileinfo_t.st_size;
    // printf("%s \r\n", buf);
    my_diy_send(sockfd, buf);

    //等待服务器回显文件信息
    ret = my_recv(sockfd, buf, BUFSIZE);
    if(ret < 0){
        goto exit_handle_1;
    }

    //和服务器确认上传
    my_diy_input_send(sockfd, buf, BUFSIZE, "Whether to upload?(yes/no)");
    if(((strstr(buf, "yes") == NULL) || (strstr(buf, "YES") == NULL)) 
    && ((strstr(buf, "no") != NULL) || (strstr(buf, "NO") != NULL))){
        printf("The client cancelled the upload\r\n");
        goto exit_handle_1;
    }

    //开始上传文件
    while(1){
        //读文件 -- 
        memset(buf, 0, BUFSIZE);
        file_transfer_t.cur_len = fread(buf, 1, BUFSIZE, fp);

        //发送文件
        send(sockfd, buf, file_transfer_t.cur_len, 0);   //发送文件 长度按照实际读取的文件长度为准
        file_transfer_t.already_len += file_transfer_t.cur_len; //传输成功的文件长度
        file_transfer_t.percent = (file_transfer_t.already_len * 100)/file_transfer_t.total_size;
        printf("file upload schdule:%d%%\r\n", file_transfer_t.percent);

        if(file_transfer_t.already_len >= file_transfer_t.total_size){
            //传输完成
            printf("client transfer finish\r\n");
            //接收服务器传输完成信息
            ret = my_recv(sockfd, buf, BUFSIZE);
            if(ret == 0){
                goto exit_handle_1;
            }
            // sleep(1);
            usleep(100);//当退出本函数,会继续接收服务器数据,需要适当延时区分两次 recv
            break;
        }
    }

    return 0;

exit_handle_1:
    fclose(fp);

exit_handle:
    return -1;     
}

目录查看API函数

int directory_check(void *arg)
{
    int ret;
    char buf[BUFSIZE] = {0};
    int sockfd = *(int *)arg;

    //等待服务器发送服务器目录 - BUFSIZE-1024范围可能超出,视情况优化
    ret = my_recv(sockfd, buf, BUFSIZE);
    if (ret == 0){
        return -1;
    }

    return 0;
}

5.功能实现

客户端执行流程:

yao@yvm:network$ ./tcp_c_download 192.168.1.227 9999
Connecting to the server successfully
S->C:
*********************************************
*******欢迎访问服务器,请输入您的操作指令********
*** 下载文件(download files):      fd    ***
*** 上传文件(upload files):        fu    ***
*** 查看目录(check the directory): dir   ***
*** 退出连接(exit the connection): exit  ***
*********************************************
Please enter action options to the server:
fu
S->C:After preparation, wait for the file to be uploaded!
The file list is as follows. Select a file to be uploaded00:10-sale_ticket.c
01:05-mutex.c
02:..
03:09-sem_mutex.c
04:test.c
05:07-cond.c
06:08-sem.c
07:03-pthread_clean.c
08:r3.c
09:r2.c
10:02-pthread.c
11:tcp_c_download
12:.
13:04-no_mutex.c
14:11-mycp.c size
15:06-while.c
16:01-getthreadid.c
17:11-mycp.c
18:a.out
19:mycp
20:r.c
Enter the corresponding file number to upload
10
path:../thread/02-pthread.c
S->C:02-pthread.c size=1172
Whether to upload?(yes/no)
yes
file upload schdule:87%
file upload schdule:100%
client transfer finish
S->C:upload over
S->C:
*********************************************
*******欢迎访问服务器,请输入您的操作指令********
*** 下载文件(download files):      fd    ***
*** 上传文件(upload files):        fu    ***
*** 查看目录(check the directory): dir   ***
*** 退出连接(exit the connection): exit  ***
*********************************************
Please enter action options to the server:
dir
S->C:tcp_client
08-tcp_client_download.c
tcp_sever
04-tcp_client_recv_send.c
..
test-c
06-udp_client.c
udp_server
05-udp_server.c
udp_client
02-pthread.c
tcp_c_download
.
09-opendir.c
tcp_server
02-tcp_client.c
03-tcp_multi_pthread_server.c
tcp_s_download
test
01-tcp_server.c
a.out
07-tcp_server_download.c

S->C:
*********************************************
*******欢迎访问服务器,请输入您的操作指令********
*** 下载文件(download files):      fd    ***
*** 上传文件(upload files):        fu    ***
*** 查看目录(check the directory): dir   ***
*** 退出连接(exit the connection): exit  ***
*********************************************
Please enter action options to the server:
fd
S->C:Command parsing succeeded. Please send the file name you want to download
Please enter the file name you want to download:
03-tcp_multi_pthread_server.c
S->C:./03-tcp_multi_pthread_server.c size = 3453
please choose your path to save the file:
test_1.c
The file save path: ./test_1.c
file download, yes or no?
yes
S->C:You have confirmed downloading the file, and the transfer is about to begin
file download schdule:29%
file download schdule:59%
file download schdule:88%
file download schdule:100%
file download finish
S->C:
*********************************************
*******欢迎访问服务器,请输入您的操作指令********
*** 下载文件(download files):      fd    ***
*** 上传文件(upload files):        fu    ***
*** 查看目录(check the directory): dir   ***
*** 退出连接(exit the connection): exit  ***
*********************************************
Please enter action options to the server:
exit
About to disconnect

服务器执行流程

yao@yvm:network$ ./tcp_s_download 
server waiting client……
client information:
client socket fd = 0
client inet:144.216.23.244 
client port = 0
server waiting client……
Waiting for the client to operate
C->S:fu

Start processing client requests
The client will upload the file
C->S:02-pthread.c size=1172
C->S:yes

Waiting for the upload
file download schdule:87%
file download schdule:100%
The file upload finish
Waiting for the client to operate
C->S:dir

Start processing client requests
The client whill check the directory
files = 22
Waiting for the client to operate
C->S:fd

Start processing client requests
The client will download the file
C->S:03-tcp_multi_pthread_server.c

path = ./03-tcp_multi_pthread_server.c
file info:./03-tcp_multi_pthread_server.c size = 3453
C->S:yes

buf = yes

The client confirms success and is about to start sending data
file download schdule:29%
file download schdule:59%
file download schdule:88%
file download schdule:100%
server transfer finish
C->S:download over
Waiting for the client to operate
C->S:exit

Start processing client requests
The client exits the connection
源码下载: Linux服务器和客户端文件传输源码-Linux文档类资源-CSDN文库icon-default.png?t=M666https://download.csdn.net/download/m0_51540567/86400299
本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到