用Linux C实现FTP站点上文件的上传与下载

花了4天的时间了解完FTP的工作原理之后,终于在三天之后写成了一个操作FTP站点的C程序。

FTP工作原理

FTP即文件传输协议,是TCP/IP协议族中的一员。一个FTP服务器可以在多个客户端之间实现文件的传输通信,其底层的工作原理就是socket通信模型

与数据库不同,Linux C没有提供与FTP操作相关的API,但Linux本身有着操作FTP的命令,这些命令常用于用户与FTP服务器之间进行命令输入的人机交互,Linux下安装FTP这个命令后,输入“ftp 【FTP服务器IP或域名】”,即可进入与ftp之间的人机交互模式。

FTP协议本身也有着对应操作FTP的命令,此命令叫做FTP协议命令。与Linux下的FTP命令不同之处在于FTP协议命令在任何操作系统下的编程语言中均可使用,此类命令常用于对FTP的编程操作。FTP协议命令使用时是不区分大小写的。本博客便是利用FTP协议命令来实现文件的上传与下载的,有关此类命令是详解,可参考以下的博客:FTP协议指令集

FTP服务器上传和下载文件分下面两个步骤进行:

  1. 客户机同FTP服务器的命令传输端口进行socket连接,连接成功后,客户端发送FTP协议命令给服务器,设置客户机与服务器之间的访问模式(一般设置为被动的pasv模式,本博客里面的程序也是如此),向服务器发出将要上传或下载文件的申请,打开对应的数据传输端口;
  2. 客户机同FTP服务器的数据传输端口进行socket连接,连接成功后,客户端向服务器发送要上传的文件里的数据,或接收服务器端传来的要下载的文件里的数据,此操作进行后,圆满完成文件的上传与下载目标。

程序流程

以下为本人FTP上传文件程序的流程图:
在这里插入图片描述
这里说一下流程图中未提及的细节:

  1. socket编程里,向已建立socket连接的主机或服务器发送数据应该调用send函数,此函数的原型是int send(int sock_fd, const char *buf, int buf_len, int flags),其中第一个参数是已连接的socket的文件描述符,第二个参数是即将发送的数据对应的字符数组,第三参数是即将发送的数据的长度,第四个参数是标志符,在简单的socket编程中一般设置为0。正常将数据发送给对应的主机之后,该函数返回的值是已成功发送的字节数;
  2. recv函数的原型是int recv(int sock_fd, const char *buf, int buf_len, int flags),此函数的参数列表和send相同,当正常接收到对应主机发来的数据后,此函数返回成功接收的字节数;
  3. type命令,用于声明传输文件的类型,一般有A类型(ASCII)、I类型(binary),具体设置为什么类型,需依照上传文件中数据的存储方式来定,图片、文本、视频、音频文件的传输,应设置为A类型,可执行文件(一般指程序编译形成的二进制代码文件)传输,应设置为I类型;
  4. mode命令,用于设置数据的传输模式,有S模式(stream)、B模式(block)、C模式(compress)。数据为字节类型时,一般设置为S模式,上传压缩文件时,一般设置为C模式;
  5. 向服务器发送pasv命令(即设置访问模式为被动模式)后,会受到一个格式类似于“227 Entering Passive Mode (192,168,2,34,137,7).”的响应,其中圆括号内的数据前四个数是IP地址后两个数137和7分别是服务器数据端口的高位和数据端口的低位,端口的计算公式在流程图里面已经给出。

文件的下载流程与上传流程类似,执行pasv命令之后,再执行“RETR 【文件名】”命令(文件下载请求),连接服务器的数据传输端口,并调用recv函数,即可读取到下载的文件里的内容,下载文件的内容在一个字符数组里,将此数组里面的内容写入一个对应的文件之后,再重命名此文件,就算完成了文件的下载(文件下载仅仅是客户端的行为,客户端一般不会使用编程的方式下载文件,所以下面的代码里没有给出文件下载的代码)

程序源码

ftp_upload.c

#include "ftp.h"

int main(int argc, char **argv)
{
    int                     	socket_fd = -1;
    int		        	    	data_fd = -1;
    int			            	remote_fd = -1;
    char                    	*ip = malloc(IP_LEN);
    struct sockaddr_in      	server_addr;
    struct sockaddr_in      	data_addr;
    char		            	buf[BUF_SIZE];
    char	            		*port_buf = malloc(BUF_SIZE);
    char		     	        cmd[CMD_LEN];
    int			            	data_port = 0;


    /*Get the server IP by domain_name*/
    dns(DOMAIN_NAME, &ip); 
    /*Create a socket_fd: port, ip, and the address family protype have been writen in*/
    socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(socket_fd < 0)
    {
        printf("Fail to create a socket to connect with server :%s\n", strerror(errno));
        return -1;
    }
    data_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(data_fd < 0)
    {
		printf("Fail to create a socket to transmit the file data :%s\n", strerror(errno));
		return -1;
    }
    /* Connect with the server: master.iot-yun.com:21 */
    server_addr.sin_port = htons(PORT);
    server_addr.sin_family = AF_INET;
    inet_aton(ip, &server_addr.sin_addr);

    if( connect(socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0)
    {
        printf("Fail to connect with the server %s :%s\n", ip, strerror(errno));
        return -1;
    }
    else
    {
    	recv(socket_fd, buf, BUF_SIZE, 0);
    	printf("%s\n", buf);
    	memset(buf, 0, BUF_SIZE);
    }

	

    send(socket_fd, "USER XXX\r\n", strlen("USER XXX\r\n"), 0);
    recv(socket_fd, buf, BUF_SIZE, 0);
    printf("%s\n", buf);
    memset(buf, 0, BUF_SIZE);

    send(socket_fd, "PASS XXX\r\n", strlen("PASS XXX\r\n"), 0);
    recv(socket_fd, buf, BUF_SIZE, 0);
    printf("%s\n", buf);
    memset(buf, 0, BUF_SIZE);

    send(socket_fd, "TYPE A\r\n", strlen("TYPE A\r\n"), 0);
    recv(socket_fd, buf, BUF_SIZE, 0);
    printf("%s\n", buf);
    memset(buf, 0, BUF_SIZE);

    send(socket_fd, "PASV\r\n", strlen("PASV\r\n"), 0);
    recv(socket_fd, buf, BUF_SIZE, 0);
    printf("%s\n", buf);
	
    strncpy(port_buf, buf, BUF_SIZE);
    data_port = get_data_port(port_buf);
    free(port_buf);
    memset(buf, 0, sizeof(buf));
	
    send(socket_fd, "STOR test.jpg\r\n", strlen("STOR test.jpg\r\n"), 0);

	
    
    data_addr.sin_family = AF_INET;
    data_addr.sin_port = htons(data_port);
    inet_aton(ip, &data_addr.sin_addr);

    if( connect(data_fd, (struct sockaddr*)&data_addr, sizeof(data_addr)) < 0)
    {
	    printf("Fail to connect with the data port %s :%s", ip, strerror(errno));
	    return -1;
    }
    remote_fd = open("./test.jpg", O_RDONLY);
    if(remote_fd < 0)
    {
	    printf("Fail to open the local file :%s\n", strerror(errno));
	    return -1;
    }
    if( read(remote_fd, buf, sizeof(buf)) < 0 )
    {
	    printf("Fail to read the local file :%s\n", strerror(errno));
	    return -1;
    }
	
	
    send(data_fd, buf, sizeof(buf), 0);
    

    return 0;
}

ftp.h

#ifndef _FTP_H
#define _FTP_H

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

#define PORT                21
#define DOMAIN_NAME         "XXX.XXX.com"
#define BUF_SIZE            65536
#define CMD_LEN             64
#define IP_LEN              32

void dns(char *domain_name, char **ip);
int get_data_port(char *buf);

#endif

ftp.c

#include "ftp.h"

void dns(char *domain_name, char **ip)
{
    struct hostent                  *host;

    host = gethostbyname(domain_name);

    inet_ntop(host->h_addrtype, host->h_addr, *ip, IP_LEN);
}

int get_data_port(char *buf)
{
	int		port_low = 0, port_high = 0, count = 0;
	char		div[] = {'(', ',', ')'};
	char		*num = NULL;

	for(num = strtok(buf, div); num; num = strtok(NULL, div)){
		count++;
		if(count == 5)
			port_high = atoi(num);
		if(count == 6)
			port_low = atoi(num);
	}
	
	return port_high*256+port_low;
}

test.jpg
test.jpg
这里说一下本人写这些代码时的痛苦教训:
1、PASV命令之前执行的命令中,一旦send了命令之后,一定得recv,否则在PASV命令执行后recv,不但收不到类似“227 Entering Passive Mode (192,168,2,34,137,7)”的相应,还会抛出“Segmentation default”的错误;
2、在上传文件的程序里,一旦执行STOR命令之后,或是连接到数据端口之后,绝对不能recv数据端口的消息,因为此时是客户端数据传给服务器,服务器不会进行任何相应,此时调用recv函数,进程会阻塞,之后程序会因为socket连接的断开而自动挂掉,文件下载的程序里也一样,就是RETR命令执行之后绝对不能调用send函数

检验文件是否上传成功

打开Linux终端,执行“ftp 【FTP服务器IP或域名】”命令后,进入人机命令交互界面,正确输入用户名和密码后,先执行“passive”命令,再执行ls命令或dir命令,在本篇博客中,执行ls命令后就可以看到本人上传的图片文件“test.jpg”

  • 10
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值