网络编程项目之FTP服务器

项目介绍

模拟FTP核心原理:客户端连接服务器后,向服务器发送一个文件。文件名可以通过参数指定,服务器端接收客户端传来的文件(文件名随意),如果文件不存在自动创建文件,如果文件存在,那么清空文件然后写入。

项目功能介绍:

均有服务器和客户端代码,基于TCP写的。

在同一路径下,将客户端可执行代码复制到其他的路径下,接下来在不同的路径下运行服务器和客户端。

相当于另外一台电脑在访问服务器。

客户端和服务器链接成功后出现以下提示:四个功能

***************list************** //列出服务器所在目录下的文件名(除目录不显示)

***********put filename********** //上传一个文件

***********get filename********** //从服务器所在路径下载文件

**************quit*************** //退出(可只退出客户端,服务器等待下一个客户端链接)

FTP客户端

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>

char buf[128] = {};
//客户端函数声明
void putfileC(char *buf, int sockfd); //客户端上传文件到服务器功能函数
void getfileC(char *buf, int sockfd); //客户端从服务器下载文件功能函数
void list(int sockfd);

int main(int argc, char const *argv[])
{
    // 1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err:");
        return -1;
    }
    // 2.填充结构体(ipv4)
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;                 // 协议族ipv4
    addr.sin_port = htons(atoi(argv[2]));      // 端口号(网络字节序)
    addr.sin_addr.s_addr = inet_addr(argv[1]); // ip地址(网络字节序)

    // 3.连接
    if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        perror("connect err");
        return -1;
    }
    //4.发送
    while (1)
    {
        fgets(buf, sizeof(buf), stdin);
        if (buf[strlen(buf) - 1] == '\n')
        {
            buf[strlen(buf) - 1] = '\0';
        }
        if (strcmp(buf, "quit") == 0)
        {
            break;
        }
        if (strcmp(buf, "list") == 0)
        {
            send(sockfd, buf, sizeof(buf), 0);
            list(sockfd);
        }
        if (strncmp(buf, "put ", 4) == 0)
        {
            send(sockfd, buf, sizeof(buf), 0);
            putfileC(buf, sockfd);
            printf("put ok\n");
        }
        if (strncmp(buf, "get ", 4) == 0)
        {
            send(sockfd, buf, sizeof(buf), 0);
            getfileC(buf, sockfd);
            printf("get ok\n");
        }
    }
    //5.关闭文件
    close(sockfd);
    return 0;
}

void list(int sockfd)
{
    while (1)
    {
        recv(sockfd, buf, sizeof(buf), 0);
        if (strcmp(buf, "end") == 0)
        {
            break;
        }
        else
            printf("%s ", buf);
    }
    printf("\n");
    return;
}

void putfileC(char *buf, int sockfd)
{
    int fd = open(buf + 4, O_RDONLY);
    if (fd < 0)
    {
        perror("fd client err");
        return;
    }
    ssize_t ret;
    while (1)
    {
        ret = read(fd, buf, sizeof(buf));
        if (ret == 0)
        {
            break;
        }
        send(sockfd, buf, sizeof(buf), 0);
        memset(buf, 0, 128);
    }
    send(sockfd, "end", sizeof(buf), 0);
    close(fd);
    return;
}

void getfileC(char *buf, int sockfd)
{
    int fd = open(buf + 4, O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (fd < 0)
    {
        perror("fd  err");
        return;
    }
    ssize_t ret;
    while (1)
    {
        memset(buf, 0, 128);
        ret = read(sockfd, buf, sizeof(buf));
        if (strcmp(buf, "end") == 0)
        {
            break;
        }
        write(fd, buf, strlen(buf));
    }
    close(fd);
    return;
}

FTP服务器

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>

char buf[128] = {};
//服务器函数声明
void menu();                            //显示目录
void list(int acceptfd);                //列出服务器当前路径的文件名
void putfileS(char *buf, int acceptfd); //服务器接受客户端上传文件功能函数
void getfileS(char *buf, int acceptfd); //服务器下载文件到客户端功能函数

int main(int argc, char const *argv[])
{

    //避免少输,出现段错误
    if (argc != 2)
    {
        printf("please input %s ip port\n", argv[0]);
        return -1;
    }
    // 1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err:");
        return -1;
    }
    // 2.填充结构体(ipv4)
    struct sockaddr_in addr, caddr;       //客户端连接的时候会自己填充信息,只需给它个空间
    addr.sin_family = AF_INET;            // 协议族ipv4
    addr.sin_port = htons(atoi(argv[1])); // 端口号(网络字节序)
    addr.sin_addr.s_addr = INADDR_ANY;    // ip地址(网络字节序)
    // addr.sin_addr.s_addr = inet_addr("0.0.0.0");

    // 3.绑定
    int ret = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
    if (ret < 0)
    {
        perror("bind err:");
        return -1;
    }
    // 4.监听
    if (listen(sockfd, 5) < 0)
    {
        perror("listen err");
        return -1;
    }
    //5.循环等待连接
    socklen_t len = sizeof(caddr);
    while (1)
    {
        //有客户端连接就显示目录
        // 5.等待连接
        int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
        if (acceptfd < 0)
        {
            perror("accpet err:");
            return -1;
        }
        printf("acceptfd = %d\n", acceptfd);
        printf("client: ip=%s port=%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
        // 6.接收
        menu();
        while (1)
        {
            ret = recv(acceptfd, buf, sizeof(buf), 0);
            if (ret == -1)
            {
                perror("recv err:");
                return -1;
            }
            else if (ret == 0)
            {
                printf("client exit\n");
                break;
            }
            else
            {
                if (strcmp(buf, "list") == 0)
                {
                    list(acceptfd);
                }
                if (strncmp(buf, "put ", 4) == 0)
                {
                    putfileS(buf, acceptfd);
                    printf("put end\n");
                }
                if (strncmp(buf, "get ", 4) == 0)
                {
                    getfileS(buf, acceptfd);
                    printf("get end\n");
                }
            }
        }
        // 7.关闭
        close(acceptfd);
    }
    close(sockfd);
    return 0;
}

void menu() //显示目录
{
    printf("***************list**************\n");
    printf("***********put filename**********\n");
    printf("***********get filename**********\n");
    printf("**************quit***************\n");
}
//列出服务器所在目录下的文件名(除目录不显示)
void list(int acceptfd)
{
    DIR *dirp;
    struct dirent *d;
    dirp = opendir("./");
    if (NULL == dirp)
    {
        perror("opendir err");
        return;
    }
    struct stat st;
    while ((d = readdir(dirp)) != NULL)
    {
        if (stat(d->d_name, &st) < 0)
        {
            perror("stat err");
            return;
        }
        if ((st.st_mode & S_IFMT) == S_IFREG)
        {
            send(acceptfd, d->d_name, sizeof(d->d_name), 0);
        }
    }
    send(acceptfd, "end", sizeof(buf), 0);
}
//服务器接受客户端上传文件功能函数
void putfileS(char *buf, int acceptfd)
{
    int fd = open(buf + 4, O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (fd < 0)
    {
        perror("fd server err");
        return;
    }
    ssize_t ret;
    while (1)
    {
        memset(buf, 0, 128);
        ret = recv(acceptfd, buf, sizeof(buf), 0);
        if (strcmp(buf, "end") == 0)
        {
            break;
        }
        write(fd, buf, strlen(buf));
    }
    close(fd);
    return;
}
//服务器下载文件到客户端功能函数
void getfileS(char *buf, int acceptfd)
{
    int fd = open(buf + 4, O_RDONLY);
    if (fd < 0)
    {
        perror("fd  err");
        return;
    }
    ssize_t ret;
    while (1)
    {
        ret = read(fd, buf, sizeof(buf));
        if (ret == 0)
        {
            break;
        }
        send(acceptfd, buf, sizeof(buf), 0);
        memset(buf, 0, 128);
    }
    send(acceptfd, "end", sizeof(buf),0);
    close(fd);
    return;
}

问题 

函数参数时把数组名传进去了,导致计算数组长度时报错,原因就是数组名被当作函数参数时会被降级为指针。

【C语言篇】数组作为函数参数_c语言数组作为函数参数-优快云博客

编译器警告:sizeof on array function parameter “arr‘ will return size “用另一个值除指针的sizeof值”,sizeof(数组名)时会遇到的坑-优快云博客

c++数组传递参数与返回_function cannot return function type-优快云博客

函数使用数组的报错_sizeof' on array function parameter 'a' will retur-优快云博客

粘包问题: 上传或下载都出现过服务器或客户端卡主现象,原因就是粘包问题没有解决,导致判断结束的"end"包与数据包粘在一起,判断条件不能满足跳不出循环一直卡主。目前我能解决的就是加个sleep函数先让发送或者接收数据的一方睡眠个几秒,或者收和发长度一致。

自己写的ftp服务端程序代码,支持{"USER", do_user }, {"PASS", do_pass }, {"CWD", do_cwd }, {"XCWD", do_cwd }, {"CDUP", do_cdup }, {"REIN", do_rein },//重新初始化,此命令终止USER,重置所有参数,控制连接仍然打开,用户可以再次使用USER命令 {"QUIT", do_quit }, /*------------传输参数命令------------*/ {"PORT", do_port },//数据端口,主要向服务器发送客户数据连接的端口 //格式为PORT h1,h2,h3,h4,p1,p2,其中32位的IP地址用h1,h2,h3,h4表示,16位的TCP端口号用p1,p2表示 {"PASV", do_pasv },//此命令要求服务器数据传输进程在指定的数据端口侦听,进入被动接收请求的状态 {"TYPE", do_type },//文件类型,可指定ASCII码、EBCDIC码、Image、本地类型文件等参数 /*------------服务命令----------------*/ {"RETR", do_retr },//下载文件 {"STOR", do_stor },//上传 {"APPE", do_appe },//上传,如文件已存在,数据附加到尾部 {"REST", do_rest },//重新开始 {"RNFR", do_rnfr }, {"RNTO", do_rnto },//重命名文件或目录 {"ABOR", do_abor },//异常终止 {"DELE", do_dele },//删除文件 {"RMD", do_rmd },//删除目录 {"XRMD", do_rmd }, {"MKD", do_mkd },//新建目录 {"XMKD", do_mkd }, {"PWD", do_pwd },//打印当前目录 {"XPWD", do_pwd }, {"LIST", do_list },//列目录详细清单 {"NLST", do_nlst },//列目录短清单 {"SYST", do_syst },//获取系统信息 {"STAT", do_stat },//返回服务器状态 {"SIZE", do_size },//获得文件大小 {"HELP", do_help }, {"NOOP", do_noop }, {"SITE", do_site }, }等命令
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-Turbo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值