以登录和文件上传、下载为例,搭建简单的TCP客户端,vs下C++编程
目录
FTP主要介绍
FTP命令与响应码
用户登录
被动模式-文件下载
被动模式-数据上传
传送门:https://www.cnblogs.com/jzincnblogs/p/5213978.html
①FTP是一种文件传输协议,属于应用层协议,基于TCP,所以客户端与服务器建立的连接是可靠、安全的,并且要经过三次握手的过程。
②FTP传输数据默认采用二进制模式,即将文件内容转换为二进制表示后再传送,而若HTML等文本文件传输时需要转换成ASCII模式。
③FTP客户端在连接服务器时需要用到两个端口,其中一个端口(默认为21)用作控制连接端口,负责发送命令和等待响应;另一个端口用作数据传输,用来建立数据传输通道,端口号为20或其他可用端口号。
④FTP有两种连接模式:主动模式与被动模式。主动模式命令为PORT,此模式下客户端需要向服务器提供一个IP地址和端口号,由服务器来连接客户端的指定端口;被动模式命令为PASV,此模式下由服务器向客户端提供IP地址与端口号,由此解决主动模式下服务器到客户端的数据端口的连接被防火墙过滤掉的问题。
⑤FTP的工作流程为:客户端连接服务器->服务器连接成功返回->客户端发送初始化命令->服务器响应命令成功返回->客户端发送用户验证消息->服务器返回验证结果->验证通过,客户端进行数据操作->服务器响应客户端操作命令->客户端发送关闭连接命令->服务器响应关闭连接命令。
命令 | 描述 |
ABOR | 中断数据连接程序 |
ACCT |
系统特权帐号 |
ALLO |
为服务器上的文件存储器分配字节 |
APPE |
添加文件到服务器同名文件 |
CDUP |
改变服务器上的父目录 |
CWD |
改变服务器上的工作目录 |
DELE |
删除服务器上的指定文件 |
HELP |
返回指定命令信息 |
LIST |
如果是文件名列出文件信息,如果是目录则列出文件列表 |
MODE |
传输模式(S=流模式,B=块模式,C=压缩模式) |
MKD |
在服务器上建立指定目录 |
NLST |
列出指定目录内容 |
NOOP | 无动作,除了来自服务器上的承认 |
PASS |
系统登录密码 |
PASV | 请求服务器等待数据连接 |
PORT | IP 地址和两字节的端口 ID |
PWD | 显示当前工作目录 |
QUIT | 从 FTP 服务器上退出登录 |
REIN | 重新初始化登录状态连接 |
REST |
由特定偏移量重启文件传递 |
RETR |
从服务器上找回(复制)文件 |
RMD |
在服务器上删除指定目录 |
RNFR |
对旧路径重命名 |
RNTO |
对新路径重命名 |
SITE |
由服务器提供的站点特殊参数 |
SMNT |
挂载指定文件结构 |
STAT |
在当前程序或目录上返回信息 |
STOR |
储存(复制)文件到服务器上 |
STOU |
储存文件到服务器名称上 |
STRU |
数据结构(F=文件,R=记录,P=页面) |
SYST | 返回服务器使用的操作系统 |
TYPE | 数据类型(A=ASCII,E=EBCDIC,I=binary) |
USER |
系统登录的用户名 |
响应代码 | 解释说明 |
110 | 新文件指示器上的重启标记 |
120 | 服务器准备就绪的时间(分钟数) |
125 | 打开数据连接,开始传输 |
150 | 打开连接 |
200 | 成功 |
202 | 命令没有执行 |
211 | 系统状态回复 |
212 | 目录状态回复 |
213 | 文件状态回复 |
214 | 帮助信息回复 |
215 | 系统类型回复 |
220 | 服务就绪 |
221 | 退出网络 |
225 | 打开数据连接 |
226 | 结束数据连接 |
227 | 进入被动模式(IP 地址、ID 端口) |
230 | 登录因特网 |
250 | 文件行为完成 |
257 | 路径名建立 |
331 | 要求密码 |
332 | 要求帐号 |
350 | 文件行为暂停 |
421 | 服务关闭 |
425 | 无法打开数据连接 |
426 | 结束连接 |
450 | 文件不可用 |
451 | 遇到本地错误 |
452 | 磁盘空间不足 |
500 | 无效命令 |
501 | 错误参数 |
502 | 命令没有执行 |
503 | 错误指令序列 |
504 | 无效命令参数 |
530 | 未登录网络 |
532 | 存储文件需要帐号 |
550 | 文件不可用 |
551 | 不知道的页类型 |
552 | 超过存储分配 |
553 | 文件名不允许 |
//参数及用法
Socket m_ftpClient;
SocketClient m_socketClient;//上一篇文章中的TCP-SocketClient文件
char m_ip[20];
int m_port;
char m_user[1000];
char m_password[1000];
int res = m_ftpClient.Connect("127.0.0.1", 21, "SCADA", "1234@qwer");
//*****************************************************************
int Login(char* ip, int port, char* user, char* password)
{
int res = 0;
strcpy_s(m_ip, ip);
m_ip[strlen(ip)] = 0;
m_port = port;
strcpy_s(m_user, user);
m_user[strlen(user)] = 0;
strcpy_s(m_password, password);
m_password[strlen(password)] = 0;
res = m_socketClient.Connect(m_ip, m_port, 100, 100);//TCP连接IP端口
if (res != 0)
{
goto ExitLine;
}
char cmdOut[100] = { 0 };
char cmdIn[100] = { 0 };
int len;
len = 100;
sprintf_s(cmdOut, "USER %s\r\n", user); //发送的命令都需要以\r\n即回车来结束,否则不识别
len = strlen(cmdOut);
res = m_socketClient.Write((byte *)cmdOut, len);//发送USER命令
if (res != 0)
{
goto ExitLine;
}
len = 100;
res = m_socketClient.Read((byte *)cmdIn, len);//获取返回结果
if (res != 0)
{
goto ExitLine;
}
if (memcmp(cmdIn, "331", 3) != 0)//比较前3个字符,USER命令返回331即需要密码,否则失败
{
res = -1;
goto ExitLine;
}
OS_Sleep(10);
memset(cmdOut, 0, 100);
sprintf_s(cmdOut, "PASS %s\r\n", password);
len = strlen(cmdOut);
res = m_socketClient.Write((byte *)cmdOut, len);//发送PASS命令
if (res != 0)
{
goto ExitLine;
}
len = 100;
res = m_socketClient.Read((byte *)cmdIn, len);//接收返回值
if (res != 0)
{
goto ExitLine;
}
if (memcmp(cmdIn, "230", 3) != 0)//前3个字符为230即成功登录
{
res = -1;
goto ExitLine;
}
ExitLine:
if (res != 0)
{
m_socketClient.Disconnect();
}
return res;
}
//参数及用法
len = 10000000;//返回的数据长度,设置最大值
char filePath[_MAX_PATH];//文件路径
memset(filePath, 0, _MAX_PATH);
GetFileData(filePath, data, &len)返回0即成功
//*******************************************************
int GetFileData(char* src, byte* bufFile, int* bufLen)
{
int res = 0;
SocketClient fileClient;
char cmdOut[100] = { 0 };
char cmdIn[1000] = { 0 };
char ip[20] = { 0 };
byte buf[1000];
int len;
if (!m_socketClient.IsConnected())//判断是否登录,登录后才可操作
{
res = Connect(m_ip, m_port, m_user, m_password);
if (res != 0)
{
goto ExitLine;
}
}
memset(cmdOut, 0, 100);
sprintf_s(cmdOut, "PASV\r\n");
len = strlen(cmdOut);
res = m_socketClient.Write((byte *)cmdOut, len);//PASV是被动模式,可以规避服务器防火墙
if (res != 0)
{
goto ExitLine;
}
len = 100;
res = m_socketClient.Read((byte *)cmdIn, len);//接收返回结果
if (res != 0)
{
goto ExitLine;
}
if (memcmp(cmdIn, "227", 3) != 0)//前3个字符返回227证明进入被动模式,cmdIn中含有服务器返回的IP和用于数据传输的端口号
{
res = -1;
goto ExitLine;
}
int ip1, ip2, ip3, ip4;
int p1, p2;
sscanf_s(cmdIn, "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)", &ip1, &ip2, &ip3, &ip4, &p1, &p2);
memset(cmdOut, 0, 100);
sprintf_s(cmdOut, "RETR %s\r\n", src); //从服务器上找回文件
len = strlen(cmdOut);
res = m_socketClient.Write((byte *)cmdOut, len);//用命令管理端口发送命令
if (res != 0)
{
goto ExitLine;
}
len = 1000;
res = m_socketClient.Read((byte *)cmdIn, len);//命令管理端口查看返回状态
if (res != 0)
{
goto ExitLine;
}
if (memcmp(cmdIn, "150", 3) != 0)//150为找回文件成功
{
res = -1;
goto ExitLine;
}
sprintf(ip, "%d.%d.%d.%d", ip1, ip2, ip3, ip4);
SocketClient fileClient;//文件用Socket
res = fileClient.Connect(ip, p1 * 256 + p2, 100, 100);//连接数据传输端口
if (res != 0)
{
goto ExitLine;
}
len = 1000;
int readed = 0;
while (fileClient.Read(buf, len) == ERRORCODE_OK)//读取文件数据
{
if (len == 0)
{
break;
}
int cpyLen = min(*bufLen, len);
memcpy(bufFile + readed, buf, cpyLen);
*bufLen -= cpyLen;
readed += cpyLen;
len = 1000;
}
*bufLen = readed;
fileClient.Disconnect();//关闭fileClient Socket后,断开数据端口连接
len = 100;
res = m_socketClient.Read((byte *)cmdIn, len);//断开数据端口连接后,返回226确认关闭数据端口连接
if (res != 0)
{
goto ExitLine;
}
if (memcmp(cmdIn, "226", 3) != 0)
{
res = -1;
goto ExitLine;
}
ExitLine:
if (res != 0)
{
m_socketClient.Disconnect();
}
return res;
}
//参数和用法
//和文件下载类似,被动模式下,由命令管理端口发送命令,连接服务器返回的数据端口发送数据
//第一个是要读取的文件路径,第二个是要存储的文件路径
//********************************************
int UploadImage(char* filePath, char* storPath)
{
int res = 0;
SocketClient fileClient;
char cmdOut[100] = { 0 };
char cmdIn[100] = { 0 };
char ip[20] = { 0 };
byte buf[1000];
int len;
if (!m_socketClient.IsConnected())
{
res = Connect(m_ip, m_port, m_user, m_password);
if (res != 0)
{
goto ExitLine;
}
}
memset(cmdOut, 0, 100);
sprintf_s(cmdOut, "PASV\r\n");
len = strlen(cmdOut);
res = m_socketClient.Write((byte *)cmdOut, len);
if (res != 0)
{
goto ExitLine;
}
len = 100;
res = m_socketClient.Read((byte *)cmdIn, len);
if (res != 0)
{
goto ExitLine;
}
if (memcmp(cmdIn, "227", 3) != 0)
{
res = -1;
goto ExitLine;
}
int ip1, ip2, ip3, ip4;
int p1, p2;
sscanf_s(cmdIn, "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)", &ip1, &ip2, &ip3, &ip4, &p1, &p2);
memset(cmdOut, 0, 100);
sprintf_s(cmdOut, "STOR %s\r\n", storPath);
len = strlen(cmdOut);
res = m_socketClient.Write((byte *)cmdOut, len);
if (res != 0)
{
goto ExitLine;
}
len = 100;
res = m_socketClient.Read((byte *)cmdIn, len);
if (res != 0)
{
goto ExitLine;
}
if (memcmp(cmdIn, "150", 3) != 0)
{
res = -1;
goto ExitLine;
}
sprintf(ip, "%d.%d.%d.%d", ip1, ip2, ip3, ip4);
res = fileClient.Connect(ip, p1 * 256 + p2, 100, 100);
if (res != 0)
{
goto ExitLine;
}
len = 1000;
FILE* fp = fopen(filePath, "rb");
if (fp == NULL)
{
res = -1;
goto ExitLine;
}
//
while (!feof(fp))
{
fread(buf, sizeof(buf), 1, fp);//要读取出的buf||读取的长度||要读取的次数||数据文件
res = fileClient.Write(buf, len);
}
if (res != 0) {
goto ExitLine;
}
fclose(fp);
fileClient.Disconnect();
len = 100;
res = m_socketClient.Read((byte *)cmdIn, len);
if (res != 0)
{
goto ExitLine;
}
if (memcmp(cmdIn, "226", 3) != 0)
{
res = -1;
goto ExitLine;
}
ExitLine:
if (res != 0)
{
m_socketClient.Disconnect();
}
return res;
}