公共部分代碼
/* common.h */ #ifndef COMMON_H #define COMMON_H #include <arpa/inet.h> #include <ctype.h> #include <dirent.h> #include <errno.h> #include <fcntl.h> #include <netdb.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/wait.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> #define DEBUG 1 #define MAXSIZE 512 #define CLIENT_PORT_ID 30020 struct command { char arg[255]; char code[5]; }; int socket_create(int port); int socket_accept(int sock_listen); int socket_connect(int port, char *host); int recv_data(int sockfd, char* buf, int bufsize); int send_response(int sockfd, int rc); void trimstr(char *str, int n); void read_input(char* buffer, int size); #endif
/* common.c */ #include "common.h" /** * 創建監聽套接字 * 錯誤返回 -1,正確返回套接字描述符 */ int socket_create(int port) { int sockfd; int yes = 1; struct sockaddr_in sock_addr; // 創建套接字 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket() error"); return -1; } // 設置本地套接字地址 sock_addr.sin_family = AF_INET; sock_addr.sin_port = htons(port); sock_addr.sin_addr.s_addr = htonl(INADDR_ANY); if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { close(sockfd); perror("setsockopt() error"); return -1; } // 綁定本地套接字地址到套接字 if (bind(sockfd, (struct sockaddr *) &sock_addr, sizeof(sock_addr)) < 0) { close(sockfd); perror("bind() error"); return -1; } // 將套接字設置為監聽狀態 if (listen(sockfd, 5) < 0) { close(sockfd); perror("listen() error"); return -1; } return sockfd; } /** * 套接字接受請求 * 錯誤返回 -1,正確返回新的連接套接字 */ int socket_accept(int sock_listen) { int sockfd; struct sockaddr_in client_addr; socklen_t len = sizeof(client_addr); sockfd = accept(sock_listen, (struct sockaddr *) &client_addr, &len); // 等待連接 if (sockfd < 0) { perror("accept() error"); return -1; } return sockfd; } /** * 連接遠端主機 * 成功返回套接字描述符,失敗返回 -1 */ int socket_connect(int port, char*host) { int sockfd; struct sockaddr_in dest_addr; /* 創建套接字 */ if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("error creating socket"); return -1; } /* 設置協議地址 */ memset(&dest_addr, 0, sizeof(dest_addr)); dest_addr.sin_family = AF_INET; dest_addr.sin_port = htons(port); dest_addr.sin_addr.s_addr = inet_addr(host); /* 在套接字上創建連接 */ if(connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) < 0 ) { perror("error connecting to server"); return -1; } return sockfd; } /** * 接收數據 * 錯誤返回 -1,正確返回接收的字節數 */ int recv_data(int sockfd, char* buf, int bufsize) { size_t num_bytes; memset(buf, 0, bufsize); /* 調用 recv 函數讀取套接字數據 */ num_bytes = recv(sockfd, buf, bufsize, 0); if (num_bytes < 0) return -1; return num_bytes; } /** * 去除字符串中的空格和換行符 */ void trimstr(char *str, int n) { int i; for (i = 0; i < n; i++) { if (isspace(str[i])) str[i] = 0; if (str[i] == '\n') str[i] = 0; } } /** * 發送響應碼到 sockfd * 錯誤返回 -1,正確返回 0 */ int send_response(int sockfd, int rc) { int conv = htonl(rc); if (send(sockfd, &conv, sizeof conv, 0) < 0 ) { perror("error sending...\n"); return -1; } return 0; } /** * 從命令行中讀取輸入 */ void read_input(char* buffer, int size) { char *nl = NULL; memset(buffer, 0, size); if ( fgets(buffer, size, stdin) != NULL ) { nl = strchr(buffer, '\n'); if (nl) *nl = '\0'; // 出現換行符,則將該位置部位'\0'(字符串結尾) } }
客戶端代碼:
/* ftclient.h */ #ifndef FTCLIENT_H #define FTCLIENT_H #include "../common/common.h" int read_reply(); void print_reply(int rc); int ftclient_read_command(char* buf, int size, struct command *cstruct); int ftclient_get(int data_sock, int sock_control, char* arg); int ftclient_open_conn(int sock_con); int ftclient_list(int sock_data, int sock_con); int ftclient_send_cmd(struct command *cmd); void ftclient_login(); #endif
/* ftclient.c */ #include "ftclient.h" int sock_control; /** * 接收服務器響應 * 錯誤返回 -1,正確返回狀態碼 */ int read_reply() { int retcode = 0; if (recv(sock_control, &retcode, sizeof retcode, 0) < 0) { perror("client: error reading message from server\n"); return -1; } return ntohl(retcode); } /** * 打印響應信息 */ void print_reply(int rc) { switch (rc) { case 220: printf("220 Welcome, server ready.\n"); break; case 221: printf("221 Goodbye!\n"); break; case 226: printf("226 Closing data connection. Requested file action successful.\n"); break; case 550: printf("550 Requested action not taken. File unavailable.\n"); break; } } /** * 解析命令行到結構體 */ int ftclient_read_command(char* buf, int size, struct command *cstruct) { memset(cstruct->code, 0, sizeof(cstruct->code)); memset(cstruct->arg, 0, sizeof(cstruct->arg)); printf("ftclient> "); // 輸入提示符 fflush(stdout); read_input(buf, size); // 等待用戶輸入命令 char *arg = NULL; arg = strtok (buf," "); arg = strtok (NULL, " "); if (arg != NULL) strncpy(cstruct->arg, arg, strlen(arg)); if (strcmp(buf, "list") == 0) strcpy(cstruct->code, "LIST"); else if (strcmp(buf, "get") == 0) strcpy(cstruct->code, "RETR"); else if (strcmp(buf, "quit") == 0) strcpy(cstruct->code, "QUIT"); else return -1; // 不合法 memset(buf, 0, 400); strcpy(buf, cstruct->code); // 存儲命令到 buf 開始處 /* 如果命令帶有參數,追加到 buf */ if (arg != NULL) { strcat(buf, " "); strncat(buf, cstruct->arg, strlen(cstruct->arg)); } return 0; } /** * 實現 get <filename> 命令行 */ int ftclient_get(int data_sock, int sock_control, char* arg) { char data[MAXSIZE]; int size; FILE* fd = fopen(arg, "w"); // 創建並打開名字為 arg 的文件 /* 將服務器傳來的數據(文件內容)寫入本地建立的文件 */ while ((size = recv(data_sock, data, MAXSIZE, 0)) > 0) fwrite(data, 1, size, fd); if (size < 0) perror("error\n"); fclose(fd); return 0; } /** * 打開數據連接 */ int ftclient_open_conn(int sock_con) { int sock_listen = socket_create(CLIENT_PORT_ID); /* 在控制連接上發起一個 ACK 確認 */ int ack = 1; if ((send(sock_con, (char*) &ack, sizeof(ack), 0)) < 0) { printf("client: ack write error :%d\n", errno); exit(1); } int sock_conn = socket_accept(sock_listen); close(sock_listen); return sock_conn; } /** * 實現 list 命令 */ int ftclient_list(int sock_data, int sock_con) { size_t num_recvd; char buf[MAXSIZE]; int tmp = 0; /* 等待服務器啟動的信息 */ if (recv(sock_con, &tmp, sizeof tmp, 0) < 0) { perror("client: error reading message from server\n"); return -1; } memset(buf, 0, sizeof(buf)); /* 接收服務器傳來的數據 */ while ((num_recvd = recv(sock_data, buf, MAXSIZE, 0)) > 0) { printf("%s", buf); memset(buf, 0, sizeof(buf)); } if (num_recvd < 0) perror("error"); /* 等待服務器完成的消息 */ if (recv(sock_con, &tmp, sizeof tmp, 0) < 0) { perror("client: error reading message from server\n"); return -1; } return 0; } /** * 輸入含有命令(code)和參數(arg)的 command(cmd) 結構 * 連接 code + arg,並放進一個字符串,然后發送給服務器 */ int ftclient_send_cmd(struct command *cmd) { char buffer[MAXSIZE]; int rc; sprintf(buffer, "%s %s", cmd->code, cmd->arg); /* 發送命令字符串到服務器 */ rc = send(sock_control, buffer, (int)strlen(buffer), 0); if (rc < 0) { perror("Error sending command to server"); return -1; } return 0; } /** * 獲取登錄信息 * 發送到服務器認證 */ void ftclient_login() { struct command cmd; char user[256]; memset(user, 0, 256); /* 獲取用戶名 */ printf("Name: "); fflush(stdout); read_input(user, 256); /* 發送用戶名到服務器 */ strcpy(cmd.code, "USER"); strcpy(cmd.arg, user); ftclient_send_cmd(&cmd); /* 等待應答碼 331 */ int wait; recv(sock_control, &wait, sizeof wait, 0); /* 獲得密碼 */ fflush(stdout); char *pass = getpass("Password: "); /* 發送密碼到服務器 */ strcpy(cmd.code, "PASS"); strcpy(cmd.arg, pass); ftclient_send_cmd(&cmd); /* 等待響應 */ int retcode = read_reply(); switch (retcode) { case 430: printf("Invalid username/password.\n"); exit(0); case 230: printf("Successful login.\n"); break; default: perror("error reading message from server"); exit(1); break; } } /* 主函數入口 */ int main(int argc, char* argv[]) { int data_sock, retcode, s; char buffer[MAXSIZE]; struct command cmd; struct addrinfo hints, *res, *rp; /* 命令行參數合法性檢測 */ if (argc != 3) { printf("usage: ./ftclient hostname port\n"); exit(0); } char *host = argv[1]; //所要連接的服務器主機名 char *port = argv[2]; //所要鏈接到服務器程序端口號 /* 獲得和服務器名匹配的地址 */ memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; s = getaddrinfo(host, port, &hints, &res); if (s != 0) { printf("getaddrinfo() error %s", gai_strerror(s)); exit(1); } /* 找到對應的服務器地址並連接 */ for (rp = res; rp != NULL; rp = rp->ai_next) { sock_control = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); // 創建控制套接字 if (sock_control < 0) continue; if(connect(sock_control, res->ai_addr, res->ai_addrlen)==0) // 和服務器連接 break; else { perror("connecting stream socket"); exit(1); } close(sock_control); } freeaddrinfo(rp); /* 連接成功,打印信息 */ printf("Connected to %s.\n", host); print_reply(read_reply()); /* 獲取用戶的名字和密碼 */ ftclient_login(); while (1) { // 循環,直到用戶輸入 quit /* 得到用戶輸入的命令 */ if ( ftclient_read_command(buffer, sizeof buffer, &cmd) < 0) { printf("Invalid command\n"); continue; // 跳過本次循環,處理下一個命令 } /* 發送命令到服務器 */ if (send(sock_control, buffer, (int)strlen(buffer), 0) < 0 ) { close(sock_control); exit(1); } retcode = read_reply(); //讀取服務器響應(服務器是否可以支持該命令?) if (retcode == 221) // 退出命令 { print_reply(221); break; } if (retcode == 502) printf("%d Invalid command.\n", retcode);// 不合法的輸入,顯示錯誤信息 else { // 命令合法 (RC = 200),處理命令 /* 打開數據連接 */ if ((data_sock = ftclient_open_conn(sock_control)) < 0) { perror("Error opening socket for data connection"); exit(1); } /* 執行命令 */ if (strcmp(cmd.code, "LIST") == 0) ftclient_list(data_sock, sock_control); else if (strcmp(cmd.code, "RETR") == 0) { if (read_reply() == 550) // 等待回復 { print_reply(550); close(data_sock); continue; } ftclient_get(data_sock, sock_control, cmd.arg); print_reply(read_reply()); } close(data_sock); } } // 循環得到更多的用戶輸入 close(sock_control); // 關閉套接字控制連接 return 0; }
makefile文件
CC := gcc CFLAGS := -Wall -g -Os SHDIR := ../common OBJS = ftclient.o $(SHDIR)/common.o all: ftclient ftclient: $(OBJS) @$(CC) -o ftclient $(CFLAGS) $(OBJS) $(OBJS) : %.o: %.c @$(CC) -c $(CFLAGS) $< -o $@ .PHONY: clean: @rm -f *.o ftclient @rm -f ../common/*.o @echo Done cleaning
服務端代碼:
/* ftserve.h */ #ifndef FTSERVE_H #define FTSERVE_H #include "../common/common.h" void ftserve_retr(int sock_control, int sock_data, char* filename); int ftserve_list(int sock_data, int sock_control); int ftserve_start_data_conn(int sock_control); int ftserve_check_user(char*user, char*pass); int ftserve_login(int sock_control); int ftserve_recv_cmd(int sock_control, char*cmd, char*arg); void ftserve_process(int sock_control); #endif
/* ftserve.c */ #include "ftserve.h" /* 主函數入口 */ int main(int argc, char *argv[]) { int sock_listen, sock_control, port, pid; /* 命令行合法性檢測 */ if (argc != 2) { printf("usage: ./ftserve port\n"); exit(0); } /* 將命令行傳進來的服務器端口號(字符串)轉換為整數 */ port = atoi(argv[1]); /* 創建監聽套接字 */ if ((sock_listen = socket_create(port)) < 0 ) { perror("Error creating socket"); exit(1); } /* 循環接受不同的客戶機請求 */ while(1) { /* 監聽套接字接受連接請求,得到控制套接字,用於傳遞控制信息 */ if ((sock_control = socket_accept(sock_listen)) < 0 ) break; /* 創建子進程處理用戶請求 */ if ((pid = fork()) < 0) perror("Error forking child process"); /* 子進程調用 ftserve_process 函數與客戶端交互 */ else if (pid == 0) { close(sock_listen); // 子進程關閉父進程的監聽套接字 ftserve_process(sock_control); close(sock_control); //用戶請求處理完畢,關閉該套接字 exit(0); } close(sock_control); // 父進程關閉子進程的控制套接字 } close(sock_listen); return 0; } /** * 通過數據套接字發送特定的文件 * 控制信息交互通過控制套接字 * 處理無效的或者不存在的文件名 */ void ftserve_retr(int sock_control, int sock_data, char* filename) { FILE* fd = NULL; char data[MAXSIZE]; size_t num_read; fd = fopen(filename, "r"); // 打開文件 if (!fd) send_response(sock_control, 550); // 發送錯誤碼 (550 Requested action not taken) else { send_response(sock_control, 150); // 發送 okay (150 File status okay) do { num_read = fread(data, 1, MAXSIZE, fd); // 讀文件內容 if (num_read < 0) printf("error in fread()\n"); if (send(sock_data, data, num_read, 0) < 0) // 發送數據(文件內容) perror("error sending file\n"); } while (num_read > 0); send_response(sock_control, 226); // 發送消息:226: closing conn, file transfer successful fclose(fd); } } /** * 響應請求:發送當前所在目錄的目錄項列表 * 關閉數據連接 * 錯誤返回 -1,正確返回 0 */ int ftserve_list(int sock_data, int sock_control) { char data[MAXSIZE]; size_t num_read; FILE* fd; int rs = system("ls -l | tail -n+2 > tmp.txt"); //利用系統調用函數 system 執行命令,並重定向到 tmp.txt 文件 if ( rs < 0) { exit(1); } fd = fopen("tmp.txt", "r"); if (!fd) exit(1); /* 定位到文件的開始處 */ fseek(fd, SEEK_SET, 0); send_response(sock_control, 1); memset(data, 0, MAXSIZE); /* 通過數據連接,發送tmp.txt 文件的內容 */ while ((num_read = fread(data, 1, MAXSIZE, fd)) > 0) { if (send(sock_data, data, num_read, 0) < 0) perror("err"); memset(data, 0, MAXSIZE); } fclose(fd); send_response(sock_control, 226); // 發送應答碼 226(關閉數據連接,請求的文件操作成功) return 0; } /** * 創建到客戶機的一條數據連接 * 成功返回數據連接的套接字 * 失敗返回 -1 */ int ftserve_start_data_conn(int sock_control) { char buf[1024]; int wait, sock_data; if (recv(sock_control, &wait, sizeof wait, 0) < 0 ) { perror("Error while waiting"); return -1; } struct sockaddr_in client_addr; socklen_t len = sizeof client_addr; getpeername(sock_control, (struct sockaddr*)&client_addr, &len); // 獲得與控制套接字關聯的外部地址(客戶端地址) inet_ntop(AF_INET, &client_addr.sin_addr, buf, sizeof(buf)); /* 創建到客戶機的數據連接 */ if ((sock_data = socket_connect(CLIENT_PORT_ID, buf)) < 0) return -1; return sock_data; } /** * 用戶資格認證 * 認證成功返回 1,否則返回 0 */ int ftserve_check_user(char*user, char*pass) { char username[MAXSIZE]; char password[MAXSIZE]; char *pch; char buf[MAXSIZE]; char *line = NULL; size_t num_read; size_t len = 0; FILE* fd; int auth = 0; fd = fopen(".auth", "r"); //打開認證文件(記錄用戶名和密碼) if (fd == NULL) { perror("file not found"); exit(1); } /* 讀取".auth" 文件中的用戶名和密碼,驗證用戶身份的合法性 */ while ((num_read = getline(&line, &len, fd)) != -1) { memset(buf, 0, MAXSIZE); strcpy(buf, line); pch = strtok (buf," "); strcpy(username, pch); if (pch != NULL) { pch = strtok (NULL, " "); strcpy(password, pch); } /* 去除字符串中的空格和換行符 */ trimstr(password, (int)strlen(password)); if ((strcmp(user,username)==0) && (strcmp(pass,password)==0)) { auth = 1; // 匹配成功,標志變量 auth = 1,並返回 break; } } free(line); fclose(fd); return auth; } /* 用戶登錄*/ int ftserve_login(int sock_control) { char buf[MAXSIZE]; char user[MAXSIZE]; char pass[MAXSIZE]; memset(user, 0, MAXSIZE); memset(pass, 0, MAXSIZE); memset(buf, 0, MAXSIZE); /* 獲得客戶端傳來的用戶名 */ if ( (recv_data(sock_control, buf, sizeof(buf)) ) == -1) { perror("recv error\n"); exit(1); } int i = 5; int n = 0; while (buf[i] != 0) //buf[0-4]="USER" user[n++] = buf[i++]; /* 用戶名正確,通知用戶輸入密碼 */ send_response(sock_control, 331); /* 獲得客戶端傳來的密碼 */ memset(buf, 0, MAXSIZE); if ( (recv_data(sock_control, buf, sizeof(buf)) ) == -1) { perror("recv error\n"); exit(1); } i = 5; n = 0; while (buf[i] != 0) // buf[0 - 4] = "PASS" pass[n++] = buf[i++]; return (ftserve_check_user(user, pass)); // 用戶名和密碼驗證,並返回 } /* 接收客戶端的命令並響應,返回響應碼 */ int ftserve_recv_cmd(int sock_control, char*cmd, char*arg) { int rc = 200; char buffer[MAXSIZE]; memset(buffer, 0, MAXSIZE); memset(cmd, 0, 5); memset(arg, 0, MAXSIZE); /* 接受客戶端的命令 */ if ((recv_data(sock_control, buffer, sizeof(buffer)) ) == -1) { perror("recv error\n"); return -1; } /* 解析出用戶的命令和參數 */ strncpy(cmd, buffer, 4); char *tmp = buffer + 5; strcpy(arg, tmp); if (strcmp(cmd, "QUIT")==0) rc = 221; else if ((strcmp(cmd, "USER") == 0) || (strcmp(cmd, "PASS") == 0) || (strcmp(cmd, "LIST") == 0) || (strcmp(cmd, "RETR") == 0)) rc = 200; else rc = 502; // 無效的命令 send_response(sock_control, rc); return rc; } /* 處理客戶端請求 */ void ftserve_process(int sock_control) { int sock_data; char cmd[5]; char arg[MAXSIZE]; send_response(sock_control, 220); // 發送歡迎應答碼 /* 用戶認證 */ if (ftserve_login(sock_control) == 1) // 認證成功 send_response(sock_control, 230); else { send_response(sock_control, 430); // 認證失敗 exit(0); } /* 處理用戶的請求 */ while (1) { /* 接收命令,並解析,獲得命令和參數 */ int rc = ftserve_recv_cmd(sock_control, cmd, arg); if ((rc < 0) || (rc == 221)) // 用戶輸入命令 "QUIT" break; if (rc == 200 ) { /* 創建和客戶端的數據連接 */ if ((sock_data = ftserve_start_data_conn(sock_control)) < 0) { close(sock_control); exit(1); } /* 執行指令 */ if (strcmp(cmd, "LIST")==0) ftserve_list(sock_data, sock_control); else if (strcmp(cmd, "RETR")==0) ftserve_retr(sock_control, sock_data, arg); close(sock_data);// 關閉連接 } } }
里面還有一個保存密碼賬戶文件.auth,這里就不列出。服務端的makefile和客戶端makefile一樣,只需要修改一下程序名及相關依賴名即可。