计网课设 C++实现 FTP客户端 和 服务器端

文章目录

      • 一、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的工作情况如下图所示(为了简便,没有画主进程)
计网课设 C++实现 FTP客户端 和 服务器端_第1张图片
服务端有两个从属进程:控制进程数据传送进程
在进行文件传送时,FTP的客户和服务器之间要建立两个并行的TCP连接:控制进程数据传送进程

控制连接在整个会话期间一直保持打开,FTP客户所发出的传送请求,通过控制连接发送给服务器端的控制进程,但控制连接并不用来传送文件。实际用于传送文件的是数据连接。服务器端的控制进程在接收到FTP客户发送来的文件传输请求后就创建数据传送进程数据连接,用来连接客户端和服务器端的数据传送进程。数据传送进程实际完成文件的传送,在传送完毕后关闭数据传送连接并结束运行。由于FTP使用与一个分离的控制连接,因为FTP的控制信息是带外传送的。

当客户进程向服务器进程发出建立连接请求时,要寻找连接服务器进程的熟知端口21,同时还要告诉服务器进程自己的另一个端口号码,用于建立数据传送连接。接着,服务器进程用自己传送数据的熟知端口20与客户进程所提供的端口号建立数据传送连接。由于FTP使用了两个不同的端口号,所以数据连接和控制连接不会发生混乱。

使用两个独立连接好处在于使得协议更加简单和容易实现,同时在传输文件时也可以进行文件传输的控制。

二、课设要求

计网课设 C++实现 FTP客户端 和 服务器端_第2张图片

三、课设完成流程

先掌握知识点,看看winsock编程基础,其实看winsock编程基础就够了 或者这个Socket过程详细解释(包括三次握手建立连接,四次握手断开连接)
再看看RFC959 FTP传输协议(中文版)这个是个备忘录,量比较多,可以看别人写的RFC959阅读笔记
接着是 Linux下常用的ftp操作命令 和 windows socket函数详解
然后看 例程1、例程2 并查阅例程中的知识点
最后代码实现

四、一些知识点

整体思路图
计网课设 C++实现 FTP客户端 和 服务器端_第3张图片
有张gif的,真香。
计网课设 C++实现 FTP客户端 和 服务器端_第4张图片
还有一张gif,该图演示了从连接建立(connect-accept)到应答会话(recv-send)以及连接关闭(shutdown-close)的序列,借助 Wireshark 等抓包工具可进阶分析 API 调用背后的 TCP 协议栈机理。
计网课设 C++实现 FTP客户端 和 服务器端_第5张图片


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()就是将一个数的高低位互换。
计网课设 C++实现 FTP客户端 和 服务器端_第6张图片
这是由字节序导致的,最常见的主机字节序有两种
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 
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, '\0', sizeof(sbuff));
	while (1) { //从文件中循环读取数据并发送
		int len = fread(sbuff, 1, sizeof(sbuff), file); //fread从file文件读取sizeof(sbuff)长度的数据到sbuff,返回成功读取的数据个数
		if (send(datatcps, sbuff, len, 0) == SOCKET_ERROR) {
			cout << "与客户端的连接中断" << endl;
			closesocket(datatcps);
			return 0;
		}
		if (len < sizeof(sbuff)) {
			break;
		}
	}
	closesocket(datatcps);
	cout << "传输完成" << endl;
	return 1;
}
void list(SOCKET sockfd) { //列出远方当前目录
	int nRead;
	memset(sbuff, '\0', sizeof(sbuff));
	while (1) {
		nRead = recv(sockClient, rbuff, sizeof(rbuff), 0);
		//recv通过sockClient套接口接受数据存入rbuff缓冲区,返回接收到的字节数
		if (nRead == SOCKET_ERROR) {
			cout << "读取时发生错误" << endl;
			exit(1);
		}
		if (nRead == 0) { //数据读取结束
			break;
		}
		//显示数据
		rbuff[nRead] = '\0';
		cout << rbuff << endl;
	}
}
int  user() {
	char operation[10], name[20];		//操作与文件名
	char order[30] = "\0";				//输入的命令
	char buff[80];						//用来存储经过字符串格式化的order
	cout << "请输入用户名指令(user 用户名):";
	cin >> operation;
	cin >> name;
	strcat(order, operation), strcat(order, " "), strcat(order, name);
	sprintf(buff, order);
	sendTCP(buff);									//发送指令
	recv(sockClient, rbuff, sizeof(rbuff), 0);		//接收信息 
	cout << rbuff << endl;
	return 1;
}
int pass() {
	char operation[10], name[20];		//操作与文件名
	char order[30] = "\0";				//输入的命令
	char buff[80];						//用来存储经过字符串格式化的order
	cout << "请输入密码指令(pass 密码):" ;
	cin >> operation;
	cin >> name;
	strcat(order, operation), strcat(order, " "), strcat(order, name);
	sprintf(buff, order);
	sendTCP(buff);									//发送指令
	recv(sockClient, rbuff, sizeof(rbuff), 0);		//接收信息 
	cout << rbuff << endl;
	if (strcmp(rbuff, "wrong") == 0) {
		return 0;
	}
	return 1;
}
//***********************函数定义***********************

int main(){
	while (1) {
		char operation[10], name[20];		//操作与文件名
		char order[30] = "\0";				//输入的命令
		char buff[80];						//用来存储经过字符串格式化的order
		FILE *fd1, *fd2;					//File协议主要用于访问本地计算机中的文件,fd指针指向要访问的目标文件 
		int cnt;

		startSock();				//启动winsock并初始化
		if (callServer() == -1) {	//发送连接请求失败
			continue;
		}

		//发送连接请求成功,初始化数据
		memset(operation, 0, sizeof(operation));
		memset(name, 0, sizeof(name));
		memset(order, 0, sizeof(order));
		memset(buff, 0, sizeof(buff));
		memset(rbuff, 0, sizeof(rbuff));
		memset(sbuff, 0, sizeof(sbuff));

		if (checkFlag == false) {//登陆
			if (user() && pass()) {
				checkFlag = true;
				continue;
			}
			else {
				continue;
			}
		}

		cout << endl << "请输入要执行的指令(输入 ? 或 help 可以打开帮助菜单) : ";
		cin >> operation;
		
		if (strncmp(operation, "get", 3) == 0 || strncmp(operation, "put", 3) == 0 || strncmp(operation, "cd", 2) == 0) { ///需要输入文件名的功能
			cin >> name;
		}else if (strncmp(operation, "quit", 4) == 0) { ///退出功能
			cout << "感谢您的使用" << endl;
			closesocket(sockClient);
			WSACleanup();
			break;
		}else if (strncmp(operation, "?", 1) == 0 || strncmp(operation, "help", 4) == 0) { ///帮助菜单功能
			help();
		}

		//将指令整合进order,并存放进buff
		strcat(order, operation), strcat(order, " "), strcat(order, name);
		sprintf(buff, order);
		sendTCP(buff);									//发送指令
		recv(sockClient, rbuff, sizeof(rbuff), 0);		//接收信息 
		cout << rbuff << endl;							//pwd功能在这里已经实现
		if (strncmp(rbuff, "get", 3) == 0) {			///下载功能
			fd1 = fopen(name, "wb");					//用二进制的方式打开文件,wb表示打开或新建一个二进制文件(只允许写数据)  
			if (fd1 == NULL){
				cout << "打开或者新建 "<< name << "文件失败" << endl;
				continue;
			}
			memset(rbuff, '\0', sizeof(rbuff));
			while ((cnt = recv(sockClient, rbuff, sizeof(rbuff), 0)) > 0) {
				fwrite(rbuff, sizeof(rbuff), cnt, fd1);	//C 库函数 size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) 把 ptr 所指向的数组中的数据写入到给定流 stream 中。
			}
			fclose(fd1);								//关闭文件
		}//get
		else if (strncmp(rbuff, "put", 3) == 0) { ///上传功能
			strcpy(fileName, rbuff + 4);
			fd2 = fopen(fileName, "rb");				//打开一个二进制文件,文件必须存在,只允许读
			if (fd2){ //成功打开
				if (!sendFile(sockClient, fd2)) {
					cout << "发送失败" << endl;
					return 0;
				}
				fclose(fd2);
			}
			else{
				strcpy(sbuff, "无法打开文件\n");
				if (send(sockClient, sbuff, sizeof(sbuff), 0)) {
					return 0;
				}
			}
		}//put
		else if (strncmp(rbuff, "dir", 3) == 0) { ///dir功能
			list(sockClient);
		}//dir
		closesocket(sockClient);	//关闭连接
		WSACleanup();				//释放Winsock
	}
	return 0;
}
/*
192.168.0.100
user gyc
pass 123456
pwd
cd Debug
get 110.txt
*/
5.2server
#include "Winsock.h"
#include "windows.h"
#include 
#include 
using namespace std;

#define RECV_PORT 3312	//接收端口
#define SEND_PORT 4302	//发送端口
#pragma comment(lib, "wsock32.lib")

SOCKET sockClient, sockServer;
sockaddr_in severAddr;//服务器地址
sockaddr_in ClientAddr;//客户端地址 

int addrLen;		//地址长度
char fileName[20];	//文件名
char order[20];		//命令
char rbuff[1024];	//接收缓冲区
char sbuff[1024];	//发送缓冲区

char namePassword[1024] = "gyc 123456";	//用户名和密码

//***************函数声明***************

DWORD startSock();
DWORD createSocket();
int sendFileRecord(SOCKET datatcps, WIN32_FIND_DATA *pfd);
int sendFileList(SOCKET datatcps);
int sendFile(SOCKET datatcps, FILE* file);
DWORD connectProcess();

//***************函数声明***************
DWORD startSock() {//初始化winsock
	WSADATA WSAData;
	if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0) {
		cout << "初始化失败" << endl;
		return -1;
	}
	return 1;
}
DWORD createSocket() {
	sockClient = socket(AF_INET, SOCK_STREAM, 0);
	if (sockClient == SOCKET_ERROR) {
		cout << "创建失败" << endl;
		WSACleanup();
		return -1;
	}
	severAddr.sin_family = AF_INET;
	severAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	severAddr.sin_port = htons(RECV_PORT);
	if (bind(sockClient, (struct sockaddr FAR*)&severAddr, sizeof(severAddr)) == SOCKET_ERROR) {
		//bind函数用于将socket和地址结构绑定
		cout << "绑定失败" << endl;
		return -1;
	}
	return 1;
}
DWORD connectProcess() {
	addrLen = sizeof(ClientAddr);//addrLen是对象地址的长度 
	if (listen(sockClient, 10) < 0) {//让套接字进入被动监听状态,参数2为请求队列的最大长度
		cout << "监听失败" << endl;
		return -1;
	}
	cout << "服务器正在监听中…" << endl;
	while (1) {
		//accept取出队列头部的连接请求
		//sockclient是处于监听的套接字
		//ClientAddr 是监听的对象地址
		sockServer = accept(sockClient, (struct sockaddr FAR*)&ClientAddr, &addrLen);
		while (1) {
			memset(rbuff, 0, sizeof(rbuff));
			memset(sbuff, 0, sizeof(sbuff));
			if (recv(sockServer, rbuff, sizeof(rbuff), 0) <= 0) {
				break;
			}
			cout << endl << "获取并执行的命令:" << rbuff << endl;
			if (strncmp(rbuff, "get", 3) == 0) {
				strcpy(fileName, rbuff + 4);
				FILE* file;//定义一个文件访问指针
				//处理下载文件请求
				file = fopen(fileName, "rb");//二进制打开文件,只允许读
				if (file) {
					sprintf(sbuff, "get %s", fileName);
					if (!send(sockServer, sbuff, sizeof(sbuff), 0)) {
						fclose(file);
						return 0;
					}
					else {//创建额外数据连接传送数据
						if (!sendFile(sockServer, file)) {
							return 0;
						}
						fclose(file);
					}
				}else {
					strcpy(sbuff, "无法打开文件\n");
					if (send(sockServer, sbuff, sizeof(sbuff), 0)) {
						return 0;
					}
				}
			}//get
			else if (strncmp(rbuff, "put", 3) == 0) {
				FILE* fd;
				int cnt;
				strcpy(fileName, rbuff + 4);
				fd = fopen(fileName, "wb");
				if (fd == NULL) {
					cout << "无法打开文件" << fileName << endl;
					return 0;
				}
				sprintf(sbuff, "put %s", fileName);
				if (!send(sockServer, sbuff, sizeof(sbuff), 0)) {
					fclose(fd);
					return 0;
				}
				memset(sbuff, '\0', sizeof(rbuff));
				while ((cnt = recv(sockServer, rbuff, sizeof(rbuff), 0)) > 0) {
					fwrite(rbuff, sizeof(char), cnt, fd);//把cnt个数据长度为char的数据从rbuff输入到fd指向的文件
				}
				cout << "成功获得文件" << fileName << endl;
				fclose(fd);
			}//put
			else if (strncmp(rbuff, "pwd", 3) == 0) {
				char path[1000];
				GetCurrentDirectory(sizeof(path), path);//找到当前进程的当前目录
				strcpy(sbuff, path);
				send(sockServer, sbuff, sizeof(sbuff), 0);
			}//pwd
			else if (strncmp(rbuff, "dir", 3) == 0) {
				strcpy(sbuff, rbuff);
				send(sockServer, sbuff, sizeof(sbuff), 0);
				sendFileList(sockServer);
			}//dir
			else if (strncmp(rbuff, "cd", 2) == 0) {
				strcpy(fileName, rbuff + 3);
				strcpy(sbuff, rbuff);
				send(sockServer, sbuff, sizeof(sbuff), 0);
				SetCurrentDirectory(fileName);//设置当前目录 
			}//cd
			else if (strncmp(rbuff, "user", 4) == 0) {
				char tbuff[1024];
				strcpy(tbuff, rbuff + 5);
				strcat(tbuff, " ");
				memset(rbuff, '\0', sizeof(rbuff));
				strcpy(sbuff, "成功获取用户名\0");
				send(sockServer, sbuff, sizeof(sbuff), 0);

				recv(sockServer, rbuff, sizeof(rbuff), 0);
				cout << endl << "获取并执行的命令:" << rbuff << endl;
				strcat(tbuff, rbuff + 5);
				if (strcmp(tbuff, namePassword) == 0) {//验证是否正确并返回数据给客户端
					send(sockServer, "right\0", sizeof(sbuff), 0);
				}else {
					send(sockServer, "wrong\0", sizeof(sbuff), 0);
				}
			}//user pass
			closesocket(sockServer);
		}
	}
}
int sendFile(SOCKET datatcps, FILE* file) {
	cout << "正在发送文件…" << endl;
	memset(sbuff, '\0', sizeof(sbuff));
	while(1) {//从文件中循环读取数据并发送至客户端
		int len = fread(sbuff, 1, sizeof(sbuff), file);//把file指针指向的文件中的内容读取到sbuff中
		if (send(datatcps, sbuff, len, 0) == SOCKET_ERROR) {
			cout << "连接失败" << endl;
			closesocket(datatcps);
			return 0;
		}
		if (len < sizeof(sbuff)) {//文件传送结束
			break;
		}
	}
	closesocket(datatcps);
	cout << "发送成功" << endl;
	return 1;
}
int sendFileList(SOCKET datatcps) {
	HANDLE hff;								//建立一个线程
	WIN32_FIND_DATA fd;						//搜索文件
	hff = FindFirstFile("*", &fd);			//查找文件来把待操作文件的相关属性读取到WIN32_FIND_DATA结构中去 
	if (hff == INVALID_HANDLE_VALUE) {		//发生错误
		const char *errStr = "列出文件列表时发生错误\n";
		cout << *errStr << endl;
		if (send(datatcps, errStr, strlen(errStr), 0) == SOCKET_ERROR) {
			cout << "发送失败" << endl;
		}
		closesocket(datatcps);
		return 0;
	}
	BOOL flag = TRUE;
	while (flag) {//发送文件信息
		if (!sendFileRecord(datatcps, &fd)) {
			closesocket(datatcps);
			return 0;
		}
		flag = FindNextFile(hff, &fd);//查找下一个文件
	}
	closesocket(datatcps);
	return 1;
}
int sendFileRecord(SOCKET datatcps, WIN32_FIND_DATA *pfd) {//发送当前的文件记录
	char fileRecord[MAX_PATH + 32];
	
	FILETIME ft;						//文件的建立时间
	FileTimeToLocalFileTime(&pfd -> ftLastWriteTime, &ft);//Converts a file time to a local file time.
	
	SYSTEMTIME lastWriteTime;
	FileTimeToSystemTime(&ft, &lastWriteTime);
	
	const char *dir = pfd -> dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ? "" : " ";
	sprintf(fileRecord, "%04d-%02d-%02d %02d:%02d %5s %10d   %-20s\n",
		lastWriteTime.wYear,
		lastWriteTime.wMonth,
		lastWriteTime.wDay,
		lastWriteTime.wHour,
		lastWriteTime.wMinute,
		dir,
		pfd -> nFileSizeLow,
		pfd -> cFileName
	);
	if (send(datatcps, fileRecord, strlen(fileRecord), 0) == SOCKET_ERROR) {
		//通过datatcps接口发送fileRecord数据,成功返回发送的字节数   
		cout << "发送失败" << endl;
		return 0;
	}
	return 1;
}
int main(){
	if (startSock() == -1 || createSocket() == -1 || connectProcess() == -1) {
		return -1;
	}
	return 1;
}

六、运行截图

6.1 开始

打开服务器端,服务器开始监听
在这里插入图片描述
打开客户端,提示输入服务器IP
在这里插入图片描述
输入服务器IP进行连接
在这里插入图片描述

6.2 用user和pass指令登录

输入用户名后服务器会返回成功获取用户名的信息
在这里插入图片描述
提示right,登录成功
在这里插入图片描述
服务器展示监听到的命令,输入英文的问号或者help可以打开帮助菜单
计网课设 C++实现 FTP客户端 和 服务器端_第7张图片
在这里插入图片描述

6.3 pwd 显示当前文件夹的绝对路径

在这里插入图片描述

6.4 dir 显示远方当前目录的文件

会列出文件的创建时间 是否为文件夹 修改时间 文件大小 文件名等信息
计网课设 C++实现 FTP客户端 和 服务器端_第8张图片

6.5 cd改变远方当前目录和路径

可以看到当前文件目录发生了改变
计网课设 C++实现 FTP客户端 和 服务器端_第9张图片

6.6 put 上传文件

当前文件目录
计网课设 C++实现 FTP客户端 和 服务器端_第10张图片
提示监听到指令并告知客户端执行成功
在这里插入图片描述
文件夹出现对应的文件
计网课设 C++实现 FTP客户端 和 服务器端_第11张图片
文件内容一致
计网课设 C++实现 FTP客户端 和 服务器端_第12张图片

6.7 get 下载文件

删除client中的文件,保留server的文件
计网课设 C++实现 FTP客户端 和 服务器端_第13张图片
使用get让客户端从服务端下载文件
在这里插入图片描述
客户端文件夹可见下载的文件
计网课设 C++实现 FTP客户端 和 服务器端_第14张图片
文件内容一致
计网课设 C++实现 FTP客户端 和 服务器端_第15张图片

6.8 quit退出程序

正常退出
在这里插入图片描述

七、参考知识

winsock编程基础
Socket过程详细解释(包括三次握手建立连接,四次握手断开连接)

博客:例程1
博客:例程2

博客:windows socket函数详解

百度文库:RFC959 FTP传输协议(中文版)
博客:RFC959阅读笔记

博客:Linux下常用的ftp操作命令

博客:WSAStartup( )详解
百度百科:WSAStartup

博客:MAKEWORD(2,2)解释

博客:sockaddr和sockaddr_in详解
百度百科:SOCKADDR_IN

百度百科:htons()
Stack overflow:htons() function in socket programing
博客:网络字节序与主机字节序

由 serverAdd.sin_addr.s_addr 引发的思考

socket()函数介绍

慎用WSACleanup()

八、注

各类函数原型及其用法最好去MSDN查文档,这里在百度和各种博客上查二手知识并不好。

应该按RFC959文档开发,有客户端的形式是不规范的,如果用户没有客户端就没法做文件传输了。

当时找资料时也没找到能直接跑起来的代码,参考一下自己实现也是没问题的

get 功能留了个 bug,自行找到并解决吧

传输过程没有用 停止等待协议

你可能感兴趣的:(#,计算机网络,#,小作品,c++,服务器,网络)