一、要求
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所在的文件夹中。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)