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

一、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的,真香。
https://img-blog.csdn.net/20160309235343789#pic_center
还有一张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, '\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 <iostream>
#include <string>
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 ? "<DIR>" : " ";
	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可以打开帮助菜单
在这里插入图片描述
在这里插入图片描述

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

在这里插入图片描述

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

会列出文件的创建时间 是否为文件夹 修改时间 文件大小 文件名等信息
在这里插入图片描述

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

可以看到当前文件目录发生了改变
在这里插入图片描述

6.6 put 上传文件

当前文件目录
在这里插入图片描述
提示监听到指令并告知客户端执行成功
在这里插入图片描述
文件夹出现对应的文件
在这里插入图片描述
文件内容一致
在这里插入图片描述

6.7 get 下载文件

删除client中的文件,保留server的文件
在这里插入图片描述
使用get让客户端从服务端下载文件
在这里插入图片描述
客户端文件夹可见下载的文件
在这里插入图片描述
文件内容一致

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查文档,这里图方便,在lj百度和各种博客上查二手知识,这并不是好的习惯。

用按RFC959文档开发,这样有客户端是不规范的。

get功能有点小bug

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章