学习网络编程No.5【TCP套接字通信】

news2024/11/13 15:20:56

引言:

北京时间: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.coloradmin.cn/o/959674.html

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

相关文章

行业追踪,2023-09-01

自动复盘 2023-09-01 凡所有相,皆是虚妄。若见诸相非相,即见如来。 k 线图是最好的老师,每天持续发布板块的rps排名,追踪板块,板块来开仓,板块去清仓,丢弃自以为是的想法,板块去留让…

云原生架构:在云环境中构建弹性应用

随着云计算技术的快速发展,云原生架构已经成为现代软件开发的热门话题。作为一种在云环境中构建和运行应用程序的方法论,云原生架构强调弹性、可扩展性和灵活性,使开发者能够更好地应对复杂的业务需求。本文将深入探讨云原生架构的核心概念、…

Screaming Frog SEO Spider,为您的网站提供全方位的优化解决方案

Screaming Frog SEO Spider是一款适用于Mac的软件,它可以帮助用户分析网站的优化信息。该软件可以模拟蜘蛛爬行的方式,抓取网站的各种信息,并将这些信息整理成易于理解的报告。这些报告可以帮助用户评估网站的优化情况,发现链接的…

【Kali Linux】高级渗透测试实战篇

这里写目录标题 前言内容简介读者对象随书资源目录 前言 对于企业网络安全建设工作的质量保障,业界普遍遵循PDCA(计划(Plan)、实施(Do)、检查(Check)、处理(Act&#xff…

排序算法的稳定性

稳定性:对于一个数,经过多次排序,保留一个数之间的相对次序 在基础类型数据上,稳定性用处不大 在非基础类型上,可以做到对于相同元素来说,排完序相同元素之间的相对次序不变 归并排序在merge的过程中先拷贝…

TL6478(TI TMS320C6748 DPS)EVM开发板技术讲座 第二讲:USB转串口驱动安装

在开展我们的TL 6748 DSP开发版开发之前,需要先安装usb转串口驱动,才能使得我们的电脑上查询到该设备,使用该设备。底板上存在UART1、UART2,将评估板的 UART2 RS232 调试串口通过 RS232 交叉串口母母线、USB 转 RS232 公头串口线连接至 PC 机的 USB 接口。1. 驱动下载 USB…

制作广告宣传片需要注意什么

广告宣传片可以通过生动的图像、音乐和文字来展示产品或服务的独特卖点,吸引潜在客户的注意力。它可以突出产品或服务的特点,使其在竞争激烈的市场中脱颖而出。在制作广告宣传片时,有几个关键要点需要注意,深圳广告宣传片制作公司…

Java 复习笔记 - 基础篇

文章目录 Java 跨平台原理高级语言的编译运行方式 Java JRE和JDKJava 注释(一)什么是注释?(二)注释的分类(三)注释的使用细节 Java 关键字(一)什么是关键字?&…

Android Activity 启动流程 二:setContentView

关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 ,擅长java后端、移动开发、商业变现、人工智能等,希望大家多多支持。 目录 一、概览二、setContentView()三…

Docker切换文件系统为VFS

一、介绍 Docker支持AUFS、Btrfs、Device Mapper、OverlayFS、VFS、ZFS六种不同的存储驱动。 1. AUFS AUFS是一种常见的存储驱动程序,它也使用了Linux内核的AUFS文件系统。它的优点是支持所有的Linux发行版,可以在不同的容器之间共享文件系统&#xf…

重大工程建造云服务平台源码 SpringCloud+Vue

技术架构: 微服务JavaSpring Cloud VueUniApp MySql 开发语言:Java 开发工具:Idea 前端框架:Vue 后端框架:Spring Cloud 数 据 库:MySql 移 动 端:UniApp 系统端口:PC端&…

Vue2 集成 CodeMirror 实现公式编辑、块状文本编辑,TAG标签功能

效果图 安装codemirror依赖 本示例为Vue2项目&#xff0c;安装低版本的依赖 npm i codemirror5.65.12 npm i vue-codemirror4.0.6 实现 实现代码如下&#xff0c;里边涉及到的变量和函数自行替换即可&#xff0c;没有其他复杂逻辑。 <template><div class"p…

排盘程序算法探寻举例(陆先生八字)

算法实现&#xff1a; 1.庚生未月&#xff0c;燥土不能生金&#xff0c;日支申金为日主墙根&#xff0c;月干辛金比劫透出傍身&#xff0c;月干强。年干甲木自做寅木强根&#xff0c;又得月支乙木中气&#xff0c;甲木强旺有力&#xff0c;时干丙火七杀得未土余气&#xff0c;…

VMware Aria Operations SSH 身份验证绕过漏洞 (CVE-2023-34039)

zhi.oscs1024.com​​​​​ 漏洞类型身份验证不当发现时间2023-08-30漏洞等级严重MPS编号MPS-d9wr-56qmCVE编号CVE-2023-34039漏洞影响广度广 漏洞危害 OSCS 描述VMware Aria Operations for Networks 是 VMware 公司提供的一款网络可视性和分析工具&#xff0c;用于优化网络…

链表形式队列

&#x1f308;队列相关概念 1.储存了数据的节点从一端&#xff08;队尾&#xff09;进入队列&#xff08;入队尾插&#xff09;&#xff0c;从另一端&#xff08;队头&#xff09;出队列&#xff08;出队头删&#xff09;&#xff0c;先进先出。进行插入操作的一端称为队尾&am…

软件测试Day4|软件测试理论02

目录 6. 测试用例基础6.1 测试用例的定义6.2 测试用例要素6.3 测试用例设计和编写的作用 7. 黑盒测试用例设计方法7.1 用例设计方法分类7.2 测试数据选择7.2.1 等价类划分&#xff08;1&#xff09;等价类划分原理&#xff08;2&#xff09;确定等价类的原则&#xff08;3&…

群晖DS923+扩展ECC 64G内存

1 有必要上64G吗&#xff1f; 如果你不运行大型应用以及安装的套件不多&#xff0c;并且不使用虚拟机&#xff0c;确实没有太大必要。 但是大内存除了这些用处&#xff0c;还会被系统作为缓存使用。在资源监控中查看内存结构&#xff0c;虽然内存利用率只有4%&#xff0c;但缓存…

企业供应链数字化怎么做?企业数字化供应链流程落地方式

什么是供应链&#xff1f;简单来说&#xff0c;供应链是围绕客户需求&#xff0c;以提高产品流通各个环节的效率为目标&#xff0c;通过资源整合的方式来实现产品从设计、生产到销售、服务整个环节的组织形态。如同人工智能、区块链、5G等技术的发展带来的各种行业变化&#xf…

mov怎么改成mp4?跟我一起操作吧

mov怎么改成mp4&#xff1f;mov因为并不是一种常见的视频文件格式&#xff0c;因此大家对这种视频文件可能知道的并不多&#xff0c;但如果你是用的是苹果手机&#xff0c;那么你会发现苹果手机拍摄的视频转移到电脑上后就是mov格式的&#xff0c;因为mov格式的视频并没有受到大…

运算放大器典型应用(一)

这里写目录标题 一、反向比例运算电路怎么优化&#xff1f;平衡电阻的讨论 二、同向比例运算电路三、电压跟随器重要事项 四、加法运算电路反向加法同向加法 五、减法运算电路专用减法器 六、积分电路微分电路七、对数指数运算电路八、测量放大电路&#xff08;仪表放大电路&am…