計網課設 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

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