Linux应用——FTP服务器的实现

FTP介绍

文件传输协议(File Transfer Protocol,FTP)是用于在网络上进行文件传输的一套标准协议,它工作在 OSI 模型的第七层, TCP 模型的第四层, 即应用层, 使用 TCP 传输而不是 UDP, 客户在和服务器建立连接前要经过一个“三次握手”的过程, 保证客户与服务器之间的连接是可靠的, 而且是面向连接, 为数据传输提供可靠保证。

FTP允许用户以文件操作的方式(如文件的增、删、改、查、传送等)与另一主机相互通信。

本文实现基本的文件传输(上传文件,下载文件,查看文件列表)。

程序设计架构

...待补充

规定的协议(宏定义和函数声明)

通信消息体
#define NAMESIZE 255
#define BUFFSIZE 1024
typedef struct{
  int type; /* 消息标志,通信双方根据标志,执行不同的操作 */
  char file_name[NAMESIZE]; /* 文件名 */
  long file_size; /* 文件大小,long类型,与系统函数表示文件大小的结构体保持一致 */
  char file_data[BUFFSIZE]; /* 一次传输的数据大小,文件太大就多传几次拼接 */
}MSG;
消息体msg.type的值
#define LIST 1  /* 查看文件列表请求 */
#define DOWNLOAD 2  /* 下载文件请求 */
#define UPLOAD 3  /* 上传文件请求 */
#define QUIT 4  /* 退出连接请求 */
#define SUCCESS 5 /* 成功标志 */
#define FAIL 6 /* 失败标志 */
#define CONTINUE 7 /* 当传输较大文件时,可能分多次传输,当还需要传输时,标志置为CONTINUE */
#define END 8 /* 当传输文件时,表示这是最后一段数据 */
其他内容
/* server */
#define DIR_PATH "public_dir" /* server保存文件的目录 */
#define SERVER_PORT 5001 /* ftp端口 */
#define BACKLOG 10 /* 最大accept pendding */

/* client */
#define DOWNLOAD_DIR "download" /* 用户下载文件保存的位置 */
#define UPLOAD_DIR "upload" /* 用户上传文件,从该目录选择文件上传 */
函数的声明

server.h

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

#define DIR_PATH "public_dir"
#define SERVER_PORT 5001
#define BACKLOG 10 /* 最大accept pendding */
#define NAMESIZE 255 /* equals dirent.h lib */
#define BUFFSIZE 1024
typedef struct{
    int type;
    char file_name[NAMESIZE];
    long file_size;
    char file_data[BUFFSIZE];
}MSG;

typedef struct{
    int fd;
    struct sockaddr_in cin;
}Cli_info;

#define LIST 1
#define DOWNLOAD 2
#define UPLOAD 3
#define QUIT 4

#define SUCCESS 5 /* server - ok */
#define FAIL 6 /* server - faile */
#define CONTINUE 7 /* server - send not end and continue */
#define END 8 /* server - send end */

int sock_init(); /* 封装一下 socket, bind, listen操作,返回一个socket fd */
void *handle_client(void *arg);  /* 子线程执行函数 */
int do_list(int cli_fd, MSG *msg); /* 客户端请求查看文件列表时,执行 */
int do_download(int cli_fd, MSG *msg); /* 客户端下载文件时,执行 */
int do_upload(int cli_fd, MSG *msg); /* 客户端请求上传文件时,执行 */
int do_send_errmsg(int cli_fd, MSG *msg); /* 客户端类型错误时,执行 */
void print_ctime(); /* 打印当前时间,记录客户端操作时间 */

 

client.h

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

#define DOWNLOAD_DIR "download" /* user download dir */
#define UPLOAD_DIR "upload" /* user upload dir */
#define NAMESIZE 255 /* file name size, equals dirent.h */
#define BUFFSIZE 1024
#define LIST 1
#define DOWNLOAD 2
#define UPLOAD 3
#define QUIT 4
#define SUCCESS 5
#define FAIL 6
#define CONTINUE 7
#define END 8

typedef struct{
    int type;
    char file_name[NAMESIZE];
    long file_size;
    char file_data[BUFFSIZE];
}MSG;

int sock_init(); /* 封装一下 socket, bind, accept */
void do_print_cmd_list(); /* 显示指令集合 */
int do_print_file_list(int sockfd, MSG *msg); /* 请求查看文件列表 */
int do_download(int sockfd, MSG *msg); /* 下载文件 */
int do_upload(int sockfd, MSG *msg); /* 上传文件 */
int do_quit(int sockfd, MSG *msg); /* 退出,通知server,关闭相关资源 */

 

程序的实现

server.c

#include "server.h"
int
main(int argc, char **argv) { int server_fd = sock_init(); if(server_fd < 0) { perror("sock init"); exit(1); } socklen_t len = sizeof(struct sockaddr_in); Cli_info *cli_info; pthread_t tid; while(1) { cli_info = (Cli_info*)malloc(sizeof(Cli_info)); bzero(&cli_info->cin, sizeof(struct sockaddr_in)); printf("main thread: wait client connected...\n"); cli_info->fd = accept(server_fd, (struct sockaddr*)&cli_info->cin, &len); if(cli_info->fd < 0) { perror("accept"); continue; } pthread_create(&tid, NULL, handle_client, (void *)cli_info); } close(server_fd); printf("server exit, bye."); return 0; } int sock_init() { int fd = socket(AF_INET, SOCK_STREAM, 0); if(fd < 0) { perror("socket"); return -1; } struct sockaddr_in sin; bzero(&sin, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; sin.sin_port = htons(SERVER_PORT); int b_reuse = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(b_reuse)); if(bind(fd, (struct sockaddr*)&sin, sizeof(sin)) < 0) { perror("bind"); return -2; } if(listen(fd, BACKLOG) < 0) { perror("listen"); return -3; } return fd; } void *handle_client(void *arg) { pthread_detach(pthread_self()); Cli_info *cli_info = (Cli_info*)arg; int cli_fd = cli_info->fd;/*获取fd*/ char ip_addr[64]; /* 获取ip addr */ inet_ntop(AF_INET, (void *)&cli_info->cin.sin_addr.s_addr, ip_addr, sizeof(ip_addr)); free(cli_info); /* 及时销毁 */ printf("Client(%s) connected. time:\t", ip_addr); print_ctime(); int ret = -1; MSG msg; while(1) { bzero(&msg, sizeof(MSG)); ret = recv(cli_fd, &msg, sizeof(MSG), 0); if(ret < 0) { perror("recv"); continue; } if(ret == 0 || msg.type == QUIT) { printf("Client(%s) quit, time:", ip_addr); print_ctime(); close(cli_fd); break; } switch(msg.type) { case LIST: do_list(cli_fd, &msg); break; case DOWNLOAD: do_download(cli_fd, &msg); break; case UPLOAD: do_upload(cli_fd, &msg); break; default: do_send_errmsg(cli_fd, &msg); break; } pthread_testcancel(); } pthread_exit(0); } /* 循环读文件 name + size, 发送 */ int do_list(int cli_fd, MSG *msg) { int ret = -1; DIR *dir = opendir(DIR_PATH); struct dirent *dirp; FILE *fp; char file_path[255]; if(dir == NULL) { perror("opendir"); return -1; } while((dirp = readdir(dir)) != NULL) { /* 小数点(.)开头是隐藏文件,跳过 */ if(!strncmp(dirp->d_name, ".", 1)) continue; bzero(file_path, sizeof(file_path)); bzero(msg, sizeof(MSG)); /* get & set msg->file_size*/ sprintf(file_path, "%s/%s", DIR_PATH, dirp->d_name); fp = fopen(file_path, "r"); fseek(fp, 0, SEEK_END); msg->file_size = ftell(fp); fclose(fp); /* set msg->type: CONTINUE */ msg->type = CONTINUE; /* set set->file_name */ strcpy(msg->file_name, dirp->d_name); ret = send(cli_fd, msg, sizeof(MSG), 0); } bzero(msg, sizeof(MSG)); msg->type = END; send(cli_fd, msg, sizeof(MSG), 0); return 0; } int do_download(int cli_fd, MSG *msg) { int ret = -1; char file_path[255]; char *buff; bzero(msg->file_data, sizeof(msg->file_data)); /* 1. check file exist */ sprintf(file_path, "%s/%s", DIR_PATH, msg->file_name); FILE *fp= fopen(file_path, "r"); if(fp == NULL) { msg->type = FAIL; strcpy(msg->file_data, "file not exist."); send(cli_fd, msg, sizeof(MSG), 0); return -1; } fseek(fp, 0, SEEK_END); msg->file_size = ftell(fp); if(msg->file_size < 1) { msg->type = FAIL; strcpy(msg->file_data, "file is empty."); send(cli_fd, msg, sizeof(msg), 0); return -2; } rewind(fp); msg->type = CONTINUE; while(1) { ret = fread(msg->file_data, BUFFSIZE, 1, fp); send(cli_fd, msg, sizeof(MSG), 0); if(ret <= 0) break; } msg->type = END; send(cli_fd, msg, sizeof(msg), 0); printf("client download file: %s\t - ", msg->file_name); print_ctime(); fclose(fp); return 0; } int do_upload(int cli_fd, MSG *msg) { int ret = -1; char file_path[255]; sprintf(file_path, "%s/%s", DIR_PATH, msg->file_name); FILE *fp = fopen(file_path, "a");/* append模式 */ while(1) { ret = recv(cli_fd, msg, sizeof(MSG), 0); if(ret <= 0)/*意外错误*/ { perror("upload recv error"); fclose(fp); return -1; } if(msg->type != CONTINUE) break; fwrite(msg->file_data, strlen(msg->file_data), 1, fp ); } fclose(fp); printf("client upload file: %s\t - ",msg->file_name); print_ctime(); return 0; } int do_send_errmsg(int cli_fd, MSG *msg) { printf("do_send error msg\n"); return 0; } /* 打印当前的格式化时间 */ void print_ctime() { time_t t; struct tm *tp; time(&t); tp = localtime(&t); printf("%d-%d-%d %d:%d:%d\n", tp->tm_year + 1900, tp->tm_mon+1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec); }

client.c

#include "client.h"
int
main(int argc, char **argv) { if(check_arg(argc, argv) < 0)/* check usage right */ exit(1); int sockfd = sock_init(argv[1], argv[2]); if(sockfd < 0) { perror("sock_init"); exit(1); } MSG msg; char cmd = 0; do_print_cmd_list(); while(1) { printf("Please input cmd: "); scanf("%c", &cmd); getchar(); switch(cmd) { case 'q': do_quit(sockfd, &msg); /* 通知server, client exit */ goto exit; break; case 'h': do_print_cmd_list(); break; case 'l': do_print_file_list(sockfd, &msg); break; case 'd': do_download(sockfd, &msg); break; case 'u': do_upload(sockfd, &msg); break; default: printf("cmd illegal, please try again.\n"); break; } } exit: close(sockfd); exit(0); } /* 检查启动参数,ip port */ int check_arg(int argc, char **argv) { if(argc != 3) { printf("Usage:./client [ip addr] [port]\n"); printf("ip addr format: xxx.xxx.xxx.xxx\n"); printf("port limit 5000~10000\n"); return -1; } /* * if ip illegal ...return -2; * if port illegal ...return -3; * FIXME * */ return 0; } int sock_init(char *ip, char *port) { int fd = socket(AF_INET, SOCK_STREAM, 0); if(fd < 0) { perror("socket"); return -1; } struct sockaddr_in sin; bzero(&sin, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = inet_addr(ip); sin.sin_port = htons(atoi(port)); if(connect(fd, (struct sockaddr*)&sin, sizeof(sin)) < 0) { perror("connect"); return -2; } return fd; } void do_print_cmd_list() { printf("\n*************************************\n"); printf("************** cmd list **************\n"); printf("*** h: view help *********************\n"); printf("*** l: print server file list ********\n"); printf("*** d: download file *****************\n"); printf("*** u: upload file *******************\n"); printf("*** q: quit client *******************\n"); printf("**************************************\n"); } int do_print_file_list(int sockfd, MSG *msg) { int ret = -1; printf("wait get file list...\n"); msg->type = LIST; ret = send(sockfd, msg, sizeof(MSG), 0); if(ret <= 0) { perror("send list request error"); return -1; } printf("*************** file list ********************\n"); printf("no\tfile name\t\tfile size\n"); int i = 1; while(1) { ret = recv(sockfd, msg, sizeof(MSG), 0); if(ret <= 0) { perror("recv list error"); break; } /* server 不想继续 */ if(msg->type != CONTINUE) break; printf("%4d: %10s\t\t%10ld\n", i++, msg->file_name, msg->file_size); } printf("************** file list end ******************\n"); } int do_download(int sockfd, MSG *msg) { int ret = -1; bzero(msg, sizeof(MSG)); printf("Please input file_name: "); fflush(stdout); scanf("%s", msg->file_name); getchar(); /* 1. send request get file size */ msg->type = DOWNLOAD; ret = send(sockfd, msg, sizeof(MSG), 0); if(ret <= 0) { perror("send request get file size error"); return -1; } char file_path[255]; sprintf(file_path, "%s/%s", DOWNLOAD_DIR, msg->file_name); FILE *fp = fopen(file_path, "a");/* append模式 */ while(1) { ret = recv(sockfd, msg, sizeof(MSG), 0); if(ret <= 0)/* 意外错误 */ { perror("recv: download error"); return -2; } if(msg->type != CONTINUE) /* 正常结束 */ { break; } fwrite(msg->file_data, strlen(msg->file_data), 1, fp); } printf("download file: %s ok!\n", msg->file_name); fclose(fp); return 0; } int do_upload(int sockfd, MSG *msg) { int ret = -1; bzero(msg, sizeof(MSG)); printf("Please input file name to upload: "); scanf("%s", msg->file_name); getchar(); char file_path[255]; sprintf(file_path, "%s/%s", UPLOAD_DIR, msg->file_name); FILE *fp = fopen(file_path, "r"); if(fp == NULL) { perror("fopen"); return -1; } fseek(fp, 0, SEEK_END); msg->file_size = ftell(fp); if(msg->file_size < 1) { printf("file is empty, can't upload."); return -2; } /* upload预请求(让server跳转到do_upload) */ msg->type = UPLOAD; ret = send(sockfd, msg, sizeof(MSG), 0); if(ret <= 0) { perror("send request error"); return -3; } /* upload */ rewind(fp); while(1) { bzero(msg->file_data, BUFFSIZE); msg->type = CONTINUE; ret = fread(msg->file_data, BUFFSIZE, 1, fp); send(sockfd, msg, sizeof(MSG), 0); if(ret <= 0) break; } /* 通知server结束upload */ bzero(msg->file_data, BUFFSIZE); msg->type = END; send(sockfd, msg, sizeof(MSG), 0); printf("upload file: %s ok.\n", msg->file_name); return 0; } int do_quit(int sockfd, MSG *msg) {
  /* !FIXME 可能还需要补充的其它结束操作 */ printf(
"do quit\n"); return 0; }

END

posted @ 2022-04-15 00:40  zj城城城城  阅读(220)  评论(0)    收藏  举报