Linux网络编程学习之---简单局域网FTP文件传输服务器

本文介绍了作者在学习Linux网络编程时完成的一项作业,即创建一个基于TCP协议的简单局域网FTP文件传输服务器。服务器端能解析客户端的指令,包括显示帮助、列出文件、下载和上传文件以及退出。提供了ftp_server.c、ftp_client.c、network.h和network.c四个源文件的相关内容。欢迎交流学习。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这两天在学习Linux网络编程,老师留了个小作业,就是做一个基于TCP协议的文件服务器。具体要求如下:
编写TCP文件服务器和客户端。

客户端功能:
支持如下指令:

help:显示客户端所有命令和说明。
list:先是服务器端可下载文件列表。
get filename:下载文件。
put filenme:上传文件。
quit:退出。

服务器端功能(单进程):

解析客户端指令并提供相应服务。

以下是代码:

ftp_server.c

/*
 * ftp_server.c
 * 
 * Copyright 2015 wnavy <whjwnavy@163.com>
 * 
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "network.h"

#define INET_NUM 8888//端口号
#define IP_ADDRESS "192.168.1.111"//IP地址

#ifndef BUFF_LEN
#define BUFF_LEN 1028
#endif

#ifndef CMD_HELP
#define CMD_HELP    11//帮助
#define CMD_LIST    22//列出所有文件
#define CMD_GET     33//下载文件
#define CMD_PUT     44//上传文件
#define CMD_QUIT    55//退出
#define CMD_ERROR   -1//错误
#endif

int main(void)
{
    int sockfd;//网络套接字
    //初始化网络连接
    sockfd = network_init(NET_SERVER, IP_ADDRESS, INET_NUM);
    if(sockfd == -1)
    {
        perror("Network init error!");
        exit(EXIT_FAILURE);
    }
    printf("LISTEN-ing...\n");

    char RCV_BUFF[BUFF_LEN];//接收缓存
    char SEND_BUFF[BUFF_LEN];//发送缓存
    int cmd_result;//命令解析结果
    int connectfd;//建立连接后用于通信的套接字文件
    int readlen;//读取到的字节数
    while(1)
    {
        if((connectfd = accept(sockfd, NULL, NULL)) == -1)//出错
        {
            perror("Connect error!\n");
            break;
        }
        printf("Connect success!\n");
        while(1)
        {
            //接收命令
            readlen = read(connectfd, RCV_BUFF, sizeof(RCV_BUFF));
            if(readlen <0)//接收出错
            {
                perror("Read error!\n");
                break;
            }
            else
            {
                if(readlen == 0)//客户端关闭文件描述符后就会断开连接
                {
                    printf("Welcome to use again!\nQUIT...\n");
                    break;
                }
                else
                {
                    //printf("RECV:%s\n",RCV_BUFF);
                    cmd_result = ftp_cmd_analyse(RCV_BUFF);//解析命令

                    switch(cmd_result)
                    {
                        case CMD_ERROR:
                            printf("CMD_ERROR!\n");
                            break;
                        case CMD_LIST:
                            printf("List file:%s\n",RCV_BUFF+5);
                            if(ftp_putlist(connectfd, RCV_BUFF+5) == -1)
                            {
                                printf("List files error!\n");
                            }
                            else
                            {
                                printf("List files success!\n");
                            }
                            break;
                        case CMD_GET://客户端从服务器下载文件
                            printf("Put files:%s\n", RCV_BUFF+4);
                            if(ftp_putfile(connectfd, RCV_BUFF+4) == -1)
                            {
                                printf("Put files error!\n");
                            }
                            else
                            {
                                printf("Put files success!\n");
                            }
                            break;
                        case CMD_PUT://客户端上传文件到服务器
                            printf("Get files:%s\n", RCV_BUFF+4);
                            if(ftp_getfile(connectfd, RCV_BUFF+4) == -1)
                            {

                                printf("Get files error!\n");
                            }
                            else
                            {
                                printf("Get files success!\n");
                            }
                            break;
                        default:
                            break;
                    }
                }
            }
        }
        close(connectfd);//客户端退出,断开连接
    }
    close(sockfd);
    return 0;
}

ftp_client.c

/*
 * ftp_client.c
 * 
 * Copyright 2015 wnavy <whjwnavy@163.com>
 *  
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "network.h"


#define INET_NUM 8888//端口号
#define IP_ADDRESS "192.168.1.111"//服务器IP

#ifndef BUFF_LEN
#define BUFF_LEN 1028
#endif

#ifndef CMD_HELP
#define CMD_HELP    11//帮助
#define CMD_LIST    22//列出所有文件
#define CMD_GET     33//下载文件
#define CMD_PUT     44//上传文件
#define CMD_QUIT    55//退出
#define CMD_ERROR   -1//错误
#endif

int main(void)
{
    int sockfd;
    //初始化网络连接
    sockfd = network_init(NET_CLIENT, IP_ADDRESS, INET_NUM);
    if(sockfd == -1)
    {
        perror("Network init error!");
        exit(EXIT_FAILURE);
    }
    printf("Connect success!\n");

    //向服务器发送数据
    char SEND_BUFF[BUFF_LEN];
    //char RCV_BUFF[BUFF_LEN];
    int cmd_result;//命令解析结果
    while(1)
    {
        fgets(SEND_BUFF, sizeof(SEND_BUFF), stdin);//从键盘输入命令
        SEND_BUFF[strlen(SEND_BUFF)-1] = '\0';//去掉最后输入的回车符
        cmd_result = ftp_cmd_analyse(SEND_BUFF);//解析命令
        switch(cmd_result)
        {
            case CMD_ERROR:
                printf("ERROR!\n");
                break;
            ////////////////////////////////////////////////////////////    
            case CMD_HELP:
                ftp_print_help();
                break;
            ////////////////////////////////////////////////////////////
            case CMD_LIST:
                printf("List file:%s\n",SEND_BUFF+5);
                //列出服务器端可下载的所有文件
                if(ftp_getlist(sockfd, SEND_BUFF+5) == -1)
                {
                    printf("List file error!\n");
                }
                else
                {
                    //printf("List file success!\n");
                }
                break;
            ////////////////////////////////////////////////////////////
            case CMD_QUIT://跳出循环,关闭文件描述符
                printf("Welcome to use again!\nQUIT!\n");
                close(sockfd);//客户端关闭文件描述符后就会自动断开连接
                exit(EXIT_SUCCESS);
                break;
            ////////////////////////////////////////////////////////////
            case CMD_GET://下载文件
                printf("Download file:%s\n",SEND_BUFF+4);
                //向服务器发送命令
                if(write(sockfd, SEND_BUFF, BUFF_LEN) == -1)
                {
                    perror("Send cmd error!");
                    break;
                }
                if(ftp_getfile(sockfd, SEND_BUFF+4) == -1)//下载文件
                {
                    printf("Download error!\n");
                }
                else
                {
                    printf("Download success!\n");
                }
                break;
            ////////////////////////////////////////////////////////////
            case CMD_PUT:
                printf("Upload file:%s\n",SEND_BUFF+4);
                //向服务器发送命令
                if(write(sockfd, SEND_BUFF, BUFF_LEN) == -1)
                {
                    perror("Send cmd error!");
                    break;
                }
                if(ftp_putfile(sockfd, SEND_BUFF+4) == -1)//上传文件
                {
                    printf("Upload error!\n");
                }
                else
                {
                    printf("Upload success!\n");
                }
                break;
            ////////////////////////////////////////////////////////////
            default:
                break;
        }
    }
    close(sockfd);//客户端关闭文件描述符后就会自动断开连接
    return 0;
}

network.h

/*
 * network.h
 * 
 * Copyright 2015 wnavy <whjwnavy@163.com>
 * 
*/
#ifndef __NETWORK_H_
#define __NETWORK_H_

#define NET_SERVER  11
#define NET_CLIENT  22

#define BUFF_LEN 1028//接收发送缓冲区大小

#define CMD_HELP    11//帮助
#define CMD_LIST    22//列出所有文件
#define CMD_GET     33//下载文件
#define CMD_PUT     44//上传文件
#define CMD_QUIT    55//退出
#define CMD_ERROR   -1//错误

#define E_NOFILE    "ERROR:No such file or directory!\n"
#define E_DODNLOAD  "ERROR:Download error!\n"
#define E_UPLOAD    "ERROR:Upload error!\n"
#define GET_LIST_END "SUCCESS:GET LIST SUCCESS!"

int     network_init(int net_type, const char* IP_ADDRESS, \
short INET_NUM);

void    ftp_print_help(void);
int     ftp_cmd_analyse(const char* cmd);
int     ftp_getlist(int getsockfd, const char* LIST_NAME);
int     ftp_putlist(int putsockfd, const char* LIST_NAME);
int     ftp_getfile(int getsockfd, const char* GET_FILENAME);
int     ftp_putfile(int putsockfd, const char* PUT_FILENAME);

#endif

network.c

/*
 * network.c
 * 
 * Copyright 2015 wnavy <whjwnavy@163.com>
 * 
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "network.h"
#include <stddef.h>
#include <dirent.h>

#define BACKLOG 10//最大连接数

/***********************************************************************
 * 函数作用:网络初始化
 * 函数参数:
 *      net_type:网络类型
 *          NET_SERVER:服务器
 *          NET_CLIENT:客户端
 *      NET_IP:IP地址
 *      NET_NUM:端口号
 * 返回值:
 *      已经创建好的网络的套接字
 * 说明:
 *      无
 **********************************************************************/
int network_init(int net_type, const char* NET_IP, short NET_NUM)
{
    int sockfd;
    //创建监听套接字
    if((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("Create socket error!");
        //exit(EXIT_FAILURE);
        return -1;
    }

    struct sockaddr_in sockadd;
    memset(&sockadd, 0, sizeof(sockadd));
    sockadd.sin_family = AF_INET;
    sockadd.sin_port = htons(NET_NUM);
    sockadd.sin_addr.s_addr = inet_addr(NET_IP);

    if(net_type == NET_CLIENT)
    {
        //连接服务器
        if(connect(sockfd, (struct sockaddr*)(&sockadd), \
        sizeof(sockadd)) == -1)
        {
            perror("Connect error!");
            //exit(EXIT_FAILURE);
            return -1;
        }
    }
    else
    {
        if(net_type == NET_SERVER)
        {
            //绑定IP地址断口号 
            if(bind(sockfd, (struct sockaddr*)(&sockadd), \
            sizeof(sockadd)) == -1)
            {
                perror("Bind error!");
                //exit(EXIT_FAILURE);
                return -1;
            }
            //监听客户端
            if(listen(sockfd, BACKLOG) == -1)
            {
                perror("Listen error!");
                //exit(EXIT_FAILURE);
                return -1;
            }
        }
        else
        {
            return -1;
        }
    }
    return sockfd;
}

/***********************************************************************
 * 函数作用:命令解析
 * 函数参数:
 *      cmd:存放命令的字符串
 * 返回值:
 *      命令解析结果(事先定义好的宏)
 * 说明:
 *      带参数的命令,参数与命令之间用空格分开
 **********************************************************************/
int ftp_cmd_analyse(const char* cmd)
{
    if(cmd == NULL)
    {
        return CMD_ERROR;
    }
    else
    {
        if(strcmp(cmd, "help") == 0)//无参数命令
            return CMD_HELP;
        else
        {
            if(strncmp(cmd, "list ", 5) == 0)//有参数命令
                return CMD_LIST;
            else
            {
                if(strcmp(cmd, "quit") == 0)//无参数命令
                    return CMD_QUIT;
                else
                {
                    if(strncmp(cmd, "get ", 4) == 0)//有参数命令
                        return CMD_GET;
                    else
                    {
                        if(strncmp(cmd, "put ", 4) == 0)//有参数命令
                            return CMD_PUT;
                        else
                        {
                            return CMD_ERROR;
                        }
                    }
                }
            }   
        }
    }
}

/***********************************************************************
 * 函数作用:打印帮助信息
 * 函数参数:
 *      无
 * 返回值:
 *      无
 * 说明:
 *      无
 **********************************************************************/
void ftp_print_help(void)
{
    printf("--------------------------------\n");
    /*printf("Thankyou  to  use  our system!\n");
    printf("Creat by:            WHJWNAVY\n");
    printf("Contact us: whjwnavy@163.com\n\n");*/
    printf("Command List:\n");
    printf("help:           search for help.\n");
    printf("list:   list all files on server.\n");
    printf("quit:          exit this system.\n");
    printf("get filename:     download file.\n");
    printf("put filename:       upload file.\n");
}

/***********************************************************************
 * 函数作用:从服务器端获取文件列表
 * 函数参数:
 *      getsockfd:用于接收文件的文件描述符
 *      LIST_NAME:路径
 * 返回值:
 *      成功:非负值(getsockfd)
 *      失败:-1
 * 说明:
 *      文件路径末尾的"/"不能省略!
 *      "list /home"这种写法是不对的,必须写成“list /home/”
 **********************************************************************/
int ftp_getlist(int getsockfd, const char* LIST_NAME)
{
    //接收缓存,BUFF_LEN必须定义,否则无法使用本函数!!!
    char GET_BUFF[BUFF_LEN];
    int readsize;
    sprintf(GET_BUFF,  "list ");
    sprintf(GET_BUFF+5, "%s", LIST_NAME);
    if(write(getsockfd, GET_BUFF, BUFF_LEN) == -1)//向服务器发送命令
    {
        perror("Send cmd error!");
        return -1;
    }
    else
    {
        while(1)//循环读取
        {
            readsize = read(getsockfd, GET_BUFF, BUFF_LEN);
            if(readsize <= 0)//读错误
            {
                perror("Get list error!");
                return -1;
            }
            else
            {
                if(strncmp(GET_BUFF, GET_LIST_END, \
                sizeof(GET_LIST_END)) == 0)//判断服务器是否发送完毕
                {
                    printf("\n");
                    break;//发送完毕,退出
                }
                else
                {
                    printf("%s", GET_BUFF);//服务器端发送完毕,显示文件
                }
            }
        }
    }
    return getsockfd;
}

/***********************************************************************
 * 函数作用:把服务器端文件列表发送到客户端
 * 函数参数:
 *      putsockfd:用于接收文件的文件描述符
 *      LIST_NAME:路径
 * 返回值:
 *      成功:非负值(putsockfd)
 *      失败:-1
 * 说明:
 *      无
 **********************************************************************/
int ftp_putlist(int putsockfd, const char* LIST_NAME)
{
    char PUT_BUFF[BUFF_LEN];
    int strn, strm;
    DIR* dp;
    struct dirent *ep;
    struct stat st;
    char LIST_PATH[256];

    dp = opendir(LIST_NAME);//打开目录

    if(dp != NULL)//打开目录成功
    {
        while(ep = readdir(dp))//循环读目录
        {
            if(ep->d_name[0] != '.')//如果不是隐藏文件或目录
            {
                //用stat函数读取文件信息,文件名带路径
                sprintf(LIST_PATH + sprintf(LIST_PATH, "%s", LIST_NAME), \
                "%s", ep->d_name);//为文件名加上路径
                if(stat(LIST_PATH, &st) != -1)
                {
                    mode_t filemode = st.st_mode & S_IFMT;
                    switch (filemode)
                    {
                        case S_IFDIR:
                            strn = sprintf(PUT_BUFF, "DIRE\t");//目录
                            break;
                        case S_IFREG:
                            strn = sprintf(PUT_BUFF, "FILE\t");//文件
                            break;
                        case S_IFBLK:
                            strn = sprintf(PUT_BUFF, "BLCK\t");//块文件
                            break;
                        case S_IFCHR:
                            strn = sprintf(PUT_BUFF, "SPEC\t");//特殊文件
                            break;
                        case S_IFIFO:
                            strn = sprintf(PUT_BUFF, "PIPE\t");//管道文件
                            break;
                        default:
                            break;
                    }
                    strm = strn;
                    strn = sprintf(PUT_BUFF+strm, "%o\t", st.st_mode & \
                    0x1ff);//权限
                    strm += strn;
                    strn = sprintf(PUT_BUFF+strm, "%ld\t", st.st_size);
                    strm += strn;
                    strn = sprintf(PUT_BUFF+strm, "%s\n", ep->d_name);
                    strn = 0;
                    strm = 0;
                    //把字符串写回客户端
                    if(write(putsockfd, PUT_BUFF, BUFF_LEN) == -1)
                    {
                        perror("Put list error!");//接收出错,返回
                        return -1;
                    }
                    memset(PUT_BUFF, 0, BUFF_LEN);
                }
                else
                {
                    perror("Stat error!");
                    return -1;
                }
            }
        }
        if(write(putsockfd, GET_LIST_END, BUFF_LEN) == -1)//发送结束
        {
            perror("Write endstring error!");
            return -1;
        }
    }
    else
    {
        if(write(putsockfd, GET_LIST_END, BUFF_LEN) == -1)//发送结束
        {
            perror("Write endstring error!");
            return -1;
        }
        perror("Can't open the directory!");
        return -1;
    }

    closedir(dp);
    return putsockfd;
}

/***********************************************************************
 * 函数作用:接收文件
 * 函数参数:
 *      getsockfd:用于接收文件的文件描述符
 *      GET_FILENAME:带路径的文件名
 * 返回值:
 *      成功:接收到的文件的文件描述符
 *      失败:-1
 * 说明:
 *      GET数据包格式:
 *      数据包总长度为1028个字节
 *      数据包头区:前4个字节,存放数据区大小
 *      数据内容区:后1024个字节,存放实际的数据
 * 
 *      服务器每次会从文件中读取1024个字节加上包头(4个字节)后发送给客户端。
 *      最后一次(读到文件末尾)时实际读取的数据小于1024个字节。可以以此判断
 *      是否读取结束。
 * 
 *      使用本函数前BUFF_LEN必须定义,否则无法使用或造成无法预知的结果!!!
 **********************************************************************/
int ftp_getfile(int getsockfd, const char* GET_FILENAME)
{
    int getfilefd;//存放接收文件的文件描述符
    int getfilesize;//实际接收的文件大小
    /*
     * #ifndef BUFF_LEN
     * #define BUFF_LEN 1028
     * #endif
    */
    //接收缓存,BUFF_LEN必须定义,否则无法使用本函数!!!
    char GET_BUFF[BUFF_LEN];


    //打开一个文件描述符用与保存来自发送端的文件
    if((getfilefd = open(GET_FILENAME, O_WRONLY|O_CREAT|O_TRUNC, 0666)) \
    == -1)
    {
        perror("Can't open or creat file!");
        return -1;
    }
    else
    {
        //接收文件
        while(getfilesize = read(getsockfd, GET_BUFF, BUFF_LEN) > 0)
        {
            /*发送端出错会发送一个“ERROR:xxxx”格式的字符串,接收端以此判断
            是否出错,如果发送端不发送此错误信息,发送端出错后挂机,接收端也
            将卡机*/
            if(strncmp(GET_BUFF, "ERROR:", 6) == 0)//接收文件出错
            {
                printf("%s", GET_BUFF);
                return -1;
            }
            else
            {
                //取出数据包头中包含的数据区大小
                memcpy(&getfilesize, GET_BUFF, 4);

                /*GET_BUFF+4是因为数据包前四个字节存放的是数据长度,之后的
                1024个字节才存放的实际的数据*/
                if(write(getfilefd, GET_BUFF+4, getfilesize) == -1)
                {
                    perror("Download error!");//接收出错,返回
                    close(getfilefd);//关闭文件
                    return -1;
                }
                if(getfilesize < (BUFF_LEN-4))//已经读取到文件末尾
                        break;//接收结束,退出
            }
        }
        close(getfilefd);//关闭文件
        return getfilefd;//接收完成,返回接收到的文件的文件描述符。
        /*但是此值仅有数值上的意义,无实际作用。因为该文件在函数返回前已被
        关闭要想在函数中通过此文件描述符打开此文件,就不要在此函数结束时关
        闭该文件!*/
    }
}

/***********************************************************************
 * 函数作用:发送文件
 * 函数参数:
 *      putsockfd:用于发送文件的文件描述符
 *      PUT_FILENAME:带路径的文件名
 * 返回值:
 *      成功:发送的文件的文件描述符
 *      失败:-1
 * 说明:
 *      无
 **********************************************************************/
int ftp_putfile(int putsockfd, const char* PUT_FILENAME)
{
    int putfilefd;//存放接收文件的文件描述符
    int putfilesize;//实际接收的文件大小
    /*
     * #ifndef BUFF_LEN
     * #define BUFF_LEN 1028
     * #endif
    */
    //接收缓存,BUFF_LEN必须定义,否则无法使用本函数!!!
    char PUT_BUFF[BUFF_LEN];    

    if((putfilefd = open(PUT_FILENAME, O_RDONLY)) == -1)//打开文件
    {
        perror("Open error!");
        write(putsockfd, E_NOFILE, BUFF_LEN);//把错误信息写回。
        /*如果不写回错误信息,发送端会卡死*/
        return -1;
    }
    else
    {
        //printf("Open %s success!\n",PUT_FILENAME);
        /*
         * 先从文件中读取1024个字节放入发送缓冲区的第五个字节开始的1024个字
         * 节中,后在把实际读取到的字节数放入发送缓冲区的前四个字节中(int型),
         * 最后再把这1028个字节发送出去。
         */
        while((putfilesize = read(putfilefd, PUT_BUFF+4, (BUFF_LEN-4))) \
        >0)
        {
            memcpy(PUT_BUFF, &putfilesize, 4);
            if(write(putsockfd, PUT_BUFF, BUFF_LEN) == -1)
            {
                perror("Put file error!");
                close(putfilefd);
                return -1;
            }
            memset(PUT_BUFF, 0, BUFF_LEN);//清空缓冲区
            //printf("\rDownloading...");
        }
    }
    close(putfilefd);
    return putfilefd;
}

<完>

第一次发贴,不足之处还望见谅
学习交流联系:

whjwnavy@163.com
805400349@qq.com

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值