FTP网络协议C++实现

2016-12-05

版权声明:本文为作者原创文章,可以随意转载,但必须在明确位置表明出处!!!

FTP协议:

FTP(File transfer protocol)文件传输协议,它是TCP/IP协议中的一种,它用于文件传输的Internet标准,FTP协议分两部分,一部分是file transfer,另一部分是file access。file transfer是FTP提供,file access是网络文件系统提供。FTP可以运行在不同的操作系统上,它是将一个完整的文件复制到另一个系统中所以我们需要有身份验证。

FTP工作原理:

FTP采用两个TCP链接来传输一个文件。

  1. 控制链接:客户端向FTP服务端发起链接,服务器端以被动的方式打开21号端口等待客户端链接到来,控制链接主要用来管理客户端和服务端的控制命令。由于命令通常来说都是客户端发出,所以IP队控制链接的类型是“最大限度的减少延时”。

  2. 数据链接:没当一个文件在客户端和服务端传输是就会建立一个数据链接,该链接的目的是传输数据,所以IP对数据链接的的服务特点是“最大限度的提高吞吐量”。

FTP数据流:

FTP允许数据流的传输方式有两种,ASCII和二进制流。

FTP应答:

客户端没发送一次命令服务器端都会返回应答,应对包涵应答码和一段描述说明,应答码第一位和第二位的含义如下:

  • 1yz:肯定预备应答。它仅仅是在发送另一个命令前期待另一个应答时启动。
  • 2yz:肯定完成应答。一个新命令可以发送。
  • 3yz:肯定中介应答。该命令已被接受,但另一个命令必须被发送。
  • 4yz:暂态否定完成应答。请求的动作没有发生,但差错状态是暂时的,所以命令可以过后再发。
  • 5yz:永久性否定完成应答。命令不被接受,并且不再重试
  • x0z:语法错误
  • x1z:信息
  • x2z:连接。应答指控制或数据连接
  • x3z:鉴别和记帐。应答用于注册或记帐命令
  • x4z:未指明
  • x5z:文件系统状态

FTP的传输模式

  1. 主动模式:客户端从任意大于1024端口好链接到服务端21好宽口,然后客户端是端口N + 1上监听来自服务端的链接,并通过这个链接于服务端进行数据传输。主动模式实际并没有于服务端建立链接,他只是告诉服务端它在N + 1号端口上监听,服务端需要主动来链接客户端,这是从外部系统建立到内部系统的链接,有可能会被内部防火墙给过滤掉。

  2. 被动模式:为了解决服务端发送的主动链接问题开发了被动模式,被动模式是客户端在控制链接上发送一个被动请求PASV,服务端返回一个数据链接端口,客户端通过此端口与服务端建立数据链接并进行数据传输。

okay,现在我们已经大概了解了FTP协议的基本内容,在开始编写代码前我们用wireshark抓包工具抓取一份完整的FTP协议包,如下图:

192.168.102.153是我的们FTP服务端地址,192.168.3.27是我本机ip地址也就是客户端地址。

  1. 第十个包是服务端发回给我们的信息,回返220 和服务器端FTP版本号,这一步的返回是是我们客户端发起的socket链接
  2. 第十一个包是客户端发送的请求命令 USER ftpadmin, 用户名为ftpadmin
  3. 第十二个包是服务端返回让客户端输入指定密码
  4. 第十三个包是客户端发送请求命令 PASS ftpadmin123,用户输入指定密码
  5. 第十四个包服务端返回用户登陆成功
  6. 第十五个包客户端发送SYST命令,询问服务端是什么操作系统
  7. 第十六个包服务端返回unix操作系统
  8. 第十七个包发送FEAT,询问服务端支持哪些扩展命令
  9. 第十八到二十六服务端应答该服务端支持的扩展命令
  10. 第三十二个包客户端发送切换目录命令CWD /home/ftpadmin,切换到ftpadmin目录下
  11. 第三十三个包服务端发回切换目录成功
  12. 第三十六个包客户端发送流传输模式命令 TYPE A, ASCII模式传输
  13. 第三十七个包服务端返回流传输模式改变成功
  14. 第三十八个包客户端发送被动传输模式PASV
  15. 第三十九个包服务端返回新的链接端口250 * 256 + 38

到这里客户端就通过服务端返回的新端口建立数据链接,并在此链接上进行数据交互。

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
WSADATA wsdata;
int nRet = WSAStartup(MAKEWORD(2,2), &wsdata);
if (0 != nRet)
{
DWORD dwErr = WSAGetLastError();
return 0;
}
SOCKET socketTemp = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
SOCKADDR_IN serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(21);
serverAddr.sin_addr.S_un.S_addr = inet_addr("192.168.102.153");
nRet = connect(socketTemp, (SOCKADDR *)&serverAddr, sizeof(serverAddr));
if (SOCKET_ERROR == nRet)
{
DWORD dwErr = WSAGetLastError();
return 0;
}
settimeout(socketTemp, true, 10000);
char recvBuf[1024] = {0};
int recvByte = recv(socketTemp, recvBuf, 1024, 0);
if (SOCKET_ERROR == recvByte)
{
DWORD dwErr = WSAGetLastError();
return 0;
}
char command[32] = {0};
strcpy(command,"USER ftpadmin\r\n");
int sendByte = send(socketTemp, command, strlen(command), 0);
if (SOCKET_ERROR == sendByte)
{
DWORD dwErr = WSAGetLastError();
return 0;
}
memset(recvBuf, 0, 1024);
recvByte = recv(socketTemp, recvBuf, 1024, 0);
if (SOCKET_ERROR == recvByte)
{
DWORD dwErr = WSAGetLastError();
return 0;
}
memset(command, 0, 32);
strcpy(command,"PASS ftpadmin123\r\n");
sendByte = send(socketTemp, command, strlen(command), 0);
if (SOCKET_ERROR == sendByte)
{
DWORD dwErr = WSAGetLastError();
return 0;
}
memset(recvBuf, 0, 1024);
recvByte = recv(socketTemp, recvBuf, 1024, 0);
if (SOCKET_ERROR == recvByte)
{
DWORD dwErr = WSAGetLastError();
return 0;
}
memset(command, 0, 32);
strcpy(command,"CWD /home/ftpadmin\r\n");
sendByte = send(socketTemp, command, strlen(command), 0);
if (SOCKET_ERROR == sendByte)
{
DWORD dwErr = WSAGetLastError();
return 0;
}
memset(recvBuf, 0, 1024);
recvByte = recv(socketTemp, recvBuf, 1024, 0);
if (SOCKET_ERROR == recvByte)
{
DWORD dwErr = WSAGetLastError();
return 0;
}
sendByte = send(socketTemp, "PASV\r\n", strlen("PASV\r\n"), 0);
if (SOCKET_ERROR == sendByte)
{
DWORD dwErr = WSAGetLastError();
return 0;
}
memset(recvBuf, 0, 1024);
recvByte = recv(socketTemp, recvBuf, 1024, 0);
if (SOCKET_ERROR == recvByte)
{
DWORD dwErr = WSAGetLastError();
return 0;
}
const char *plisten;
plisten = strchr(recvBuf,'(');
plisten++;
union
{
unsigned char b[4];
unsigned long ui;
}ip;
union
{
unsigned char b[2];
unsigned short up;
}port;
unsigned char ip_port[6];
sscanf(plisten,"%u,%u,%u,%u,%u,%u",&ip_port[0],&ip_port[1],&ip_port[2],&ip_port[3],&ip_port[4],&ip_port[5],&ip_port[6]);
ip.b[3] = ip_port[0];
ip.b[2] = ip_port[1];
ip.b[1] = ip_port[2];
ip.b[0] = ip_port[3];
port.b[1] = ip_port[4];
port.b[0] = ip_port[5];
SOCKET socketData = socket(AF_INET, SOCK_STREAM, 0);
if (INVALID_SOCKET == socketData)
{
DWORD dwErr = WSAGetLastError();
closesocket(socketData);
return false;
}
SOCKADDR_IN dataAddr;
dataAddr.sin_family = AF_INET;
dataAddr.sin_port = htons(port.up);
dataAddr.sin_addr.S_un.S_addr = htonl(ip.ui);
nRet = connect(socketData, (SOCKADDR*)&dataAddr, sizeof(dataAddr));
if (SOCKET_ERROR == nRet)
{
DWORD dwErr = WSAGetLastError();
closesocket(socketData);
return false;
}
//上传文件
memset(command, 0, 32);
//strcpy_s(command,"STOR 1.txt\r\n");
sendByte = send(socketTemp, "STOR 1.txt\r\n", strlen("STOR 1.txt\r\n"), 0);
if (SOCKET_ERROR == sendByte)
{
DWORD dwErr = WSAGetLastError();
return 0;
}
memset(recvBuf, 0, 1024);
recvByte = recv(socketTemp, recvBuf, 1024, 0);
if (SOCKET_ERROR == recvByte)
{
DWORD dwErr = WSAGetLastError();
return 0;
}
if (recvBuf[0] != '2')
{
closesocket(socketData);
return false;
}
HANDLE hFile = CreateFile("D:\\1.txt",GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if (INVALID_HANDLE_VALUE == hFile)
{
DWORD dwErr = GetLastError();
return -1;
}
char szBuff[4096] = {0};
DWORD dwReadByte = 0;
do
{
memset(szBuff, 0, 4096);
BOOL bRet = ReadFile(hFile, szBuff, sizeof(szBuff), &dwReadByte, NULL);
if (!bRet)
{
DWORD dwErr = GetLastError();
break;
}
if (dwReadByte > 0)
{
int nRet = send(socketData, szBuff, dwReadByte, 0);
if (SOCKET_ERROR == nRet)
{
DWORD dwErr = WSAGetLastError();
break;
}
}
} while (dwReadByte > 0);
memset(szBuff, 0, 4096);
CloseHandle(hFile);
closesocket(socketData);
memset(recvBuf, 0, 1024);
recvByte = recv(socketTemp, recvBuf, 1024, 0);
if (SOCKET_ERROR == recvByte)
{
DWORD dwErr = WSAGetLastError();
return 0;
}
char sendBuff[8] = {0};
strcpy_s(sendBuff,"QUIT\r\n");
sendByte = send(socketTemp, "QUIT\r\n", strlen("QUIT\r\n"), 0);
if (SOCKET_ERROR == sendByte)
{
DWORD dwErr = WSAGetLastError();
return 0;
}

代码的逻辑就是结合协议和抓包分析写出来的,唯一取巧的地方是使用联合结构体来获取IP地址和端口号,联合体的结构是共享内存,并且按低地址开始存储正好是我们需要的。这个地方的设计大家可以借鉴一下。


推荐我的微信公众号:爱做饭的老谢


上一篇:基于模板类的排序算法(希尔排序,归并排序)
下一篇:基于模板类的排序算法(基数排序,堆排序)