基于TCP协议开发文件传输协议一
创始人
2025-05-28 15:43:03

文章目录

  • socket通讯
  • TCP粘包问题
    • TCP粘包问题解决
    • 关于TCP缓冲区
    • 封装socket常用函数
  • 多进程网络服务程序框架
    • TCP长连接与短连接
    • 多进程的服务程序
    • 扩展:僵尸进程
    • Linux一切皆文件

简介:基于TCP协议开发文件传输系统,搭建多进程网络服务程序框架,实现TCP长连接心跳机制、文件上传与下载、异步通信实现快速传输。

socket通讯

什么是Socket?
Socket(套接字),用来描述IP地址和端口,是通信链的句柄,是支持TCP/IP协议的网络通信的基本操作单元,是对网络通信过程中端点的抽象表示。
通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。Socket()函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。

插述:
TCP(Transmission Control Protocol)传输控制协议,是一种面向连接的、可靠的、基于字节流的通信协议。数据在传输前要建立连接,传输完毕后还要断开连接。客户端在收发数据前要使用 connect() 函数和服务器建立连接。建立连接的目的是保证IP地址、端口、物理链路等正确无误,为数据的传输开辟通道。
在这里插入图片描述

TCP粘包问题

socket缓冲区和数据的传递过程,可以看到数据的接收和发送是无关的,read()/recv() 函数不管数据发送了多少次,都会尽可能多的接收数据。也就是说,read()/recv() 和 write()/send() 的执行次数可能不同。

例如,write()/send() 重复执行三次,每次都发送字符串"abc",那么目标机器上的 read()/recv() 可能分三次接收,每次都接收"abc";也可能分两次接收,第一次接收"abcab",第二次接收"cabc";也可能一次就接收到字符串"abcabcabc"。

假设我们希望客户端每次发送一位学生的学号,让服务器端返回该学生的姓名、住址、成绩等信息,这时候可能就会出现问题,服务器端不能区分学生的学号。例如第一次发送 1,第二次发送 3,服务器可能当成 13 来处理,返回的信息显然是错误的。

这就是数据的“粘包”问题,客户端发送的多个数据包被当做一个数据包接收。也称数据的无边界性,read()/recv() 函数不知道数据包的开始或结束标志(实际上也没有任何开始或结束标志),只把它们当做连续的数据流来处理。
在这里插入图片描述
测试粘包:
在这里插入图片描述

TCP粘包问题解决

采用自定义的报文格式:报文长度+报文内容
在这里插入图片描述
在这里插入图片描述

(*ibuflen) = 0;  // 报文长度变量初始化为0。// 先读取报文长度,4个字节。if (Readn(sockfd,(char*)ibuflen,4) == false) return false;(*ibuflen)=ntohl(*ibuflen);  // 把报文长度由网络字节序转换为主机字节序。// 再读取报文内容。if (Readn(sockfd,buffer,(*ibuflen)) == false) return false;

关于TCP缓冲区

什么是tcp缓冲区?
每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。

缓冲区的意义
write()/send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是TCP协议负责的事情。
TCP协议独立于 write()/send() 函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,比如nagle算法,这取决于当时的网络情况、当前线程是否空闲等诸多因素,不由程序员控制。
read()/recv() 函数也是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取。

I/O缓冲区特性
1、I/O缓冲区在每个TCP套接字中单独存在;
2、I/O缓冲区在创建套接字时自动生成;
3、即使关闭套接字也会继续传送输出缓冲区中遗留的数据;
4、关闭套接字将丢失输入缓冲区中的数据。

输入输出缓冲区的默认大小一般都是 8K,可以通过 getsockopt() 函数获取:

int servSock = socket(PF_INET, SOCK_STREAM, 0);
unsigned optVal;
int optLen = sizeof(int);
getsockopt(servSock, SOL_SOCKET, SO_SNDBUF, (char*)&optVal, &optLen);
printf("Buffer length: %d\n", optVal);

运行结果:
Buffer length: 8192

封装socket常用函数

bool CTcpServer::InitServer(const unsigned int port,const int backlog)
{// 如果服务端的socket>0,关掉它,这种处理方法没有特别的原因,不要纠结。if (m_listenfd > 0) { close(m_listenfd); m_listenfd=-1; }if ( (m_listenfd = socket(AF_INET,SOCK_STREAM,0))<=0) return false;// 忽略SIGPIPE信号,防止程序异常退出。signal(SIGPIPE,SIG_IGN);   // 打开SO_REUSEADDR选项,当服务端连接处于TIME_WAIT状态时可以再次启动服务器,// 否则bind()可能会不成功,报:Address already in use。//char opt = 1; unsigned int len = sizeof(opt);int opt = 1; unsigned int len = sizeof(opt);setsockopt(m_listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,len);    memset(&m_servaddr,0,sizeof(m_servaddr));m_servaddr.sin_family = AF_INET;m_servaddr.sin_addr.s_addr = htonl(INADDR_ANY);   // 任意ip地址。m_servaddr.sin_port = htons(port);if (bind(m_listenfd,(struct sockaddr *)&m_servaddr,sizeof(m_servaddr)) != 0 ){CloseListen(); return false;}if (listen(m_listenfd,backlog) != 0 ){CloseListen(); return false;}return true;
}bool CTcpServer::Accept()
{if (m_listenfd==-1) return false;m_socklen = sizeof(struct sockaddr_in);if ((m_connfd=accept(m_listenfd,(struct sockaddr *)&m_clientaddr,(socklen_t*)&m_socklen)) < 0)return false;return true;
}

析构函数中会释放socket连接。

多进程网络服务程序框架

TCP长连接与短连接

1、使用方法不同。长连接是client方与server方先建立连接,连接建立后不断开,然后再进行报文发送和接收。短连接是Client方与server每进行一次报文收发交易时才进行通讯连接,交易完毕后立即断开连接。此方式常用于一点对多点通讯

2、操作过程不同。长连接的操作步骤是:建立连接、数据传输…、保持连接、数据传输、关闭连接。短连接的操作步骤是:建立连接、数据传输、关闭连接、建立连接、数据传输、关闭连接。

3、使用时机不同。长连接:短连接多用于操作频繁,点对点的通讯,而且长连接数不能太多的情况。每个TCP连接的建立都需要三次握手,每个TCP连接的断开要四次握手。

在HTTP/1.0中,默认使用的是短连接,也就是说,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。如果客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源,如js文件、图像文件、CSS文件等;当浏览器每遇到这样一个Web资源,就会建立一个HTTP会话。

但从HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头中加入 Connection:keep-alive

在这里插入图片描述
心跳机制:心跳机制是定时发送一个自定义的结构体(心跳包),让对方知道自己还活着,以确保连接的有效性的机制。
客户端心跳:以xml格式向客户端发送心跳包

// 心跳。 
bool srv000()    
{char buffer[1024];SPRINTF(buffer,sizeof(buffer),"0");printf("发送:%s\n",buffer);if (TcpClient.Write(buffer)==false) return false; // 向服务端发送请求报文。memset(buffer,0,sizeof(buffer));if (TcpClient.Read(buffer)==false) return false; // 接收服务端的回应报文。printf("接收:%s\n",buffer);return true;
}

多进程的服务程序

在这里插入图片描述
一个服务端响应客户端,服务端程序不会退出。
子进程处理业务,父进程继续accept,接受客户端的请求。

加入子进程,解决了子进程不能退出,在循环内退出后,会产生僵尸进程
解决僵尸进程:信号处理函数中,加入single()函数,则父进程不需要等待。

while (true){// 等待客户端的连接请求。if (TcpServer.Accept()==false){logfile.Write("TcpServer.Accept() failed.\n"); FathEXIT(-1);}logfile.Write("客户端(%s)已连接。\n",TcpServer.GetIP());if (fork()>0) { TcpServer.CloseClient(); continue; }  // 父进程继续回到Accept()。// 子进程重新设置退出信号。signal(SIGINT,ChldEXIT); signal(SIGTERM,ChldEXIT);TcpServer.CloseListen();// 子进程与客户端进行通讯,处理业务。char buffer[102400];// 与客户端通讯,接收客户端发过来的报文后,回复ok。while (1){memset(buffer,0,sizeof(buffer));if (TcpServer.Read(buffer)==false) break; // 接收客户端的请求报文。logfile.Write("接收:%s\n",buffer);strcpy(buffer,"ok");if (TcpServer.Write(buffer)==false) break; // 向客户端发送响应结果。logfile.Write("发送:%s\n",buffer);}ChldEXIT(0);

扩展:僵尸进程

在这里插入图片描述
在这里插入图片描述
僵尸进程处理的三种方式

  1. 内核向父进程发送SIGCHLD信号,
    2.wait函数
    但父进程会阻塞
    3.在信号处理函数中,调用wait,不需要等待
    在这里插入图片描述

Linux一切皆文件

Linux中所有内容都是以文件的形式保存和管理,即:一切皆文件。
普通文件是文件。
目录(在win下称为文件夹)是文件。
硬件设备(键盘、硬盘、打印机)是文件。
套接字(socket)、网络通信等资源也都是文件。

启动一个 linux 进程时,程序默认打开三个 I/O 设备文件:标准输入文件 stdin,标准输出文件 stdout,标准错误输出文件 stderr,而每打开一个文件,就有一个代表该打开文件的文件描述符(比较小的整数)。而一般情况下,stdin、stdout 和 stderr 对应的文件描述符就是 0、1、2。
在这里插入图片描述
查看文件描述符:
在这里插入图片描述
fd打开越多,则资源消耗越大,所以在工程中,总是关闭可以关闭的fd.

相关内容

热门资讯

【实验报告】实验一 图像的... 实验目的熟悉Matlab图像运算的基础——矩阵运算;熟悉图像矩阵的显示方法࿰...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
大模型落地比趋势更重要,NLP... 全球很多人都开始相信,以ChatGPT为代表的大模型,将带来一场NLP领...
Linux学习之端口、网络协议... 端口:设备与外界通讯交流的出口 网络协议:   网络协议是指计算机通信网...
kuernetes 资源对象分... 文章目录1. pod 状态1.1 容器启动错误类型1.2 ImagePullBackOff 错误1....
STM32实战项目-数码管 程序实现功能: 1、上电后,数码管间隔50ms计数; 2、...
TM1638和TM1639差异... TM1638和TM1639差异说明 ✨本文不涉及具体的单片机代码驱动内容,值针对芯...
Qt+MySql开发笔记:Qt... 若该文为原创文章,转载请注明原文出处 本文章博客地址:https://h...
Java内存模型中的happe... 第29讲 | Java内存模型中的happen-before是什么? Java 语言...
《扬帆优配》算力概念股大爆发,... 3月22日,9股封单金额超亿元,工业富联、鸿博股份、鹏鼎控股分别为3.0...
CF1763D Valid B... CF1763D Valid Bitonic Permutations 题目大意 拱形排列࿰...
SQL语法 DDL、DML、D... 文章目录1 SQL通用语法2 SQL分类3 DDL 数据定义语言3.1 数据库操作3.2 表操作3....
文心一言 VS ChatGPT... 3月16号,百度正式发布了『文心一言』,这是国内公司第一次发布类Chat...
CentOS8提高篇5:磁盘分...        首先需要在虚拟机中模拟添加一块新的硬盘设备,然后进行分区、格式化、挂载等...
Linux防火墙——SNAT、... 目录 NAT 一、SNAT策略及作用 1、概述 SNAT应用环境 SNAT原理 SNAT转换前提条...
部署+使用集群的算力跑CPU密... 我先在开头做一个总结,表达我最终要做的事情和最终环境是如何的,然后我会一...
Uploadifive 批量文... Uploadifive 批量文件上传_uploadifive 多个上传按钮_asing1elife的...
C++入门语法基础 文章目录:1. 什么是C++2. 命名空间2.1 域的概念2.2 命名...
2023年全国DAMA-CDG... DAMA认证为数据管理专业人士提供职业目标晋升规划,彰显了职业发展里程碑及发展阶梯定义...
php实现助记词转TRX,ET... TRX助记词转地址网上都是Java,js或其他语言开发的示例,一个简单的...
【分割数据集操作集锦】毕设记录 1. 按要求将CSV文件转成json文件 有时候一些网络模型的源码会有data.json这样的文件里...
Postman接口测试之断言 如果你看文字部分还是不太理解的话,可以看看这个视频,详细介绍postma...
前端学习第三阶段-第4章 jQ... 4-1 jQuery介绍及常用API导读 01-jQuery入门导读 02-JavaScri...
4、linux初级——Linu... 目录 一、用CRT连接开发板 1、安装CRT调试工具 2、连接开发板 3、开机后ctrl+c...
Urban Radiance ... Urban Radiance Fields:城市辐射场 摘要:这项工作的目标是根据扫描...
天干地支(Java) 题目描述 古代中国使用天干地支来记录当前的年份。 天干一共有十个,分别为:...
SpringBoot雪花ID长... Long类型精度丢失 最近项目中使用雪花ID作为主键,雪花ID是19位Long类型数...
对JSP文件的理解 JSP是java程序。(JSP本质还是一个Servlet) JSP是&#...
【03173】2021年4月高... 一、单向填空题1、大量应用软件开发工具,开始于A、20世纪70年代B、20世纪 80年...
LeetCode5.最长回文子... 目录题目链接题目分析解题思路暴力中心向两边拓展搜索 题目链接 链接 题目分析 简单来说࿰...