学习网络编程No.5【TCP套接字通信】
引言:
北京时间:2023/8/25/15:52,昨天刚把耗时3天左右的文章更新,充分说明我们这几天并不是在摆烂中度过,而是在为了更文不懈奋斗,历时这么多天主要是因为该部分知识比较陌生,所以需要我们花费大量的时间去细细研究,为后面无论是TCP套接字,还是网络的学习都能更加融会贯通。并且这几天在闲暇时间把《一念永恒》听了一下,目前还在过渡期,不过根据一些伏笔我认为小高潮即将来临,根据前期的一些内容我意识到为什么该小说能被动漫公司拍成动漫,主要应该是因为耳根对于主角前期的角色塑造相比于其它小说来说更加独具匠心,以人性最朴实的长生、怕死为目的,再配上各种小心思和漫不经心为行文规律,很好的就将大众在心目中对人性的理解给刻画出来,再加上耳根独到的幽默理解,可以说一个完美的小说主角就被呈现出来了。这也可能就是耳根能屹立网文巅峰的原因吧!相比那些爽文,其实也不错,但是相比于这种角色刻画更深的小说,差距还是非常大的,白金就是白金,虽然套路单一,但是文笔确实无可挑剔!ok,不谈了,该篇博客我们承接上篇博客有关UDP套接字的知识,来看一看有关TCP套接字相关的知识,并根据TCP套接字实现一份TCP版本的网络通信客户端和服务端。
深入套接字编程
在上篇博客中我们重点对socket编程中的sockaddr结构体进行了深入理解,并且对有关socket编程的接口进行了详细认识,最后结合sockaddr_in结构体和有关接口实现了三种不同场景的UDP服务端/客户端,并成功让其完成数据的接收和传输,真正意义上实现了一份基于UDP协议的socket网络通信代码。但由于时间以及内容问题,上篇博客我们只讲解了基于UDP版本的socket网络通信,所以该篇博客就让我们一起来看看如何使用socket编程实现一份TCP版本的服务端/客户端,从而实现基于TCP协议的socket网络通信代码。
理解TCP版本的套接字接口
在上篇博客中,我们对套接字常见API进行了详解介绍,但准确的来说我们介绍的大部分都是基于UDP版本下的套接字接口,所以此时对于TCP版本来说,我们需要补充几个在TCP版本下套接字编程中会被使用到的接口,其中为什么在TCP版本下需要新增不同的套接字接口,本质原因非常简单,也就是基于TCP特点带来区别,因为TCP需要满足可靠性传输,事先建立连接,面向字节流等原则。所以接下来我们就来看看TCP版本套接字编程的新增接口吧!
-
int listen(int sockfd, int backlog);
功能:将套接字设置为监听状态(服务器处于阻塞),用于监听指定端口号客户端的连接请求,并且维护一个监听队列/等待队列用于存储向该服务端发送的连接请求,最终将该监听队列提供给accept接口使用,从而实现服务端和客服端之间的连接。换一个角度理解,也就是说服务器代码在执行时会被阻塞在该处(监听),从而一直处于监听状态,只有当某客户端发送连接请求被监听到(存储在监听队列)之后,代码才会继续向后执行,从而让accept接口完成连接。第一个参数sockfd(重新理解sockfd): 在创建套接字时返回的一个套接字描述符,虽然之前我们一直称其为文件描述符,但更准确应该称为套接字描述符,同理存储在对应的文件描述符表上,是一种套接字的引用(套接字的标识符),也就是可通过该套接字描述符访问到套接字,从而可以对套接字进行管理和操作,最终让套接字实现不同的功能,完成对应的工作(接口)。第二个参数backlog:同理上述所说,该参数与监听队列有关,用于表示监听队列存储客户端连接请求的上限(也就是个数)。返回值:同理监听成功返回0,监听失败返回-1且错误码被设置。 -
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:同理上述listen接口中所说,该接口用于与指定端口号客户端建立连接,直接从监听队列中获取客户端请求,注意:在客户端使用connect接口发送连接请求时,并不会直接将客户端的端口号和IP地址发送给服务端,而将端口号和IP地址发送给服务端的这个过程一般是系统依据TCP协议完成,所以最终accept接口再根据TCP协议对监听队列中对客户端发送的请求连接进行解包,从而获取客户端IP地址和端口号的同时,也获取到服务端的IP地址和端口号(客户端发送),从而让客户端和服务端建立连接。第一个参数sockfd:同理套接字描述符,用于对指定套接字进行accept操作,第二个参数addr:同理作为输出型参数,接收客户端的IP地址和端口号,让服务端可以显式的打印和识别客户端的IP地址和端口号,第三个参数addrlen:同理表示sockaddr_in结构体的长度,也就是大小。返回值:注意,此时这个返回值是一个新的套接字描述符,也就是说在accept接口中一定也存在socket接口的使用,具体为什么需要返回新的文件描述符,而不是像UDP版本一样使用一个套接字文件描述符就能实现网络通信,下述讲解。 -
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:同理上述accept接口中所说,在客户端中使用,向服务端发送连接请求,当然基于此时我们对listen和accept接口的理解,明白,此时connect接口发送的请求肯定是先被listen监听到,监听成功之后才被accept处理,尝试建立连接,并且对于connect接口来说,并不是每次一申请就能成功,一般需要多次申请,因为有很多原因会导致失败,如:监听队列满了的情况,然后同理就是该客户端向指定服务端发送请求,肯定是需要知道该服务端的IP地址和端口号,所以connect接口在发送请求时,就会把服务端的IP地址和端口号发送过去,这也就是为什么accept接口默认不仅知道客户端IP地址和端口号,还知道服务端IP地址和端口号的原因。第一个参数sockfd:同理套接字描述符,用于对指定套接字进行connect操作,第二个参数addr:同理,表示客户端需要知道向那个服务端发送连接请求,表示服务端的IP地址和端口号,第三个参数addrlen:同理表示服务端sockaddr_in的长度。返回值:同理成功返回0,失败返回-1且错误码被设置。 -
ssize_t read(int fd, void *buf, size_t count);
功能:首先明白在当初学习系统编程之文件系统相关知识时,对该接口有过一定的了解,该接口就是用来从特定的文件描述符中读取数据,同理此时对于套接字描述符而言,read接口同样可以从其读取数据,第一个参数fd:同理套接字描述符,向指定套接字中读取数据,第二个参数buffer:作为输出型参数,存储被读取数据,第三个参数count:读取数据的字节数,返回值:同理返回实际读取数据的字节数。 -
ssize_t write(int fd, const void *buf, size_t count);
功能:同理,该接口用来向特定的文件描述符中写入数据,在socket编程中也就是向套接字描述符中写入数据,第一个参数fd:同理套接字描述符,第二个参数buffer:存储需要写入数据,第三个参数count:写入数据的大小。返回值:同理写入数据的实际字节数。
行文来到此处,对于有关TCP版本新增的套接字接口我们就大致有了一定了解,但是根据上述所说,此时我们还需要解决一个问题,也就是为什么accept接口将服务端和客户端建立连接之后,需要返回一个新的套接字描述符,而不是像UDP版本一样,使用一个套接字描述符就能实现,本质也就是我想强调出TCP与UDP实现网络通信之间的区别,所以在特别区分UDP和TCP不同版本的套接字通信之前,此时我们回顾一下UDP版本下的套接字通信,第一步:同理创建套接字,获取套接字描述符,第二步:初始化sockaddr_in结构体,并将其绑定到内核套接字中,第三步:使用sendto和recvfrom这类多参数接口用于指定目标地址以及接收目标地址,最终成功实现客户端和服务端之间的数据传输。可发现在UDP版本下实现套接字通信相比于TCP需要使用listen、accept和connect等接口来说较为容易,而根据上述对接口的分析以及功能,TCP版本下的套接字通信,不仅需要对服务端套接字监听,且还需要对监听队列中的客户端连接请求建立联系,然后在建立连接之后才允许进行数据间传输,所以对于TCP套接字来说,此时就不仅仅只是像UDP一样完成数据间传输就行,重点在于它还需要对服务端和客户端建立连接,所以同理对于listen接口和accept接口来说,它们本质都需要直接作用于套接字描述符,从而让套接字具备监听和建立连接的能力,且又因为套接字还需要具备数据传输的能力,所以如果一个套接字监听到客户端请求,完成连接,进行数据传输时,此时其它客户端就不能再使用该套接字与该服务端建立连接,因为此时对于服务端来说,它唯一的套接字此时正在完成数据传输工作,所以这样就会导致服务端的效率非常低下,不能并发处理多个客户端,所以最终为了解决这一问题,此时在accpet接口完成服务端与客户端的连接之后,就需要返回一个新的套接字描述符,本质也就是向操作系统再申请一个网络通信请求并分配一定的资源(空间资源等),只有这样,才能让每个客户端在与该服务端建立连接之后都拥有一个属于自己的套接字,本质理解也就是让服务端不再像UDP版本一样,整份代码只有唯一的套接字,而是多个套接字,每与一个客户端建立连接,就有一个新的套接字供其使用,让服务端在建立连接和传输数据之间无冲突。
注意: 虽然TCP协议需要满足可靠性传输、建立连接、面向字节流等特性,但并不意味着上述接口就能实现这些特性,如TCP协议需要满足可靠性传输,而想要实现可靠性传输则需要通过一系列机制,如:确认应答、重传、流量控制和拥塞控制等… ,这些机制的实现都是体现在TCP协议当中,也就是说我们使用套接字编程实现TCP版本的网络通信,本质只需要让系统识别这是一份TCP协议版本的代码,并且编码符合TCP的这三大特性就行,本质也就是因为这些特性都是TCP协议规定好的,我们只需要遵从,这也就是为什么我们在编码时使用read和write接口作为来进行数据传输,因为其具有面向字节流的特性。同理再次强调,套接字的本质就是一种网络通信机制,socket创建套接字的本质也就是向操作系统申请进行网络通信请求,向操作系统分配资源的一个过程,并且使用各种接口对套接字进行操作,本质就是一种对网络通信功能的实现与控制而已。
正式进入TCP套接字代码编写
在正式进入代码编写前,我们需要先明白几个点,首先在上述对TCP套接字接口有了一定理解,此时我们明白TCP套接字相对于UDP套接字而言,关键就在于在数据传输前让服务端与客户端建立连接,所以我们对accept接口进行着重讲解,明白为什么其返回值是一个新的套接字描述符,但,此时我们要明白,无论是UDP版本的服务端还是TCP版本的服务端,如果我们不对其进行多执行流的控制,那么它们无论如何都不支持客户端的并发访问,所以就算是TCP套接字中accept会返回一个新的套接字描述符,它也无法同时进行数据传输和与客户端建立连接,本质就是因为单执行流必须按照顺序执行代码,明白这点之后,此时对于TCP套接字网络通信我们依然分为三个场景,单执行流服务端、父子进程服务端、多线程及线程池服务端。
首先是客户端的实现
同理,客户端代码不存在特殊改动,所以此时客户端代码我们只举例一份,如下所示:
第一个场景:单执行流实现
重点强调
: 根据上述代码及其注解,我们需要重点明白两点,一点是在某客户端向服务端发送连接请求之前,该服务端是在listen接口处处于阻塞状态,而不是accept接口处,二点是accept接口中自带数据发送和接收功能,也就是recvfrom和sendto接口的实现,并且本质建立连接这个概念就是在内部对recvfrom和sendto接口进行封装,因为accept接口天生具有获取客户端地址信息和服务端地址信息的能力(客户端会将服务端的地址信息也传过来),从外部理解也就是在服务端与客户端之间建立一个双向通道通信,而对于返回一个新的套接字描述符的本质也就可以理解为返回accept接口中使用socket接口专门为recvfrom和sendto接口创建的那个套接字描述符。
第二个场景:父子进程实现服务端
对于使用父子进程来实现多执行流解决服务端并发访问问题,从上述代码来看非常合理,唯一的缺点就是创建进程带来的效率消耗问题,本质也就是该服务端除了会为每一个客户端创建一个新套接字描述符之外,此时还需要为每一个客户端创建一个子进程,所以这也就是为什么我们需要使用多线程方法来改善的原因。当然这部分知识下述讲到,这里我重点想要回顾一下父进程回收子进程的问题,上述代码是一种设计套路,并不是经典的我们学过的回收子进程的方法,我们学过的父进程的回收子进程的方法一共有三种,第一种是直接使用waitpid接口,但因为此时我们的目的是子进程处理客户端数据的同时,父进程同时也可以去调用accept接口,让另一个客户端和服务端建立连接,所以我们不能让父进程处于阻塞式等待,所以在使用waitpid接口时,应该采用非阻塞式等待, waitpid(id, nullptr, WNOHANG);
但最后因为需要防止父进程在执行accept接口时,监听队列中没有客户端的请求连接,而导致父进程被阻塞在accept接口处,无法执行waitpid接口回收子进程,所以这个方法不推荐在服务端上使用,第二种是使用子进程退出信号回收子进程,因为子进程退出时会向父进程发送一个退出信号SIGCHLD,所以我们只需要在父进程收到该信号时,让父进程将该信号的信号递达方式设置为忽略,此时系统自动会帮父进程回收该子进程, signal(SIGCHLD, SIG_IGN);
第三种同理是使用子进程退出信号SIGCHLD处理,在父进程收到该信号时,让父进程将该信号的默认处理方式设置为waitpid接口,实现子进程的回收,signal(SIGCHLD, handler);
明白了上述有关子进程回收相关知识,对于使用父子进程实现服务端的知识,我们就理解到这,本质没有难度,重点就在于子进程的回收而已。
第三个场景:多线程实现服务端
以上代码就是将服务端以多线程的形式实现多执行流来解决并发访问问题,本质对比父子进程实现多执行流,好处就在于不需要频繁创建进程,提高服务端效率,编码关键就在于新建线程参数控制,其它过程同理本质就是为了实现多执行流,一个负责处理客户端数据,一个负责建立连接。当然因为我们学习了线程池相关的知识,所以对于这种临时创建线程的方法,我们依然可以使用线程池的方法来改进,具体下篇博客详解,主要是因为还需要重点讲解有关日志方面的知识,所以需要留一份代码来看看日志具体如何使用。
总结:有关TCP版本的socket网络通信我们就讲到这啦!剩余有关线程池服务端这个终极版本,我们留到下篇博客结合日志一起学习,See you!
相关文章:

学习网络编程No.5【TCP套接字通信】
引言: 北京时间:2023/8/25/15:52,昨天刚把耗时3天左右的文章更新,充分说明我们这几天并不是在摆烂中度过,而是在为了更文不懈奋斗,历时这么多天主要是因为该部分知识比较陌生,所以需要我们花费…...
常用的时间段的时间戳
获取 昨天这个时间的时间戳 Calendar calendar Calendar.getInstance(); //当前时间calendar.add(Calendar.DAY_OF_YEAR,-1); Long dd calendar.getTime().getTime()/1000;System.out.println(dd);计算今天0点的时间戳 Long time System.currentTimeMillis(); //当前…...
博客系统后台控制层接口编写
BlogColumnCon CrossOrigin RequestMapping("/back/blogColumn") RestController public class BlogColumnCon {Autowiredprivate BlogColumnService blogColumnService;/*** 新增** param blogColumn* return*/PostMapping("/add")public BaseResult add…...
生成 MySQL 删除索引、创建索引、分析表的 SQL 语句
目录 1. 生成删除索引 SQL 语句 2. 生成创建索引的 SQL 语句 3. 生成分析表的 SQL 语句 1. 生成删除索引 SQL 语句 mysql -uwxy -p12345 -S /data/18253/mysqldata/mysql.sock -e " select concat(alter table \,table_schema,\.\,table_name,\ ,drop_index,;)from ( …...
mongodb建用户
玛德折腾了2个小时,、mongodb 建用户。艹 [rootk8-master mongodb]# cat docker-compose.yaml version: 2 services: mongodb: container_name: mongodb_2.0 image: mongo:4.4 restart: always environment: TZ: Asia/Shanghai MONGO_INITDB_ROOT_USERNAME: admin M…...

无门槛访问ChatGPT升级版-数据指北AI
大家好,我是脚丫先生 (o^^o) 给小伙伴们介绍ChatGPT升级版不需要任何门槛,不需要单独搞账号,只要邮箱登录的方式,即可访问平台,以用户体验为首要,让所有人都能无门槛的使用目前市面上最强大的AI智能聊天&a…...

前端需要学习哪些技术?
前端工程师岗位缺口一直很大,符合岗位要求的人越来越少,所以学习前端的同学要注意,一定要把技能学到扎实,做有含金量的项目,这样在找工作的时候展现更大的优势。 缺人才,又薪资高,那么怎样才能…...

详解排序算法(附带Java/Python/Js源码)
冒泡算法 依次比较两个相邻的子元素,如果他们的顺序错误就把他们交换过来,重复地进行此过程直到没有相邻元素需要交换,即完成整个冒泡,时间复杂度。 比较相邻的元素。如果第一个比第二个大,就交换它们两个;…...

手写Mybatis:第8章-把反射用到出神入化
文章目录 一、目标:元对象反射类二、设计:元对象反射类三、实现:元对象反射类3.1 工程结构3.2 元对象反射类关系图3.3 反射调用者3.3.1 统一调用者接口3.3.2 方法调用者3.3.3 getter 调用者3.3.4 setter 调用者 3.4 属性命名和分解标记3.4.1 …...

基于AI智能分析网关EasyCVR视频汇聚平台关于能源行业一体化监控平台可实施应用方案
随着数字经济时代的到来,实体经济和数字技术深度融合已成为经济发展的主流思路。传统能源行业在运营管理方面也迎来了新的考验和机遇。许多大型能源企业已开始抓住机遇,逐步将视频监控、云计算、大数据和人工智能技术广泛应用于生产、维护、运输、配送等…...

《Flink学习笔记》——第四章 Flink运行时架构
4.1 系统架构 Flink运行时架构 Flink 运行时由两种类型的进程组成:一个 JobManager 和一个或者多个 TaskManager。 1、作业管理器(JobManager) JobManager是一个Flink集群中任务管理和调度的核心,是控制应用执行的主进程。也就…...

vue3使用Elementplus 动态显示菜单icon不生效
1.问题描述 菜单icon由后端提供,直接用的字符串返回,前端使用遍历显示,发现icon不会显示 {id: 8, path:/userManagement, authName: "用户管理", icon: User, rights:[view]}, <el-menu-item :index"menu.path" v-f…...

升级iOS17后iPhone无法连接App Store怎么办?
最近很多用户反馈,升级最新iOS 17系统后打开App Store提示"无法连接",无法正常打开下载APP。 为什么升级后无法连接到App Store?可能是以下问题导致: 1.网络问题导致App Store无法正常打开 2.网络设置问题 3.App Sto…...

antd日期选择禁止
1、年月日——日期禁止当天之前的,不包括当天的(带有时间的除外) 2、年月日——日期禁用当天之前的(包括当天的) 部分代码如下:...

数据结构--树4.1
目录 一、树的定义 二、结点的分类 三、结点间的关系 四、结点的层次 五、树的存储结构 一、树的定义 树(Tree)是n(n>0)个结点的有限集。当n0时称为空树,在任意一个非空树中: ——有且仅有一个特定的…...
webpack(二)webpack介绍与基础配置
什么是webpack webpack最初的目标是实现前端项目模块化,旨在更高效的管理和维护项目中的每一个资源。 可以看做是模块打包机,分析你的项目结构,找到javascript模块以及其它一些浏览器不能直接运行的拓展语言(Scss、TypeScript等&…...
RabbitMQ | 在ubuntu中使用apt-get安装高版本RabbitMQ
目录 一、官方脚本 二、彻底卸载 三、重新安装 1.安装高版本Erlang 2.安装RabbitMQ 一、官方脚本 直接使用apt安装的rabbitmq版本较低,甚至可能无法使用死信队列等插件。首先提供一个 官方 的安装脚本: #!/usr/bin/sh sudo apt-get install curl …...

springboot集成es 插入和查询的简单使用
第一步:引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId><version>2.2.5.RELEASE</version></dependency>第二步:…...

liunx下ubuntu基础知识学习记录
使用乌班图 命令安装使用安装网络相关工具安装dstat抓包工具需要在Ubuntu内安装openssh-server gcc安装vim安装hello word输出1. 首先安装gcc 安装这个就可以把gcc g一起安装2. 安装VIM3.编译运行代码 解决ubuntu与主机不能粘贴复制 命令安装使用 安装网络相关工具 使用ifconf…...

基于Googlenet深度学习网络的螺丝瑕疵检测matlab仿真
目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ....................................................................................% 获…...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...

Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...

k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)
本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...

Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...

C++实现分布式网络通信框架RPC(2)——rpc发布端
有了上篇文章的项目的基本知识的了解,现在我们就开始构建项目。 目录 一、构建工程目录 二、本地服务发布成RPC服务 2.1理解RPC发布 2.2实现 三、Mprpc框架的基础类设计 3.1框架的初始化类 MprpcApplication 代码实现 3.2读取配置文件类 MprpcConfig 代码实现…...