学习网络编程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!

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

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

相关文章

京东搜索EE链路演进 | 京东云技术团队

导读 搜索系统中容易存在头部效应,中长尾的优质商品较难获得充分的展示机会,如何破除系统的马太效应,提升展示结果的丰富性与多样性,助力中长尾商品成长是电商平台搜索系统的一个重要课题。其中,搜索EE系统在保持排序…

C#-SQLite-使用教程笔记

微软官网资料链接(可下载文档) 教程参考链接:SQLite 教程 - SQLite中文手册 项目中对应的system.dat文件可以用SQLiteStudio打开查看 参考文档:https://d7ehk.jb51.net/202008/books/SQLite_jb51.rar 总结介绍 1、下载SQLiteS…

RK3399平台开发系列讲解(内核调试篇)IO 数据工具:iostat和iotop

🚀返回专栏总目录 文章目录 一、iostat 命令二、/proc/diskstats 文件三、iotop 命令沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 在 Linux 系统上,iostat 和 iotop 这两个 IO 数据工具非常常用。它们都是性能分析领域中不可缺少的工具性软件。 一、iostat 命令…

mysql主从复制与读写分离

一,主从复制 1,为什么要做主从复制 单台mysql在安全性,高可用和高并发方面都无法满足实际的需求,所以可以选择配置多台主从数据库服务器以实现读写分离。 2,主从复制的原理 主从复制是为了保证数据的完整性&#xff0c…

界面控件DevExpress .NET应用安全 Web API v23.1亮点:支持Swagger模式

DevExpress拥有.NET开发需要的所有平台控件,包含600多个UI控件、报表平台、DevExpress Dashboard eXpressApp 框架、适用于 Visual Studio的CodeRush等一系列辅助工具。 DevExpress 今年第一个重要版本v23.1日前已正式发布了,该版本拥有众多新产品和数十…

javaee spring aop实现事务 项目结构

spring配置文件 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xmlns:context"http://www.springframewo…

最小生成树Kruskal、Prim算法C++

什么是最小生成树 连通图&#xff1a; 在无向图中&#xff0c;若从顶点v1到顶点v2有路径&#xff0c;则称顶点v1和顶点v2是连通的。如果图中任意一对顶点都是连通的&#xff0c;则称此图为连通图。 生成树&#xff1a; 一个连通图的最小连通子图称作为图的生成树。有n个顶点的…

ARM编程模型-寄存器组

Cortex A系列ARM处理器共有40个32位寄存器,其中33个为通用寄存器,7个为状态寄存器。usr模式和sys模式共用同一组寄存器。 通用寄存器包括R0~R15,可以分为3类: 未分组寄存器R0~R7分组寄存器R8~R14、R13(SP) 、R14(LR)程序计数器PC(R15)、R8_fiq-R12_fir为快中断独有 在不同模…

centos中得一些命令 记录

redis命令 链接redis数据库的命令 redis-cli如果 Redis 服务器在不同的主机或端口上运行&#xff0c;你需要提供相应的主机和端口信息。例如&#xff1a; redis-cli -h <hostname> -p <port>连接成功后&#xff0c;你将看到一个类似于以下的提示符&#xff0c;表…

手写Mybatis:第12章-完善ORM框架,增删改查操作

文章目录 一、目标&#xff1a;完善增删改查二、设计&#xff1a;完善增删改查三、实现&#xff1a;完善增删改查3.1 工程结构3.2 完善增删改查类图3.3 扩展解析元素3.4 新增执行方法3.4.1 执行器接口添加update3.4.2 执行器抽象基类3.4.3 简单执行器 3.5 语句处理器实现3.5.1 …

【Eclipse】Project interpreter not specified 新建项目时,错误提示,已解决

目录 0.环境 1&#xff09;问题截图&#xff1a; 2&#xff09;错误发生原因&#xff1a; 1.解决思路 2.具体步骤 0.环境 windows 11 64位&#xff0c;Eclipse 2021-06 1&#xff09;问题截图&#xff1a; 2&#xff09;错误发生原因&#xff1a; 由于我手欠&#xff0c;将…

YOLOV8实例分割——详细记录环境配置、自定义数据处理到模型训练与部署

前言 Ultralytics YOLOv8是一种前沿的、最先进的&#xff08;SOTA&#xff09;模型&#xff0c;它在前代YOLO版本的成功基础上进行了进一步的创新&#xff0c;引入了全新的特性和改进&#xff0c;以进一步提升性能和灵活性。作为一个高速、精准且易于操作的设计&#xff0c;YO…

搭建个人hMailServer 邮件服务实现远程发送邮件

文章目录 1. 安装hMailServer2. 设置hMailServer3. 客户端安装添加账号4. 测试发送邮件5. 安装cpolar6. 创建公网地址7. 测试远程发送邮件8. 固定连接公网地址9. 测试固定远程地址发送邮件 hMailServer 是一个邮件服务器,通过它我们可以搭建自己的邮件服务,通过cpolar内网映射工…

拓扑排序算法 -- dfs、bfs

210. 课程表 II 该题用到「拓扑排序」的算法思想&#xff0c;关于拓扑排序&#xff0c;直观地说就是&#xff0c;让你把⼀幅图「拉平」&#xff0c;⽽且这个「拉平」的图⾥⾯&#xff0c;所有箭头⽅向都是⼀致的&#xff0c;⽐如上图所有箭头都是朝右的。 很显然&#xff0c;如…

视频汇聚/视频云存储/视频监控管理平台EasyCVR部署后无法正常启用是什么问题?该如何解决?

安防监控/视频监控/视频汇聚平台EasyCVR能在复杂的网络环境中&#xff0c;将分散的各类视频资源进行统一汇聚、整合、集中管理&#xff0c;在视频监控播放上&#xff0c;视频云存储/安防监控汇聚平台EasyCVR支持多种播放协议&#xff0c;包括&#xff1a;HLS、HTTP-FLV、WebSoc…

物联网应用中蓝牙模块怎么选?_蓝牙模块厂家

在蓝牙模块选型前期&#xff0c;一定要了解应用场景以及需要实现的功能&#xff08;应用框图&#xff09;&#xff0c;以及功能实现过程中所能提供调用的接口&#xff08;主从设备&#xff0c;功能&#xff09;&#xff0c;考虑模块供电&#xff0c;尺寸&#xff0c;接收灵敏度…

“深入探究SpringMVC的工作原理与入门实践“

目录 引言1. 什么是SpringMVC?1.1. 模型1.2. 视图1.3. 控制器 2. SpringMVC的工作流程2.1. 客户端发送请求2.2. DispatcherServlet的处理2.3. 处理器映射器的使用2.4. 处理器的执行2.5. 视图解析器的使用2.6. 视图的渲染 3. SpringMVC的核心组件4. 弹簧MVC总结 引言 SpringMV…

GitHub打不开解决方法——授人以渔

打不开GitHub的原因之一&#xff0c;DNS地址解析到了无法访问的ip。&#xff08;为什么无法访问&#xff1f;&#xff09; 1、打开GitHub看是哪个域名无法访问&#xff0c;F12一下 2、DNS解析看对应的域名目前哪个IP可以访问 DNS解析的网址&#xff1a; &#xff08;1&#x…

3D开发工具HOOPS Publish如何快速创建交互式3D PDF文档?

HOOPS Publish是一款功能强大的SDK&#xff0c;可以创作丰富的工程数据并将模型文件导出为各种行业标准格式&#xff0c;包括PDF、STEP、JT和3MF。HOOPS Publish核心的3D数据模型是经过ISO认证的PRC格式(ISO 14739-1:2014)&#xff0c;它为装配树、拓扑和几何、产品制造信息和视…

将序数与比特币智能合约集成:第 1 部分

将序数与比特币智能合约集成&#xff1a;第 1 部分 最近&#xff0c;比特币序数在区块链领域引起了广泛关注。 据称&#xff0c;与以太坊 ERC-721 等其他代币标准相比&#xff0c;Ordinals 的一个主要缺点是缺乏对智能合约的支持。 我们展示了如何向 Ordinals 添加智能合约功…