利用原始socket简单实现FTP的客户端和服务器端程序

1.设计目的
本设计旨在利用原始socket简单实现FTP(File Transfer Protocol,文件传输协议)的客户端和服务器端程序,能够实现get、put、pwd、dir、cd等基本交互命令。

2.具体要求
用socket 编程接口编写两个程序,分别为客户程序(client.c)和服务器程序(server.c),该程序应能实现下述命令功能:
get:取远方的一个文件
put:传给远方一个文件
pwd:显示远主当前目录
dir:列出远方当前目录
cd :改变远方当前目录
? :显示你提供的命令
quit :退出返回

3.程序实现
3.1 client.c(客户端代码实现)

#include "Winsock.h"
#include "windows.h" 
#include "stdio.h" 
#include "time.h" 
#include <iostream>
using namespace std;
#define RECV_PORT 3312 
#define SEND_PORT 4302  
#pragma comment(lib, "wsock32.lib")
SOCKET sockclient;
char filename[20];                    	 //文件名  
sockaddr_in ServerAddr;					//服务器地址 
char rbuff[1024];            			 //接收缓冲区  
char sbuff[1024];						//发送缓冲区  
char InputIP[20];						//存储输入的服务器IP


void help()//处理help
{
 cout << "                         欢迎进入迷你FTP帮助菜单              " << endl
<< "                  * * * * * * * * * * * * * * * * * * * * *       " << endl
<< "                  1.get....................下载(接受)文件      " << endl
<< "                     get的用法: get 文件名                         " << endl << endl
<< "                  2.put.................上传(发送)文件       " << endl
<< "                     put的用法:put 文件名                         " << endl
<< "                  3.pwd..........显示当前文件夹的绝对路径       " << endl
<< "                  4.dir............显示远方当前目录的文件       " << endl << endl
<< "                  5.cd.............改变远方当前目录和路径       " << endl
<< "                   cd的用法(进入下级目录): cd 路径名             " << endl
<< "                   cd的用法(进入上级目录): cd ..                 " << endl << endl
<< "                  6.?或者help................进入帮助菜单      " << endl
<< "                  7.quit..........................退出FTP       " << endl
<< "                  * * * * * * * * * * * * * * * * * * * * *       " << endl;

void list(SOCKET sockfd)
{
	int nRead;
	while (true)
	{
		nRead = recv(sockclient, rbuff, 1024, 0);
		//recv函数通过sockclient套接口接受数据存入rbuff缓冲区,返回接受到的字节数      
		if (nRead == SOCKET_ERROR)
		{
			printf("read response error!\n");
			exit(1);
		}
		if (nRead == 0)//数据读取结束        
			break;
		//显示数据   
		rbuff[nRead] = '\0';
		printf("%s", rbuff);
	}
}

/*********************** put:传给远方一个文件***************************/
int SendFile(SOCKET datatcps, FILE* file)
{
	printf(" sending file data..");
	for (;;)  //从文件中循环读取数据并发送客户端       
	{
		int r = fread(sbuff, 1, 1024, file);//fread函数从file文件读取1个1024长度的数据到sbuff,返回成功读取的元素个数            
		if (send(datatcps, sbuff, r, 0) == SOCKET_ERROR)
		{
			printf("lost the connection to client!\n");
			closesocket(datatcps);
			return 0;
		}
		if (r<1024)                      //文件传送结束    
			break;
	}
	closesocket(datatcps);
	printf("done\n");
	return 1;
}


DWORD StartSock()//启动winsock 
{
	WSADATA WSAData;
	char a[20];
	memset(a, 0, 20);
	if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0)//加载winsock版本
	{
		printf("sock init fail!\n");
		return (-1);
	}
	if (strncmp(InputIP, a, 20) == 0)
	{
		printf("请输入连接的主机IP:");
		scanf("%s", &InputIP);
	}
	//设置地址结构
	ServerAddr.sin_family = AF_INET;//AF_INET表示使用IP地址族 
	ServerAddr.sin_addr.s_addr = inet_addr(InputIP);//指定服务器IP 
	ServerAddr.sin_port = htons(RECV_PORT);//设置端口号 
	return(1);
}


//创建套接字  
DWORD CreateSocket()
{
	sockclient = socket(AF_INET, SOCK_STREAM, 0);//当socket函数成功调用时返回一个新的SOCKET(Socket Descriptor) 
	if (sockclient == SOCKET_ERROR)
	{
		printf("sockclient create fail! \n");
		WSACleanup();
		return(-1);
	}
	return(1);
}
DWORD CallServer() //发送连接请求  
{
	CreateSocket();
	
	if (connect(sockclient, (struct  sockaddr*)&ServerAddr, sizeof(ServerAddr)) == SOCKET_ERROR)  
	{//connect函数创建与指定外部端口的连接
		printf("Connect fail \n");
		memset(InputIP, 0, 20);
		return(-1);
	}
	return(1);
}

DWORD TCPSend(char data[])   //发送命令  
{
	int length;
	length = send(sockclient, data, strlen(data), 0);
	//send函数通过sockclient接口发送data里面的数据,发送成功返回发送的字节数  
	if (length <= 0)
	{
		printf("send data error ! \n");
		closesocket(sockclient);
		WSACleanup();
		return(-1);
	}
	return(1);
}


int main()
{
	char messge1[10];			//定义输入要处理的文件名  
	char messge2[20];			//定义输入要处理的文件名 
	char order[30];				//输入的命令    
	order[0] = '\0';
	char buff[80];				//用以存储经过字串格式化的order  
	FILE *fd;					//File协议主要用于访问本地计算机中的文件,fd指针指向要访问的目标文件  
	FILE *fd2;
	int count;
	int sin_size = sizeof(ServerAddr);
	StartSock();
	if (CallServer() == -1)
		return main();			//发送连接请求失败,返回主函数  
	printf("\n请输入命令(输入?或help进入帮助菜单):\n");
	memset(buff, 0, 80);			//清空数组   
	memset(messge2, 0, 20);
	memset(order, 0, 30);
	memset(messge1, 0, 10);
	memset(rbuff, 0, 1024);
	memset(sbuff, 0, 1024);
	scanf("%s", &messge1);//s%输入字符串
	if (strncmp(messge1, "get", 3) == 0)
		scanf("%s", &messge2);
	if (strncmp(messge1, "put", 3) == 0)
		scanf("%s", &messge2);
	if (strncmp(messge1, "cd", 2) == 0)
		scanf("%s", &messge2);
	strcat(order, messge1);			//把messge1加在order的末尾   
	strcat(order, " ");    			//命令中间的空格    
	strcat(order, messge2);			//把messge2加在order的末尾     
	sprintf(buff, order);			//把调整格式的order存入buff

	//help和?    
	if (strncmp(messge1, "help", 4) == 0) {
		help();
	}
	if (strncmp(messge1, "?", 1) == 0){
		help();
	}

	if (strncmp(messge1, "quit", 4) == 0)
	{
		printf("                    欢迎再次进入迷你FTP,谢谢使用!\n");
		closesocket(sockclient);
		WSACleanup();
		return 0;
	}
	TCPSend(buff);//发送buff里面的数据        
	recv(sockclient, rbuff, 1024, 0);
	printf(rbuff);

	if (strncmp(rbuff, "get", 3) == 0)      //get
	{
		fd = fopen(messge2, "wb");//使用二进制方式,打开文件,wb只写打开或新 建一个二进制文件;只允许写数据。              
		if (fd == NULL)
		{
			printf("open file %s for weite failed!\n", messge2);
			return 0;
		}
		while ((count = recv(sockclient, rbuff, 1024, 0))>0)
		{
			fwrite(rbuff, sizeof(rbuff), count, fd);
		}
		//把count个数据长度为size0f()的数据从 rbuff输入到fd指向的目标文件             
		fclose(fd);        //关闭文件    
	}

	if (strncmp(rbuff, "put", 3) == 0)   //put 
	{
		strcpy(filename, rbuff + 9);
		fd2 = fopen(filename, "rb");//rb读写打开一个二进制文件,只允许读写数据。 
		if (fd2)
		{
			if (!SendFile(sockclient, fd2)){
				printf("send failed!");
				return 0;
			}
			fclose(fd2);
		}//关闭文件 

		else//打开文件失败  
		{
			strcpy(sbuff, "can't open file!\n");
			if (send(sockclient, sbuff, 1024, 0))
				return 0;
		}
	}

	if (strncmp(rbuff, "dir", 3) == 0)   	//dir 
	{
		printf("\n");
		list(sockclient);				//列出接受到的列表内容
	}
	if (strncmp(rbuff, "pwd", 3) == 0)
	{
		list(sockclient);				//列出接受到的内容--绝对路径
	}
	if (strncmp(rbuff, "cd", 2) == 0){} 	 //cd 

	closesocket(sockclient);			//关闭连接
	WSACleanup();						//释放Winsock    
	return main();
}

3.2 server.c(服务端代码实现)

#include "Winsock.h" 
#include "windows.h" 
#include "stdio.h"  
#define RECV_PORT 3312  
#define SEND_PORT 4302  
#pragma   comment(lib, "wsock32.lib")
SOCKET sockclient, sockserver;
 struct sockaddr_in ServerAddr;//服务器地址
 struct sockaddr_in ClientAddr;//客户端地址 

/***********************全局变量***********************/
int Addrlen;//地址长度 
char filename[20];//文件名 
char order[10];//命令  
char rbuff[1024];//接收缓冲区  
char sbuff[1024];//发送缓冲区  


DWORD StartSock()    //初始化winsock   
{
	WSADATA WSAData;
	if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0)
	{
		printf("socket init fail!\n");
		return (-1);
	}
	return(1);
}

DWORD CreateSocket()
{
	sockclient = socket(AF_INET, SOCK_STREAM, 0);
	if (sockclient == SOCKET_ERROR)
	{
		printf("sockclient create fail ! \n");
		WSACleanup();
		return(-1);
	}
	ServerAddr.sin_family = AF_INET;
	ServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	ServerAddr.sin_port = htons(RECV_PORT);
	if (bind(sockclient, (struct  sockaddr  FAR  *)&ServerAddr, sizeof(ServerAddr)) == SOCKET_ERROR)
	{                     //bind函数将套接字和地址结构绑定   
		printf("bind is the error");
		return(-1);
	}
	return (1);
}

int SendFileRecord(SOCKET datatcps, WIN32_FIND_DATA *pfd)     //用来发送当前文件记录 
{
	char filerecord[MAX_PATH + 32];
	FILETIME ft;         //文件建立时间   
	FileTimeToLocalFileTime(&pfd->ftLastWriteTime, &ft);
	SYSTEMTIME lastwtime;     //SYSTEMTIME系统时间数据结构   
	FileTimeToSystemTime(&ft, &lastwtime);
	char *dir = pfd->dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY ? "<DIR>" : " ";
	sprintf(filerecord, "%04d-%02d-%02d %02d:%02d  %5s %10d   %-20s\n",
		lastwtime.wYear,
		lastwtime.wMonth,
		lastwtime.wDay,
		lastwtime.wHour,
		lastwtime.wMinute,
		dir,
		pfd->nFileSizeLow,
		pfd->cFileName);
	if (send(datatcps, filerecord, strlen(filerecord), 0) == SOCKET_ERROR)
	{ //通过datatcps接口发送filerecord数据,成功返回发送的字节数   
		printf("Error occurs when sending file list!\n");
		return 0;
	}
	return 1;
}

int SendFileList(SOCKET datatcps)
{
	HANDLE hff;//建立一个线程  
	WIN32_FIND_DATA fd;   //搜索文件   
//可以通过FindFirstFile()函数根据当前的文件存放路径查找该文件来把待操作文件的相关属性读取到WIN32_FIND_DATA结构中去  
	if (hff == INVALID_HANDLE_VALUE)//发生错误  
	{
		const char *errstr = "can't list files!\n";
		printf("list file error!\n");
		if (send(datatcps, errstr, strlen(errstr), 0) == SOCKET_ERROR)
		{
			printf("error occurs when senging file list!\n");
		}
		closesocket(datatcps);
		return 0;
	}

	BOOL fMoreFiles = TRUE;
	while (fMoreFiles)
	{//发送此项文件信息   
		if (!SendFileRecord(datatcps, &fd))
		{
			closesocket(datatcps);
			return 0;
		}
		//搜索下一个文件    
		fMoreFiles = FindNextFile(hff, &fd);
	}
	closesocket(datatcps);
	return 1;
}

int SendFile(SOCKET datatcps, FILE* file)
{
	printf(" sending file data..");
	for (;;)   //从文件中循环读取数据并发送客户端   
	{
		int r = fread(sbuff, 1, 1024, file);//把file里面的内容读到sbuff缓冲区   
		if (send(datatcps, sbuff, r, 0) == SOCKET_ERROR)
		{
			printf("lost the connection to client!\n");
			closesocket(datatcps);
			return 0;
		}
		if (r<1024)//文件传送结束    
			break;
	}
	closesocket(datatcps);
	printf("done\n");
	return 1;
}

//连接  
DWORD ConnectProcess()
{
	Addrlen = sizeof(ClientAddr);
	if (listen(sockclient, 5)<0)
	{
		printf("Listen error");
		return(-1);
	}
	printf("服务器监听中...\n");
	for (;;)
	{
		sockserver = accept(sockclient, (struct sockaddr FAR *)&ClientAddr, &Addrlen);
		//accept函数取出连接队列的第一个连接请求,sockclient是处于监听的套接字ClientAddr 是监听的对象地址,         
		//Addrlen是对象地址的长度 
		for (;;)
		{
			memset(rbuff, 0, 1024);
			memset(sbuff, 0, 1024);
			if (recv(sockserver, rbuff, 1024, 0) <= 0)
			{
				break;
			}
			printf("\n");
			printf("获取并执行的命令为:");
			printf(rbuff);
			if (strncmp(rbuff, "get", 3) == 0)
			{
				strcpy(filename, rbuff + 4); printf(filename);
				FILE *file; //定义一个文件访问指针    
				//处理下载文件请求    
				file = fopen(filename, "rb");//打开下载的文件,只允许读写   
				if (file)
				{
					sprintf(sbuff, "get file %s\n", filename);
					if (!send(sockserver, sbuff, 1024, 0))
					{
						fclose(file);      return 0;
					}
					else
					{//创建额外数据连接传送数据     
						if (!SendFile(sockserver, file))
							return 0;
						fclose(file);
					}
				}//file   

				else//打开文件失败    
				{
					strcpy(sbuff, "can't open file!\n");
					if (send(sockserver, sbuff, 1024, 0))
						return 0;
				} //lost 
			}//get

			if (strncmp(rbuff, "put", 3) == 0)
			{
				FILE *fd;
				int count;
				strcpy(filename, rbuff + 4);
				fd = fopen(filename, "wb");
				if (fd == NULL)
				{
					printf("open file %s for weite failed!\n", filename);
					return 0;
				}
				sprintf(sbuff, "put file %s", filename);
				if (!send(sockserver, sbuff, 1024, 0))
				{
					fclose(fd);
					return 0;
				}
				while ((count = recv(sockserver, rbuff, 1024, 0))>0)//recv函数返回接受的字节数赋给count          
					fwrite(rbuff, sizeof(char), count, fd);
				//把count个数据长度为size0f()的数据从rbuff输入到fd指向的目标文件
				printf(" get %s succed!\n", filename);
				fclose(fd);
			}//put 

			if (strncmp(rbuff, "pwd", 3) == 0){
				char   path[1000];
				GetCurrentDirectory(1000, path);//找到当前进程的当前目录  
				strcpy(sbuff, path);
				send(sockserver, sbuff, 1024, 0);
			}//pwd   

			if (strncmp(rbuff, "dir", 3) == 0){
				strcpy(sbuff, rbuff);
				send(sockserver, sbuff, 1024, 0);
				SendFileList(sockserver);//发送当前列表 
			}//dir 

			if (strncmp(rbuff, "cd", 2) == 0)
			{
				strcpy(filename, rbuff + 3);
				strcpy(sbuff, rbuff);
				send(sockserver, sbuff, 1024, 0);
				SetCurrentDirectory(filename);//设置当前目录 
			}//cd  
			closesocket(sockserver);
		}//for 2
	}//for 1 
}


int main()
{
	if (StartSock() == -1)
		return(-1);
	if (CreateSocket() == -1)
		return(-1);
	if (ConnectProcess() == -1)
		return(-1);
	return(1);
}

4.简单的演示效果如下图:
先输入自己电脑的IP地址,然后回车就可以进入到主界面了

这里写图片描述

这里写图片描述

最后的最后,题主开通了公众号:有理唔理,喜欢的话可以关注一下,一起探讨技术与生活。

评论 41
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值