C语言FTP文件传输(完成基本文件传输的功能)

文章目录

  • 前言
  • 一、实现思路
  • 二、实现FTP服务器
  • 三、实现FTP客户端
  • 四、实现体验
  • 总结


前言

本篇文章带大家来完成一下C语言FTP文件传输助手最基础的功能,也就是客户端和服务器之间进行最基础的文件传输的功能。

一、实现思路

实现一个基本的 FTP 客户端和服务器,可以按照以下思路进行:

1.客户端首先请求下载文件,并将文件名发送到服务器。

2.服务器收到文件名后,找到对应的文件,并将文件大小发送回客户端。

3.客户端接收到文件大小后,准备接收数据(如分配内存),并通知服务器可以开始发送数据。

4.服务器收到开始接收数据的指令后,开始发送文件数据。

5.客户端接收数据并保存,完成后通知服务器数据接收完毕。

6.最后,双方关闭连接,结束文件传输。

二、实现FTP服务器

创建FTPServer.c和FTPServer.h来管理服务器代码:

FTPServer.c:

#include "FTPServer.h"
#include <stdio.h>// 定义全局变量和库
char g_recvbuf[1024] = { 0 };  // 用于接收来自客户端的数据缓冲区
int g_filesize = 0;  // 存储文件的大小
#pragma comment(lib, "Ws2_32.lib")  // 链接 Winsock 库SOCKET sockfd;  // 套接字描述符char* g_filebuf; // 用于存储文件内容的内存空间// 初始化 Winsock 库
bool initSocket(void)
{int iResult;WSADATA wsaData;// 调用 WSAStartup 函数以初始化 Winsock 库iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);if (iResult != 0) {printf("WSAStartup failed: %d\n", iResult);return false;}return true;
}// 关闭套接字和清理 Winsock
bool closeSocket(void)
{closesocket(sockfd);  // 关闭套接字WSACleanup();  // 清理 Winsock
}// 监听客户端的请求
void listenToClient(void)
{// 1. 创建套接字sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (INVALID_SOCKET == sockfd){printf("socket failed: %ld\n", WSAGetLastError());WSACleanup();return;}// 2. 绑定 IP 地址和端口号struct sockaddr_in seraddr;seraddr.sin_family = AF_INET;  // 使用 IPv4seraddr.sin_addr.S_un.S_addr = ADDR_ANY;  // 监听所有网络接口seraddr.sin_port = htons(SERPORT);  // 端口号,SERPORT 需要在代码中定义if (0 != bind(sockfd, (struct sockaddr*)&seraddr, sizeof(seraddr))){printf("bind failed: %ld\n", WSAGetLastError());WSACleanup();return;}// 3. 监听端口if (0 != listen(sockfd, LISTEN_NUM))  // LISTEN_NUM 是最大挂起连接数{printf("listen failed: %ld\n", WSAGetLastError());WSACleanup();return;}// 4. 等待连接struct sockaddr_in clientaddr;int len = sizeof(clientaddr);SOCKET clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);if (INVALID_SOCKET == clientfd){printf("accept failed: %ld\n", WSAGetLastError());WSACleanup();return;}printf("connect is ok\n");// 处理客户端消息while (processMsg(clientfd)){Sleep(100);  // 休眠 100 毫秒}
}// 读取文件内容
bool readFile(SOCKET clientfd, MSGHEADER* pmsg)
{// 打开指定的文件FILE* fp = fopen(pmsg->fileinfo.fileName, "rb");if (NULL == fp){printf("open %s is err\n", pmsg->fileinfo.fileName);// 文件打开失败,将错误消息发送给客户端MSGHEADER msg;msg.msgID = MSG_OPENFILE_FALID;  // 错误消息标识符if (SOCKET_ERROR == send(clientfd, (const char*)&msg, sizeof(msg), 0)){printf("send is err\n");}return false;}// 计算文件大小fseek(fp, 0, SEEK_END);g_filesize = ftell(fp);fseek(fp, 0, SEEK_SET);// 发送文件大小和文件名MSGHEADER msg;msg.msgID = MSG_FILESIZE;  // 文件大小消息标识符msg.fileinfo.filesize = g_filesize;// 处理文件名,确保没有路径,只保留文件名char tfname[200] = { 0 }, text[100];_splitpath(pmsg->fileinfo.fileName, NULL, NULL, tfname, text);strcat(tfname, text);strcpy(msg.fileinfo.fileName, tfname);if (SOCKET_ERROR == send(clientfd, (const char*)&msg, sizeof(msg), 0)){printf("send is err\n");}// 分配内存并读取文件内容g_filebuf = calloc(g_filesize + 1, sizeof(char));  // +1 为了存储结束符if (NULL == g_filebuf){printf("申请内存失败\n");return false;}fread(g_filebuf, sizeof(char), g_filesize, fp);fclose(fp);return true;
}// 发送文件内容
void SendFile(SOCKET clientfd, MSGHEADER* pmsg)
{MSGHEADER msg;msg.msgID = MSG_READY_READ;  // 文件准备好消息标识符msg.packet.filesize = g_filesize;memcpy(msg.packet.payload, g_filebuf, g_filesize);  // 复制文件内容到消息中printf("server start send file\n");send(clientfd, (const char*)&msg, sizeof(msg), 0);printf("server send file end\n");return true;
}// 处理客户端消息
bool processMsg(SOCKET clientfd)
{int len = 0;// 接收来自客户端的消息memset(g_recvbuf, 0, sizeof(g_recvbuf));  // 清空接收缓冲区len = recv(clientfd, g_recvbuf, 1024, 0);if (len <= 0){printf("客户端下线: %ld\n", WSAGetLastError());return false;}// 将接收到的消息转换为 MSGHEADER 类型MSGHEADER* recvMSG = (MSGHEADER*)g_recvbuf;switch (recvMSG->msgID){case MSG_FILENAME:{readFile(clientfd, recvMSG);  // 读取文件}break;case MSG_SENDFILE:{printf("MSG_SENDFILE\n");SendFile(clientfd, recvMSG);  // 发送文件}break;case MSG_SUCCESSED:{printf("MSG_SUCCESSED\n");  // 文件接收成功}break;default:break;}return true;
}

FTPServer.h:

#pragma once#include <stdbool.h>
#include <winsock2.h>
#include <ws2tcpip.h>// 定义服务端口号
#define SERPORT		8080
// 定义最大监听队列长度
#define LISTEN_NUM	10// 初始化套接字
bool initSocket(void);// 关闭套接字
bool closeSocket(void);// 监听客户端连接
void listenToClient(void);// 处理客户端消息
bool processMsg(SOCKET clientfd);// 消息标记枚举
enum MSGTAG
{MSG_FILENAME = 1, // 文件名MSG_FILESIZE,     // 文件大小MSG_READY_READ,   // 准备接收MSG_SENDFILE,     // 发送MSG_SUCCESSED,    // 传输完成MSG_OPENFILE_FALID // 告诉客户端文件找不到
};#pragma pack(1) // 取消结构体的内存对齐// 消息头结构体
typedef struct MsgHeader
{enum MSGTAG msgID; // 当前消息标记union MyUnion{// 文件信息结构struct{int filesize; // 文件大小char fileName[256]; // 文件名}fileinfo;// 文件数据包结构struct{int filesize; // 文件大小char payload[1024 - sizeof(int) * 2]; // 文件内容}packet;};}MSGHEADER;#pragma pack() // 恢复默认对齐方式

main.c:

#include <stdio.h>
#include "FTPServer.h"int main(void)
{initSocket();listenToClient();closeSocket();return 0;
}

代码思路:

这段代码的实现思路可以分为几个主要部分:

  1. 初始化和关闭套接字:

    • initSocket(void):设置服务器套接字,通常包括创建套接字、绑定到指定端口、设置监听等步骤。
    • closeSocket(void):关闭套接字,释放资源,结束网络通信。
  2. 监听客户端连接:

    • listenToClient(void):使服务器开始监听传入的客户端连接请求,并将请求排入队列。
  3. 处理客户端消息:

    • processMsg(SOCKET clientfd):处理客户端发送的消息,包括接收数据、解析消息头和内容,并根据 msgID 执行相应的操作(例如接收文件名、文件大小,发送文件数据等)。
  4. 消息定义和数据结构:

    • 使用 enum MSGTAG 定义消息标识符,区分不同的消息类型。
    • MsgHeader 结构体封装了消息头和消息体,其中包括文件信息和数据包内容,利用 union 来处理不同消息类型的具体数据。
  5. 网络协议设计:

    • 通过 MsgHeader 结构体定义消息格式,确保客户端和服务器之间的数据传输具有一致的结构,避免因数据布局不同而产生的问题。
  6. 内存对齐:

    • 使用 #pragma pack(1) 确保结构体在内存中的布局与网络传输中的布局一致,防止因内存对齐产生的额外填充字节影响数据的解析。

三、实现FTP客户端

FTPClient.c:

#include "FTPClient.h" // 包含自定义的头文件
#include <stdio.h>
#include <string.h>
#include <malloc.h>#pragma comment(lib, "Ws2_32.lib") // 链接 Winsock 库SOCKET sockfd; // 套接字描述符char g_recvbuf[1024]; // 用于接收从服务器发来的消息的缓冲区int g_sizefile = 0; // 文件总大小
char* g_filebuf; // 用于存储文件内容的内存空间char g_filename[256]; // 文件名// 发送文件名给服务端
void downloadFileName(SOCKET serverfd)
{char filename[1024] = { 0 };scanf("%s", filename); // 获取用户输入的文件名MSGHEADER file;file.msgID = MSG_FILENAME;strcpy(file.fileinfo.fileName, filename); // 将文件名拷贝到结构体中// 将文件名发送给服务器send(serverfd, (const char*)&file, sizeof(file), 0);
}// 准备接收来自服务器的文件
void readyread(SOCKET serverfd, MSGHEADER* pmsg)
{// 分配内存空间以存储文件g_sizefile = pmsg->fileinfo.filesize;g_filebuf = calloc(g_sizefile + 1, sizeof(char));if (g_filebuf == NULL){printf("申请内存空间失败\n");}else{MSGHEADER msg;msg.msgID = MSG_SENDFILE;send(serverfd, (const char*)&msg, sizeof(msg), 0); // 通知服务器可以发送文件}strcpy(g_filename, pmsg->fileinfo.fileName); // 保存文件名printf("pmsg->name :%s pmsg->size : %d\n", pmsg->fileinfo.fileName, pmsg->fileinfo.filesize);
}// 将文件内容写入新文件中
void writefile(SOCKET serverfd, MSGHEADER* pmsg)
{int filesize = pmsg->packet.filesize; // 获取文件大小printf("filesize : %d g_filename : %s\n", filesize, g_filename);// 打开文件以进行写入FILE* pf = fopen(g_filename, "wb");if (NULL == pf){printf("打开文件失败\n");return;}// 写入文件内容fwrite(pmsg->packet.payload, sizeof(char), filesize, pf);fclose(pf); // 关闭文件// 提示服务器文件接收成功MSGHEADER msg;msg.msgID = MSG_SUCCESSED;send(serverfd, (const char*)&msg, sizeof(msg), 0);
}bool initSocket(void)
{// 初始化 Winsockint iResult;WSADATA wsaData;iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);if (iResult != 0){printf("WSAStartup failed: %d\n", iResult);return false;}return true;
}bool closeSocket(void)
{closesocket(sockfd); // 关闭套接字WSACleanup(); // 清理 Winsock
}void ConnectToHost(void)
{// 创建套接字sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (INVALID_SOCKET == sockfd){printf("socket failed: %ld\n", WSAGetLastError());WSACleanup();return;}// 绑定 IP 和端口号并连接服务器struct sockaddr_in seraddr;seraddr.sin_family = AF_INET;inet_pton(AF_INET, "192.168.199.1", &seraddr.sin_addr); // 服务器 IP 地址seraddr.sin_port = htons(SERPORT); // 服务器端口号if (0 != connect(sockfd, (struct sockaddr*)&seraddr, sizeof(seraddr))){printf("connect failed: %ld\n", WSAGetLastError());WSACleanup();return;}// 发送文件名给服务端downloadFileName(sockfd);// 处理从服务器接收到的消息while (processMsg(sockfd)){Sleep(100); // 每 100 毫秒检查一次}
}bool processMsg(SOCKET serverfd)
{// 接收来自服务端的消息recv(serverfd, g_recvbuf, 1024, 0);MSGHEADER* pmsg = (MSGHEADER*)&g_recvbuf;// 处理接收到的消息switch (pmsg->msgID){case MSG_OPENFILE_FALID:{// 文件未找到,重新发送文件名downloadFileName(serverfd);}break;case MSG_FILESIZE:{readyread(serverfd, pmsg); // 准备接收文件}break;case MSG_READY_READ:{printf("MSG_READY_READ\n");writefile(serverfd, pmsg); // 将文件写入本地}break;default:break;}return true;
}

FTPClient.h:

#pragma once#include <stdbool.h>   // 为布尔类型定义
#include <winsock2.h>  // Windows套接字库头文件
#include <ws2tcpip.h> // 提供IP协议族的套接字操作函数// 定义服务器端口号
#define SERPORT        8080// 定义监听队列的最大连接数
#define LISTEN_NUM     10// 初始化套接字函数的声明
bool initSocket(void);// 关闭套接字函数的声明
bool closeSocket(void);// 连接到主机的函数声明
void ConnectToHost(void);// 处理客户端消息的函数声明
bool processMsg(SOCKET clientfd);// 消息类型标记的枚举定义
enum MSGTAG
{MSG_FILENAME = 1,     // 文件名MSG_FILESIZE,         // 文件大小MSG_READY_READ,       // 准备接收MSG_SENDFILE,         // 发送文件MSG_SUCCESSED,        // 传输完成MSG_OPENFILE_FALID    // 文件打开失败
};// 消息头结构体定义
typedef struct MsgHeader
{enum MSGTAG msgID; // 当前消息标记,用于标识消息的类型// 联合体,用于存储不同类型的消息内容union MyUnion{// 文件信息结构体struct{int filesize;            // 文件大小char fileName[256];     // 文件名} fileinfo;// 文件传输数据包结构体struct{int filesize;           // 文件大小char payload[1024 - sizeof(int) * 2]; // 文件内容(1024字节中减去两个int类型的空间)} packet;};} MSGHEADER;

main.c:

#include <stdio.h>
#include "FTPClient.h"int main(void)
{initSocket();ConnectToHost();closeSocket();return 0;
}

实现思路:

1.初始化Socket:通过initSocket函数初始化Winsock库。
2.建立连接:ConnectToHost函数创建一个TCP套接字,连接到指定的服务器IP和端口。
3.发送文件名:downloadFileName函数获取用户输入的文件名并发送给服务器。
4.处理消息:processMsg函数接收来自服务器的消息,并根据消息类型执行不同的操作。
5.准备接收文件:readyread函数分配内存以存储文件,并发送准备接收文件的确认消息。
6.写入文件:writefile函数将接收到的文件数据写入本地文件中,并通知服务器文件接收成功。
7.关闭Socket:closeSocket函数关闭套接字并清理Winsock库。

四、实现体验

能够进行正常的文件传输,但是我们这个FTP文件传输助手还是有一些缺陷的,他无法传输比较大的文件,那么在下篇文章我们来优化一下这个问题吧。
在这里插入图片描述

总结

本篇文章主要实现了基本的FTP文件传输功能,下篇文章我们继续优化代码,实现一些其他新的功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/400592.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【生成式人工智能-十一一个不修改模型就能加速语言模型生成的方法】

一个加速语言模型生成的方法 现在语言模型的一个弊端speculative decoding预言家预测的问题 speculative decoding 模块的实现方法NAT Non-autoregressive模型压缩使用搜索引擎 一些更复杂些的speculative decoding 实现方式 speculative decoding 是一个适用于目前生成模型的加…

WSL 忘记ubuntu的密码

文章目录 1. 以管理员身份打开 PowerShel2.输入命令 wsl.exe -d Ubuntu-20.04 --user root3.输入命令 passwd username 修改用户密码&#xff0c;username即待重置的用户的名称 1. 以管理员身份打开 PowerShel 2.输入命令 wsl.exe -d Ubuntu-20.04 --user root 注意版本号是自…

Springboot整合Flowable入门-学习笔记

目录 1、定义流程&#xff08;画图&#xff09; 2、Springboot部署流程 3、Springboot删除所有流程 4、Springboot根据 流程部署ID 查询 流程定义ID 5、Springboot启动(发起)流程 6、Springboot查询任务 6.1全部任务 6.2我的任务&#xff08;代办任务&#xff09; 7、…

JVM知识总结(性能调优)

文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 性能调优 何时进行JVM调优&#xff1f; 遇到以下情况&#xff0c…

傻瓜式一步到位Mysql 8.0 密码修改

5.7之前修改密码语句 update user set authentication_string password(“root”) where user “root”; mysql 5.7.9以后废弃了password字段和password()函数&#xff1b;并在user表加了authentication_string:字段表示用户密码 #进入到mysql 安装目录下 #停止 mysql 服务 …

怎么调试python脚本

打开pycharm community 2019.1软件&#xff0c;创建一个项目。 创建一个py后缀的文件作为示范&#xff0c;文件名自己定义。 编写代码&#xff0c;然后右键点击进行运行&#xff0c;查看一下是否有问题。 点击右上角的虫子图标&#xff0c;然后下面会有控制面板出来&#xff0c…

基于C11的简单log,支持C++的‘<<’风格和C的‘可变参数’风格

基于C11的简单log&#xff0c;支持C的‘<<’风格和C的‘可变参数’风格 日志仅由richlog.h单个文件实现功能&#xff0c;软件集成简单。 支持C的std::cout的<<风格的日志打印&#xff0c;也支持C的printf风格的日志打印 日志多线程安全&#xff0c;采用C11 mute…

SpringBoot整合日志功能(slf4j+logback)详解

目录 一、日志门面与日志实现 1.1 什么是日志门面和日志实现&#xff1f; 1.2 为什么需要日志门面&#xff1f; 二、简介 三、日志格式 四、记录日志 4.1 使用日志工厂 4.2 使用Lombok的Slf4j注解 五、日志级别 5.1 日志级别介绍 5.2 配置日志级别 5.3 指定某个包下…

分类预测|基于粒子群优化核极限学习机的Adaboost集成模型数据分类预测Matlab程序 PSO-KELM-Adaboost

分类预测|基于粒子群优化核极限学习机的Adaboost集成模型数据分类预测Matlab程序 PSO-KELM-Adaboost 文章目录 前言分类预测|基于粒子群优化核极限学习机的Adaboost集成模型数据分类预测Matlab程序 PSO-KELM-Adaboost 一、PSO-KELM-Adaboost模型1. 核化极限学习机 (KELM)2. 粒子…

数据库原理面试-核心概念-问题理解

目录 1.数据库、数据库系统与数据库管理系统 2.理解数据独立性 3.数据模型 4.模式、外模式和内模式 5.关系和关系数据库 6.主键与外键 7.SQL语言 8.索引与视图 9.数据库安全 10.数据库完整性 11.数据依赖和函数依赖 12.范式&#xff1f;三范式&#xff1f;为什么要遵…

用栈访问最后若干元素——682、71、388

682. 棒球比赛&#xff08;简单&#xff09; 你现在是一场采用特殊赛制棒球比赛的记录员。这场比赛由若干回合组成&#xff0c;过去几回合的得分可能会影响以后几回合的得分。 比赛开始时&#xff0c;记录是空白的。你会得到一个记录操作的字符串列表 ops&#xff0c;其中 ops[…

【redis的大key问题】

在使用 Redis 的过程中&#xff0c;如果未能及时发现并处理 Big keys&#xff08;下文称为“大Key”&#xff09;&#xff0c;可能会导致服务性能下降、用户体验变差&#xff0c;甚至引发大面积故障。 本文将介绍大Key产生的原因、其可能引发的问题及如何快速找出大Key并将其优…

基于llama.cpp实现Llama3模型的guff格式转换、4bit量化以及GPU推理加速(海光DCU)

重要说明&#xff1a;本文从网上资料整理而来&#xff0c;仅记录博主学习相关知识点的过程&#xff0c;侵删。 序言 本文使用llama.cpp框架&#xff0c;对 Llama3-8B-Instruct 模型进行gguf格式转换&#xff0c;8bit量化&#xff0c;并在CPU和GPU上对8bit模型进行推理。 测试…

基于SpringBoot的企业资产管理系统

TOC springboot117基于SpringBoot的企业资产管理系统 系统概述 1.1 研究背景 智慧养老是面向居家老人、社区及养老机构的传感网系统与信息平台&#xff0c;并在此基础上提供实时、快捷、高效、低成本的&#xff0c;物联化、互联化、智能化的养老服务。 随着科技进步&#…

mysql中log

目录 MySQL 日志系统概述 日志类型 日志的作用和重要性 Mermaid图示 1. Undo Log 和 Redo Log 的协同工作图 2. Redo Log 确保持久性的流程图 Undo Log&#xff08;回滚日志&#xff09; 事务的原子性&#xff08;Atomicity&#xff09;保障 事务回滚机制 MVCC&#…

【二叉树进阶】--- 二叉搜索树转双向链表 最近公共祖先

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; 数据结构 本篇博客我们继续了解一些二叉树的进阶算法。 &#x1f3e0; 二叉搜索 树转化为双向循环链表 &#x1f4cc; 题目内容 将二叉搜索树转化为排序…

失败:Windows--WSL2--Ubuntuon--Docker

编写目的&#xff1a; 在Windows上安装Docker&#xff0c;用Docker安装Gitlab、Jenkins等软件。 文章记录一下Windows上安装Docker的过程。 参考文档&#xff1a; 旧版 WSL 的手动安装步骤 | Microsoft Learn 下面用"参考文档"代替 目录 第一步&#xff1a;启…

SAP与网易大数据系统集成案例

一、项目环境 江西某药业有限公司是一家以医药产业为主营、资本经营为平台的大型民营企业集团。公司成立迄今&#xff0c;企业经营一直呈现稳健、快速发展的态势集团总销售额超40亿元。 为了帮助企业更有效的进行分配和管理&#xff0c;包括人力、物资、时间和预算等资源&a…

UVa1660/LA3031 Cable TV Network

UVa1660/LA3031 Cable TV Network 题目链接题意分析AC 代码 题目链接 本题是2004年icpc欧洲区域赛东南欧赛区的题目 题意 给定一个n&#xff08;n≤50&#xff09;个点的无向图&#xff0c;求它的点连通度&#xff0c;即最少删除多少个点&#xff0c;使得图不连通。如下图所示…

C语言----约瑟夫环

约瑟夫环 实例说明&#xff1a; 本实例使用循环链表实现约瑟夫环。给定一组编号分别是4、7、5、9、3、2、6、1、8。报数初始值由用户输入&#xff0c;这里输入4&#xff0c;如图12.18所示&#xff0c;按照约瑟夫环原理打印输出队列。 实现过程&#xff1a; (1)在 VC6.0中创建…