Linux多线程基本概念

news2024/11/26 16:34:06

目录

​编辑

1.什么是进程,线程,并发,并行

优点

缺点

 什么资源是线程应该私有的呢

为什么线程切换成本更低呢

3.线程控制

pthread_create

 lpthread选项

 makefile

代码实现

 ps -aL

 什么是LWP

 轻量级进程ID与进程ID之间的区别

LWP与pthread_create创建的线程之间的关系

4.线程中止,等待,分离

pthread_exit函数

pthread_cancel函数 

 线程等待

pthread_join​编辑

 线程分离 

pthread_detach

5.线程互斥

进程线程间的互斥相关背景概念

 多线程共享资源访问的不安全问题(举个抢票的例子)

互斥量的接口


今天推荐一首歌曲 

黄昏      周传雄

依然记得从你口中~

说出再见坚决如铁~

昏暗中有种烈日灼身的错觉~

黄昏的地平线~

划出一句离别~

爱情进入永夜~

开始我们的学习吧!

1.什么是进程,线程,并发,并行

进程是资源分配的最小单位,有独立的地址空间和系统资源。
线程是cpu调度,程序执行的最小单位,一个线程只属于一个进程,而一个进程可以有多个线程,多个线程共享同一个进程的资源。在多核系统下允许几个线程各自独立的在处理器上运行,操作系统提供线程就是为了方便有效地实现这种并发性。

一切进程至少都有一个执行线程

线程在进程内部运行,本质是在进程地址空间内运行

在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化

多进程:同时运行QQ、微信、浏览器 , 多线程: 用浏览器同时进行浏览网页、播放视频)

并发是把cpu运行时间划分成若干个时间段,每个时间段再分配给各个线程执行, 当一个线程在运行时,其他线程处于挂起状态。

并行是同一时刻当一个cpu执行一个线程时,另一个cpu可以执行另一个线程,两个线程互不抢占cpu资源,是真正意义上的 不同线程在同一时刻同时执行

形象地解释:
进程是拥有一系列资源的集合,这些资源包括内存空间、内核对象、资源文件等等。我们将进程理解为一个工厂,工厂本身不能运作,需要有人来操作。那么这些工人就是线程,每一个工人操作自己的一台设备,这个设备就可以看成是线程的栈,他由这个工人自己使用。一个工厂里有多台设备时,如果只有一个人那么他就需要去一个个的去操作工厂里的设备,如果这些设备需要同时运行,那么这样操作效率太低。因此,工厂会多聘用几个工人,他们每个人操作自己的设备,这样效率就会大大提高。工人在操作设备时,可能两个人需要使用同一个工具,这个工具是全局的变量,因此他们可以共同访问,但是一个工人要去使用这个工具时,他会等在那里,等另一个人使用完,然后他就可以接过工具,继续干活了,这就是线程的同步。创建多个工厂就是多进程程序。工人操作的每台设备还是属于该工厂,因此线程是依附于进程的,占用进程的地址空间,线程之间也可以相互访问对方的地址,需要通过传址能实现,但是一般不会出现这样的情况,试想能有多大的机会在一个函数中访问另一个函数的的局部变量。在代码的实现中,我们可以将线程仅仅看成一函数去分析,只不过他是并发进行的。


工厂就是进程,工人就是线程,工厂所占的位置就是进程空间,工厂里的设备和工具就是数据和资源,多个工人同时工作就是多线程,几个工人要用同时使用一个工具就是线程同步。

重点

进程是资源分配的基本单位,线程是调度的基本单位


线程独有:栈,寄存器,信号屏蔽字,errno...等信息,因此各个线程各自有各自的栈区,但是堆区共用

任何一个线程都可以创建或撤销另一个线程

进程比线程安全的原因是每个进程有独立的虚拟地址空间

Linux内核中有没有真正意义上的线程呢?没有,linux用进程的PCB来模拟线程,是完全属于自己实现的一套方案!
站在CPU的角度来看,每一个PCB,都可以称之为轻量级进程,因为它只需要PCB即可,而进程承担分配的资源更多,量级更重!
Linux线程是CPU调度的基本单位,进程是承担分配系统资源的基本实体!

2.线程的优缺点

优点


缺点

 什么资源是线程应该私有的呢

重要的两点

线程的上下文结构也必须是线程的私有资源。(寄存器)
每个线程都有自己的私有栈结构

 

为什么线程切换成本更低呢

3.线程控制

在Linux下,PCB<=其他OS内的PCB(进程控制块) 

Linux下的进程统称为,轻量级进程

pthread_create

pthread_create是创建线程的一个接口

返回值:成功返回0;   失败返回错误码

 lpthread选项

(如果在编译时不带-lpthread选项,可以看到g++报错pthread_create()函数未定义,其实就是因为链接器链接不上具体的动态库,此时就可以看出来linux内核中并没有真正意义的线程,他无法提供创建线程的接口,而只能通过第三方库libpthread.so或libpthread.a来提供创建线程的接口。)

 makefile
//编写makefile  带上 -lpthread
test:test.cpp
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -f test
代码实现
#include <iostream>
#include <pthread.h>
#include <unistd.h>

using namespace std;

void *threadRoutine(void *args)//新线程回调在这里
{
while(true)
{
cout<<"新线程:"<<(char*)args <<" running..."<< endl;
sleep(1);
}

}

int main()
{
    pthread_t tid;  //创建线程
    pthread_create(&tid,nullptr,threadRoutine,(void*)"thread 1");
                                                    //进程的名字


//下面是主线程
while(true)
{
cout<<"main线程:"<<" running..."<< endl;
sleep(1);
}
}

 ps -aL

ps命令用于查看进程信息,其中-L选项用于查看轻量级进程信息
pthread_self() 用于获取用户态线程的tid,而并非轻量级进程ID
getpid() 用于获取当前进程的id,而并非某个特定轻量级进程

通过ps -aL就可以看到正在运行的线程有哪些,可以看到有两个标识符,一个是PID,一个是LWP(light weight process),所以CPU在调度那么多的PCB时,其实是以LWP作为每个PCB的标识符,以此来区分进程中的多个轻量级进程。


主线程的PID和LWP是相同的,所以从CPU调度的角度来看,如果进程内只有一个执行流,那么LWP和PID标识符对于CPU来说都是等价的,但当进程内有多个执行流时,CPU是以LWP作为标识符来调度线程,而不是以PID来进行调度。

 什么是LWP

LWP(轻量级进程)是操作系统中用于调度和管理线程的内核层面的实体。

LWP是轻量级进程,在Linux下进程是资源分配的基本单位,线程是cpu调度的基本单位,而线程使用进程pcb描述实现,并且同一个进程中的所有pcb共用同一个虚拟地址空间,因此相较于传统进程更加的轻量化

 轻量级进程ID与进程ID之间的区别

  1. 概念:

    • 进程ID(PID)是操作系统为每个正在运行的进程分配的唯一标识符。它是在进程创建时由操作系统分配的,并在整个进程的生命周期中保持不变。
    • 轻量级进程ID(LWP ID)是在多线程操作系统中,用于标识线程或轻量级进程(也称为执行上下文)的标识符。一个进程可以包含多个轻量级进程,并且每个轻量级进程都有自己的LWP ID。
  2. 操作方式:

    • 对于PID,通常可以使用系统调用(如fork()和exec())创建新进程或操作现有进程。
    • 对于LWP ID,通常使用线程库(如pthread)创建新线程或操作现有线程。一个进程的LWP ID只在该进程内部可见,对于其他进程来说是不可见的。
  3. 调度与资源分配:

    • 操作系统通过PID进行进程调度和资源分配。进程调度通常是基于运行队列中进程的优先级和调度算法来进行决策的。
    • 在多线程操作系统中,内核会将CPU时间划分给不同的LWP。调度和资源分配是基于LWP而不是整个进程进行的。
  4. 上下文切换:

    • 在进程切换时,操作系统需要保存和恢复整个进程的上下文,这包括进程的寄存器状态、打开的文件、堆栈等。
    • 在轻量级进程切换时,只需要保存和恢复当前线程的上下文,这是因为同一进程内的线程共享同一内存空间和打开的文件。

需要注意的是,LWP ID是在多线程操作系统中使用的概念,而PID是在所有操作系统中都存在的概念。在某些操作系统中,LWP ID可能与线程ID(TID)或任务ID(TID)等概念等效或相似。

LWP与pthread_create创建的线程之间的关系

LWP(轻量级进程)与pthread_create创建的线程之间存在一种关系,可以理解为LWP是内核层面对线程的调度和管理的实体,而pthread_create创建的线程则是用户层面对线程的抽象。

具体来说,pthread_create是一个线程库函数,用于在用户空间创建一个新的线程。这个线程由操作系统内核分配一个LWP,并将其标识为一个用户线程,也称为轻量级进程。该LWP会在它所属的进程中与其他LWP共享进程的地址空间、文件描述符等资源。

LWP与pthread_create创建的线程之间的关系可以总结如下:

  1. 一个进程可以包含多个LWP,每个LWP都有一个唯一的LWP ID。
  2. 每个LWP可以与一个或多个pthread_create创建的线程相关联,这些线程共享其所属进程的资源。
  3. 每个pthread_create创建的线程有自己的线程ID,并且在用户层面上可见,可以使用线程库提供的函数进行操作和管理。

需要注意的是,LWP的创建和管理是由操作系统内核完成的,而pthread_create函数是线程库提供的接口,它在内部会使用操作系统提供的系统调用来创建和管理LWP。因此,对于用户来说,他们只需要使用pthread_create接口来创建和操作线程,而无需直接与LWP进行交互。


4.线程中止,等待,分离

线程终止总共有三种方式,分别为return,pthread_exit,pthread_cancel

1. 从线程函数 return 。这种方法对主线程不适用 , main 函数 return 相当于调用 exit
(exit是中止进程的)
2. 线程可以调用 pthread_ exit 终止自己。
3. 一个线程可以调用 pthread_ cance l 终止同一进程中的另一个线程。
pthread_exit函数

那个线程调用pthread_exit函数, 那个线程就退出。俗称“谁调用谁退出” 

pthread_cancel函数 

在有多个线程的情况下,主线程调用pthread_cancel(pthread_self()), 则主线程状态为Z, 其他线程正常运行

主线程调用pthread_exit只是退出主线程,并不会导致进程的退出 

 线程等待

与进程类似,进程退出之后要被等待,也就是回收进程的资源,否则会出现僵尸进程,僵尸的这种状态可以通过ps指令+axj选项看到,同时会产生内存泄露的问题。


线程终止同样也需要被等待,但线程这里没有僵尸线程这样的概念,如果不等待线程同样也会造成资源泄露,也就是PCB资源未被回收,线程退出的状态我们是无法看到的,我们只能看到进程的Z状态。

pthread_join

原生线程库给我们提供了对应的等待线程的接口,其中join的第二个参数是一个输出型参数,在join的内部会拿到线程函数的返回值,然后将返回值的内容写到这个输出型参数指向的变量里面,也就是写到我们用户定义的ret指针变量里,通过这样的方式来拿到线程函数的返回值。

 线程分离 

若要进行分离,推荐创建完线程之后立马设置分离

pthread_detach

新创建出来的线程默认状态是joinable的,也就是说你必须通过pthread_join去等待线程,否则就会造成内存泄露。


但如果我们压根就不想等待线程,那调用pthread_join就是一种负担,这个时候我们就可以通过分离线程的手段,来告诉操作系统,现在我这个线程要和进程分离了,我不再共享进程的地址空间了,我也不要进程的任何资源了,我们俩人以后就形同陌路,互不相干了!操作系统你现在就把我回收吧,我已经和进程没有任何关系了!


所以在设置线程为分离状态后,操作系统会立即回收线程的所有资源,而不需要等待线程自动退出或者是手动来释放资源,表示我们现在已经不关心这个线程了!


joinable和detach是线程的两个对立的状态,一个线程不能既是joinable又是分离的,并且如果线程被设置为detach,那么就不可以用join来等待线程,否则是会报错的

int pthread_detach(pthread_t thread);

 

5.线程互斥
 

进程线程间的互斥相关背景概念

临界资源: 多线程执行流共享的资源就叫做临界资源
临界区: 每个线程内部,访问临界资源的代码,就叫做临界区
互斥: 任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
原子性: 不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

 多线程共享资源访问的不安全问题(举个抢票的例子)

假设现在有一份共享资源tickets,如果我们想让多个线程都对这个资源进行操作,也就是tickets- -的操作,但下面两份代码分别出现了不同的结果,上面代码并没有出现问题,而下面代码却出现了票为负数的情况,这是怎么回事呢?

其实问题产生就是由于多线程被调度器调度的特性导致的。

抢票代码 

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
#include<cstdio>
#include<iostream>

using namespace std;

//加锁保护
//pthread_mutex_t mtx =PTHREAD_MUTEX_INITIALIZER; 
//pthread_mutex_t  是原生线程库提供的一个数据类型


int tickets=3000;
//在并发访问的时候,会导致数据不一致的问题


void *gettickets(void *args)
{
	(void*)args;   
	         //每个线程内部,访问临界资源的代码,就叫做临界区
	
	while(true)
	{
		//pthread_mutex_lock(&mtx);  //加锁
		if(tickets>0)
		{
			usleep(10000);
			printf("%s:%d\n",(char*)args,tickets);
			tickets--;
	    // pthread_mutex_unlock(&mtx); //解锁
		}
		else
		{
	   // pthread_mutex_unlock(&mtx); 

			break;
		}
	}
	return nullptr;
}


int main()
{
	pthread_t t1,t2,t3;

    //线程创建
	pthread_create(&t1,nullptr,gettickets,(void*)"thread one");
	pthread_create(&t2,nullptr,gettickets,(void*)"thread two");
	pthread_create(&t3,nullptr,gettickets,(void*)"thread three");
	
	//线程等待
	pthread_join(t1,nullptr);
	pthread_join(t2,nullptr);
	pthread_join(t3,nullptr);


	return 0;
}

结果会出现负数(多线程在并发访问的时候,可能会导致数据不一致的问题)

要解决以上问题,需要做到三点:
代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区

要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量

谁持有锁谁才能进入临界区,你没有锁那就只能在临界区外面乖乖的阻塞等待,等待锁被释放,然后你去竞争这把锁,竞争到就拿着锁进入临界区执行代码,竞争不到就老样子,继续乖乖的在临界区外面阻塞等待

互斥量的接口
初始化互斥量有两种方法:
静态分配:
  pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

 动态分配: 

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t
*restrict attr);
参数:
mutex:要初始化的互斥量
attr:NULL

把上面的代码改成这样,就不会出现出现负数的问题,这里用的是静态分配 

//加锁保护
pthread_mutex_t mtx =PTHREAD_MUTEX_INITIALIZER; 

//pthread_mutex_t 是原生线程库提供的一个数据类型 ,静态


int tickets=3000;
//在并发访问的时候,会导致数据不一致的问题


void *gettickets(void *args)
{
	(void*)args;   
	         //每个线程内部,访问临界资源的代码,就叫做临界区
	
	while(true)
	{
		pthread_mutex_lock(&mtx);  //加锁
		if(tickets>0)
		{
			usleep(10000);
			printf("%s:%d\n",(char*)args,tickets);
			tickets--;
	     pthread_mutex_unlock(&mtx); //解锁
		}
		else
		{
	    pthread_mutex_unlock(&mtx); 

			break;
		}
	}
	return nullptr;
}

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

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

相关文章

【腾讯云云上实验室】用向量数据库—实践相亲社交应用

快速入口 &#x1f449;向量数据库_大模型知识库_向量数据存储_向量数据检索- 腾讯云 (tencent.com) 文章目录 前言1. 向量数据库概念及原理1.1 向量数据库概念1.2 向量数据库核心原理1.3 向量数据库优缺点1.4 向量数据库与传统数据库的区别 2. 腾讯云向量数据库的基本特性及优…

虹科方案 | 如何破解CAN与车载以太网之间数据传输和协议转换的难题?

导读&#xff1a;在车辆网络时代&#xff0c;数据传输和协议转换在通信领域中扮演着至关重要的角色。它们不仅能够实现车辆内部系统之间的互联互通&#xff0c;还支持车辆与外部网络进行通信&#xff0c;从而为驾驶者带来更智能、便捷的驾驶体验。本文将介绍CAN总线与车载以太网…

【JAVA届的一代神】——Spring框架体系及Spring IOC思想【面试常问!】

文章目录 Spring简介Spring体系结构SpringIOC控制反转思想自定义对象容器Spring实现IOCSpring容器类型容器接口容器实现类 对象的创建方式使用构造方法使用工厂类的方法使用工厂类的静态方法 对象的创建策略对象的销毁时机生命周期方法获取Bean对象的方式通过id/name获取通过类…

眼精星票证识别系统操作教程与技巧

眼精星票证识别系统是一款高效、准确的票证识别工具&#xff0c;可以帮助用户快速将各种票证识别成结构化数据&#xff0c;并支持批量合并导出Excel。该系统的使用非常简单&#xff0c;只需要按照以下步骤进行操作即可&#xff1a; 1. 下载并安装眼精星票证识别系统 来百度AP…

SSM图书捐赠网站系统开发mysql数据库web结构java编程计算机网页源码eclipse项目

一、源码特点 SSM 图书捐赠网站系统是一套完善的信息系统&#xff0c;结合springMVC框架完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库 &#xff0c;系统主要采用B/…

深入理解 Docker 核心原理:Namespace、Cgroups 和 Rootfs

来自&#xff1a;探索云原生 https://www.lixueduan.com 原文&#xff1a;https://www.lixueduan.com/posts/docker/03-container-core/ 通过这篇文章你可以了解到 Docker 容器的核心实现原理&#xff0c;包括 Namespace、Cgroups、Rootfs 等三个核心功能。 后续文章会演示如…

k8s中安装consul集群

一、准备知识 headless services一般结合StatefulSet来部署有状态的应用&#xff0c;比如kafka集群&#xff0c;mysql集群&#xff0c;zk集群等&#xff0c;也包括本文要部署的consul集群。 0、consul集群 consul集群的分布式协议算法采用的是raft协议&#xff0c;这意味着必…

UI彩虹外链网盘系统整站源码/PHP网盘与外链分享程序/整站+模版文件

源码简介&#xff1a; 全新UI彩虹外链网盘系统源码&#xff0c;它是PHP网盘与外链分享程序&#xff0c;提供了整站模版文件&#xff0c;前后端美化模板。 彩虹外链网盘美化模板是一款专为PHP网盘和外链分享程序设计的模板。它具备多种功能&#xff0c;包括支持所有格式文件的…

记录本地与服务器之间数据传输方法(上传、下载文件)

文章目录 一、使用scp命令实现参数说明示例说明 二、使用工具实现windows系统苹果系统如有启发&#xff0c;可点赞收藏哟~ 一、使用scp命令实现 scp 是 secure copy &#xff08;安全复制&#xff09;的缩写, scp 是基于 ssh 登陆进行安全的远程文件拷贝命令。相当于 cp 命令 …

ChatGPT进阶:提示工程的神秘面纱与实战指南

文章目录 一、提示工程的概念与原理二、提示工程的实践方法三、提示工程的挑战与展望四、实战案例分析总结《ChatGPT进阶&#xff1a;提示工程入门》内容简介作者简介陈颢鹏&#xff1a;李子菡&#xff1a; 目录获取方式 在人工智能领域&#xff0c;对话系统已经成为了一个热门…

数智赋能 锦江汽车携手苏州金龙打造高质量盛会服务

作为一家老牌客运公司&#xff0c;成立于1956年的上海锦江汽车服务有限公司&#xff08;以下简称锦江汽车&#xff09;&#xff0c;拥有1200多辆大巴和5000多辆轿车&#xff0c;是上海乃至长三角地区规模最大的专业旅游客运公司。面对客运市场的持续萎缩&#xff0c;锦江汽车坚…

不用再羡慕“SpaceX“ 民营星际荣耀打造国产可回收火箭双曲线二号

大家好&#xff0c;我是极智视界&#xff0c;欢迎关注我的公众号&#xff0c;获取我的更多前沿科技分享 邀您加入我的知识星球「极智视界」&#xff0c;星球内有超多好玩的项目实战源码和资源下载&#xff0c;链接&#xff1a;https://t.zsxq.com/0aiNxERDq 大家似乎习惯了对马…

PDF不小心关闭没保存?这4个方法教你拯救文件!

“我刚刚在查看一个PDF文件&#xff0c;但是一不小心我就把它关闭了&#xff0c;而且我还忘记保存了。这可怎么办呢&#xff1f;PDF不小心关闭没保存应该怎么解决呢&#xff1f;还有办法恢复吗&#xff1f;” PDF文档是我们日常工作和学习中常用的文件格式&#xff0c;但有时候…

QQ录屏保存到哪了?教你快速找到保存位置

qq录屏是许多用户常用的屏幕录制工具&#xff0c;可是一旦录制结束&#xff0c;许多人不清楚如何找到和管理录制的视频文件。那么&#xff0c;您知道qq录屏保存到哪了吗&#xff1f;本文将深入研究qq录制视频功能&#xff0c;以帮助您了解如何存储和管理这些重要的录制视频文件…

【Android Gradle】之一小时 Gradle及 wrapper 入门

&#x1f604;作者简介&#xff1a; 小曾同学.com,一个致力于测试开发的博主⛽️&#xff0c;主要职责&#xff1a;测试开发、CI/CD 如果文章知识点有错误的地方&#xff0c;还请大家指正&#xff0c;让我们一起学习&#xff0c;一起进步。 &#x1f60a; 座右铭&#xff1a;不…

【算法萌新闯力扣】:合并两个有序链表

力扣题目&#xff1a;合并两个有序链表 开篇 今天是备战蓝桥杯的第24天及算法村开营第2天。根据算法村的讲义&#xff0c;来刷链表的相关题目。今天要分享的是合并两个有序链表。 题目链接: 21.合并两个有序链表 题目描述 代码思路 通过创建一个新链表&#xff0c;然后遍历…

DEM分析

一、实验名称&#xff1a; DEM分析 二、实验目的&#xff1a; 通过本实验练习&#xff0c;掌握DEM的建立与应用基本方法。 三、实验内容和要求&#xff1a; 实验内容&#xff1a; 利用ARCGIS软件相关分析工具及实验数据&#xff0c;创建DEM&#xff0c;并计算相应坡度的区…

Android 单元测试初体验

Android 单元测试初体验 前言一、单元测试是什么&#xff1f;二、简单使用1.依赖2.单元测试代码简单模版及解释 总结 前言 当初在学校学安卓的时候&#xff0c;老师敢教学进度&#xff0c;翻到单元测试这一章节的时候提了两句&#xff0c;没有把单元测试当重点讲&#xff0c;只…

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《考虑碳排放分摊的综合能源服务商交易策略》

这篇文章的标题表明它将讨论一个关于综合能源服务商交易策略的主题&#xff0c;而在这个策略中&#xff0c;特别考虑了碳排放分摊的因素。以下是对标题中各关键词的解读&#xff1a; 综合能源服务商&#xff1a; 这指的是在能源领域提供多种服务的企业或组织&#xff0c;可能涵…

https到底把什么加密了?

首先直接说结论&#xff0c; https安全通信模式&#xff0c;是使用TLS加密传输所有的http协议。再重复一遍&#xff0c;是所有&#xff01; 通常将TLS加密传输http这个通信过程称为https&#xff0c;如果使用协议封装的逻辑结构来表达就是&#xff1a; IP TCP TLS 【 HTTP 】…