目录
1.要求
模拟FTP核心原理:客户端连接服务器后,向服务器发送一个文件。文件名可以通过参数指定,服务器端接收客户端传来的文件(文件名随意),如果文件不存在自动创建文件,如果文件存在,那么清空文件然后写入。
项目功能介绍:
均有服务器和客户端代码,基于TCP写的。
客户端和服务器链接成功后出现以下提示:四个功能
***************list**************//列出服务器所在目录下的普通文件名
***********put filename**********//从客户端所在路径上传文件
***********get filename**********//从服务器所在路径下载文件
**************quit***************//退出(可只退出客户端,服务器等待下一个客户端链接)
2. 功能思想
2.1 list功能思想:
list:客户端输入list-------》把list发送给服务器--------》判断是否为list-------》目录操作-----》循环读目录文件-----》判断是否为普通文件-------》是普通文件就发送给客户端-----》客户端接收并打印到终端---------》发送一个结束的标志
2.2 put filename功能思想:
put filename(cp):客户端输入put 文件名------》把put 文件名发送给服务器------》服务器判断是否为put ---------》(cp:源文件在客户端所在路径下,目标文件在服务器所在路径下)---》客户端循环读源文件内容发送给服务器,服务器循环接收客户端内容,写到目标文件里---------》发送一个结束的标志
3.功能代码
3.1 list 服务器向客户端发送目录
服务器端:获取服务器端目录,并发送给客户端
// 获取服务器目录 void list(int acceptfd) { DIR *dir; dir = opendir("./"); // 打开服务器所在目录 struct dirent *list; // 定义指向目录文件的指针 struct stat st; // 保存文件属性、 // 循环读取目录下的文件 while ((list = readdir(dir)) != NULL) { stat(list->d_name, &st); // 获取文件类型 if ((st.st_mode & __S_IFMT) == __S_IFREG) // 判断是否为普通文件 { // 把文件名字发送给客户端 send(acceptfd, list->d_name, sizeof(list->d_name), 0); } } send(acceptfd, "end", 128, 0); // 向客户端发送结束标志 }
客户端:接收服务器端发送的目录并输出
// 获取服务器目录下文件 void list(int acceptfd) { int ret; char buf[128] = {0}; // 循环接收服务器发送的目录信息 while (1) { memset(buf, 0, sizeof(buf)); ret = recv(acceptfd, buf, sizeof(buf), 0); if (ret < 0) { perror("recv err"); return; } if (strcmp(buf, "end") == 0) // 服务器发送结束 break; else printf("%s ", buf); } printf("\n"); return; }
3.2 put 服务器接收客户端文件
服务器端:接收客户端发送的文件
// 服务器下载文件 void putfile(int acceptfd, char *p) { int putfd; char buf[128] = ""; // 暂存接收的内容 ssize_t num; // 实际接收的字符个数 // 打开文件:如果没有,需要创建;如果有了,清空文件 putfd = open(p + 4, O_WRONLY | O_CREAT | O_TRUNC, 0755); if (putfd < 0) { perror("open put file err"); return; } // 循环接收客户端发来的消息 while (1) { memset(buf, 0, sizeof(buf)); num = recv(acceptfd, buf, sizeof(buf), 0); if (num < 0) { perror("recv error"); return; } if (strcmp(buf, "end") == 0) // end结束 { break; } else { write(putfd, buf, strlen(buf)); // 向文件中写入 } } close(putfd); return; }
客户端代码:向服务器发送文件
// 向服务器上传文件 void putfile(int acceptfd, char *p) { int putfd; char buf[128] = ""; size_t num; // 上传文件给服务器 // 打开源文件 putfd = open(p + 4, O_RDONLY); if (putfd < 0) { perror("open err"); return; } // 循环读原文件发送给服务器 while (1) { memset(buf, 0, sizeof(buf)); num = read(putfd, buf, sizeof(buf) - 1); buf[num] = '\0'; if (num == 0) // 文件发送完毕 { send(acceptfd, "end", 128, 0); printf("send end\n"); break; } send(acceptfd, buf, 128, 0); } close(putfd); return; }
3.3 get 服务器向客户端发送文件
服务器端:向客户端发送文件
// 服务器发送文件 void getfile(int acceptfd, char *p) { int getfd; char buf[128] = ""; ssize_t num; // num读取个数 // 打开需要发送给客户端的文件 getfd = open(p + 4, O_RDONLY); if (getfd < 0) { perror("open get file error"); return; } // 循环读取文件发送给客户端 while (1) { memset(buf, 0, sizeof(buf)); num = read(getfd, buf, sizeof(buf) - 1); buf[num] = '\0'; if (num == 0) // 判断是否发送完毕 { send(acceptfd, "end", 128, 0); printf("发送完毕\n"); break; } send(acceptfd, buf, 128, 0); // 发送内容到客户端 memset(buf, 0, sizeof(buf)); } close(getfd); return; }
客户端:客户端接收服务器发送的文件
// 从服务器下载文件 void getfile(int acceptfd, char *p) { int getfd; ssize_t num; char buf[128] = ""; // 打开文件 getfd = open(p + 4, O_WRONLY | O_CREAT | O_TRUNC, 0666); if (getfd < 0) { perror("open err"); return; } // 循环接收客户端发来消息,写到目标文件里 while (1) { memset(buf, 0, sizeof(buf)); num = recv(acceptfd, buf, sizeof(buf), 0); if (num < 0) { perror("recv err"); return; } if (strcmp(buf, "end") == 0) { printf("接收结束\n"); break; } else { write(getfd, buf, strlen(buf)); } } close(getfd); return; }
3.4 功能判断
服务器端:接收客户端发送的命令
// 6.接收客户端的命令 char buf[128] = ""; ssize_t num; while (1) { // 每次接收前清空buf memset(buf, 0, sizeof(buf)); num = recv(acceptfd, buf, sizeof(buf), 0); // num为接收到的字符个数 if (num < 0) // 接收失败 { perror("recv error"); return -1; } else if (num == 0) // 客户端退出 { perror("client exit"); break; } else // 正常接收到字符 { printf("buf: %s \n", buf); } // 判断命令操作 if (strncmp(buf, "list", 4) == 0) // 获取目录 { list(acceptfd); } else if (strncmp(buf, "put ", 4) == 0) // 接收客户端文件 { putfile(acceptfd, buf); } else if (strncmp(buf, "get ", 4) == 0) // 向客户端发送文件 { getfile(acceptfd, buf); } else if (strcmp(buf, "quit") == 0) // 退出 { break; } }
客户端:判断终端输入的命令
// 4.发送/接收数据 send()/recv() // 一旦连接服务器成功以后,从终端获取用户输入的数据,然后发送给服务器 while (1) { help(); // 从终端获取命令 fgets(buf, sizeof(buf), stdin); if (buf[strlen(buf) - 1] == '\n') { buf[strlen(buf) - 1] = '\0'; } send(clientfd, buf, sizeof(buf), 0); if (strcmp("list", buf) == 0) // 接收服务器发来的文件名 { list(clientfd); } else if (strncmp("put ", buf, 4) == 0) // 上传文件给服务器 { putfile(clientfd, buf); } else if (strncmp("get ", buf, 4) == 0) // 从服务器下载文件 { getfile(clientfd, buf); } else if (strcmp("quit", buf) == 0) break; }
4.完整代码
4.1 服务器端完整代码
#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <sys/types.h> #include <netinet/ip.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <dirent.h> #include <stdlib.h> // 菜单 void help() { printf("***************list**************\n"); // 列出服务器所在目录下的普通文件名 printf("***********put <filename>**********\n"); // 从客户端所在路径上传文件 printf("***********get <filename>**********\n"); // 从服务器所在路径下载文件 printf("**************quit***************\n"); // 退出(可只退出客户端,服务器等待下一个客户端链接) } // 获取服务器目录 void list(int acceptfd) { DIR *dir; dir = opendir("./"); // 打开服务器所在目录 struct dirent *list; // 定义指向目录文件的指针 struct stat st; // 保存文件属性、 // 循环读取目录下的文件 while ((list = readdir(dir)) != NULL) { stat(list->d_name, &st); // 获取文件类型 if ((st.st_mode & __S_IFMT) == __S_IFREG) // 判断是否为普通文件 { // 把文件名字发送给客户端 send(acceptfd, list->d_name, sizeof(list->d_name), 0); } } send(acceptfd, "end", 128, 0); // 向客户端发送结束标志 } // 服务器下载文件 void putfile(int acceptfd, char *p) { int putfd; char buf[128] = ""; // 暂存接收的内容 ssize_t num; // 实际接收的字符个数 // 打开文件:如果没有,需要创建;如果有了,清空文件 putfd = open(p + 4, O_WRONLY | O_CREAT | O_TRUNC, 0755); if (putfd < 0) { perror("open put file err"); return; } // 循环接收客户端发来的消息 while (1) { memset(buf, 0, sizeof(buf)); num = recv(acceptfd, buf, sizeof(buf), 0); if (num < 0) { perror("recv error"); return; } if (strcmp(buf, "end") == 0) // end结束 { break; } else { write(putfd, buf, strlen(buf)); // 向文件中写入 } } close(putfd); return; } // 服务器发送文件 void getfile(int acceptfd, char *p) { int getfd; char buf[128] = ""; ssize_t num; // num读取个数 // 打开需要发送给客户端的文件 getfd = open(p + 4, O_RDONLY); if (getfd < 0) { perror("open get file error"); return; } // 循环读取文件发送给客户端 while (1) { memset(buf, 0, sizeof(buf)); num = read(getfd, buf, sizeof(buf) - 1); buf[num] = '\0'; if (num == 0) // 判断是否发送完毕 { send(acceptfd, "end", 128, 0); printf("发送完毕\n"); break; } send(acceptfd, buf, 128, 0); // 发送内容到客户端 memset(buf, 0, sizeof(buf)); } close(getfd); return; } int main(int argc, char const *argv[]) { int res; int serverfd; // 服务器fd int acceptfd; // 1.创建流式套接字 serverfd = socket(AF_INET, SOCK_STREAM, 0); if (serverfd < 0) { perror("serverfd socket error"); ; return -1; } printf("server socket scuess\n"); // 2.指定本地的网络信息 struct sockaddr_in // 定义结构体变量 myaddr struct sockaddr_in myaddr; // 长度 socklen_t addrlen = sizeof(myaddr); memset(&myaddr, 0, addrlen); myaddr.sin_family = AF_INET; // ipv4族 myaddr.sin_addr.s_addr = INADDR_ANY; myaddr.sin_port = htons(atoi(argv[1])); // 端口 // 3.绑定套接字 bind() // 绑定自己的地址 res = bind(serverfd, (struct sockaddr *)&myaddr, addrlen); if (res < 0) { perror("bind error"); return -1; } printf("bind scuess\n"); // 4.监听套接字 listen() res = listen(serverfd, 5); if (res < 0) { perror("listen error"); return -1; } printf("listen scuess\n"); // 5.链接客户端的请求 accept() // 定义代表客户端的结构体变量 struct sockaddr_in acceptaddr; while (1) { acceptfd = accept(serverfd, (struct sockaddr *)&acceptaddr, &addrlen); if (acceptfd < 0) { perror("accept error"); return -1; } printf("acceptfd: %d \n", acceptfd); printf("新的连接过来了\n"); printf("ip = %s, port = %d\n", inet_ntoa(acceptaddr.sin_addr), ntohs(acceptaddr.sin_port)); help(); // 6.接收客户端的命令 char buf[128] = ""; ssize_t num; while (1) { // 每次接收前清空buf memset(buf, 0, sizeof(buf)); num = recv(acceptfd, buf, sizeof(buf), 0); // num为接收到的字符个数 if (num < 0) // 接收失败 { perror("recv error"); return -1; } else if (num == 0) // 客户端退出 { perror("client exit"); break; } else // 正常接收到字符 { printf("buf: %s \n", buf); } // 判断命令操作 if (strncmp(buf, "list", 4) == 0) // 获取目录 { list(acceptfd); } else if (strncmp(buf, "put ", 4) == 0) // 接收客户端文件 { putfile(acceptfd, buf); } else if (strncmp(buf, "get ", 4) == 0) // 向客户端发送文件 { getfile(acceptfd, buf); } else if (strcmp(buf, "quit") == 0) // 退出 { break; } } close(acceptfd); } close(serverfd); return 0; }
4.2 客户端完整代码
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <arpa/inet.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <sys/stat.h> #include <dirent.h> #include <fcntl.h> // 菜单 void help() { printf("***************list**************\n"); // 列出服务器所在目录下的普通文件名 printf("***********put <filename>**********\n"); // 从客户端所在路径上传文件 printf("***********get <filename>**********\n"); // 从服务器所在路径下载文件 printf("**************quit***************\n"); // 退出(可只退出客户端,服务器等待下一个客户端链接) } // 获取服务器目录下文件 void list(int acceptfd) { int ret; char buf[128] = {0}; // 循环接收服务器发送的目录信息 while (1) { memset(buf, 0, sizeof(buf)); ret = recv(acceptfd, buf, sizeof(buf), 0); if (ret < 0) { perror("recv err"); return; } if (strcmp(buf, "end") == 0) // 服务器发送结束 break; else printf("%s ", buf); } printf("\n"); return; } // 向服务器上传文件 void putfile(int acceptfd, char *p) { int putfd; char buf[128] = ""; size_t num; // 上传文件给服务器 // 打开源文件 putfd = open(p + 4, O_RDONLY); if (putfd < 0) { perror("open err"); return; } // 循环读原文件发送给服务器 while (1) { memset(buf, 0, sizeof(buf)); num = read(putfd, buf, sizeof(buf) - 1); buf[num] = '\0'; if (num == 0) // 文件发送完毕 { send(acceptfd, "end", 128, 0); printf("send end\n"); break; } send(acceptfd, buf, 128, 0); } close(putfd); return; } // 从服务器下载文件 void getfile(int acceptfd, char *p) { int getfd; ssize_t num; char buf[128] = ""; // 打开文件 getfd = open(p + 4, O_WRONLY | O_CREAT | O_TRUNC, 0666); if (getfd < 0) { perror("open err"); return; } // 循环接收客户端发来消息,写到目标文件里 while (1) { memset(buf, 0, sizeof(buf)); num = recv(acceptfd, buf, sizeof(buf), 0); if (num < 0) { perror("recv err"); return; } if (strcmp(buf, "end") == 0) { printf("接收结束\n"); break; } else { write(getfd, buf, strlen(buf)); } } close(getfd); return; } int main(int argc, char const *argv[]) { int clientfd; char buf[128] = ""; ssize_t num; // 1.创建流式套接字 clientfd = socket(AF_INET, SOCK_STREAM, 0); if (clientfd < 0) { perror("clientfd socket error"); return -1; } printf("client socket scuess\n"); // 2.指定服务器的网络信息 struct sockaddr_in // 定义结构体变量 server_addr struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; // ipv4族 server_addr.sin_addr.s_addr = inet_addr(argv[1]); // ip地址 server_addr.sin_port = htons(atoi(argv[2])); // 端口 // 3.请求链接服务器 connect() // 长度 socklen_t addrlen = sizeof(server_addr); // 客户端的socket连接服务器 int ret = connect(clientfd, (struct sockaddr *)&server_addr, addrlen); if (ret < 0) { perror("connect err"); return -1; } printf("connect scuess\n"); // 4.发送/接收数据 send()/recv() // 一旦连接服务器成功以后,从终端获取用户输入的数据,然后发送给服务器 while (1) { help(); // 从终端获取命令 fgets(buf, sizeof(buf), stdin); if (buf[strlen(buf) - 1] == '\n') { buf[strlen(buf) - 1] = '\0'; } send(clientfd, buf, sizeof(buf), 0); if (strcmp("list", buf) == 0) // 接收服务器发来的文件名 { list(clientfd); } else if (strncmp("put ", buf, 4) == 0) // 上传文件给服务器 { putfile(clientfd, buf); } else if (strncmp("get ", buf, 4) == 0) // 从服务器下载文件 { getfile(clientfd, buf); } else if (strcmp("quit", buf) == 0) break; } close(clientfd); return 0; }