用C語言實現FTP


1關於ftp

FTP協議包括兩個組成部分,其一為FTP服務器,其二為FTP客戶端。其中FTP服務器用來存儲文件,用戶可以使用FTP客戶端通過FTP協議訪問位於FTP服務器上的資源。

默認情況下FTP協議使用TCP端口中的 20和21這兩個端口,其中20用於傳輸數據,21用於傳輸控制信息。

FTP支持兩種模式,一種方式叫做Standard (也就是 PORT方式,主動方式),一種是 Passive(也就是PASV,被動方式)。 Standard模式 FTP的客戶端發送 PORT 命令到FTP服務器。Passive模式FTP的客戶端發送 PASV命令到 FTP Server。

Port

FTP 客戶端首先和FTP服務器的TCP 21端口建立連接,通過這個通道發送命令,客戶端需要接收數據的時候在這個通道上發送PORT命令。 PORT命令包含了客戶端用什么端口接收數據。在傳送數據的時候,服務器端通過自己的TCP 20端口連接至客戶端的指定端口發送數據。 FTP server必須和客戶端建立一個新的連接用來傳送數據。

Passive

在建立控制通道的時候和Standard模式類似,但建立連接后發送的不是Port命令,而是Pasv命令。FTP服務器收到Pasv命令后,隨機打開一個高端端口(端口號大於1024)並且通知客戶端在這個端口上傳送數據的請求,客戶端連接FTP服務器此端口,然后FTP服務器將通過這個端口進行數據的傳送,這個時候FTP server不再需要建立一個新的和客戶端之間的連接。

很多防火牆在設置的時候都是不允許接受外部發起的連接的,所以許多位於防火牆后或內網的FTP服務器不支持PASV模式,因為客戶端無法穿過防火牆打開FTP服務器的高端端口;而許多內網的客戶端不能用PORT模式登陸FTP服務器,因為從服務器的TCP 20無法和內部網絡的客戶端建立一個新的連接,造成無法工作。

      我這次實現簡化了FTP協議過程,原本ftp使用兩個端口分別傳輸數據和控制信息,我只用了一個端口同時完成這兩個功能。實現了一些簡單命令,如ls、download、upload。

2算法流程

client.c:

server.c:

3代碼及注釋

client.c

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

#define N 256

void commd_help();
void commd_exit();
void commd_ls(struct sockaddr_in, char *);
void commd_get(struct sockaddr_in , char *);
void commd_put(struct sockaddr_in , char *);

int main(int argc, char *argv[])
{
    char commd[N];
    struct sockaddr_in addr;
    int len;
    bzero(&addr, sizeof(addr));     //將&addr中的前sizeof(addr)字節置為0,包括'\0'
    addr.sin_family = AF_INET;      //AF_INET代表TCP/IP協議
    addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //將點間隔地址轉換為網絡字節順序
    addr.sin_port = htons(8989);    //轉換為網絡字節順序
    len = sizeof(addr);

    while(1)
    {
        printf("ftp>");
        bzero(commd,N);
        //fgets函數從stdin流中讀取N-1個字符放入commd中
        if(fgets(commd,N,stdin) == NULL)
        {
            printf("Fgets Error!\n");
            return -1;
        }

        commd[strlen(commd)-1]='\0';    //fgets函數讀取的最后一個字符為換行符,此處將其替換為'\0'

        printf("Input Command Is [ %s ]\n",commd);

        if(strncmp(commd,"help",4) == 0) //比較兩個字符串前4個字節,若相等則返回0
        {
            commd_help();
        }else if(strncmp(commd, "exit",4) == 0)
        {
            commd_exit();
            exit(0);   //結束進程
        }else if(strncmp(commd, "ls" , 2) == 0)
        {
            commd_ls(addr, commd);
        }else if(strncmp(commd, "get" , 3) == 0)
        {
            commd_get(addr, commd);
        }else if(strncmp(commd, "put", 3) ==0 )
        {
            commd_put(addr, commd);
        }else
        {
            printf("Command Is Error!Please Try Again!\n");
        }

    }
    return 0;
}
/*
**幫助信息
*/
void commd_help()
{

    printf("\n=---------------------歡迎使用FTP--------------------------|\n");
    printf("|                                                           |\n");
    printf("|                 help:顯示所有FTP服務器命令                 |\n");
    printf("|                                                           |\n");
    printf("|                 exit:離開FTP服務器                         |\n");
    printf("|                                                           |\n");
    printf("|                 ls : 顯示FTP服務器的文件列表               |\n");
    printf("|                                                           |\n");
    printf("|                 get <file>:從FTP服務器下載文件            |\n");
    printf("|                                                           |\n");
    printf("|                 put <file>:上傳文件到FTP服務器             |\n");
    printf("|                                                           |\n");
    printf("|-----------------------------------------------------------|\n");

    return ;
}
/*
**退出FTP服務器
*/
void commd_exit()
{
    printf("Bye!\n");
}
/*
**顯示文件列表
*/
void commd_ls(struct sockaddr_in addr, char *commd)
{
    int sockfd;
    //創建套接字
    if((sockfd=socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        printf("Socket Error!\n");
        exit(1);
    }

    if(connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        printf("Connect Error!\n");
        exit(1);
    }
    //將commd指向的內容寫入到sockfd所指的文件中,此處即指套接字
    if(write(sockfd, commd, N) < 0)
    {
        printf("Write Error!\n");
        exit(1);
    }

    while(read(sockfd, commd, N) > 0)  //從sockfd中讀取N字節內容放入commd中,
    {                                   //返回值為讀取的字節數
        printf(" %s ",commd);
    }
    printf("\n");

    close(sockfd);
    return ;
}
/*
**實現文件的下載                            
*/
void commd_get(struct sockaddr_in addr, char *commd)
{
    int fd;
    int sockfd;
    char buffer[N];
    int nbytes;
    //創建套接字,並進行錯誤檢測
    if((sockfd=socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        printf("Socket Error!\n");
        exit(1);
    }
    //connect函數用於實現客戶端與服務端的連接,此處還進行了錯誤檢測
    if(connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        printf("Connect Error!\n");
        exit(1);
    }
    //通過write函數向服務端發送數據
    if(write(sockfd, commd, N) < 0)
    {
        printf("Write Error!At commd_get 1\n");
        exit(1);
    }
    //利用read函數來接受服務器發來的數據
    if(read(sockfd, buffer, N) < 0)
    {
        printf("Read Error!At commd_get 1\n");
        exit(1);
    }
    //用於檢測服務器端文件是否打開成功
    if(buffer[0] =='N')
    {
        close(sockfd);
        printf("Can't Open The File!\n");
        return ;
    }
    //open函數創建一個文件,文件地址為(commd+4),該地址從命令行輸入獲取
    if((fd=open(commd+4, O_WRONLY|O_CREAT|O_TRUNC, 0644)) < 0)
    {
        printf("Open Error!\n");
        exit(1);
    }
    //read函數從套接字中獲取N字節數據放入buffer中,返回值為讀取的字節數
    while((nbytes=read(sockfd, buffer, N)) > 0)
    {
        //write函數將buffer中的內容讀取出來寫入fd所指向的文件,返回值為實際寫入的字節數
        if(write(fd, buffer, nbytes) < 0)
        {
            printf("Write Error!At commd_get 2");
        }
    }

    close(fd);
    close(sockfd);

    return ;

}
/*
**實現文件的上傳                            
*/
void commd_put(struct sockaddr_in addr, char *commd)
{
    int fd;
    int sockfd;
    char buffer[N];
    int nbytes;
    //創建套接字
    if((sockfd=socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        printf("Socket Error!\n");
        exit(1);
    }
    //客戶端與服務端連接
    if(connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        printf("Connect Error!\n");
        exit(1);
    }
    //從commd中讀取N字節數據,寫入套接字中
    if(write(sockfd, commd, N)<0)
    {
        printf("Wrtie Error!At commd_put 1\n");
        exit(1);
    }
    //open函數從(commd+4)中,讀取文件路徑,以只讀的方式打開
    if((fd=open(commd+4, O_RDONLY)) < 0)
    {
        printf("Open Error!\n");
        exit(1);
    }
    //從fd指向的文件中讀取N個字節數據
    while((nbytes=read(fd, buffer, N)) > 0)
    {
        //從buffer中讀取nbytes字節數據,寫入套接字中
        if(write(sockfd, buffer, nbytes) < 0)
        {
            printf("Write Error!At commd_put 2");
        }
    }

    close(fd);
    close(sockfd);

    return ;
}

server.c

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

#define N 256//文件名和命令名最長為256字節

void commd_ls(int);
void commd_get(int, char *);
void commd_put(int, char *);

int main(int arg, char *argv[])
{
    int ser_sockfd,cli_sockfd;
    struct sockaddr_in ser_addr,cli_addr;
    int ser_len, cli_len;
    char commd [N];
    bzero(commd,N);//將commd所指向的字符串的前N個字節置為0,包括'\0'

    if((ser_sockfd=socket(AF_INET, SOCK_STREAM, 0) ) < 0)
    {
        printf("Sokcet Error!\n");
        return -1;
    }

    bzero(&ser_addr,sizeof(ser_addr));
    ser_addr.sin_family = AF_INET;
    ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);//本地ip地址
    ser_addr.sin_port = htons (8989);//轉換成網絡字節
    ser_len = sizeof(ser_addr);
    //將ip地址與套接字綁定
    if((bind(ser_sockfd, (struct sockaddr *)&ser_addr, ser_len)) < 0)
    {
        printf("Bind Error!\n");
        return -1;
    }
    //服務器端監聽
    if(listen(ser_sockfd, 5) < 0)
    {
        printf("Linsten Error!\n");
        return -1;
    }

    bzero(&cli_addr, sizeof(cli_addr));
    ser_len = sizeof(cli_addr);

    while(1)
    {
        printf("server>");
        //服務器端接受來自客戶端的連接,返回一個套接字,此套接字為新建的一個,並將客戶端的地址等信息存入cli_addr中
        //原來的套接字仍處於監聽中
        if((cli_sockfd=accept(ser_sockfd, (struct sockaddr *)&cli_addr, &cli_len)) < 0)
        {
            printf("Accept Error!\n");
            exit(1);
        }
        //由套接字接收數據時,套接字把接收的數據放在套接字緩沖區,再由用戶程序把它們復制到用戶緩沖區,然后由read函數讀取
        //write函數同理
        if(read(cli_sockfd, commd, N) < 0)  //read函數從cli_sockfd中讀取N個字節數據放入commd中
        {
            printf("Read Error!\n");
            exit(1);
        }

        printf("recvd [ %s ]\n",commd);

        if(strncmp(commd,"ls",2) == 0)
        {
            commd_ls(cli_sockfd);
        }else if(strncmp(commd,"get", 3) == 0 )
        {
            commd_get(cli_sockfd, commd+4);
        }else if(strncmp(commd, "put", 3) == 0)
        {
            commd_put(cli_sockfd, commd+4);
        }else
        {
            printf("Error!Command Error!\n");
        }
    }

    return 0;
}
/*
**顯示文件列表
*/
void commd_ls(int sockfd)
{
    DIR * mydir =NULL;
    struct dirent *myitem = NULL;
    char commd[N] ;
    bzero(commd, N);
    //opendir為用來打開參數name 指定的目錄, 並返回DIR*形態的目錄流
    //mydir中存有相關目錄的信息
    if((mydir=opendir(".")) == NULL)
    {
        printf("OpenDir Error!\n");
        exit(1);
    }

    while((myitem = readdir(mydir)) != NULL)//用來讀取目錄,返回是dirent結構體指針
    {
        if(sprintf(commd, myitem->d_name, N) < 0)//把文件名寫入commd指向的緩沖區
        {
            printf("Sprintf Error!\n");
            exit(1);
        }

        if(write(sockfd, commd, N) < 0 )//將commd緩沖區的內容發送會client
        {
            printf("Write Error!\n");
            exit(1);
        }
    }

    closedir(mydir);//關閉目錄流
    close(sockfd);

    return ;
}
/*
**實現文件的下載                            
*/
void commd_get(int sockfd, char *filename)
{
    int fd, nbytes;
    char buffer[N];
    bzero(buffer, N);

    printf("get filename : [ %s ]\n",filename);
    if((fd=open(filename, O_RDONLY)) < 0)//以只讀的方式打開client要下載的文件
    {
        printf("Open file Error!\n");
        buffer[0]='N';
        if(write(sockfd, buffer, N) <0)
        {
            printf("Write Error!At commd_get 1\n");
            exit(1);
        }
        return ;
    }

    buffer[0] = 'Y';    //此處標示出文件讀取成功
    if(write(sockfd, buffer, N) <0)
    {
        printf("Write Error! At commd_get 2!\n");
        close(fd);
        exit(1);
    }

    while((nbytes=read(fd, buffer, N)) > 0)//將文件內容讀到buffer中
    {
        if(write(sockfd, buffer, nbytes) < 0)//將buffer發送回client
        {
            printf("Write Error! At commd_get 3!\n");
            close(fd);
            exit(1);
        }
    }

    close(fd);
    close(sockfd);

    return ;
}
/*
**實現文件的上傳                            
*/
void commd_put(int sockfd, char *filename)
{
    int fd, nbytes;
    char buffer[N];
    bzero(buffer, N);

    printf("get filename : [ %s ]\n",filename);
    if((fd=open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0644)) < 0)//以只寫的方式打開文件,若文件存在則清空,若文件不存在則新建文件
    {
        printf("Open file Error!\n");
        return ;
    }

    while((nbytes=read(sockfd, buffer, N)) > 0)//將client發送的文件寫入buffer
    {
        if(write(fd, buffer, nbytes) < 0)//將buffer中的內容寫到文件中
        {
            printf("Write Error! At commd_put 1!\n");
            close(fd);
            exit(1);
        }
    }
    close(fd);
    close(sockfd);

    return ;
}

效果:

server:

client:

因為我在代碼中將讀取文件的大小設置為不超過256字節,當上傳(或下載)文件大小超過256字節,后面內容則為空。

所以上穿(或下載)一個超過256字節的文件后,查看文件,發現文件部分內容丟失,則可以證明FTP實現成功,文件是從服務器端下載的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM