概述
文章目录
- 一、FTP课本知识
- 1.1 概述
- 1.2 基本工作原理
- 二、课设要求
- 三、课设完成流程
- 四、一些知识点
- 五、代码
- 5.1client
- 5.2server
- 六、运行截图
- 6.1 开始
- 6.2 用user和pass指令登录
- 6.3 pwd 显示当前文件夹的绝对路径
- 6.4 dir 显示远方当前目录的文件
- 6.5 cd改变远方当前目录和路径
- 6.6 put 上传文件
- 6.7 get 下载文件
- 6.8 quit退出程序
- 七、参考知识
- 八、注
一、FTP课本知识
计算机网络第七版 谢希仁编著
1.1 概述
FTP提供交互式访问,允许客户指明文件的类型和格式,允许文件具有存取权限。FTP屏蔽了各计算机系统的细节,因此适合于异构网络中任意计算机之间传送文件。RFC959很早就成为了互联网的正式标准。
1.2 基本工作原理
FTP采用客户服务器模式,即Client-Server(C/S)结构。
一个FTP服务器进程可以同时为多个客户进程提供服务。
FTP的工作情况如下图所示(为了简便,没有画主进程)
服务端有两个从属进程:控制进程和数据传送进程。
在进行文件传送时,FTP的客户和服务器之间要建立两个并行的TCP连接:控制进程和数据传送进程。
控制连接在整个会话期间一直保持打开,FTP客户所发出的传送请求,通过控制连接发送给服务器端的控制进程,但控制连接并不用来传送文件。实际用于传送文件的是数据连接。服务器端的控制进程在接收到FTP客户发送来的文件传输请求后就创建数据传送进程和数据连接,用来连接客户端和服务器端的数据传送进程。数据传送进程实际完成文件的传送,在传送完毕后关闭数据传送连接并结束运行。由于FTP使用与一个分离的控制连接,因为FTP的控制信息是带外传送的。
当客户进程向服务器进程发出建立连接请求时,要寻找连接服务器进程的熟知端口21,同时还要告诉服务器进程自己的另一个端口号码,用于建立数据传送连接。接着,服务器进程用自己传送数据的熟知端口20与客户进程所提供的端口号建立数据传送连接。由于FTP使用了两个不同的端口号,所以数据连接和控制连接不会发生混乱。
使用两个独立连接好处在于使得协议更加简单和容易实现,同时在传输文件时也可以进行文件传输的控制。
二、课设要求
三、课设完成流程
先掌握知识点,看看winsock编程基础,其实看winsock编程基础就够了 或者这个Socket过程详细解释(包括三次握手建立连接,四次握手断开连接)
再看看RFC959 FTP传输协议(中文版)这个是个备忘录,量比较多,可以看别人写的RFC959阅读笔记
接着是 Linux下常用的ftp操作命令 和 windows socket函数详解
然后看 例程1、例程2 并查阅例程中的知识点
最后代码实现
四、一些知识点
整体思路图
有张gif的,真香。
还有一张gif,该图演示了从连接建立(connect-accept)到应答会话(recv-send)以及连接关闭(shutdown-close)的序列,借助 Wireshark 等抓包工具可进阶分析 API 调用背后的 TCP 协议栈机理。
WSAStartup向操作系统说明我们要用哪个库文件。
MAKEWORD用于声明调用不同的Winsock版本。
例如MAKEWORD(2,2)就是调用2.2版,MAKEWORD(1,1)就是调用1.1版。
struct sockaddr 和 struct sockaddr_in 这两个结构体用来处理网络通信的地址。
sin_family指代协议族,在socket编程中只能是AF_INET
sin_port存储端口号(使用网络字节顺序),在linux下,端口号的范围0~65535,同时0~1024范围的端口号已经被系统使用或保留。
sin_addr存储IP地址,使用in_addr这个数据结构
sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。
简单地说,htons()就是将一个数的高低位互换。
这是由字节序导致的,最常见的主机字节序有两种
1. Little endian:将低序字节存储在起始地址
2. Big endian:将高序字节存储在起始地址
网络字节顺序是TCP/IP中规定好的一种数据表示格式,网络字节顺序采用big endian排序方式。
为了进行转换 bsd socket提供了转换的函数 有下面四个
htons 把unsigned short类型从主机序转换到网络序
htonl 把unsigned long类型从主机序转换到网络序
ntohs 把unsigned short类型从网络序转换到主机序
ntohl 把unsigned long类型从网络序转换到主机序
(s 就是short l是long h是host n是network)
在使用little endian的系统中,这些函数会把字节序进行转换。
而在使用big endian类型的系统中,这些函数会定义成空宏。
inet_addr();
返回:若字符串有效则将字符串转换为32位二进制网络字节序的IPV4地址,否则为INADDR_NONE
socket函数原型:int socket(int domain, int type, int protocol);
第二个参数用了SOCK_STREAM:Tcp连接,提供序列化的、可靠的、双向连接的字节流,支持带外数据传输。(FTP就是带外数据传输)
函数socket()的第3个参数protocol用于制定某个协议的特定类型,即type类型中的某个类型。通常某协议中只有一种特定类型,这样protocol参数仅能设置为0;
由于WSACleanup()的调用会导致将socket的初始化资源从Windows Sockets的实现中注销,并且该实现释放为应用程序或DLL分配的任何资源
然后是服务端的一些知识
跟客户端其实差不多,查一查就好了
五、代码
为了方便贴代码,都打一起了
代码跟例程很相似,不过改了很多bug(发送和接收信息都不清空缓冲区的。。),例程1编译不了,虽然稍微改改就可以运行。
5.1client
#include "Winsock.h"
#include "windows.h"
#include "time.h"
#include "stdio.h"
#include <iostream>
using namespace std;
#define RECV_PORT 3312 //接收端口
#define SEND_PORT 4302 //发送端口
#pragma comment(lib, "wsock32.lib") //加载ws2_32.dll,它是Windows Sockets应用程序接口, 用于支持Internet和网络应用程序。
SOCKET sockClient;
//客户端对象
sockaddr_in serverAddr; //服务器地址
char inputIP[20];
//存储输入的服务器IP
char fileName[20];
//文件名
char rbuff[1024];
//接收缓冲区
char sbuff[1024];
//发送缓冲区
bool checkFlag = false;
//标志是否通过登陆
//***********************函数声明***********************
DWORD startSock();
//启动winsock并初始化
DWORD createSocket();
//创建socket
DWORD callServer();
//发送连接请求
void help();
//菜单
void list(SOCKET sockfd);
//列出远方当前目录
DWORD sendTCP(char data[]);
//发送要执行的命令至服务端
int user();
//上传用户名
int pass();
//上传密码
int sendFile(SOCKET datatcps, FILE* file); //put 传送给远方一个文件
//***********************函数声明***********************
//***********************函数定义***********************
DWORD startSock() { //启动winsock并初始化
WSADATA WSAData;
char a[20];
memset(a, 0, sizeof(a));
if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0) { //加载winsock版本
cout << "sock初始化失败" << endl;
return -1;
}
if (strncmp(inputIP, a, sizeof(a)) == 0) {
cout << "请输入要连接的服务器IP:";
cin >> inputIP;
}
//设置地址结构
serverAddr.sin_family = AF_INET;
//表明底层是使用的哪种通信协议来递交数据的,AF_INET表示使用 TCP/IPv4 地址族进行通信
serverAddr.sin_addr.s_addr = inet_addr(inputIP); //指定服务器IP,十进制转化成二进制IPV4地址
serverAddr.sin_port = htons(RECV_PORT);
//设置端口号,htons用于将主机字节序改为网络字节序
return 1;
}
DWORD createSocket() { //创建socket
//要使用套接字,首先必须调用socket()函数创建一个套接字描述符,就如同操作文件时,首先得调用fopen()函数打开一个文件。
sockClient = socket(AF_INET, SOCK_STREAM, 0);//当scoket函数成功调用时返回一个新的SOCKET(Socket Descriptor) //SOCK_STREAM(流式套接字):Tcp连接,提供序列化的、可靠的、双向连接的字节流。支持带外数据传输
if (sockClient == SOCKET_ERROR) {
cout << "创建socket失败" << endl;
WSACleanup();//终止Ws2_32.dll 的使用
return -1;
}
return 1;
}
DWORD callServer() { //发送连接请求
createSocket();
if (connect(sockClient, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {//connect()创建与指定外部端口的连接
cout << "连接失败" << endl;
memset(inputIP, 0, sizeof(inputIP));
return -1;
}
return 1;
}
void help() { //帮助菜单
cout << "
___________________________________________
" << endl
<< "
|
FTP帮助菜单
|
" << endl
<< "
| 1、get 下载文件 [输入格式: get 文件名 ]
|
" << endl
<< "
| 2、put 上传文件 [输入格式:put 文件名]
|
" << endl
<< "
| 3、pwd 显示当前文件夹的绝对路径
|
" << endl
<< "
| 4、dir 显示远方当前目录的文件
|
" << endl
<< "
| 5、cd
改变远方当前目录和路径
|
" << endl
<< "
|
进入下级目录: cd 路径名
|
" << endl
<< "
|
进入上级目录: cd ..
|
" << endl
<< "
| 6、? 或者 help 进入帮助菜单
|
" << endl
<< "
| 7、quit 退出FTP
|
" << endl
<< "
|___________________________________________|
" << endl;
}
DWORD sendTCP(char data[]) { //发送要执行的命令至服务端
int length = send(sockClient, data, strlen(data), 0);
if (length <= 0) {
cout << "发送命令至服务端失败" << endl;
closesocket(sockClient);//当不使用socket()创建的套接字时,应该调用closesocket()函数将它关闭,就如同调用fclose()函数关闭一个文件,用来进行套接字资源的释放。
WSACleanup();
return -1;
}
return 1;
}
int sendFile(SOCKET datatcps, FILE* file) { //put 传送给远方一个文件
cout << "正在传输文件…" << endl;
memset(sbuff, '