C语言实现FTP文件传输

C语言实现FTP文件传输,第1张


一、要求

FTP实则为两个程序的互相交流,把信息指令相互发送并处理。



1.客户端请求下载文件 —把文件名发送给服务器。



2.服务器接收客户端发送的文件名 —根据文件名找到文件,把文件大小发送给客户端。



3.客户端接收到文件大小—准备开始接受,开辟内存,准备完成要告诉服务器可以发送了。



4.服务器接受的开始发送的指令开始发送。



5.开始接收数据,存起来,接受完成,告诉服务器接收完成。



6.关闭连接。



二、FTPclient

1. ftpclient.h

#pragma once
#include 
#pragma comment(lib,"ws2_32.lib")  // 加载静态库
#include 

#define SPORT 8888                 // 服务器端口号
#define PACKET_SIZE (1024 - sizeof(int) * 3)



// 定义标记
enum MSGTAG
{
	MSG_FILENAME       = 1,        // 文件名称                服务器使用
	MSG_FILESIZE       = 2,        // 文件大小                客户端使用
	MSG_READY_READ     = 3,        // 准备接受                客户端使用
	MSG_SENDFILE       = 4,        // 发送                    服务器使用
	MSG_SUCCESSED      = 5,        // 传输完成                两者都使用
	MSG_OPENFILE_FAILD = 6         // 告诉客户端文件找不到    客户端使用
}; 

#pragma pack(1)                     // 设置结构体1字节对齐**************

struct MsgHeader                    // 封装消息头
{
	enum MSGTAG msgID;              // 当前消息标记   4
	union MyUnion
	{
		struct Mystruct
		{
			int fileSize;           // 文件大小  4
			char fileName[256];     // 文件名    256
		}fileInfo;
		struct
		{
			int nStart;             // 包的编号
			int nsize;              // 该包的数据大小
			char buf[PACKET_SIZE];
		}packet;
	};

};

#pragma pack()

// 初始化socket库
bool initSocket();

// 关闭socket库
bool closeSocket();

// 监听客户端连接
void connectToHost();

// 处理消息
bool processMag(SOCKET serfd);

// 获取文件名
void downloadFileName(SOCKET serfd);

// 文件内容读进内存
void readyread(SOCKET, struct MsgHeader*);

// 写入文件内容
bool writeFile(SOCKET, struct MsgHeader*);

2. ftpclient.c

#include 
#include        
#include "ftpclient.h"   

char g_fileName[256];     // 保存服务器发送过来的文件名
char *g_fileBuf;          // 接受存储文件内容
char g_recvBuf[1024];     // 接受消息缓冲区
int g_fileSize;           // 文件总大小

int main(void)
{
	initSocket();

	connectToHost();

	closeSocket();

	return 0;
}

// 初始化socket库
bool initSocket()
{
	WSADATA wsadata;

	if (0 != WSAStartup(MAKEWORD(2, 2), &wsadata))        // 启动协议,成功返回0
	{
		printf("WSAStartup faild: %d\n", WSAGetLastError());
		return false;
	}

	return true;
}

// 关闭socket库
bool closeSocket()
{
	if (0 != WSACleanup())
	{
		printf("WSACleanup faild: %d\n", WSAGetLastError());
		return false;
	}

	return true;
}

// 监听客户端连接
void connectToHost()
{
	// 创建server socket套接字 地址、端口号,AF_INET是IPV4
	SOCKET serfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	if (INVALID_SOCKET == serfd)
	{
		printf("socket faild:%d", WSAGetLastError());
		return;
	}

	// 给socket绑定IP地址和端口号
	struct sockaddr_in serAddr;
	serAddr.sin_family = AF_INET;
	serAddr.sin_port = htons(SPORT);                       // htons把本地字节序转为网络字节序
	serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 服务器的IP地址
	
	// 连接到服务器
	if (0 != connect(serfd, (struct sockaddr*)&serAddr, sizeof(serAddr)))
	{
		printf("connect faild:%d", WSAGetLastError());
		return;
	}

	printf("连接成功!\n");

	downloadFileName(serfd);                              // 输入文件名

	// 开始处理消息,100为发送消息间隔
	while (processMag(serfd))
	{
		//Sleep(100);
	}
}

// 处理消息
bool processMag(SOCKET serfd)
{

	recv(serfd, g_recvBuf, 1024, 0);                     // 收到消息   
	struct MsgHeader* msg = (struct MsgHeader*)g_recvBuf;

	/*
	*MSG_FILENAME       = 1,       // 文件名称                服务器使用
	*MSG_FILESIZE       = 2,       // 文件大小                客户端使用
	*MSG_READY_READ     = 3,       // 准备接受                客户端使用
	*MSG_SENDFILE       = 4,       // 发送                    服务器使用
	*MSG_SUCCESSED      = 5,       // 传输完成                两者都使用
	*MSG_OPENFILE_FAILD = 6        // 告诉客户端文件找不到    客户端使用
    */

	switch (msg->msgID)
	{
	case MSG_OPENFILE_FAILD:         // 6
		downloadFileName(serfd);
		break;
	case MSG_FILESIZE:               // 2  第一次接收
		readyread(serfd,msg);
		break;
	case MSG_READY_READ:             // 3
		writeFile(serfd, msg);
		break;
	case MSG_SUCCESSED:              // 5
		printf("传输完成!\n");
		closeSocket(serfd);
		return false;
		break;
	}
	return true;
}

void downloadFileName(SOCKET serfd)
{
	char fileName[1024];
	struct MsgHeader file;

	printf("输入下载的文件名:");

	gets_s(fileName, 1023);                                  // 输入文件路径               
	file.msgID = MSG_FILENAME;                               // MSG_FILENAME = 1
	strcpy(file.fileInfo.fileName, fileName);

	send(serfd, (char*)&file, sizeof(struct MsgHeader), 0);  // 发送、IP地址、内容、长度    第一次发送给服务器
}

void readyread(SOCKET serfd, struct MsgHeader* pmsg)
{
	// 准备内存 pmsg->fileInfo.fileSize
	g_fileSize = pmsg->fileInfo.fileSize;
	strcpy(g_fileName, pmsg->fileInfo.fileName);
	
	g_fileBuf = calloc(g_fileSize + 1, sizeof(char));         // 申请空间

	if (g_fileBuf == NULL)
	{
		printf("申请内存失败\n");
	}
	else
	{
		struct MsgHeader msg;  // MSG_SENDFILE = 4
		msg.msgID = MSG_SENDFILE;

		if (SOCKET_ERROR == send(serfd, (struct MsgHeader*)&msg, sizeof(struct MsgHeader), 0))   // 第二次发送
		{
			printf("客户端 send error: %d\n", WSAGetLastError());
			return;
		}
	}

	printf("size:%d  filename:%s\n", pmsg->fileInfo.fileSize, pmsg->fileInfo.fileName);
}

bool writeFile(SOCKET serfd, struct MsgHeader* pmsg)
{
	if (g_fileBuf == NULL)
	{
		return false;
	}

	int nStart = pmsg->packet.nStart;
	int nsize = pmsg->packet.nsize;

	memcpy(g_fileBuf + nStart, pmsg->packet.buf, nsize);    // strncmpy一样
	printf("packet size:%d %d\n", nStart + nsize, g_fileSize);

	if (nStart + nsize >= g_fileSize)                       // 判断数据是否发完数据
	{
		FILE* pwrite;
		struct MsgHeader msg;

		pwrite = fopen(g_fileName, "wb");
		msg.msgID = MSG_SUCCESSED;

		if (pwrite == NULL)
		{
			printf("write file error...\n");
			return false;
		}

		fwrite(g_fileBuf, sizeof(char), g_fileSize, pwrite);
		fclose(pwrite);

		free(g_fileBuf);
		g_fileBuf = NULL;

		send(serfd, (char*)&msg, sizeof(struct MsgHeader), 0);
		
		return false;
	}

	return true;
}


三、FTPserver

1. ftpserver.h

#pragma once
#include 
#include 
#pragma comment(lib,"ws2_32.lib")  // 加载静态库

#define SPORT 8888                 // 服务器端口号
#define PACKET_SIZE (1024 - sizeof(int) * 3)

// 定义标记
enum MSGTAG
{
	MSG_FILENAME       = 1,         // 文件名称              服务器使用
	MSG_FILESIZE       = 2,         // 文件大小              客户端使用
	MSG_READY_READ     = 3,         // 准备接受              客户端使用
	MSG_SENDFILE       = 4,         // 发送                  服务器使用
	MSG_SUCCESSED      = 5,         // 传输完成              两者都使用
	MSG_OPENFILE_FAILD = 6          // 告诉客户端文件找不到  客户端使用
};

#pragma pack(1)                     // 设置结构体1字节对齐

struct MsgHeader                    // 封装消息头
{
	enum MSGTAG msgID;              // 当前消息标记   4
	union MyUnion
	{
		struct Mystruct 
		{
		    int fileSize;           // 文件大小  4
	        char fileName[256];     // 文件名    256
		}fileInfo;
		struct 
		{
			int nStart;             // 包的编号
			int nsize;              // 该包的数据大小
			char buf[PACKET_SIZE];
		}packet;
	};
	
};

#pragma pack()

// 初始化socket库
bool initSocket();

// 关闭socket库
bool closeSocket();

// 监听客户端连接
void listenToClient();

// 处理消息
bool processMag(SOCKET clifd);

// 读取文件,获得文件大小
bool readFile(SOCKET, struct MsgHeader*);

// 发送文件
bool sendFile(SOCKET, struct MsgHeader*);

2. ftpserver.c

#include 
#include "ftpserver.h"

char g_recvBuf[1024] = { 0 };      // 用来接收客户端消息
int g_fileSize;                    // 文件大小
char* g_fileBuf;                   // 储存文件

int main(void)
{
	initSocket();

	listenToClient();

	closeSocket();

	return 0;
}

// 初始化socket库
bool initSocket()
{
	WSADATA wsadata;

	if (0 != WSAStartup(MAKEWORD(2, 2), &wsadata))  // 启动协议,成功返回0
	{
		printf("WSAStartup faild: %d\n", WSAGetLastError());
		return false;
	}

	return true;
}

// 关闭socket库
bool closeSocket()
{
	if (0 != WSACleanup())
	{
		printf("WSACleanup faild: %d\n", WSAGetLastError());
		return false;
	}

	return true;
}

// 监听客户端连接
void listenToClient()
{
	// 创建server socket套接字 地址、端口号,AF_INET是IPV4
	SOCKET serfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	if (INVALID_SOCKET == serfd)
	{
		printf("socket faild:%d", WSAGetLastError());
		return;
	}

	// 给socket绑定IP地址和端口号
	struct sockaddr_in serAddr;

	serAddr.sin_family = AF_INET;
	serAddr.sin_port = htons(SPORT);             // htons把本地字节序转为网络字节序
	serAddr.sin_addr.S_un.S_addr = ADDR_ANY;     // 监听本机所有网卡

	if (0 != bind(serfd, (struct sockaddr*)&serAddr, sizeof(serAddr)))
	{
		printf("bind faild:%d", WSAGetLastError());
		return;
	}

	// 监听客户端连接
	if (0 != listen(serfd, 10))                  // 10为队列最大数
	{
		printf("listen faild:%d", WSAGetLastError());
		return;
	}

	// 有客户端连接,接受连接
	struct sockaddr_in cliAddr;
	int len = sizeof(cliAddr);

	SOCKET clifd = accept(serfd, (struct sockaddr*)&cliAddr, &len);

	if (INVALID_SOCKET == clifd)
	{
		printf("accept faild:%d", WSAGetLastError());
		return;
	}

	printf("接受成功!\n");

	// 开始处理消息
	while (processMag(clifd))
	{
		Sleep(100);
	}

}

// 处理消息
bool processMag(SOCKET clifd)
{
	// 成功接收消息返回收到的字节数,否则返回0
	int nRes = recv(clifd, g_recvBuf, 1024, 0);         // 接收

	if (nRes <= 0)
	{
		printf("客户端下线...%d", WSAGetLastError());
	}

	// 获取接受的的消息
	struct MsgHeader* msg = (struct MsgHeader*)g_recvBuf;
	struct MsgHeader exitmsg;    

	/*
    *MSG_FILENAME       = 1,    // 文件名称                服务器使用
    *MSG_FILESIZE       = 2,    // 文件大小                客户端使用
    *MSG_READY_READ     = 3,    // 准备接受                客户端使用
    *MSG_SENDFILE       = 4,    // 发送                    服务器使用
    *MSG_SUCCESSED      = 5,    // 传输完成                两者都使用
    *MSG_OPENFILE_FAILD = 6     // 告诉客户端文件找不到    客户端使用
    */

	switch (msg->msgID) 
	{
	case MSG_FILENAME:          // 1  第一次接收
		printf("%s\n", msg->fileInfo.fileName);
		readFile(clifd,msg);
		break;
	case MSG_SENDFILE:          // 4
		sendFile(clifd,msg);
		break;
	case MSG_SUCCESSED:         // 5

		exitmsg.msgID = MSG_SUCCESSED; 

		if (SOCKET_ERROR == send(clifd, (char*)&exitmsg, sizeof(struct MsgHeader), 0))   //失败发送给客户端
		{
			printf("send faild: %d\n", WSAGetLastError());
		}

		printf("完成!\n");
		closeSocket(clifd);

		return false;
		break;
	}

	printf("%s\n", g_recvBuf);

	return true;
}

/*
*1.客户端请求下载文件 —把文件名发送给服务器
*2.服务器接收客户端发送的文件名 —根据文件名找到文件,把文件大小发送给客户端
*3.客户端接收到文件大小—准备开始接受,开辟内存  准备完成要告诉服务器可以发送了
*4.服务器接受的开始发送的指令开始发送
*5.开始接收数据,存起来     接受完成,告诉服务器接收完成
*6.关闭连接
*/

bool readFile(SOCKET clifd, struct MsgHeader* pmsg)
{
	FILE* pread = fopen(pmsg->fileInfo.fileName, "rb");

	if (pread == NULL)
	{
		printf("找不到[%s]文件...\n", pmsg->fileInfo.fileName);

		struct MsgHeader msg;
		msg.msgID = MSG_OPENFILE_FAILD;                                             // MSG_OPENFILE_FAILD = 6

		if (SOCKET_ERROR == send(clifd, (char*)&msg, sizeof(struct MsgHeader), 0))   // 失败发送给客户端
		{
			printf("send faild: %d\n", WSAGetLastError());
		}

		return false;
	}

	// 获取文件大小
	fseek(pread, 0, SEEK_END);
	g_fileSize = ftell(pread);
	fseek(pread, 0, SEEK_SET);

	// 把文件大小发给客户端
	char text[100];
	char tfname[200] = { 0 };
	struct MsgHeader msg;

	msg.msgID = MSG_FILESIZE;                                       // MSG_FILESIZE = 2
	msg.fileInfo.fileSize = g_fileSize;

	_splitpath(pmsg->fileInfo.fileName, NULL, NULL, tfname, text);  //******************************************

	strcat(tfname, text);
	strcpy(msg.fileInfo.fileName, tfname);

	send(clifd, (char*)&msg, sizeof(struct MsgHeader),0);            // 文件名和后缀、文件大小发回客户端  第一次发送给客户端

	//读写文件内容
	g_fileBuf = calloc(g_fileSize + 1, sizeof(char));

	if (g_fileBuf == NULL)
	{
		printf("内存不足,重试\n");
		return false;
	}

	fread(g_fileBuf, sizeof(char), g_fileSize, pread); 
	g_fileBuf[g_fileSize] = '\0';

	fclose(pread);
	return true;
}

bool sendFile(SOCKET clifd, struct MsgHeader* pms)
{
    struct MsgHeader msg;                                                     // 告诉客户端准备接收文件
	msg.msgID = MSG_READY_READ;

	// 如果文件的长度大于每个数据包能传送的大小(1012),那么久分块
	for (size_t i = 0; i < g_fileSize; i += PACKET_SIZE)                       // PACKET_SIZE = 1012
	{
		msg.packet.nStart = i;

		// 包的大小大于总数据的大小
		if (i + PACKET_SIZE + 1 > g_fileSize)
		{
			msg.packet.nsize = g_fileSize - i;
		}
		else
		{
			msg.packet.nsize = PACKET_SIZE;
		}

		memcpy(msg.packet.buf, g_fileBuf+msg.packet.nStart, msg.packet.nsize);

		if (SOCKET_ERROR == send(clifd, (char*)&msg, sizeof(struct MsgHeader), 0))  // 告诉客户端可以发送
		{
			printf("文件发送失败:%d\n", WSAGetLastError());
		}
	}

	return true;
}


四、运行、运行结果

1.运行可以通过exe文件运行,或运行FTPserver后,运行FTPclient端是启动新实例。


2.运行结束后FTPclient会自行关闭。


3.文件传输到exe所在的文件夹中。


 

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/578175.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-11
下一篇 2022-04-11

发表评论

登录后才能评论

评论列表(0条)

保存