2024年C C++最全FTP协议讲解_retr 和stor,看完阿里P9大牛的“C C++成长笔记”我悟了

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

命令端口

一般来说,客户端有一个 Socket 用来连接 FTP 服务器的相关端口,它负责 FTP 命令的发送和接收返回的响应信息。一些操作如“登录”、“改变目录”、“删除文件”,依靠这个连接发送命令就可完成。

数据端口

对于有数据传输的操作,主要是显示目录列表,上传、下载文件,我们需要依靠另一个 Socket来完成。

如果使用被动模式,通常服务器端会返回一个端口号。客户端需要用另开一个 Socket 来连接这个端口,然后我们可根据操作来发送命令,数据会通过新开的一个端口传输。

如果使用主动模式,通常客户端会发送一个端口号给服务器端,并在这个端口监听。服务器需要连接到客户端开启的这个数据端口,并进行数据的传输。

下面对 FTP 的主动模式和被动模式做一个简单的介绍。

主动模式 (PORT)

主动模式下,客户端随机打开一个大于 1024 的端口向服务器的命令端口 P,即 21 端口,发起连接,同时开放N +1 端口监听,并向服务器发出 “port N+1” 命令,由服务器从它自己的数据端口 (20) 主动连接到客户端指定的数据端口 (N+1)。

FTP 的客户端只是告诉服务器自己的端口号,让服务器来连接客户端指定的端口。对于客户端的防火墙来说,这是从外部到内部的连接,可能会被阻塞。

被动模式 (PASV)

为了解决服务器发起到客户的连接问题,有了另一种 FTP 连接方式,即被动方式。命令连接和数据连接都由客户端发起,这样就解决了从服务器到客户端的数据端口的连接被防火墙过滤的问题。

被动模式下,当开启一个 FTP 连接时,客户端打开两个任意的本地端口 (N > 1024 和 N+1) 。

第一个端口连接服务器的 21 端口,提交 PASV 命令。然后,服务器会开启一个任意的端口 (P > 1024 ),返回如“227 entering passive mode (127,0,0,1,4,18)”。 它返回了 227 开头的信息,在括号中有以逗号隔开的六个数字,前四个指服务器的地址,最后两个,将倒数第二个乘 256 再加上最后一个数字,这就是 FTP 服务器开放的用来进行数据传输的端口。如得到 227 entering passive mode (h1,h2,h3,h4,p1,p2),那么端口号是 p1*256+p2,ip 地址为h1.h2.h3.h4。这意味着在服务器上有一个端口被开放。客户端收到命令取得端口号之后, 会通过 N+1 号端口连接服务器的端口 P,然后在两个端口之间进行数据传输。

主要用到的 FTP 命令

FTP 每个命令都有 3 到 4 个字母组成,命令后面跟参数,用空格分开。每个命令都以 "\r\n"结束。

要下载或上传一个文件,首先要登入 FTP 服务器,然后发送命令,最后退出。这个过程中,主要用到的命令有 USER、PASS、SIZE、REST、CWD、RETR、PASV、PORT、QUIT。

USER: 指定用户名。通常是控制连接后第一个发出的命令。“USER gaoleyi\r\n”: 用户名为gaoleyi 登录。

PASS: 指定用户密码。该命令紧跟 USER 命令后。“PASS gaoleyi\r\n”:密码为 gaoleyi。

SIZE: 从服务器上返回指定文件的大小。“SIZE file.txt\r\n”:如果 file.txt 文件存在,则返回该文件的大小。

CWD: 改变工作目录。如:“CWD dirname\r\n”。

PASV: 让服务器在数据端口监听,进入被动模式。如:“PASV\r\n”。

PORT: 告诉 FTP 服务器客户端监听的端口号,让 FTP 服务器采用主动模式连接客户端。如:“PORT h1,h2,h3,h4,p1,p2”。

RETR: 下载文件。“RETR file.txt \r\n”:下载文件 file.txt。

STOR: 上传文件。“STOR file.txt\r\n”:上传文件 file.txt。

REST: 该命令并不传送文件,而是略过指定点后的数据。此命令后应该跟其它要求文件传输的 FTP 命令。“REST 100\r\n”:重新指定文件传送的偏移量为 100 字节。

QUIT: 关闭与服务器的连接。

FTP 响应码

客户端发送 FTP 命令后,服务器返回响应码。

响应码用三位数字编码表示:

第一个数字给出了命令状态的一般性指示,比如响应成功、失败或不完整。

第二个数字是响应类型的分类,如 2 代表跟连接有关的响应,3 代表用户认证。

第三个数字提供了更加详细的信息。

第一个数字的含义如下:

1 表示服务器正确接收信息,还未处理。

2 表示服务器已经正确处理信息。

3 表示服务器正确接收信息,正在处理。

4 表示信息暂时错误。

5 表示信息永久错误。

第二个数字的含义如下:

0 表示语法。

1 表示系统状态和信息。

2 表示连接状态。

3 表示与用户认证有关的信息。

4 表示未定义。

5 表示与文件系统有关的信息。

Socket 编程的几个重要步骤

Socket 客户端编程主要步骤如下:

  1. socket() 创建一个 Socket
  2. connect() 与服务器连接
  3. write() 和 read() 进行会话
  4. close() 关闭 Socket

Socket 服务器端编程主要步骤如下:

  1. socket() 创建一个 Socket
  2. bind()
  3. listen() 监听
  4. accept() 接收连接的请求
  5. write() 和 read() 进行会话
  6. close() 关闭 Socket

回页首

实现 FTP 客户端上传下载功能

下面让我们通过一个例子来对 FTP 客户端有一个深入的了解。本文实现的 FTP 客户端有下列功能:

  1. 客户端和 FTP 服务器建立 Socket 连接。
  2. 向服务器发送 USER、PASS 命令登录 FTP 服务器。
  3. 使用 PASV 命令得到服务器监听的端口号,建立数据连接。
  4. 使用 RETR/STOR 命令下载/上传文件。
  5. 在下载完毕后断开数据连接并发送 QUIT 命令退出。

本例中使用的 FTP 服务器为 filezilla。在整个交互的过程中,控制连接始终处于连接的状态,数据连接在每传输一个文件时先打开,后关闭。

客户端和 FTP 服务器建立 Socket 连接

当客户端与服务器建立连接后,服务器会返回 220 的响应码和一些欢迎信息。

图 1. 客户端连接到服务器端

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

清单 1. 客户端连接到 FTP 服务器,接收欢迎信息
SOCKET control_sock;
struct hostent *hp;
struct sockaddr_in server;
memset(&server, 0, sizeof(struct sockaddr_in));

/* 初始化socket */
control_sock = socket(AF_INET, SOCK_STREAM, 0);
hp = gethostbyname(server_name);
memcpy(&server.sin_addr, hp->h_addr, hp->h_length);
server.sin_family = AF_INET;
server.sin_port = htons(port);

/* 连接到服务器端 */
connect(control_sock,(struct sockaddr *)&server, sizeof(server));
/* 客户端接收服务器端的一些欢迎信息 */
read(control_sock, read_buf, read_len);
客户端登录 FTP 服务器

当客户端发送用户名和密码,服务器验证通过后,会返回 230 的响应码。然后客户端就可以向服务器端发送命令了。

图 2. 客户端登录 FTP 服务器

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

清单 2. 客户端发送用户名和密码,登入 FTP 服务器
/* 命令 ”USER username\r\n” */
sprintf(send_buf,"USER %s\r\n",username);
/*客户端发送用户名到服务器端 */
write(control_sock, send_buf, strlen(send_buf));
/* 客户端接收服务器的响应码和信息,正常为 ”331 User name okay, need password.” */
read(control_sock, read_buf, read_len);

/* 命令 ”PASS password\r\n” */
sprintf(send_buf,"PASS %s\r\n",password);
/* 客户端发送密码到服务器端 */
write(control_sock, send_buf, strlen(send_buf));
/* 客户端接收服务器的响应码和信息,正常为 ”230 User logged in, proceed.” */
read(control_sock, read_buf, read_len);
客户端让 FTP 服务器进入被动模式

当客户端在下载/上传文件前,要先发送命令让服务器进入被动模式。服务器会打开数据端口并监听。并返回响应码 227 和数据连接的端口号。

图 3. 客户端让服务器进入被动模式

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

清单 3. 让服务器进入被动模式,在数据端口监听
/* 命令 ”PASV\r\n” */
sprintf(send_buf,"PASV\r\n");
/* 客户端告诉服务器用被动模式 */
write(control_sock, send_buf, strlen(send_buf));
/*客户端接收服务器的响应码和新开的端口号,
* 正常为 ”227 Entering passive mode (<h1,h2,h3,h4,p1,p2>)” */
read(control_sock, read_buf, read_len);
客户端通过被动模式下载文件

当客户端发送命令下载文件。服务器会返回响应码 150,并向数据连接发送文件内容。

图 4. 客户端从FTP服务器端下载文件

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

清单 4. 客户端连接到 FTP 服务器的数据端口并下载文件
/* 连接服务器新开的数据端口 */
connect(data_sock,(struct sockaddr *)&server, sizeof(server));
/* 命令 ”CWD dirname\r\n” */
sprintf(send_buf,"CWD %s\r\n", dirname);
/* 客户端发送命令改变工作目录 */
write(control_sock, send_buf, strlen(send_buf));
/* 客户端接收服务器的响应码和信息,正常为 ”250 Command okay.” */
read(control_sock, read_buf, read_len);

/* 命令 ”SIZE filename\r\n” */
sprintf(send_buf,"SIZE %s\r\n",filename);
/* 客户端发送命令从服务器端得到下载文件的大小 */
write(control_sock, send_buf, strlen(send_buf));
/* 客户端接收服务器的响应码和信息,正常为 ”213 <size>” */
read(control_sock, read_buf, read_len);

/* 命令 ”RETR filename\r\n” */
sprintf(send_buf,"RETR %s\r\n",filename);
/* 客户端发送命令从服务器端下载文件 */
write(control_sock, send_buf, strlen(send_buf));
/* 客户端接收服务器的响应码和信息,正常为 ”150 Opening data connection.” */
read(control_sock, read_buf, read_len);

/* 客户端创建文件 */
file_handle = open(disk_name, CRFLAGS, RWXALL);
for( ; ; ) {
... ...
/* 客户端通过数据连接 从服务器接收文件内容 */
read(data_sock, read_buf, read_len);
/* 客户端写文件 */
write(file_handle, read_buf, read_len);
... ...	
}
/* 客户端关闭文件 */
rc = close(file_handle);
客户端退出服务器

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

客户端退出服务器

[外链图片转存中…(img-4I8spsHP-1715526868993)]
[外链图片转存中…(img-2t68rGuL-1715526868993)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 30
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
实现FTP协议的客户端和服务端需要以下步骤: 1. 定义FTP协议的数据包格式 2. 实现客户端和服务端的TCP连接 3. 实现FTP命令的解析和执行 4. 实现文件的上传和下载功能 以下是一个简单的C++实现,仅供参考: 服务端代码: ```c++ #include <iostream> #include <fstream> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <stdio.h> using namespace std; #define FTP_PORT 21 #define MAXLINE 1024 int listenfd, connfd; void error(const char* msg) { cerr << msg << endl; exit(1); } void send_msg(int fd, const char* msg) { write(fd, msg, strlen(msg)); } void send_data(int fd, const char* data, int len) { send_msg(fd, "150 Opening data connection.\r\n"); int datafd = accept(connfd, NULL, NULL); if (datafd == -1) { error("Error accepting data connection"); } write(datafd, data, len); close(datafd); send_msg(fd, "226 Transfer complete.\r\n"); } void do_user(const char* arg) { send_msg(connfd, "331 Password required for user.\r\n"); } void do_pass(const char* arg) { send_msg(connfd, "230 User logged in.\r\n"); } void do_quit(const char* arg) { send_msg(connfd, "221 Goodbye.\r\n"); close(connfd); close(listenfd); exit(0); } void do_retr(const char* arg) { ifstream file(arg, ios::binary); if (!file) { send_msg(connfd, "550 Failed to open file.\r\n"); return; } file.seekg(0, ios::end); int filesize = file.tellg(); file.seekg(0, ios::beg); char* buffer = new char[filesize]; file.read(buffer, filesize); file.close(); send_data(connfd, buffer, filesize); delete[] buffer; } void do_stor(const char* arg) { send_msg(connfd, "150 Ok to send data.\r\n"); int datafd = accept(connfd, NULL, NULL); if (datafd == -1) { error("Error accepting data connection"); } ofstream file(arg, ios::binary); if (!file) { send_msg(connfd, "550 Failed to open file.\r\n"); return; } char buffer[MAXLINE]; int n; while ((n = read(datafd, buffer, MAXLINE)) > 0) { file.write(buffer, n); } file.close(); close(datafd); send_msg(connfd, "226 Transfer complete.\r\n"); } void handle_command(const char* command, const char* arg) { cout << "Command: " << command << " Arg: " << arg << endl; if (strcmp(command, "USER") == 0) { do_user(arg); } else if (strcmp(command, "PASS") == 0) { do_pass(arg); } else if (strcmp(command, "QUIT") == 0) { do_quit(arg); } else if (strcmp(command, "RETR") == 0) { do_retr(arg); } else if (strcmp(command, "STOR") == 0) { do_stor(arg); } else { send_msg(connfd, "500 Unknown command.\r\n"); } } void handle_client() { char buffer[MAXLINE]; char command[MAXLINE]; char arg[MAXLINE]; send_msg(connfd, "220 FTP server ready.\r\n"); while (true) { int n = read(connfd, buffer, MAXLINE); if (n == 0) { break; } buffer[n] = '\0'; cout << "Received: " << buffer; if (sscanf(buffer, "%s %s", command, arg) == 2) { handle_command(command, arg); } else if (sscanf(buffer, "%s", command) == 1) { handle_command(command, ""); } else { send_msg(connfd, "500 Unknown command.\r\n"); } } } int main(int argc, char const *argv[]) { struct sockaddr_in servaddr, cliaddr; listenfd = socket(AF_INET, SOCK_STREAM, 0); if (listenfd == -1) { error("Error creating socket"); } memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(FTP_PORT); if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) { error("Error binding socket"); } if (listen(listenfd, 5) == -1) { error("Error listening on socket"); } cout << "FTP server listening on port " << FTP_PORT << endl; while (true) { socklen_t clilen = sizeof(cliaddr); connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &clilen); if (connfd == -1) { error("Error accepting connection"); } cout << "Accepted connection from " << inet_ntoa(cliaddr.sin_addr) << endl; handle_client(); } return 0; } ``` 客户端代码: ```c++ #include <iostream> #include <fstream> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <stdio.h> using namespace std; #define FTP_PORT 21 #define MAXLINE 1024 int sockfd; void error(const char* msg) { cerr << msg << endl; exit(1); } void send_msg(const char* msg) { write(sockfd, msg, strlen(msg)); } void send_data(const char* data, int len) { send_msg("TYPE I\r\n"); int datafd = socket(AF_INET, SOCK_STREAM, 0); if (datafd == -1) { error("Error creating data socket"); } struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = 0; if (bind(datafd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) { error("Error binding data socket"); } struct sockaddr_in cliaddr; memset(&cliaddr, 0, sizeof(cliaddr)); socklen_t clilen = sizeof(cliaddr); if (getsockname(datafd, (struct sockaddr*)&cliaddr, &clilen) == -1) { error("Error getting data socket address"); } char buffer[MAXLINE]; sprintf(buffer, "PORT %s,%d,%d\r\n", inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port >> 8, cliaddr.sin_port & 0xff); send_msg(buffer); send_msg("STOR file.txt\r\n"); listen(datafd, 1); int connfd = accept(datafd, NULL, NULL); if (connfd == -1) { error("Error accepting data connection"); } write(connfd, data, len); close(connfd); close(datafd); char response[MAXLINE]; read(sockfd, response, MAXLINE); cout << response; } void do_user(const char* arg) { char buffer[MAXLINE]; sprintf(buffer, "USER %s\r\n", arg); send_msg(buffer); char response[MAXLINE]; read(sockfd, response, MAXLINE); cout << response; } void do_pass(const char* arg) { char buffer[MAXLINE]; sprintf(buffer, "PASS %s\r\n", arg); send_msg(buffer); char response[MAXLINE]; read(sockfd, response, MAXLINE); cout << response; } void do_quit(const char* arg) { send_msg("QUIT\r\n"); char response[MAXLINE]; read(sockfd, response, MAXLINE); cout << response; close(sockfd); exit(0); } void do_put(const char* arg) { ifstream file(arg, ios::binary); if (!file) { cerr << "Failed to open file: " << arg << endl; return; } file.seekg(0, ios::end); int filesize = file.tellg(); file.seekg(0, ios::beg); char* buffer = new char[filesize]; file.read(buffer, filesize); file.close(); send_data(buffer, filesize); delete[] buffer; } void handle_command(const char* command, const char* arg) { cout << "Command: " << command << " Arg: " << arg << endl; if (strcmp(command, "USER") == 0) { do_user(arg); } else if (strcmp(command, "PASS") == 0) { do_pass(arg); } else if (strcmp(command, "QUIT") == 0) { do_quit(arg); } else if (strcmp(command, "PUT") == 0) { do_put(arg); } else { cerr << "Unknown command: " << command << endl; } } void handle_client() { char buffer[MAXLINE]; char command[MAXLINE]; char arg[MAXLINE]; while (true) { cout << "ftp> "; cin.getline(buffer, MAXLINE); if (strlen(buffer) == 0) { continue; } if (sscanf(buffer, "%s %s", command, arg) == 2) { handle_command(command, arg); } else if (sscanf(buffer, "%s", command) == 1) { handle_command(command, ""); } else { cerr << "Invalid command: " << buffer << endl; } } } int main(int argc, char const *argv[]) { struct sockaddr_in servaddr; sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { error("Error creating socket"); } memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = inet_addr(argv[1]); servaddr.sin_port = htons(FTP_PORT); if (connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) { error("Error connecting to server"); } char response[MAXLINE]; read(sockfd, response, MAXLINE); cout << response; handle_client(); return 0; } ``` 这是一个非常简单的实现,只有最基本的功能。为了实现更完整和稳健的FTP客户端和服务端,还需要考虑更多的情况和异常处理。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值