IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 文件传输系统的实现 -> 正文阅读

[网络协议]文件传输系统的实现

文件传输系统

项目介绍:创建一个文件传输系统,实现客户端在服务器上下载或者上传文件。
项目描述:本项目中聊天采用 TCP 的通信方式,在客户端上对服务器的指定目录可以进行ls命令,并且可以下载服务器指定目录的内容,并且支持断点续传的功能,用户也可以给服务器上传文件。

服务器端配置文件info.cnf

#ip addr 
ips=127.0.0.1
#ser port
port=6000

#listen(), 
lismax=5

设计一个数据结构保存服务器socket初始化数据。

/*sock_init.h*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define ips_max  32

struct info_data
{
    char ips[ips_max];
    int port;
    int lismax;
};

//获取配置文件中的ip port lismax
int read_info_cnf(struct info_data * p);
#include "sock_init.h"

int read_info_cnf(struct info_data * p)
{
    if ( NULL == p )
    {
        return -1;
    }

    FILE * fp = fopen("info.cnf","r");
    if ( NULL == fp )
    {
        return -1;
    }

    while( 1 )
    {
        char buff[128] = {0};
        fgets(buff,128,fp);

        if ( buff[0] == '\0' )
        {
            break;
        }

        if ( buff[0] == '#' || buff[0] == '\n' )
        {
            continue;
        }

        buff[strlen(buff)-1] = '\0';
        if ( strncmp(buff,"ips=",4) == 0 )
        {
            strcpy(p->ips,buff + 4);
            continue;
        }
        else if ( strncmp(buff,"port=",5) == 0 )
        {
            p->port = atoi(buff + 5);
            continue;
        }
        else if ( strncmp(buff,"lismax=",7) == 0 )
        {
            p->lismax = atoi(buff+7);
            continue;
        }
        else
        {
            printf("info.cnf err:%s",buff);
        }
    }

    fclose(fp);
    return 0;
}

服务器设计如下

#include "thread.h"
#include "sock_init.h"

int create_socket(struct info_data *p);
int accept_client(int sockfd);
int main()
{
    struct info_data dt;
    read_info_cnf(&dt);//读取ip, port

    int sockfd = create_socket(&dt);//利用数据结构创建套接字
    if ( -1 == sockfd )
    {
        printf("create sockfd err\n");
        exit(0);
    }

    while( 1 )
    {
        int c = accept_client(sockfd);  //获取客户端套接字
        if ( -1 == c )
        {
            continue;
        }

        start_thread(c);  //开启工作线程
    }
}

int accept_client(int sockfd)
{
    if ( -1 == sockfd )
    {
        return -1;
    } 

    struct sockaddr_in caddr;
    int len = sizeof(caddr);
    int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
    return c;
}

int create_socket(struct info_data *p)
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if ( -1 == sockfd )
    {
        return -1;
    }

    struct sockaddr_in saddr;
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(p->port);
    saddr.sin_addr.s_addr = inet_addr(p->ips);

    int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    if ( -1 == res )
    {
        return -1;
    }

    if ( listen(sockfd,p->lismax) == -1 )
    {
        return -1;
    }

    return sockfd;
}

线程函数设计如下:

/*thread.h*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <sys/wait.h>
#include<fcntl.h>
#include<signal.h>
int falg=1;
void start_thread(int c);

利用lseek函数,信号的应用。因为网络的send()和recv()相当于管道的读写端,服务器再给客户端send的过程中如果客户端异常退出。服务器会接收的SIGPIPE的信号使服务器关闭,所以下面代码中修改该信号。

#include "thread.h"

#define ARG_MAX 10

void recv_file(int c, char* name)//对应up命令,客户端上传文件,未设计断点续传
{
    if (c == -1 || name == NULL)
    {
        return;
    }
    int fd = open(name, O_CREAT | O_WRONLY, 0600);
    if (fd == -1)
    {
        send(c, "err", 3, 0);
        return;
    }
    send(c,"ok#",3,0);
     char status[128] = { 0 };
    int curr_size = 0;
    int num = 0;
    if (recv(c, status, 127, 0) <= 0) 
    {
        return;
    }

    if (strncmp(status, "ok#", 3) != 0)
    {
        return;
    }
    int filesize = atoi(status + 3);
    char data[1024] = { 0 };
    while(1)
    {
        num = recv(c, data, 1024, 0);
        write(fd, data, num);
        curr_size += num;

        float f = curr_size * 100.0 / filesize;
        printf("%s:%.2f%%\r",name, f);
        fflush(stdout);
        if (curr_size >= filesize)
        {
            break;
        }
    }  
    send(c, "ok#", 3, 0);
    printf("\n");
    close(fd);
}
void fun()
{
    falg=0;
}
void fun1()
{
    falg=1;
}
void send_file(int c, char* name)//对应get命令,客户端下载文件,支持断点续传
{
    if (name == NULL)
    {
        send(c, "err", 3, 0);
        return;
    }

    int fd = open(name,O_RDONLY);
    if (fd == -1)
    {
        send(c, "err", 3, 0);
        return;
    }

    int filesize = lseek(fd, 0, SEEK_END);//获取文件大小
    lseek(fd, 0, SEEK_SET);

    char status[32] = { 0 };
    sprintf(status, "ok#%d", filesize);

    send(c, status, strlen(status), 0);
    memset(status, 0, 32);
    int num = recv(c, status, 31, 0);
    if (num <= 0)
    {
        printf("客户端异常退出");
        close(fd);
        return;
    }

    if (strcmp(status, "err") == 0)
    {
        close(fd);
        return;
    }
    int size= atoi(status + 3);//获取断点续传大小
    lseek(fd, size, SEEK_SET);
    char data[1024];
    num = 0;
    fun1();
    while ((num = read(fd, data, 1024)) > 0)//循环发送
    {
        signal(SIGPIPE,fun);//防止客户端异常退出导致服务器退出
        if(falg)
        {
        send(c, data, num, 0);
        }
        else
        {
        break;
        }
    }

    close(fd);
}
char *get_cmd(char buff[], char *myargv[])
{
    if (buff == NULL || myargv == NULL)
    {
        return NULL;
    }

    int i = 0;
    char *ptr = NULL;
    char *s = strtok_r(buff, " ", &ptr);
    while (s != NULL)
    {
        myargv[i++] = s;
        s = strtok_r(NULL, " ", &ptr);
    }

    return myargv[0];
}

int run_cmd(int c, char *cmd, char *myargv[])
{
    int fd[2] = {0};//创建无名管道
    if (pipe(fd) == -1)
    {
        printf("pipe err\n");
        send(c, "er1", 3, 0);
        return -1;
    }

    pid_t pid = fork();//创建子进程
    if (pid == -1)
    {
        printf("fork err\n");
        send(c, "er1", 3, 0);
        return -1;
    }

    if (pid == 0)
    {
        close(fd[0]);
        dup2(fd[1], 1);//重定向输出到管道读端
        dup2(fd[1], 2);//重定向错误到管道读端
        execvp(cmd, myargv);//子进程替换
        printf("cmd not find");
        exit(0);
    }
    close(fd[1]);
    wait(NULL);
    char send_buff[1024] = {"ok#"};
    int num = read(fd[0], send_buff + 3, 1020);//读取管道读端
    send(c, send_buff, num + 3, 0);//将读取到的内容发送给客户端
    close(fd[0]);
}
void *work_thread(void *arg)
{
    int c = (int)arg;

    while (1)
    {
        char buff[128] = {0};
        int n = recv(c, buff, 127, 0); // get a.mp4 , rm a.c , ls
        if (n <= 0)
        {
            break;
        }

        char *myargv[ARG_MAX] = {0};
        char *cmd = get_cmd(buff, myargv);//获取命令

        if (strcmp(cmd, "get") == 0)
        {
            send_file(c, myargv[1]);//下载
        }
        else if (strcmp(cmd, "up") == 0)
        {
            recv_file(c,myargv[1]);//上传
        }
        else
        {
            if ( run_cmd(c,cmd,myargv) == -1 )//执行其他命令
            {
                continue;
            }
        }
    }
    close(c);
    printf("client close\n");
}

void start_thread(int c)//创建工作线程
{
    pthread_t id;
    pthread_create(&id, NULL, work_thread, (void *)c);
}

客户端设计如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<fcntl.h>
void send_file(int c, char* name, char* s)//up命令
{
    if (c == -1 || s == NULL || strlen(s) == 0 || name == NULL)
    {
        return;
    }
    int fd = open(name,O_RDONLY);
    if (fd == -1)
    {
        return;
    }
    int filesize = lseek(fd, 0, SEEK_END);//获取所上传的文件的大小
    lseek(fd, 0, SEEK_SET);
 
    if (filesize==0)//
    {
        close(fd);
        return;
    }
    if (fd == -1)
    {
        return;
    }   
    //发送up a.c
    send(c, s, strlen(s), 0);
    char status[128] = { 0 };
    if (recv(c, status, 127, 0) <= 0) 
    {
        printf("服务器出错\n");
        return;
    }

    if (strncmp(status, "ok#", 3) != 0)
    {
        return;
    }
    memset(status, 0, 128);
    sprintf(status, "ok#%d", filesize);
    send(c, status, strlen(status), 0);//告诉服务器文件大小
     memset(status, 0, 128);
     printf("上传的文件大小:%d\n",filesize);
    int curr_size = 0;
    int num = 0;
    char data[1024] = { 0 };
    while(1)
    {
        num = read(fd, data, 1024);
        send(c, data, num, 0);
        curr_size += num;//已经上传的大小
        float f = curr_size * 100.0 / filesize;//所上传文件的百分比
        printf("up%s:%.2f%%\r",name, f);
        fflush(stdout);
        if (curr_size >= filesize)
        {
            break;
        }
    }
    printf("\n");
    if (recv(c, status, 127, 0) <= 0) 
    {
        printf("服务器出错\n");
        return;
    }

    if (strncmp(status, "ok#", 3) == 0)
    {
        printf("上传成功\n");
        return;
    }

    close(fd);
}
void recv_file(int c, char* name, char* s)//get命令
{
    if (c == -1 || s == NULL || strlen(s) == 0 || name == NULL)
    {
        return;
    }

    //发送get a.c
    send(c, s, strlen(s), 0);
    char status[128] = { 0 };
    if (recv(c, status, 127, 0) <= 0) 
    {
        printf("服务器出错\n");
        return;
    }

    if (strncmp(status, "ok#", 3) != 0)
    {
        printf("文件不存在或者没有权限\n");
        return;
    }
    int filesize = atoi(status + 3);//获取文件大小
    printf("下载的文件大小:%d\n",filesize);
    char name1[20];
    strcpy(name1,name);
    strcat(name, ".tmp");//加上后缀
    int fd = open(name, O_CREAT | O_WRONLY, 0600);
    int size = lseek(fd, 0, SEEK_END);//获取断点续传大小
    lseek(fd, size, SEEK_SET);
    if (filesize==0)
    {
        send(c, "err", 3, 0);
        close(fd);
        return;
    }
    if (fd == -1)
    {
        send(c, "err", 3, 0);
        return;
     }
    memset(status, 0, 128);
    sprintf(status, "ok#%d", size);
    send(c,status, strlen(status), 0);//发送服务器断点续传大小

    int curr_size = 0+size;//已下载的大小
    int num = 0;
    char data[1024] = { 0 };
    while(1)
    {
        num = recv(c, data, 1024, 0);
        write(fd, data, num);
        curr_size += num;//已下载的大小

        float f = curr_size * 100.0 / filesize;//下载百分比
        printf("down%s:%.2f%%\r",name, f);
        fflush(stdout);
        if (curr_size >= filesize)
        {
            break;
        }
    }
    rename(name, name1);
    printf("\n");
    close(fd);
}
int connect_ser(char* ips, int port)//连接服务器
{
    if ( ips == NULL || port <= 0 )
    {
        return -1;
    }

    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if ( sockfd == -1 )
    {
        return -1;
    }

    struct sockaddr_in saddr;
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(port);
    saddr.sin_addr.s_addr = inet_addr(ips);

    int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    if ( res == -1 )
    {
        return -1;
    }

    return sockfd;
}
void get_arg(int argc, char* argv[], char** s, int* p)
{
    if ( argc <= 1 || argv == NULL || s == NULL || p == NULL)
    {
        return;
    }

    for( int i = 1; i < argc; i++ )
    {
        if ( strncmp(argv[i],"ip=",3) == 0)
        {
            //检测ip地址是否合法
            *s = argv[i] + 3;
            continue;
        }

        if ( strncmp(argv[i],"port=",5) == 0)
        {
            //安全检查 未完成
            *p = atoi(argv[i]+5);
            continue;
        }

    }
}

char* get_cmd(char buff[],char* myargv[])//获取cmd命令
{
    if ( buff == NULL || myargv == NULL)
    {
        return NULL;
    }

    char * s = strtok(buff," ");
    int i = 0;
    while( s != NULL )
    {
        myargv[i++] = s;
        s = strtok(NULL," ");
    }
    return myargv[0];
}

int main(int argc, char* argv[])
{
    char* ips = "127.0.0.1";
    int port = 6000;

    get_arg(argc,argv,&ips,&port);

    int sockfd = connect_ser(ips,port);
    if ( sockfd == -1 )
    {
        printf("connect to server failed :%s ,port:%d\n",ips, port);
        exit(0);
    }

    while( 1 )
    {
        char buff[128] = {0};
        printf("connect: %s >> ",ips);
        fflush(stdout);

        fgets(buff,128,stdin);//ls, get a.c
        buff[strlen(buff) - 1] = 0;

        char cmd_buff[128] = {0};
        strcpy(cmd_buff,buff);

        char* myargv[10] = {0};
        char* cmd = get_cmd(buff,myargv);

        if ( strcmp(cmd,"get") == 0 )
        {
            recv_file(sockfd, myargv[1], cmd_buff);
        }
        else if ( strcmp(cmd,"up") == 0)
        {
            send_file(sockfd,myargv[1],cmd_buff);
        }
        else if ( strcmp(cmd,"end") == 0)
        {
            break;
        }
        else
        {
            char recv_buff[1024] = {0};
            send(sockfd,cmd_buff,strlen(cmd_buff),0);

            int num = recv(sockfd,recv_buff,1023,0);
            if ( num <= 0 )
            {
                printf("ser close\n");
                break;
            }

            if ( strncmp(recv_buff,"ok#",3) == 0)
            {
                printf("%s\n",recv_buff+3);
            }
            else
            {
                printf("执行命失败\n");
            }
            
        }
        

    }
    close(sockfd);
}

总结

了解到lseek命令,了解到管道的读写端的异常输出信号,熟悉到TCP编程的流程。

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-08-23 17:02:36  更:2021-08-23 17:04:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 21:13:35-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码