自定义博客皮肤VIP专享

*博客头图:

格式为PNG、JPG,宽度*高度大于1920*100像素,不超过2MB,主视觉建议放在右侧,请参照线上博客头图

请上传大于1920*100像素的图片!

博客底图:

图片格式为PNG、JPG,不超过1MB,可上下左右平铺至整个背景

栏目图:

图片格式为PNG、JPG,图片宽度*高度为300*38像素,不超过0.5MB

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+
  • 博客(78)
  • 收藏
  • 关注

原创 传输层协议——TCP协议

TCP协议既要保证可靠性,同时又尽可能的提高通信效率。可靠性:检验和。序列号。确认应答。超时重传。连接管理。流量控制。拥塞控制。提高效率:滑动窗口。快重传。延迟应答。捎带应答。

2024-05-16 09:50:01 1032 39

原创 传输层协议——UDP协议

当主机从网络中获取到数据后,需要自底向上进行数据的交付,而这个数据最终应该交给上层的哪个应用软件进程,就是由该数据当中的目的端口号来决定的。因为最终我们需要将数据交给应用层,而且是通过传输层将最终数据交给应用层的,所以传输层必定能够提取出相应的端口号,从而知道应该将数据交付给应用层的哪一个进程。在传输层,将报头和有效载荷分开后,提取出报头中的16位UDP长度,用其减去报头固定长度8字节,就是有效载荷的大小。比如HTTP,FTP,SSH等这些广为使用的应用层协议,它们的端口号都是固定的。

2024-05-10 10:11:38 915 36

原创 应用层协议——http协议和https协议

通常我们程序员在网络编程的时候,一般是处于应用层的。而我们的http和https协议就是典型的应用层协议。

2024-05-06 09:19:45 970 39

原创 协议的定制之序列化与反序列化 | 守护进程

a,+,b三个数据组成一组结构化的数据,我们首先想到使用一个结构体将三个数据打包,一起发送给服务器,而我们所用的各种通信函数只允许我们传输字符串,所以我们必须要将结构化的数据先转化成字符串,然后才能发送给服务器。对于网络计算器的客户端,我们使用一个结构体去存储左右操作数和操作符,这个结构体我们称为请求(Request),在向服务端发送请求的时候,我们需要转成字符串才能发送,于是我们需要有序列化的函数,当然,收到结果后,我们也需要反序列化函数,将字符串结果转成结构化结果。所以说,守护进程也是一种孤儿进程。

2024-04-22 12:30:04 1073 51

原创 网络编程套接字(三)之TCP服务器简单实现

所以我们发现,当其中一个客户端在和服务器进行通信的时候,另一个客户端并不能与服务器通信,也就是说服务器在某一时刻只能向一个客户端提供服务,只有对一个客户端提供服务完成后,才能对下一个服务器提供服务。第一步,创建套接字使用的函数和UDP完全一样,只不过我们需要把socket的第二个参数由基于数据报的SOCK_DGRAM 更改为基于字节流式的SOCK_STREAM,SOCK_STREAM提供的就是一个有序的、可靠的、全双工的、基于连接的流式服务。而服务器首先需要做的就是进行监听,等待客户端的连接。

2024-04-19 10:00:00 1377 46

原创 网络编程套接字(二)之UDP服务器简单实现

注:实际上,一款网络服务器不建议指明一个IP,也就是不要显示地绑定IP,因为一个服务器上可能会有多张网卡,所以IP可能不止一个,如果只绑定一个明确的IP,最终的数据可能用别的IP来访问端口号,这就无法访问,所以真实的服务器IP一般采用INADDR_ANY(全0,任意地址)代表任意地址bind。首先,我们在该文件中,将服务器封装成一个类,而作为一款服务器,必须要有自己的端口号,同时网络服务器需要有对应的IP地址,文件描述符sock_:进行各种各样的数据通信,在类内进行读写操作。

2024-04-15 09:14:04 1901 49

原创 网络编程套接字(一)

因为在现在的互联网世界,每台主机的IP地址都是唯一的,而且是全球唯一的,所以一个IP地址能够标识世界上一个唯一的一个主机。举个例子,你点开了抖音客户端,就相当于在手机上启动了一个进程,那么字节跳动公司的服务器将会把抖音的内容,通过网络传输给你的手机,但是不会仅仅传到手机上就好了,而是会将信息传给手机上的抖音客户端,也就是你启动的一个进程!所以说,通过全网唯一IP地址能够找到唯一的主机,然后通过端口号能够标识网络上的某一台主机的某一个进程,那么我们通过(IP地址,端口号)的组合,就能够找到全网唯一的进程了!

2024-04-10 10:00:00 1505 52

原创 网络基础知识入门

通过信号的 “频率” 和 “强弱” 来表示 0 和 1 这样的信息,而不同的计算机厂商,有的可能用高频率表示0,低频率表示1,有的厂商则相反,这样的话计算机之间就很难通信了,所以不同的计算机之间要想传递各种不同的信息,计算机厂商就需要约定好双方的数据格式,这就是一种在物理层上的协议。TCP/IP是因特网的正式网络协议(后面会讲),是一组在许多独立主机系统之间提供互联功能的协议,规范因特网上所有计算机互联时的传输、解释、执行、互操作,解决计算机系统的互联、互通、操作性,是被公认的网络通信协议的国际工业标准。

2024-04-07 17:59:00 2090 42

原创 Linux之 线程池 | 单例模式的线程安全问题 | 其他锁

如果临界区的代码执行时间较短的话,我们一般就最好使用自旋锁,而不是互斥锁,因为互斥锁申请失败,是要阻塞等待,是需要发生上下文切换的,如果临界区执行的时间比较短,那可能上下文切换的时间会比临界区代码执行的时间还要长。3、自旋锁:说到自旋锁,我们不得不说一说我们之前所用到的锁,我们之前所用的锁都是互斥锁,当线程没有竞争到互斥锁时,它会阻塞等待,只有等锁被释放了后,才能去重新申请锁。于是,我们可以通过线程池预先创建出一批线程,线程池维护着这些线程,线程等待着监督管理者分配可并发执行的任务。如果相等则用新值更新。

2024-04-01 13:19:01 1744 67

原创 Linux之信号量 | 消费者生产者模型的循环队列

我们在对环形队列进行访问时,当队列为空或者为满,生产者和消费者就会指向同一个位置,这时我们就需要生产者和消费者互斥和同步了,如果为空,让生产者先访问,为满就让消费者先访问。实际上并不是真正的环形队列,因为我们没有这种数据结构,它的实现是通过数组模拟的,当数据加入到最后的位置时直接模等于数组的大小即可,这样就可以回到数组的起始位置。但是,我们最理想的方案,其实是:如果在同一时刻,不同的执行流要访问的是临界资源的不同区域,那么我们是允许它们同时进行临界资源的访问,这样就大大提高了效率。这样就可以提高效率了。

2024-03-28 10:00:00 1284 57

原创 Linux生产者消费者模型之阻塞队列

生产者消费者模型是高效的。其高效体现在一个线程拿出来任务可能正在做处理,它在做处理的同时,其他线程可以继续从队列中拿任务,继续处理,所以其高效是我们可以让多个线程并发的同时处理多个任务!生产者线程也可以不断地并发地派发任务。

2024-03-25 09:21:08 1218 47

原创 Linux之线程同步

此时我们会发现唤醒这三个线程时具有明显的顺序性,因为这些线程启动时默认都会在该条件变量下去等待,而我们每次都唤醒的是在当前条件变量下等待的头部线程,当该线程执行完代码后会继续排到等待队列的尾部进行等待。这是因为如果个别线程的竞争力特别强,每次都能够申请到锁,但申请到锁之后什么也不做,所以在我们看来这个线程就一直在申请锁和释放锁,那么它就可以一直抢票。为了解决这个问题,我们增加一个限制:当一个线程释放锁后,这个线程不能立马再次申请锁,该线程必须排到这个锁的资源等待队列的最后。3、唤醒线程去访问临界资源。

2024-03-19 10:00:00 1644 67

原创 Linux之线程互斥

接着,判断%al的内容 >0,返回,成功拿到锁。可是,访问临界资源时,多个线程要申请同一把锁,那么就必须要能够看到同一把锁,那么这个锁不就成了一个临界资源了吗,那锁是怎么保证自己的安全的呢?发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请锁,但没有竞争到互斥量,那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁,再去申请锁。大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。

2024-03-15 10:00:00 2118 61

原创 Linux之线程控制

注:在Linux中,线程与内核的LWP是一一对应的,实际上操作系统调度的时候是根据LWP调度的,而不是PID,只不过我们之前接触到的都是单线程进程,其PID和LWP是相等的,所以对于单线程进程来说,调度时采用PID和LWP是一样的。如果主线程不对新线程进行等待,那么这个新线程的资源也是不会被回收的。一个线程如果被分离了,这个线程依旧要使用该进程的资源,依旧在该进程内运行,甚至这个线程崩溃了一定会影响其他线程,只不过这个线程退出时不再需要主线程去join了,当这个线程退出时系统会自动回收该线程所对应的资源。

2024-03-11 10:00:00 1440 49

原创 Linux之线程概念

在语言中,我们知道,用户自己申请的空间是存在于地址空间的堆区上的。可是,堆区是一整块空间,我们每次申请只是申请了其中的一小块,并且我们只是说明了申请空间的大小,拿到的是空间的起始地址。如果,我们多次申请了空间,那么我们怎么知道第一次申请的空间是从堆区哪里到哪里呢,第二次申请的空间是从堆区哪里到哪里呢?于是os就必须对堆区进行更加精细的管理。

2024-03-07 10:00:00 1692 51

原创 Linux之进程信号

从理论上来说,是可以的,因为内核具有最高的执行权限。但是,我们不能这样做。因为如果在用户自定义的捕捉函数里面有非法操作,比如清空数据,如果在内核态执行这样的代码,后果将不堪设想。所以,不能让操作系统直接去执行用户的代码。

2024-03-04 10:00:00 2056 53

原创 Linux之进程间通信(system V 共享内存)

进程间通信的本质就是让不同的进程看到同一个资源。而前面我们讲到了进程通信的最基础,最传统的方法——管道。我们知道了,无论是匿名管道还是命名管道,它们让不同进程看到同样的资源的方法,就是通过访问同样的文件来看到同样的资源。进程间是相互独立的,因此进程的各种数据是存储在物理内存的不同区域的。那么,如果两个不同的进程能够访问到同一块内存空间,是不是就相当于看到了同样的资源。那么有没有这样的方法呢?答案是肯定的,system V中的共享内存就是这样的一种进程间通信的方法。

2024-01-28 09:57:31 1784 102

原创 Linux之进程间通信(管道)

什么是管道?1、管道是Unix中最古老的进程间通信的形式。2、我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。管道只能进行单向通信,用来传输资源:数据。我们按下面的步骤来分析:1、首先,父进程分别以读和写的方式,打开了一个文件。2、然后,父进程fork创建了一个子进程,我们知道,子进程会继承父进程,指向同一个文件。3、我们规定,父进程对文件写入,子进程对文件读取。关闭父子进程各自不需要的功能。即:父进程关闭读的文件描述符,子进程关闭写的文件描述符。

2024-01-21 10:48:35 2448 66

原创 Linux之静态库和动态库

静态库和动态库下面我们将从工程师的角度,去了解静态库和动态库的形成过程,以及实现它们的制作。并且了解如何将自己的库交给别人,让别人也可以使用。

2024-01-14 10:22:19 2593 67

原创 Linux之简单的Shell命令行解释器

我们前面学习了进程创建,进程终止,进程等待,进程替换,通过这些内容我们可以来进行实现简单的shell命令行解释器。下面我们直接来看一看如何去实现shell命令行解释器。

2024-01-08 10:25:29 2082 51

原创 Linux之文件系统与软硬链接

而inode其实是一个结构体,它的里面除了文件的各种属性信息外,还有一个int blocks数组,该数组里保存的数字,就是对应的data block的编号,通过这个数组,我们就可以找到属于该文件的所有内容。在inode bitmap中对应的比特位由0置为1,找到其inode table,把属性填进去,文件的数据写到block里,再inode和block建立映射关系,然后block bitmap中对应的比特位由0变为1,接着文件名和inode编号建立映射关系,最后返回inode编号,创建成功。

2024-01-04 10:30:49 2403 52

原创 Linux之缓冲区的理解

缓冲区的本质就是一段内存空间。我们知道,内存的速度比磁盘的速度快了几个数量级。所以数据如果直接从内存写到磁盘,那么访问外设效率比较低,那就太消耗时间了。所以缓冲区的意义就是通过减少与外设的IO次数,来节省进程进行数据IO的时间。所以C语言中就提供了缓冲区。而有了缓冲区的存在,可以提高整机效率,并提高用户的响应速度。

2023-12-30 11:53:28 2187 49

原创 Linux之基础I/O

而在操作系统层面,比如在Linux下,只认fd,而且只有操作系统才能直接访问文件,那么各种语言为了既方便用户使用,又要遵循操作系统的规则,必定在FILE结构体里面封装了fd,这样才能在系统层面去使用文件。当进程打开一个文件时,该文件从磁盘当中加载到内存,形成对应的struct file,OS将该struct file连入文件双链表,并将该结构体的首地址填入到fd_array数组当中下标为3的位置,使得fd_array数组中下标为3的指针指向该struct file,最后返回该文件的 fd 给进程。

2023-12-25 11:53:27 4778 56

原创 Linux之进程(五)(进程控制)

比如:有的数据是只读的,所以父子进程完全可以共享,而有的数据又可写,为了不让父进程数据的修改影响子进程或者子进程数据的修改影响父进程,我们要写时拷贝。第一个参数是要执行程序的路径,第二个参数是一个指针数组,数组当中的内容表示你要如何执行这个程序,数组以NULL结尾,第三个参数是你自己设置的环境变量。实际上main函数只是用户级别代码的入口,main函数也是被其他函数调用的,所以当main函数调用结束后就应该给操作系统返回相应的退出信息,而这个所谓的退出信息就是以退出码的形式作为main函数的返回值返回的。

2023-12-21 10:00:00 2864 45

原创 Linux之进程(四)(进程地址空间)

其实,程序在编译时,编译器就会给每一个变量、每一个函数都编写一个地址,这个地址就是虚拟地址。当程序被加载到物理内存中,虚拟地址会被填写进页表的左侧。然后将一个代码加载到内存的什么位置的这个物理地址填写到页表的右侧。这就是映射关系的构建。我们通常所说的地址,一般都是虚拟地址。而不是内存中的物理地址。我们在语言中使用的new,malloc等申请的空间在本质上也都是申请的虚拟地址空间,而不是物理内存空间。

2023-12-17 15:00:22 646 36

原创 Linux之进程(三)(环境变量)

在命令行上运行./mytest_g的时候,bash是一个系统进程,mytest_g会变成一个进程,是bash的子进程,而环境变量具有全局属性的根本原因是会被子进程继承下去,因为环境变量定义给bash,而子进程会全部继承下去,这就被称为环境变量。再比如,我们使用的各种系统命令(ls, grep等),我们都知道其实它们是存在一些文件中的,我们在使用时也可以通过完整的路径去使用它们。要执行一个程序,那就需要先找到这个程序,怎么去找,这时候就需要带上路径,如我们经常使用的 ./ 的方式。这两个是命令行参数。

2023-12-12 10:00:00 499 29

原创 Linux之进程(二)

优先级:进程获取cpu资源分配的先后顺序,就是指进程的优先权。优先权高的进程有优先执行权利。为什么有优先级?优先级存在的主要原因就是CPU资源是有限的,一个CPU一次只能运行一个进程。如果进程太多,需要通过优先级来竞争资源。所以需要存在进程的优先级,来确定进程获取CPU资源的先后顺序。

2023-12-06 12:18:34 371 23

原创 Linux之进程(一)

肯定不是的,这样做没有任何意义。如果系统中存在许多进程,而有部分进程在短期内不会被调度,代码和数据在短期内不会被执行,此时如果内存空间不足,操作系统就可以把代码和数据暂时保存到磁盘上,节省一部分空间,该进程暂时被挂起了,这就是挂起状态。例如,某一进程要求对磁盘进行写入操作,那么在磁盘进行写入期间,该进程就处于深度睡眠状态,是不会被杀掉的,因为该进程需要等待磁盘的回复(是否写入成功)以做出相应的应答。1、进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。

2023-12-06 12:18:18 364 19

原创 冯·诺依曼体系结构和操作系统

1、CPU读取数据(数据+代码)都是要从内存中读取。站在数据的角度,CPU不和外设直接交互。2、CPU要处理数据,需要先将外设中的数据,加载到内存。站在数据的角度,外设只和内存直接交互。3、不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)。

2023-11-27 10:14:30 591 30

原创 二叉树进阶OJ题

当cur不为空或者栈不为空的时候(一开始栈是空的,cur不为空),循环继续:先把左路节点存放进栈中,同时把值存入v中,一直循环,直到此时的左路节点为空,访问结束。如p,q在同一侧,那么该节点一定不是p,q的公共祖先,继续递归寻找,如果p,q在左子树,就到左子树中找,想反的就去右子树中找。首先,我们要明确的是:第一层只有一个节点,所以第一个数组只有一个数,所以只循环一次;思路:我们定义一个栈,在栈里面取到一个节点时:右子树是否访问过,如果没有访问,迭代子问题访问,如果访问过了,则访问这个根节点,pop出栈。

2023-11-27 10:12:23 260 18

原创 调试器gdb

1、程序的发布方式有两种,debug模式和release模式,而想进行调试只能在发布方式为debug版本下。2、Linux 通过gcc/g++编译出来的二进制程序,默认是release模式,这也就意味着程序无法调试。3、在Linux下要使用gdb调试,必须在源代码生成二进制程序的时候,加上 -g 选项。至此,所有Linux下的基础工具都已经学完了。有了这些工具,我们可以在Linux下进行代码的编写,编译和调试。使我们在Linux下编写代码显得十分方便。

2023-11-26 08:00:00 385 13

原创 软件包管理器yum和git

手机端的用户就可以通过应用商店,通过点击搜索、下载安装,其实就是请求服务器上的某个可执行程序或安装包,将它下载到你的手机上,然后安装,就有了相应的APP。在Linux下,我们也不知道我们需要的工具在哪里,所以Linux社区以及对应的开发人员开发出来的Linux工具会发布到服务器中,而yum的作用就相当于上面的应用商店,通过yum,我们可以搜索各种Linux下的工具,然后下载安装。yum安装软件只能一个装完了再装另一个,正在使用yum安装一个软件的过程中,如果再尝试用yum安装另外一个软件,yum会报错。

2023-11-23 10:00:00 404 22

原创 项目自动化构建工具——make/Makefile

一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。这就和缓冲区有关了。而非伪目标,并不能总是执行成功,比如:上面的例子,一旦你make好了,即生成了mytest后,除非你修改了test.c,不然你是无法再重新make的。2、如果找到,它会从上到下按顺序找文件中的第一个目标文件,在上面的例子中,他会找到“mytest”这个文件,并把这个文件作为最终的目标文件。

2023-11-20 08:00:00 405 14

原创 编辑器vim和编译器gcc/g++

答案就是:系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函数“printf”了,而这也就是链接的作用。我们来看一个问题:在我们的C程序中,并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实“printf”函数的呢?该模式是我们后面用的最频繁的编辑模式。

2023-11-15 08:00:00 594 18

原创 Linux中的权限

举个例子:司机不一定懂得汽车的构造和运行原理,所有汽车的设计者就将这些东西用一个外壳包装起来,用普通人容易掌握的方向盘和挡位来和汽车的发动机等设备进行“交互”,这样普通人也可以驾驶结构和运行原理复杂的汽车了。既然有多个用户在同一个目录下能够创建文件,那就说明这些用户对该目录有写的权限,而有了对该目录的写的权限后,我们自然而然就有了对该目录下的所以文件进行删除的权限。我辛辛苦苦写好的文件,别人只要动一动键盘,就可以在不经过我的同意下删除它,这当然是不被允许的。如:u对该文件有读、写的权限,没有执行的权限。

2023-11-08 11:11:52 535 18

原创 Linux的常见指令(三)

但是上图中我们没有紧跟文件名,那么cat就会这样执行:我们输入什么,它就显示什么(本质上就是从键盘输入了内容)。前面我们知道了使用head和tail分别可以取文件前面和后面的内容,那么如果我们要取到中间一部分的内容,该怎么办呢?首先当前路径下最先是没有 file.txt 文件的,但是我们输入了 1 命令后就有了该文件,而且文件内容是 1 中输入的内容。上图 ”>“ 符号的作用是1、创建没有的文件(如果有文件就直接写)2、本来应该显示到显示器的内容,被写入到了文件中(覆盖式写入)。默认会匹配文本中的关键字。

2023-11-08 11:02:16 460 15

原创 Linux的常见指令(二)

功能: 用于查看日历等时间信息,如只有一个参数,则表示年份,即显示该年的所有月份,如有两个参数,则表示月份和年份。但是使用 less 时,就可以使用 ”上下键“ 等按键的功能来往前往后翻看文件,更方便用来查看一个文件的内容。功能:head 用来显示档案的开头至标准输出中,默认head命令打印其相应文件的开头10行。功能:tail 用来显示档案的开头至标准输出中,默认tail命令打印其相应文件的开头10行。它是用来显示开头某个数量的文字区块, head 用来显示档案的开头至标准输出中。

2023-11-06 08:00:00 1251 11

原创 Linux的常见指令(一)

对于Linux系统来说,无论是中央处理器、内存、磁盘驱动器、键盘、鼠标,还是用户等都是文件,Linux系统管理的命令是它正常运行的核心。目录和文件是在磁盘上建立的,空文件是在磁盘上占用空间的(文件包括文件内容和文件的各种属性/元数据)即使本身没有内容,文件的各种属性也要占用空间。功能:在当前路径下创建一个普通文件(touch命令可更改文档或目录的最近修改的日期时间,包括存取时间和更改时间,或者新建一个不存在的文件)Linux系统中,磁盘上的文件和目录组成一颗目录树,每个节点都是目录或文件。

2023-11-03 08:00:00 891 16

原创 C++之类型转换

如果父类的指针(或引用)指向的是一个父类对象,那么将其转换为子类的指针(或引用)是不安全,会存在越界的风险,因为转换后可能会访问子类的资源,而这些资源是父类对象没有的。static_cast 用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用static_cast,但它不能用于两个不相关的类型进行转换。而*p指向的是内存中的a,内存中的a被修改了。而我们在监视窗口看到的是内存中的 “a”。如果父类的指针(或引用)指向的是一个子类对象,那么将其转换为子类的指针(或引用)则是安全的。

2023-10-31 08:00:00 999 8

原创 C++之特殊类的设计

缺点:1、一个程序中有多个单例,并且有先后创建初始化顺序要求时,饿汉模式无法控制(无法控制初始化顺序)。2、如果饿汉单例类,初始化时任务多,会影响程序的启动速度。一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。优点: 1、一个程序中有多个单例,并且有先后创建初始化顺序要求时,懒汉模式可以控制(能够控制初始化顺序)。1、一般情况下,单例对象不需要释放。2、提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建或析构。

2023-10-28 08:00:00 896 7

C++的运算符重载代码

C++的运算符重载代码

2023-08-02

空空如也

TA创建的收藏夹 TA关注的收藏夹

TA关注的人

提示
确定要删除当前文章?
取消 删除