这个实现了客户端和服务端文件的相互传输(只在本机上运行过),如果是要两台计算机相互传数据要改ip
给大家看一下实现过程(.exe文件要先开服务端的)
输入1
直接将快捷方式拖拽上去就有绝对路径了,然后回车
用的是分组传输
然后服务端就接受到传输的文件了 ,从服务端取文件的过程是一样的。
上代码
客户端的头文件
ftpclient.h
#pragma once
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib") // 加载静态库
#include <stdbool.h>
#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, // 告诉客户端文件找不到 客户端使用
MSG_CLIENTREADSENT = 7, //客户端发送路径和文件大小
MSG_SERVERREAD = 8, //服务端申请空间
MSG_CLIENTSENT = 9 //客户端传输
};
#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*);
//服务端发送文件路径和大小 然后在自己的缓冲区将文件缓存下来
void clientReadySend(SOCKET);
//准备开始发送文件
bool sendFile(SOCKET, struct MsgHeader*);
客户端.c文件
ftpclient.c
#include <stdio.h>
#include <stdlib.h>
#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");
while (1)
{
int flag;
printf("请你选择是要接受文件还是选择结束\n");
printf("***************************************\n");
printf("1.传输文件给服务端\n");
printf("2.从服务端取文件\n");
printf("3.退出程序\n");
printf("***************************************\n");
do {
scanf_s("%d", &flag);
} while (!(flag == 1 || flag == 2 || flag == 3));
system("cls");
if (flag == 1)
{
printf("现在开始向服务端传输文件");
clientReadySend(serfd);
while(processMag(serfd))
{}
}
else if(flag == 2)
{
printf("现在客户端开始接收文件\n");
downloadFileName(serfd);// 开始处理消息,100为发送消息间隔
while (processMag(serfd))
{}
}
else
{
printf("系统要退出了...\n");
closesocket(serfd);
return false;
}
printf("\nPress Any Key To Continue:");
_getch();
system("cls");
}
}
// 处理消息
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");
return false;
break;
case MSG_SERVERREAD:
printf("准备传输完成");
sendFile(serfd, msg);
break;
}
return true;
}
void downloadFileName(SOCKET serfd)
{
char fileName[1024];
struct MsgHeader file;
printf("输入下载的文件名:");
scanf("%s", fileName); // 输入文件路径
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, (char*)&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;
}
void clientReadySend(SOCKET serfd)
{
struct MsgHeader msg;
msg.msgID = MSG_CLIENTREADSENT;
char fileName[1024] = { 0 };
printf("请输入要上传的文件名:");
scanf("%s", fileName);
FILE* pread = fopen(fileName, "rb");
fseek(pread, 0, SEEK_END);
g_fileSize = ftell(pread);
fseek(pread, 0, SEEK_SET);
strcpy(msg.fileInfo.fileName, fileName);
msg.fileInfo.fileSize = g_fileSize;
send(serfd, (char*)&msg, sizeof(struct MsgHeader), 0);
g_fileBuf = calloc(g_fileSize + 1, sizeof(char));
if (g_fileBuf == NULL)
{
printf("内存不足,重试\n");
}
fread(g_fileBuf, sizeof(char), g_fileSize, pread);
g_fileBuf[g_fileSize] = '\0';
fclose(pread);
}
bool sendFile(SOCKET serfd, struct MsgHeader* pms)
{
struct MsgHeader msg; // 告诉客户端准备接收文件
msg.msgID = MSG_CLIENTSENT;
// 如果文件的长度大于每个数据包能传送的大小(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(serfd, (char*)&msg, sizeof(struct MsgHeader), 0)) // 告诉客户端可以发送
{
printf("文件发送失败:%d\n", WSAGetLastError());
}
}
return true;
}
服务端头文件
ftpserver.h
#pragma once
#include <stdbool.h>
#include <WinSock2.h>
#include <string.h>
#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, // 告诉客户端文件找不到 客户端使用
MSG_CLIENTREADSENT = 7, //客户端发送路径和文件大小
MSG_SERVERREAD = 8, //服务端申请空间
MSG_CLIENTSENT = 9 //客户端传输
};
#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*);
//接收文件名和文件大小,创建空间
void serverReady(SOCKET, struct MsgHeader*);
//开始写文件
bool writeFile(SOCKET , struct MsgHeader* );
服务端.c文件
ftpserver.c
#include <stdio.h>
#include "ftpserver.h"
char g_recvBuf[1024] = { 0 }; // 用来接收客户端消息
int g_fileSize; // 文件大小
char* g_fileBuf; // 储存文件
char g_fileName[256];
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(200);
}
}
// 处理消息
bool processMag(SOCKET clifd)
{
// 成功接收消息返回收到的字节数,否则返回0
int nRes = recv(clifd, g_recvBuf, 1024, 0); // 接收
if (nRes <= 0)
{
printf("客户端下线...%d", WSAGetLastError());
return false;
}
// 获取接受的的消息
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());
return false;
}
printf("完成!\n");
break;
case MSG_CLIENTREADSENT: //7
serverReady(clifd, msg);
break;
case MSG_CLIENTSENT:
writeFile(clifd, msg);
break;
}
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;
}
void serverReady(SOCKET clifd, struct MsgHeader* pmsg)
{
g_fileSize = pmsg->fileInfo.fileSize;
char text[100];
char tfname[200] = { 0 };
_splitpath(pmsg->fileInfo.fileName, NULL, NULL, tfname, text); //只需要最后的名字加后缀
strcat(tfname, text);
strcpy(g_fileName, tfname);
g_fileBuf = calloc(g_fileSize + 1, sizeof(char)); // 申请空间
if (g_fileBuf == NULL)
{
printf("申请内存失败\n");
}
else
{
struct MsgHeader msg;
msg.msgID = MSG_SERVERREAD;
if (SOCKET_ERROR == send(clifd, (struct MsgHeader*)&msg, sizeof(struct MsgHeader), 0)) // 第二次发送
{
printf("客户端 send error: %d\n", WSAGetLastError());
return;
}
}
printf("filename:%s size:%d \n", pmsg->fileInfo.fileName, pmsg->fileInfo.fileSize);
}
bool writeFile(SOCKET clifd, 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(clifd, (char*)&msg, sizeof(struct MsgHeader), 0);
return false;
}
return true;
}
代码写的很粗糙,很多地方都没有注释大家看着玩就好了。
下面是我的一些对于实现c语言socket编程的一些函数总结:
服务器先启动
Socket是初始化socket库
先对TCP服务端建立一个服务端的socket
socket(int domain,int type,int protocol)函数 只是声明这个socket是什么类型的
1.domain:协议域,常见的协议组用AF_INET AF_INET6 AF_LOCAL AF_ROUTE . 协议族决定了socket的地址类型,在通信中必须采用相应的地址.
2.type: 指定socket的类型.
- 流格式套接字(SOCK_STREAM):也称之为面向连接的套接字
- 数据报格式套接字(SOCK_DGRAM):也称之为无连接的套接字
protocol:协议,常见的协议有IPPROTO_TCP、IPPTOTO_UDP、 IPPROTO_SCTP、IPPROTO_TIPC他们分别对应这TCP传输协议,UDP传输协议,STCP传输协议,TIPC传输协议.当protocol为0时,会自动选择type类型对应的默认协议.
bind(int sockfd,const struct* addr,socklen_t addrlen) 给socket绑定IP地址和端口号
1.sockfd:即为socket描述字,他是通过socket()函数创建的,唯一标识一个socket.bind函数就是将这个描述子绑定一个名字
2.addr:一个const struct sockaddr*指针,根据创建地址结构根据地址创建socket时的地址协议不同而不同.
struct sockaddr_in serAddr; 先在serAddr里面进行绑定协议族、端口、IP
3.addrlen:对应地址的长度.通常服务器在启动时会绑定一个总所周知的地址(ip地址+端口号),客户端不用指定系统自动分配,所以通常服务端在listen之前要调用bind(),而客户端不会调用,在connect()时由系统随机生成一个.
大小一般为sizeof(serAddr)
将地址绑定到一个socket时,需要将主机字节序转换为网络字节序。
主机字节学是指处理器存储数据的字节顺序 Inter x86是不重要保存在低地址,重要部分保存在高地址
网络字节序是重要部分保存在低地址,不重要部分保存在高地址
listen(int sockfd,int backlog)
listen函数的第一个参数时即将要监听的socket描述字,第二个参数为相应的socket可以排队的最大连接数。socket()创建的socket默认是一个主动类型,listen则将socket变成被动类型,等待客户连接请求。
connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen)
connect函数的第一个参数是客户端socket的描述,第二个参数是socket的地址,第三个函数是socket的地址的长度。客户端通过调用connect函数建立与TCP服务器的连接。
accept(int sockfd,struct sockaddr *addr , socklen_t addrlen)
accept函数的第一个函数为服务器端的socket描述字,是服务器一开始调动socket()函数产生的,而accept函数返回的是已经连接的socket描述字。第二个参数用于返回客户端的协议地址,第三个表示地址的长度
编译软件用的是visual studio2022
在敲代码中遇到的一些问题:
1.有些地方会警告报错,要在视图中找到属性页,然后将C/C++常规中的SDL改为否
2.gets_s函数会造成缓冲区的一些泄露(这个地方我也不是很懂)在地址读取的时候我换的是scanf_s函数
这个程序用了比较多的技巧,要对c语言有一定的理解
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)