lalallala

  1. 进程间通信+++++++

    • 无名管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

      1. 它是半双工的(即数据只能同一时刻在一个方向上流动),实际是做单工使用,具有固定的读端和写端。

      2. 它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。

      3. 它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。

    • 有名管道 (fifo) : 有名管道也是半双工的通信方式,但是它允许任意进程间的通信。

      1. FIFO可以在无关的进程之间交换数据,与无名管道不同。

      2. FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中

      3. 不能使用lseek进行定位

    • 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生,可以实现用户空间和内核空间的数据交互,是异步通信方式。(软件层面对中断机制的模拟)

    • 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

      1. 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存

      2. 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。

      3. 每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。

      4. 支持信号量组。

    • 消息队列( message queue ) : 消息队列是将消息的链表存放在内核中,并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

      1. 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。

      2. 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。

      3. 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取

    • 共享内存( shared memory ) :共享内存就是将内核中一段区域映射到能被其他进程所访问的各自进程空间中,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。

      1. 共享内存是最快的一种 IPC,因为进程是直接对内存进行存取

      2. 因为多个进程可以同时操作,所以需要进行同步。

      3. 信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

  2. OSI七层模型+++++

    网络分层思想:使网络协议有统一的规定和规则;各层分别完成不同的任务,利于网络的维护,出现错误时,更方便管理人员按层查验错误

      • 物理层:通信线路,将计算机通信社设备使用物理线路连接起来。数据的格式:字节流。协议:各种硬件线路的制造标准
      • 数据链路层:建立相邻结点的数据连接。数据格式:帧,形成物理地址。协议:ARP协议 重要设备:交换机
      • 网络层:形成了计算机网络。数据格式:包(分组),使用网络地址标记网络中的计算机。协议:ip协议
      • 传输层:提供传输功能。数据格式:包(分组)。协议:TCP协议,UDP协议。
        • TCP协议(传输控制协议):提供可靠的,面向连接的通信
        • UDP协议(用户数据协议):提供不可靠的,无连接的通信
      • 会话层:建立,维持和终止通信的
      • 表示层:数据的编码和解码
      • 应用层:通过应用进程间的交互来完成特定网络应用。如域名系统DNS,支持万维网应用的HTTP协议,支持电子邮件的SMTP协议。数据单元称为报文
  3. 多线程与多进程+++++

    对比维度多进程多线程总结
    数据共享,同步数据共享复杂,需要用IPC;数据是分开的,同步简单因为共享进程数据,数据共享简单,但也是因为这个原因导致同步复杂各有优势
    内存,CPU占用内存多,切换复杂,CPU利用率低占用内存少,切换简单,CPU利用率高线程占优
    创建销毁,切换创建销毁、切换复杂,速度慢创建销毁、切换简单、速度很快线程占优
    编程,调试编程简单,调试简单编程复杂,调试复杂进程占优
    可靠性进程间不会互相影响一个线程挂掉将导致整个进程挂掉进程占优
    分布式适用于多核,多机分布式;如果一台机器不够,扩展到多台机器比较简单适应于多核分布式进程占优

    使用环境:

    1. 需要频繁创建销毁的优先用线程

    2. 需要进行大量计算的优先使用线程(所谓大量计算,当然就要耗费很多CPU,切换频繁,这种情况下使用线程是最合理的)

    3. 强相关的处理使用线程,弱相关的使用进程

      一般的server需要完成如下任务:消息收发、消息处理。”消息收发“和”消息处理“就是弱相关的任务,而”消息处理“里面可能又分为”消息解码“,”业务处理“,这两个任务相对来说相关性就要强多了,因此,”消息收发“和”消息处理“可以分进程设计,”消息解码“、”业务处理“可以分线程设计

    4. 可能要扩展到多机分布的用进程,多核分布的用线程

    5. 都满足需求的情况,使用最熟悉的

  4. 并发服务器和传统服务器++++++++

    • 统服务器:在同一时刻只能响应一个客户端的请求

    • 并发服务器:在同一时刻可以响应多个客户端的请求

      • 多进程,多线程,I/O多路复用模型
    • I/O多路复用:

      应用程序中同时处理多路输入输出流,若采用阻塞模式,将得不到预期的目的;

      ​ 若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间;

      ​ 若设置多个进程,分别处理一条数据通路,将产生进程间的同步与通信问题,使程序变得更加复杂;

      ​ 比较好的方法是使用I/O多路复用。其基本思想是:

      ​ 先构造一张有关描述符的表,然后调用一个函数。当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。

      ​ 函数返回时告诉进程那个描述符已就绪,可以进行I/O操作

    • 多线程服务器:服务器为每一个连接上的客户端提供一个线程,专门为这个客户端服务,这样就能实现低并发(10-2,300)高活跃度的情况

      ​ 多线程服务器主要用于园区网络,或中小型网络服务。多个线程共享了进程的资源,当主线程有任何问题时都会影响子线程。并且由于资源的共享会导致线程在实现访问共享资源时产生问题,要使用复杂的同步互斥机制来避免共享的问题,这样会让程序变得过于复杂,以及不稳定。

    • 多进程并发服务器:多进程服务器主要用于大型的稳定的网络服务,各个进程间是彼此独立的,主进程有任何问题不会影响到子进程。进程的产生是要靠fork复制父进程的资源的,因此多进程服务器的缺点是对于资源需求量特别大,cpu的调度较为复杂,所以多进程服务器对于计算机硬件和操作系统的要求比较高。

      ​ 每次链接上一个客户端都要fork出一个子进程为该客户端服务,子进程拷贝了父进程的文件描述符,所以需要在子进程中关闭监听描述符。子进程退出时,不能产生僵尸进程,同时父进程也不能直接调用wait阻塞等待,这样会影响下一次accept。一般来说这种情况,使用信号来解决,即fork之前提前注册信号捕捉函数,在信号处理函数里实现wait回收子进程的资源,如果有多个进程退出,则需要循环回收

  5. TCP与UDP区别++++++++

    1. TCP是面向连接的,可靠的,基于流的传输层协议,而UDP是面向无连接的,不可靠的,基于数据报的传输层协议。
    2. TCP有三次握手和四次挥手的过程,而UDP没有
    3. TCP协议和UDP协议在编程时使用的函数不同
    4. TCP需要的资源比较多,而UDP需要的资源较少
    5. TCP适合数据量较大的可靠通信,UDP适合数据量较小的不可靠 通
    6. TCP由于面向连接的特性,实际上是点对点的通信;而UDP由于面向无连接所以可以实现一对多的通信
    7. TCP的协议头比UDP协议头复杂,占用空间大
    8. UDP适合拥有较好实时性不关系准确数据的通信;而TCP适合对于数据准确性要求高的数据通信
  6. 守护进程++++++

    #include<stdio.h>
    #Include<unistd.h>
    #include<stdlib.h>
    #include<sys/types.h>
    #include<sys/stat.h>
    #include<fcntl.h>
    int main(int argc,char *argv[])
    {
        //1.创建子进程,使父进程退出,令子进程变成孤儿进程被init进程收养。目的:使子进程在形式发上脱离终端控制
    	pid_t pid=fork();
    	if(pid<0)
    	{
    		return -1;
    	}
    	else if(pid>0)
    	{
    		exit(0);
    	}
    	else//子进程空间
    	{
            //2.创建新会话,使其成为会话组组长。进程组:多个进程构成一个进程组。会话期(组):多个进程组构成一个会话期。目的:子进程继承自父进程,所以也继承了父进程的会话期、进程组、控制终端,因此创建新会话,使其成为会话组组长,目的是使子进程脱离原会话期、进程组、控制终端的控制
    		setsid();
            //3.修改子进程的工作目录为不可删除目录,一般为根目录。目的:子进程会继承父进程的工作目录,但守护进程在运行中是不能被删除工作目录的,所以需要更改其工作目录
    		chdir("/var")/chdir("/")
    		//4.重设继承自父进程的文件权限掩码。目的:为了增加子进程使用文件的灵活性
            umask(0);
            //关闭继承自父进程的所有文件描述符,getdtablesize()函数是获取整个进程中所有被打开的文件描述符。目的:关闭不需要的文件,减少不必要的资源浪费
            int i=0;
    		for(i=0;i<getdtablesize();i++)
    		{
    			close(i);
    		}
    		//为守护进程操作空间
    		char buf[]="i am d deamon process";
    		while(1)
    		{
    			int fd=open("deamon.txt",O_REWR|O_CREAT|O_APPEND,0666);
    			if(fd<0)
    			{
    				continue;
    			}
    			write(fd,buf,sizeof(buf));
    			close(fd);
    			sleep(2);
    		}
    	}
    	return 0;
    }
    
  7. 线程死锁与处理方式+++

    • 概念: 多个并发进程因争夺系统资源而产生相互等待的现象。线程A需要资源X,而线程B需要资源Y,而双方都掌握有对方所要的资源,这种情况称为死锁,或死亡拥抱。

    • 原理: 当一组进程中的每个进程都在等待某个事件发生,而只有这组进程中的其他进程才能触发该事件,这就称这组进程发生了死锁。

    • 本质原因:

    ​ 1)系统资源有限。

    ​ 2)进程推进顺序不合理。

    • 死锁产生的4个必要条件

      1、互斥:某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。

      2、占有且等待:一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。

      3、不可抢占:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。

      4、循环等待:存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。

      当以上四个条件均满足,必然会造成死锁,发生死锁的进程无法进行下去,它们所持有的资源也无法释放。这样会导致CPU的吞吐量下降。所以死锁情况是会浪费系统资源和影响计算机的使用性能的。那么,解决死锁问题就是相当有必要的了。

    • 避免死锁的方法

      (1).设置等待超时,当一个线程拿起A资源,等待B资源的时候,如果等待的时间过长则超时,释放A资源,重新等待

      (2).如果线程需要AB两个资源,等待A资源的时候阻塞,然后判断B资源是否可以使用,如果B资源正在被别的线程使用,那么该线程就释放A资源,不阻塞,重新排队等待

  8. 线程同步与互斥+++

    PV操作

    • 概念:一种实现进程互斥与同步的有效方法,包含P操作与V操作
    1. P操作:如果信号量的值大于0,则减少信号量的值,程序继续向下运行,访问临界资源;如果信号量的值等于0,说明临界资源已经被占用了,此时当前的执行P操作的进程或线程就会阻塞在P操作的等待队列中

    2. V操作:如果P操作的等待队列中没有阻塞的进程或线程,则增加信号量的值;如果有阻塞在P操作的等待队列的进程或线程,唤醒那个阻塞的进程或线程

      注:PV操作既可以实现同步,也可以实现互斥,取决于信号量的初始值

  9. 线程池++

    ​ 大多数的网络服务器,包括Web服务器都具有一个特点,就是单位时间内必须处理数目巨大的连接请求,但是处理时间却是比较短的。在传统的多线程服务器模型中是这样实现的:一旦有个请求到达,就创建一个新的线程,由该线程执行任务,任务执行完毕之后,线程就退出。这就是"即时创建,即时销毁"的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数非常频繁,那么服务器就将处于一个不停的创建线程和销毁线程的状态。这笔开销是不可忽略的,尤其是线程执行的时间非常非常短的情况。

    线程池就是为了解决上述问题的,它的==实现原理是这样的:在应用程序启动之后,就马上创建一定数量的线程,放入空闲的队列中。这些线程都是处于阻塞状态,这些线程只占一点内存,不占用CPU。当任务到来后,线程池将选择一个空闲的线程,将任务传入此线程中运行。当所有的线程都处在处理任务的时候,线程池将自动创建一定的数量的新线程,用于处理更多的任务。执行任务完成之后线程并不退出,而是继续在线程池中等待下一次任务。当大部分线程处于阻塞状态时,线程池将自动销毁一部分的线程,回收系统资源。=

  10. 线程安全与线程可重入++

    线程安全:
    线程安全函数:在C语言中局部变量是在栈中分配的,任何未使用静态数据或其他共享资源的函数都是线程安全的。
    使用全局变量的函数是非线程安全的。
    使用静态数据或其他共享资源的函数,必须通过加锁的方式来使函数实现线程安全。
    线程安全的(Thread-Safe):
    如果一个函数在同一时刻可以被多个线程安全地调用,就称该函数是线程安全的。
    线程安全函数解决多个线程调用函数时访问共享资源的冲突问题。
    可重入(Reentrant):
    函数可以由多于一个线程并发使用,而不必担心数据错误。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重 入性解决函数运行结果的确定性和可重复性。

    可重入函数编写规范为:
    1、不在函数内部使用静态或全局数据
    2、不返回静态或全局数据,所有数据都由函数的调用者提供。
    3、使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。
    4、如果必须访问全局变量,利用互斥机制来保护全局变量。
    5、不调用不可重入函数。
    两者之间的关系:
    1、一个函数对于多个线程是可重入的,则这个函数是线程安全的。
    2、一个函数是线程安全的,但并不一定是可重入的。【使用互斥锁实现的线程安全】
    3、可重入性要强于线程安全性。

  11. io多路复用+

    select

    • 工作原理:将需要监听的文件描述符放进一个监控列表,如果有文件描述符的状态发生改变,内核空间会对监控列表的文件描述符进行无差别轮询,轮询出发生IO的文件描述符,然后进行IO操作

    • epoll

      • 工作原理:epoll改善了select函数的不足,epoll实现了监控更多文件描述符,同时提高了轮询的效率

      • 实现:

        1. 创建了一个epoll的管理对象,“句柄”
        2. 向这个epoll的管理对象中注册要被监控的文件描述符和该文件描述符将会发生的改变事件
        3. 使用epoll对象等待注册的事件发生。此时我们就知道了是哪个描述符的状态改变了
        4. 可以直接针对于发生状态改变的文件描述符进行操作
      • select和epoll的联系和区别

        ​ epoll和select相似的是,支持高并发10W+,低活跃度的IO,如果活跃度较高,则epoll和select的效率没有明显差别

        ​ 无论是select 还是epoll 都是IO多路复用,都是单进程单任务,使用监控集合来实现异步请求,性能瓶颈是无法实现高并发高活跃度。若想要实现高并发,高活跃度,则需要更加复杂的服务器

  12. 数据粘包与处理措施++

    • 概念:数据包粘包问题简单来说就是一个数据包里的信息不实一条完整的消息,可能包含上一条数据的后半部分和下一个数据包的前半部分,也可能是连着将好几个小数据包拼接成一个数据包。

    • 产生原因:

      1. 应用层调用write方法,将应用层的缓冲区中的数据拷贝到套接字的发送缓冲区。而发送缓冲区有一个SO_SNDBUF(缓存区大小)的限制,如果应用层的缓冲区数据大小大于套接字发送缓冲区的大小,则数据需要进行多次的发送。

      2. TCP所传输的报文段有MSS(网络传输数据最大值)的限制,如果套接字缓冲区的大小大于MSS,也会导致消息的分割发送。

      3. 由于链路层最大发送单元MTU(网络传输最大报文包),在IP层会进行数据的分片。这些情况都会导致一个完整的应用层数据被分割成多次发送,导致接收对等方不是按完整数据包的方式来接收数据。(MSS加包头数据就等于MTU)

      4. 发送端需要等缓冲区满才发送出去,造成粘包(由Nagle算法造成的发送端的粘包)

      5. 接收方不及时接收缓冲区的包,造成多个包接收(接收端接收不及时造成的接收端粘包)

    • 解决方式:

      1. 发送定长包。如果每个消息的大小都是一样的,那么在接收对等方只要累计接收数据,直到数据等于一个定长的数值就将它作为一个消息
      2. 包尾加上\r\n标记。FTP协议正是这么做的。但问题在于如果数据正文中也含有\r\n,则会误判为消息的边界。
      3. 包头加上包体长度。包头是定长的4个字节,说明了包体的长度。接收对等方先接收包体长度,依据包体长度来接收包体。
  13. 多态+++++++

  14. 智能指针+++++

    ​ 智能指针就是为了方便释放堆内存和解决各种需要程序员注意的细节而出现的,智能指针不强制程序员手动释放堆内存。当智能指针离开作用域时,智能指针会自动释放它指向的堆内存。这篇教程将讲解如何简单地使用智能指针。

  • 独占指针
    类std::unique_ptr是独占指针的类型,独占指针是智能指针的一种,它在memory标准库中。

基础示例
先来看看独占指针的创建和使用:

     #include <iostream> // std::cout std::endl std::boolalpha
     #include <memory> // std::unique_ptr
     
     int main(void)
     {
         // 创建默认的独占指针,默认的独占指针被初始化为nullptr
         std::unique_ptr<int> pointer1;
         std::cout << std::boolalpha << "默认独占指针被初始化为nullptr:" << (pointer1 == nullptr) << std::endl;
     // 申请堆内存并且初始化内存数据为2333, 然后将内存地址赋值给pointer1
     pointer1 = std::make_unique<int>(2333);
     std::cout << *pointer1 << std::endl;
     
     // 申请堆内存并且初始化内存数据为666, 然后将内存地址初始化给pointer2
     auto pointer2 = std::make_unique<int>(666); 
     std::cout << *pointer2 << std::endl;
     
     return 0;
     }
-------------------------------------------------------------------------     
 输出结果:
 默认独占指针被初始化为nullptr:true
 2333
 666
 

基础讲解

以下是创建默认的独占指针pointer,用来保存int类型的内存地址。默认创建的独占指针会被初始化为指针空值nullptr:

std::unique_ptr pointer;

以下这个函数是申请int类型的堆内存并且保存到一个独占指针中,然后返回这个独占指针:

std::make_unique(666);

函数里的参数是函数<>里类型的构造参数,上面的这个函数内部实现的功能类似于new int(666);;当函数不填写参数时,即std::make_unique();,就相当于new int();。

以上就是创建独占指针的方法,而使用方法和普通的指针一样。

基础补充

类std::unique_ptr声明的指针独占一份堆内存。也就是说,一份堆内存同一时间只能由一个独占指针保存它的地址,其他指针都不能通过赋值来保存这份堆内存的地址;如果想用其他独占指针保存这份堆内存,则只能通过转移来赋值。先看看一个简单的栗子:

#include // std::cout std::endl std::boolalpha
#include // std::unique_ptr

int main(void)
{
auto pointer1 = std::make_unique(666);
// auto pointer2 = pointer1; // 赋值操作, 去掉开头注释编译将会报错
auto pointer2 = std::move(pointer1); // 转移操作
std::cout << std::boolalpha << (pointer1 == nullptr) << std::endl;
std::cout << *pointer2 << std::endl;
return 0;

}

输出结果:
true
666




   - 共享指针
     类std::shared_ptr是共享指针的类型,共享指针是智能指针的一种,它在memory标准库中。

     顾名思义,共享指针保存的内存地址可以共享给其他共享指针。由于它可以共享,为了保证它的安全性,共享指针自身所需内存也比独占指针要多一点,而内部会比独占指针多小小操作,所以性能会有小小下降。共享指针的额外消耗非常小,一般可以忽略;如果系统资源对这些损耗非常敏感,可以考虑尽量使用独占指针。

     **基础示例**

     ```c
     #include <iostream> // std::cout std::endl std::boolalpha
     #include <memory> // std::shared_ptr
     
     int main(void)
     {
         // 创建默认的共享指针,默认的共享指针被初始化为nullptr
         std::shared_ptr<int> pointer1;
         std::cout << std::boolalpha;
         std::cout << "默认共享指针被初始化为nullptr:" << (pointer1 == nullptr) << std::endl;
     pointer1 = std::make_shared<int>(2333);
     std::cout << "pointer1指向的内存的值:" << *pointer1 << std::endl << std::endl;
     
     // pointer1赋值给pointer2
     auto pointer2 = pointer1;
     std::cout << "pointer2保存的地址是否等于pointer1保存的地址:" << (pointer1 == pointer2) << std::endl;
     std::cout << "pointer2指向的内存的值:" << *pointer2 << std::endl << std::endl;
     
     // pointer1保存的内存地址转移给pointer3
     auto pointer3 = std::move(pointer1); // 
     std::cout << "转移后pointer1是否保存指针空值:" << (pointer1 == nullptr) << std::endl;
     std::cout << "pointer2保存的地址是否等于pointer3保存的地址:" << (pointer2 == pointer3) << std::endl;
     std::cout << "pointer2指向的内存的值:" << *pointer2 << std::endl;
     std::cout << "pointer3指向的内存的值:" << *pointer3 << std::endl;
     
     return 0;
     }
     ------------------------------------------------------------------------
       输出结果:
     
     默认共享指针被初始化为nullptr:true
     pointer1指向的内存的值:2333
     
     pointer2保存的地址是否等于pointer1保存的地址:true
     pointer2指向的内存的值:2333
     
     转移后pointer1是否保存指针空值:true
     pointer2保存的地址是否等于pointer3保存的地址:true
     pointer2指向的内存的值:2333
     pointer3指向的内存的值:2333  
         
     ```
     **基础讲解**

     ``
     共享指针的操作和独占指针的操作类似,使用函数std::make_shared申请内存并返回一个共享指针。而共享指针可以把它保存的地址共享给其他共享指针,独占指针则不行。
     ``
     **补充知识**

     ```c
     智能指针从C++11开始加入。
     函数std::make_unique()从C++14开始加入。
     ```

3. STL+++++++

4. 继承++++++++

5. 虚函数与纯虚函数++

6. private、public、protected+



1. 单例模式++

2. 抽象工厂模式+

   

   

1. 标准IO与文件IO+++
2. 文件描述符与文件流指针+
3. 静态库与动态库++



1. 指针+++

2. 指针函数与函数指针+

3. static用法++

4. const用法+

   



1. hash表+++
2. 单链表逆序+
3. 快速排序+
4. 二叉树层次遍历+
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值