14.9 Socket 高效文件传输

news2025/1/17 4:01:32

网络上的文件传输功能也是很有必要实现一下的,网络传输文件的过程通常分为客户端和服务器端两部分。客户端可以选择上传或下载文件,将文件分块并逐块发送到服务器,或者从服务器分块地接收文件。服务器端接收来自客户端的请求,根据请求类型执行对应的操作,并根据发送的文件名或其他标识来确定要传输的文件。

在实现文件传输之前,需要先打开要传输的文件,并获取文件的大小信息,也可以通过其他方式获取文件的信息。在客户端和服务器端都准备就绪后,可以通过套接字来发送文件数据。在传输文件的过程中,可以将文件分解为若干个数据包进行传输,以减少数据传输中的丢包或传输错误。每个数据包的长度可以根据实际情况进行选择,通常选择1024字节或更大,也可以设置成更小的值。传输文件的过程中,还需要实现一定的错误处理机制,例如检测传输过程中的超时、丢包、不完整数据等情况,并在必要时进行错误重传或协商其他解决方案。

首先无论时服务端还是客户端都需要封装两个函数,其中GetFileName()函数用于当用户传入文件的具体路径信息时自动获取到该文件的文件名,第二个函数GetFileSize()则用于传入文件路径并自动获取到该文件的字节数。

// 传入路径得到文件名
char* GetFileName(char* Path)
{
  if (strchr(Path, '\\'))
  {
    char ch = '\\';
    char* ref = strrchr(Path, ch) + 1;
    return ref;
  }
  else
  {
    char ch = '/';
    char* ref = strrchr(Path, ch) + 1;
    return ref;
  }
}

// 获取文件大小
int GetFileSize(std::string FileName)
{
  FILE* pointer = NULL;
  pointer = fopen(FileName.c_str(), "rb");
  if (pointer != NULL)
  {
    fseek(pointer, 0, SEEK_END);
    int size = ftell(pointer);
    fclose(pointer);
    return size;
  }
  return 0;
}

接着我们来看一下RecvFile()接收文件函数是如何实现的,首先第一个发送用于向服务端发出我需要下载具体的那个目录下的文件,接着服务端会返回该目录文件的长度,此时我们通过fopen()创建一个新文件,并以此循环接收该文件的长度,每次接收成功后自动的fwrite写出到文件中,当文件被接收完毕后,则通过fclose(pointer)保存并关闭文件。

// 接收文件
bool RecvFile(SOCKET ptr, char* LocalPath, char* RemoteFile)
{
  // 发送需要下载的文件路径
  send(ptr, RemoteFile, strlen(RemoteFile), 0);

  // 接收文件长度
  long long file_size = 0;
  recv(ptr, (char*)&file_size, sizeof(int), 0);
  if (file_size <= 0)
  {
    return false;
  }

  // 保存文件到指定目录下
  char *file_name = GetFileName(RemoteFile);
  char file_all_name[1024] = { 0 };

  strcat(file_all_name, LocalPath);
  strcat(file_all_name, file_name);

  std::cout << "生成保存路径: " << file_all_name << std::endl;
  FILE* pointer = fopen(file_all_name, "wb");
  char buffer[1024] = { 0 };

  if (pointer != NULL)
  {
    long long length = 0;
    long long total_length = 0;

    // 循环接收字节数据,每次接收1024字节
    while ((length = recv(ptr, buffer, 1024, 0)) > 0)
    {
      // 写出文件并判断是否写出成功
      if (fwrite(buffer, sizeof(char), length, pointer) < length)
      {
        break;
      }

      // 每次累加递增
      total_length += length;
      memset(buffer, 0, 1024);

      // 判断文件长度是否全部接收完毕
      if (total_length >= file_size)
      {
        std::cout << "文件接收完毕, 接收字节数: " << total_length << std::endl;
        fclose(pointer);
        return true;
      }
    }
    fclose(pointer);
  }
  return false;
}

对于SendFile()发送文件而言首先我们接收到客户端传来的文件路径,并通过该路径得到该文件的具体长度,第一次调用发送函数将文件的长度传递给客户端,此时打开我们所需要发送的文件,并通过循环的方式向客户端传输,当数据包传输完毕后则自动关闭文件。

// 发送指定文件
bool SendFile(SOCKET ptr)
{
  // 接收文件路径
  char file_path[1024] = { 0 };
  recv(ptr, file_path, 1024, 0);

  // 得到文件长度并发送给服务端
  long long file_size = GetFileSize(file_path);

  if (file_size <= 0)
  {
    return false;
  }
  send(ptr, (char*)&file_size, sizeof(int), 0);
  std::cout << "发送文件长度: " << file_size << std::endl;

  // 循环发送数据
  char buffer[1024] = { 0 };
  FILE* pointer = fopen(file_path, "rb");
  if (pointer != NULL)
  {
    long long length = 0;
    long long total_length = 0;

    // 循环发送数据
    while ((length = fread(buffer, sizeof(char), 1024, pointer)) > 0)
    {
      send(ptr, buffer, length, 0);
      memset(buffer, 0, 1024);
      total_length += length;
    }

    if (total_length == file_size)
    {
      return true;
    }
  }
  return false;
}

14.9.1 服务端实现

如下代码展示了如何使用Winsock进行TCP协议的文件传输。首先使用WSAStartup函数对Winsock库进行初始化。然后创建一个socket,设置IP地址、端口号等信息,并将该socket和本地服务端的地址绑定起来。接下来对该socket进行监听,等待客户端的连接请求。

当有客户端连接请求到来时,accept函数会接收请求,并创建一个新的socket与客户端进行通信。在与客户端通信的过程中,可以通过sendrecv函数进行数据的传输,实现文件的上传和下载功能。此处的代码调用RecvFile函数,该函数为自定义实现的接收文件函数,负责接收数据并将接收到的文件存储到指定的路径下。

int main(int argc, char* argv[])
{
  WSADATA wsaData;
  if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    exit(1);

  // 声明并初始化一个服务端(本地)的地址结构 
  sockaddr_in server_addr;
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.S_un.S_addr = INADDR_ANY;
  server_addr.sin_port = htons(8087);

  // 创建socket 
  SOCKET m_Socket = socket(AF_INET, SOCK_STREAM, 0);
  if (SOCKET_ERROR == m_Socket)
    exit(1);

  // 绑定socket和服务端(本地)地址 
  if (SOCKET_ERROR == bind(m_Socket, (LPSOCKADDR)&server_addr, sizeof(server_addr)))
    exit(1);

  // 监听 
  if (SOCKET_ERROR == listen(m_Socket, 10))
    exit(1);

  sockaddr_in client_addr;
  int client_addr_len = sizeof(client_addr);

  SOCKET m_New_Socket = accept(m_Socket, (sockaddr*)&client_addr, &client_addr_len);

  // 接收远程d://lyshark.exe放到本地的d://11g/目录下
  bool ref = RecvFile(m_New_Socket, (char*)"d://11g/", (char*)"d://lyshark.exe");
  std::cout << "接收状态: " << ref << std::endl;

  closesocket(m_New_Socket);
  closesocket(m_Socket);

  WSACleanup();
  system("pause");
  return 0;
}

14.9.2 客户端实现

如下客户端代码实现了一个基于TCP协议的文件传输客户端。首先使用WSAStartup函数对Winsock库进行初始化。然后创建一个socket,并设置服务端的IP地址和端口号。之后通过connect函数与服务端建立连接,连接成功后调用SendFile函数进行文件传输,将指定的文件发送到服务端。文件传输完成后,关闭socket连接,清除Winsock资源。

int main(int argc, char* argv[])
{
  while (true)
  {
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
      exit(1);

    // 创建socket 
    SOCKET c_Socket = socket(AF_INET, SOCK_STREAM, 0);

    //指定服务端的地址 
    sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    server_addr.sin_port = htons(8087);

    if (SOCKET_ERROR != connect(c_Socket, (LPSOCKADDR)&server_addr, sizeof(server_addr)))
    {
      bool ref = SendFile(c_Socket);
      std::cout << "文件发送状态: " << ref << std::endl;
    }
    closesocket(c_Socket);
    WSACleanup();
    Sleep(1000);
  }
  return 0;
}

文件传输功能代码就这些,其实理解起来并不难,读者可自行编译并运行上述代码,运行后则可接收远程d://lyshark.exe文件,并放到本地的d://11g/目录下,输出效果图如下;

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/323aa250.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【软考】6.1 信息安全及技术

《信息安全和信息系统安全》 信息安全系统的体系架构 网络安全空间五大要素&#xff1a;认证、权限、完整、加密、不可否认&#xff08;抵赖&#xff09; 信息安全含义及属性 信息安全三要素&#xff1a;保密性、完整性、可用性最小授权原则&#xff1a;该有什么权限&#xf…

软件测试基础知识 + 面试理论(超详细)

一、什么是软件&#xff1f; 软件是计算机系统中的程序和相关文件或文档的总称。 二、什么是软件测试&#xff1f; 说法一&#xff1a;使用人工或自动的手段来运行或测量软件系统的过程&#xff0c;以检验软件系统是否满足规定的要求&#xff0c;并找出与预期结果之间的差异…

Mac系统快速切换和管理node版本

如何下载node 不要下载最新版&#xff0c;推荐先下载稳定版的nodejs.org/zh-cn/downl…&#xff0c;下载完一键式安装即可&#xff0c;安装完成后&#xff0c;命令行查看是否成功。 1、使用n切换node版本 node有一个模块n&#xff0c;是专门用来管理node.js的版本的。npm是用…

数据导入与预处理-第4章-Python标准库之json

文章目录 资源json概述json案例dumps案例dump案例中文编码问题格式化问题 loads案例load案例 资源 参考:https://zhuanlan.zhihu.com/p/436465279 https://blog.csdn.net/impoijimlq/article/details/130445399 json概述 什么是json json是一种轻量级的文本数据 交换格式jso…

【代码软实力】职场高效沟通技巧

一、沟通三要素 二、什么是沟通 三、沟通的难点 四、向上沟通-结构化表达 上级结构化表达的案例 五、平级沟通-非暴力沟通 平级非暴力沟通案例 六、下级沟通-行为影响结果原则BIC 下级沟通案例分析 思维导图

如何在MT4和MT5复制交易?有缺点吗?anzo capital昂首资本1秒答

很多投资者不知道如何在MT4和MT5上复制交易信号&#xff1f;其实很简单&#xff0c;今天anzo capital昂首资本1分钟解答&#xff0c;复制交易的方法包括&#xff1a; 在MetaQuotes社区进行交易复制和信号的传播。MT4和MT5均具备连接到这个社区的功能。以下是使用该功能的步骤&…

Crypto(2)攻防世界-幂数加密

先看题&#xff0c;给出了flag格式和幂数加密的方式。 附件里的内容为8842101220480224404014224202480122 正常的二进制幂数加密只有0&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5&#xff0c;是不会出现8的。通过百度了解到这是云影密码、 简单说来…

Anaconda安装

前言&#xff1a; 在学习机器学习时&#xff0c;一般都会使用Anaconda。 Anaconda是一个强大的开源数据科学平台,它将很多好的工具整合在一起&#xff0c;极大地简化了使用者的工作流程&#xff0c;并能够帮助使用者解决一系列数据科学难题。 有小伙伴纠结先安装python还是安…

【好玩的开源项目】Linux系统之部署跳一跳经典小游戏

【好玩的开源项目】Linux系统之部署跳一跳经典小游戏 一、跳一跳小游戏介绍1.1 跳一跳小游戏简介1.2 项目地址 二、本次实践介绍2.1 本地环境规划2.2 本次实践介绍 三、安装httpd软件3.1 检查yum仓库3.2 安装httpd软件3.3 启动httpd服务3.4 查看httpd服务3.5 防火墙和selinux设…

通过这个技术,浏览器可以运行Node.js、Rust、Python、PHP、C++、Java代码了!

近日&#xff0c;WebContainers 发布重要更新&#xff0c;WASI&#xff08;WebAssembly 系统接口&#xff09;已全面集成到 WebContainers 中。这是一个重要里程碑&#xff0c;它扩大了可以使用浏览器执行的操作&#xff0c;是 Web 开发的全新范例&#xff0c;允许运行大量原生…

Java封装:面向对象的三大特性之一

&#x1f451;专栏内容&#xff1a;Java⛪个人主页&#xff1a;子夜的星的主页&#x1f495;座右铭&#xff1a;前路未远&#xff0c;步履不停 目录 一、封装的概念二、访问修饰限定符三、包1、包的概念2、导入包中的类3、常见的包 嗨&#xff01;前面我们简单的认识了一下什么…

vite+vue3.0 + TypeScript+element-plus环境搭建

1、环境要求&#xff1a;node版本16以上 2、搭建vite项目 npm create vitelatest cmd运行下面命令 cd vite-project npm install npm run dev 谷歌浏览器访问http://127.0.0.1:5173/ 查看效果 3、安装element-plus组件 npm运行以下命令进行安装element-plus npm install elem…

汽车屏类产品(二):360全景环视(SVC)、多分割显示、行车记录

前言 随着新能源汽车的快速发展,带动了车载器件的大发展,大的比如域控,小的创新更是不断涌现。而车载显示屏可以说是一大类产品,产品形态也是愈发多样化,比如:仪表cluster、中控IVI、副驾屏、行车记录仪、流媒体后视镜、透明A柱屏、方向盘屏(替代方向盘按键)、门饰板显…

基因组的Phasing原理

1.Phasing的概念 Phasing&#xff0c;或者说Genotype Phasing&#xff0c;它的中文名有很多&#xff1a;基因定相、基因分型、单倍体分型、单倍体构建等在不同的语境下都有人说过。但不管如何&#xff0c;所谓Phasing就是要把一个二倍体&#xff08;甚至是多倍体&#xff09;基…

Vue 网络处理 - axios 异步请求的使用,请求响应拦截器(最佳实践)

目录 一、axiox 1.1、axios 简介 1.2、axios 基本使用 1.2.1、下载核心 js 文件. 1.2.2、发送 GET 异步请求 1.2.3、发送 POST 异步请求 1.2.4、发送 GET、POST 请求最佳实践 1.3、请求响应拦截器 1.3.1、拦截器解释 1.3.2、请求拦截器的使用 1.3.3、响应拦截器的使…

配电房智能化改造在加油站等的应用

随着科技的发展和智能化趋势的推进&#xff0c;对加油站配电房进行智能化改造成为了一个必然的选择。智能化改造不仅可以提高加油站的工作效率&#xff0c;减少事故发生率&#xff0c;还可以实现能源的合理利用&#xff0c;提高经济效益。 力安科技加油站智能化改造升级是一种高…

深度学习——卷积神经网络(CNN)基础三

深度学习——卷积神经网络&#xff08;CNN&#xff09;基础三 文章目录 前言五、汇聚层&#xff08;池化层&#xff09;5.1. 最大池化和平均池化5.2. 填充和步幅5.3. 多个通道5.3. 小结 六、卷积神经网络&#xff08;LeNet&#xff09;6.1. LeNet6.2. 模型训练6.3. 小结 总结 前…

Centos7 安装 MySQL5.7 步骤

Centos7 安装 MySQL5.7 步骤 前言&#xff1a;一 .使用yum源方式安装1、卸载系统自带 mariadb查看并卸载系统自带的 Mariadb 2、下载并安装MySQL官方的 Yum2.1 下载mysql的yum源配置2.2 安装mysql的yum源2.3 使用yum方式安装mysql2.3.1 安装过程中报错解决问题描述解决方案 3、…

JTS-通过Coordinate点截断几何Geometry

背景 通过一堆点&#xff0c;线上的点或者靠近线的点&#xff0c;来截取线段&#xff0c;将线段截取成多段 代码片段 /*** 通过点截取线&#xff0c;点可以是线上的形状点也可以是靠近线的点** 线 ------------------------------------------* 点 . . . …

YOLO目标检测——复杂场景人员数据集【含对应voc、coco和yolo三种格式标签】

实际项目应用&#xff1a;安防监控、人群管理、自动驾驶、城市规划、人机交互等等数据集说明&#xff1a;YOLO目标检测数据集&#xff0c;真实场景的高质量图片数据&#xff0c;数据场景丰富。使用lableimg标注软件标注&#xff0c;标注框质量高&#xff0c;含voc(xml)、coco(j…