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

浙公网安备 33010602011771号