用C语言编写的一个简单的文件服务器

前言

本文章将介绍如何用C语言在Linux环境下搭建一个C/S模型的文件服务器,功能类似于百度云,可以上传,下载,查看云端的文件,现在暂且给他取个名字叫“浩云”吧。

一、功能使用说明和应用示例

在这里插入图片描述

客户端:连接成功后就可以上传,下载,查看了

在这里插入图片描述
在这里插入图片描述

服务器端:主要负责打印一下调试信息(不重要)

在这里插入图片描述

二、构建代码的框架和思路

1.采用TCP还是UDP?

我们要做的是一个文件服务器系统,首先考虑的就是要选择一种通信协议来进行文件的传输,我们毫不犹豫的选择TCP协议,因为它是一种可靠的传输协议,我们传文件,基本的要保证的就是文件的完整性。而且一些常见的传文件的协议,的,如TFTP,FTP也都是采用的TCP传输协议。

2.采用多进程还是多线程?

我们的服务器不能只为一个客户服务,所以服务器这边还的采用多进程或者多线程来对没一个连接成功的客户端服务。我是选择采用多进程并发服务器,虽然开销比多线程大,但是它稳定性强,而且同时可接受的客户端的数量也比线程要多(一个进程里挤太多线程容易卡,而且一个线程崩溃了还会影响其他线程)。

3.基本功能:上传,下载,查看

1.上传和下载

对于文件的上传和下载,其实搞定了其中之一,另一个问题就不大了,所以这里就只大概说一下上传嘛:
客户端要上传文件,首先要把文件的文件名发给服务器,然后服务器再在它的本地创建一个同名的空文件,还有确定文件的大小,用于后面计算文件传输的进度。后续就是对连接套接字connfd进行读写就OK了,代码示例如下:

服务器:
int upload(int connfd)//upload file
{
    int ret = 0;
    // recv filename
    char filename[F_NAME_SIZE] = {0};
    char filename2[2*F_NAME_SIZE] = {0};
    ret = read(connfd, filename, 32);//recv filename  1
    if(ret == -1) 
    {   
        perror("recv filename");
        return -1; 
    }   
    printf("recv file :%s\n",filename);
    sprintf(filename2, "./cloud_file/%s",filename);
    //create file
    int fd = open(filename2, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if(fd == -1) 
    {   
        perror("create file");
        return -1; 
    }   
    //recv filesize
    int filesize;
    char filesize_buf[F_SIZE] = {0};
    ret = read(connfd, filesize_buf, 8);//recv filesize  2
    if(ret == -1) 
    {   
        perror("recv filesize");
        return -1; 
    }   
    filesize = atoi(filesize_buf);
    printf("file size :%d\n",filesize);
    //recv file
    int n = 0;
    char buf[TCP_MAXSIZE] = {0};
    int size;
    while((n = read(connfd, buf, sizeof(buf))) > 0)//recv file  3
    {   
        size =lseek(fd, 0, SEEK_END);
        printf("%d\n",size);
        if(size == filesize)
        {   
            printf("recv complete !\n");
            break;
        }   
        write(fd, buf, n); 
    }   
    close(fd);                                                                                                                                                                                                                    
    return 0;
}

客户端:
int upload(int sockfd)//upload file
{
    int ret = 0;
    //get filename
    printf("---your--local--file---\n");
    system("ls ./local_file");
    printf("------list--over-------\n");
    printf("Please select the file you want to upload\n");
    char filename[F_NAME_SIZE] = {0};
    char filename2[2*F_NAME_SIZE] = {0};
    fgets(filename, sizeof(filename), stdin);
    filename[strlen(filename)-1] = '\0';
    sprintf(filename2, "./local_file/%s",filename);
    //open file
    int fd = open(filename2, O_RDONLY);
    if(fd == -1)
    {
        perror("open");
        return -1;
    }
    //send filename
    ret =  write(sockfd, filename, strlen(filename)+1);//send filename  1
    if(ret == -1)
    {
        perror("send filename");
        return -1;
    }
    sleep(1);//sleep  1
    //get filesize
    int filesize = lseek(fd, 0, SEEK_END);
    lseek(fd, 0, SEEK_SET);
    printf("file size :%d\n",filesize);
    char filesize_buf[F_SIZE] = {0};
    sprintf(filesize_buf, "%d", filesize);
    ret =  write(sockfd, filesize_buf, strlen(filesize_buf)+1);//send filesize  2
    if(ret == -1)
    {
        perror("send filesize");
        return -1;
    }
    sleep(1);//sleep 2
    //send file
    int n = 0;
    char buf[TCP_MAXSIZE] = {0};
    while((n = read(fd, buf, sizeof(buf))) > 0) //send file  3
    {
        write(sockfd, buf, n);
        //    printf("Downloading... [%.2f%%]\r",((float)lseek(fd, 0, SEEK_END)/filesize)*100);
        //     fflush(stdout);
    }
    close(fd);                                                                                                                                                                                                                    
    sleep(1);//sleep  3
    write(sockfd, "ok", 2);//send ok
    printf("upload complete !\n");
    return 0}
2.查看云文件

查看云文件非常简单,我直接就用的system命令将云文件夹的所以文件名输出到一个文本文件,然后再按行读取文件的内容发给客户端,这里主要遇到的问题就是文件读完了没有退出,我想了一个办法就是让他读到文件的最后一个标志文件就退出。
示例代码如下:

服务器:
int list(int connfd)
{
    system("ls ./cloud_file > filetable.txt");
    FILE *fp = fopen("filetable.txt", "r");
    char filename[F_NAME_SIZE] = {0};
    while(fgets(filename, sizeof(filename), fp) != NULL)
    {   
        write(connfd, filename, strlen(filename));
    }   
    fclose(fp);
    printf("---------------------\n");
    system("ls ./cloud_file");
    printf("------list--over-----\n");
    return 0;
}

客户端:
int list(int sockfd)
{
    char filename[F_NAME_SIZE] = {0};
    printf("------cloud--file------\n");
    while(read(sockfd, filename,sizeof(filename)) > 0)
    {
        if(strncmp(filename, "zzzz_file_tail", 14) == 0) break;
        printf("%s",filename);
        memset(filename, 0, sizeof(filename));
    }
    printf("------list--over-------\n");
    return 0;
}

4.其他扩展

1.有了基本的功能还可以增加一个注册登录的功能,我这里采用的是sqlite3数据库,这是一个非常轻巧的数据库,源代码好像只有3万行左右的C代码,常用在嵌入式领域。
少废话,直接上代码:

int Register(int sockfd)
{
    char recv_msg_buf[128];
 
    printf("registering...\n");//p  1
    printf("please input your username :<name>\n");//p  2
 
    char username[U_NAME_SIZE] = {0};
REIN_NAME:
    memset(username, 0, sizeof(username));
    fgets(username, sizeof(username), stdin);
    username[strlen(username)-1] = '\0';
    write(sockfd, username, strlen(username)+1);//send  name
 
    memset(recv_msg_buf, 0, sizeof(recv_msg_buf));
    read(sockfd, recv_msg_buf, sizeof(recv_msg_buf));//recv  1
    puts(recv_msg_buf); 
    if(recv_msg_buf[0] == 'T') goto REIN_NAME;
 
    char key[KEY_SIZE]  ={0};
    scanf("%s",key);
    write(sockfd, key, strlen(key)+1);//send  key
 
    printf("Register successful !\nyour username :<%s> key :<%s>\n",username,key);//p  3 
    return 0;
}
int Login(int sockfd)
{
    char recv_msg_buf[128];
    char buf[U_NAME_SIZE + KEY_SIZE] = {0};//name and key
    char key[KEY_SIZE]  ={0};
    printf("logining...\n");//p  1    
    printf("please input your username and key :<name> <key>\n");//p  2
 
    fgets(buf, sizeof(buf), stdin);
    buf[strlen(buf)-1] = '\0';
    write(sockfd, buf, strlen(buf)+1);//send  name and key
REIN_KEY :
    memset(recv_msg_buf, 0, sizeof(recv_msg_buf));
    read(sockfd, recv_msg_buf, sizeof(recv_msg_buf));//recv  1
    puts(recv_msg_buf); 
    if(recv_msg_buf[0] == 'P')// Password mistake, please re-enter!
    {
        scanf("%s",key);
        write(sockfd, key, strlen(key)+1);//r-send  key
        goto REIN_KEY;
    }
    else if(recv_msg_buf[0] == 'N' || recv_msg_buf[0] == 'Y')//No such user ! or You have made too many mistakes ! 
        return -1;
    return 0;//Login successful !
}                                       

总结

代码里面有些地方不得不用到了goto语句,可能破坏了代码的结构。期待你们有更好的办法解决。
本文章尚未完善,只是稍微罗列了写框架,后续会继续补充更改。。。

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值