3、嵌入式C++——字节校招
线程、进程基本概念
多进程、多线程的优缺点⭐⭐⭐⭐
A:
1)多进程更加健壮,多线程更加脆弱。
2)创建进程的开销远大于创建多线程的开销
3)进程资源比线程占有的多,所以进程性能比线程好。
4)多进程通讯需要跨越进程边界,所以不适合大量数据传输;线程因为共享内存,适合进行大量数据的传送。
5)多线程逻辑要比多进程逻辑更加简单
6)多线程需要复杂的同步和加锁控制机制
7)进程数量可以通过增加CPU的数量来提升,但是线程数量与线程的堆栈大小以及虚拟内存大小有关。什么时候用进程,什么时候用线程⭐⭐⭐
A:
注重安全稳定选择多进程,注重于高并发频繁切换选择多进程。多进程、多线程同步(通讯)的方法⭐⭐⭐⭐
A:
进程间通信:
(1)管道/无名管道 (2)信号 (3)消息队列 (4)共享内存 (5)socket (6)信号量
线程间通信:
(1)信号量 (2)读写锁 (3)条件变量 (4)互斥锁 (5)自旋锁进程的空间模型⭐⭐⭐
32位系统中,当系统运行一个程序,就会创建一个进程,系统为其分配4G的虚拟地址空间,其中0-3G是用户空间,3-4G是内核空间,具体如图,内核空间是受保护的,用户不能对该空间进行读写操作,否则可能出现段错误。其中栈空间有向下的箭头,代表数据地址增加的空间是往下的,新的数据的地址的值反而更小,堆空间则是往上。
栈的空间有限,堆是很大的自由存储区,程序在编译期对变量和函数分配内存都在栈上进行,且程序运行过程中函数调用时参数的传递也是在栈上进行。
注意:64位操作系统下的虚拟内存空间大小:地址空间大小不是2^32,也不是2^64,而一般是2^48。因为并不需要2^64那么大的寻址空间,过大的空间只会造成资源的浪费。所以64位Linux一般使用48位表示虚拟空间地址,40位标识物理地址。0x00000000000000000x00007fffffffffff表示用户空间, 0xFFFF8000000000000xFFFFFFFFFFFFFFFF表示内核空间,共提供256TB(2^48)的寻址空间。进程线程的状态转换图 什么时候阻塞,什么时候就绪⭐⭐
父进程、子进程的关系以及区别⭐⭐⭐⭐⭐
共有:
○用户号UIDs和用户组号GIDs
○环境Environment
○堆栈
○共享内存
○打开文件的描述符
○执行时关闭(Close-on-exec)标志
○信号(Signal)控制设定
○进程组号
○当前工作目录
○根目录
○文件方式创建屏蔽字
○资源限制
○控制终端
独占:
○进程号PID
○不同的父进程号
○自己的文件描述符和目录流的拷贝
○子进程不继承父进程的进程正文(text),数据和其他锁定内存(memory locks)
○不继承异步输入和输出
父进程调用fork()以后,克隆出一个子进程,子进程和父进程拥有相同内容的代码段、数据段和用户堆栈。但其实父进程只复制了自己的PCB块,而代码段,数据段和用户堆栈内存空间是与子进程共享的。只有当子进程在运行中出现写操作时,才会产生中断,并为子进程分配内存空间。(只是复制了PCB,使用读时共享,写时复制的原则)上下文⭐⭐
状态的上下文切换
进程的上下文切换
线程的上下文切换
虚拟地址的上下文切换实时操作系统与分时操作系统
分时系统:是 一个系统能够同时为两个或两个以上的帐户服务
实时系统:Linux就是实时系统,但是也可以改为分时系统。
实时特点:
1)多任务;
2)有线程优先级
3)多种中断级别
小的嵌入式操做系统常常须要实时操做系统,内核要知足实时操做系统的要求。
- 并发,同步,异步,互斥,阻塞,非阻塞的理解⭐⭐⭐⭐⭐
互斥:同一时刻只能有一个进程访问,但是访问次序是无序的。
同步:在互斥的基础上,对资源进行有序的访问。
同步:同步就是顺序执行,执行完一个再执行下一个,需要等待、协调运行。
异步:异步和同步是相对的,异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。
阻塞和非阻塞是当进程在访问数据时,根据IO操作的就绪状态不同而采取的不同处理方式,比如主程序调用一个函数要读取一个文件的内容,阻塞方式下主程序会等到函数读取完再继续往下执行,非阻塞方式下,读取函数会立刻返回一个状态值给主程序,主程序不等待文件读取完就继续往下执行。一般来说可以分为:同步阻塞,同步非阻塞,异步阻塞,异步非阻塞。
同步阻塞:发送方发出请求后一直等待(同步),接收方开始读取文件,如果不能马上得到读取结果就一直等,直到获取读取结果再响应发送发,等待期间不可做其他操作(阻塞)。
异步非阻塞:发送方发出请求后,不等待响应,继续其他工作(异步),接收方读取文件如果不能马上得到结果,也不等待,而是马上返回取做其他事情。当IO操作(读取文件)完成以后,将完成状态和结果通知接收方,接收方在响应发送方。(效率最高)
同步:是一件事分为很多步,必须一步一步做完,这中间不能去做别的。(需要协调推进速度)
阻塞:当某一个资源被多个线程访问,但是同一时刻只能被一个线程使用,后来的线程因为是同步的,所有都必须阻塞在此。
(同步不一定阻塞,但是阻塞一定是同步)
- 如何创建守护进程:⭐⭐
僵尸、孤儿。守护进程的概念:
孤儿进程:当父进程退出后它的子进程还在运行,那么这些子进程就是孤儿进程。孤儿进程将被init进程所收养,并由init进程对它们完成状态收集工作。
僵尸进程:当子进程退出后而父进程并未接收结束子进程(如调用waitpid获取子进程的状态信息),那么子进程仍停留在系统中,这就是僵尸进程。
守护进程:是在后台运行不受终端控制的进程(如输入、输出等)。网络服务大部分就是守护进程。
如何创建守护进程:
1)创建子进程,父进程退出:因为守护进程是在后台运行不受终端控制的进程,父进程退出后控制台就以为该程序结束了,我们就可以在子进程进行自己的任务,同时用户仍可以在控制台输入指令,从而在形式上做到了与控制台脱离。
2)在子进程中创建新的会话(脱离控制终端):使用系统函数setsid()来创建一个新的会话,并担任该会话组的组长,摆脱原会话的控制==>摆脱原进程的控制==>摆脱原控制台的控制。
3)改变当前目录为根目录:1.1.7小节知道子进程继承父进程的目录信息,但进程运行时对当前目录下的文件系统不能卸载,这会有很多隐藏的麻烦,建议使用根目录作为当前目录,当然也可以使用其他目录。
4)重设文件权限掩码,关闭文件描述符:子进程还继承父进程文件权限掩码,即屏蔽掉文件权限中的对应位。此时子进程需将其重置为0,即在此时有大的权限,从而提高该守护进程灵活度。最后,关闭从父进程继承的已经打开的文件描述符,如不进行关闭将造成浪费资源以及子进程所有文件系统无法卸载等错误。
{ pid_t pid; pid = fork(); If(pid < 0) //创建子进程失败 { perror("fail to fork"); exit(0); }else if(pid > 0){ //父进程退出 exit(0); }else{ //进入子进程 setsid(); //创建新会话 umask(0); //重置文件权限掩码 pid = fork(); if(pid != 0) { exit(0); } chdir("/"); //设置当前目录为根目录 int maxfd = getdtablesize(); while(maxfd--) { close(maxfd); //关闭文件描述符 } while(1) { syslog(LOG_INFO,"im deamon\n"); sleep(1); } } return 0; }
可以发现子进程里再次创建了一个子进程,虽非必要,但却是对守护进程进行一点优化:
第一次fork:这里第一次fork的作用在shell终端里造成一个程序已经运行完毕的假象,同时创建新会话的进程不能是进程组组长,所以父进程是进程组组长是不能创建新会话的,需要子进程中执行。所以到这里子进程便成为了一个新会话组的组长啦。
第二次fork:第二次fork可以保证不会因为错误操作重新打开终端,因为只有会话组组长可以打开一个终端,再第二次fork后的子进程就不是会话组组长啦。
正确处理孤儿进程、僵尸进程的方法⭐⭐⭐⭐⭐
孤儿进程的处理:
孤儿进程也就是没有父进程的进程,孤儿进程的处理就由进程号为1的Init进程负责,就像一个福利院一样,专门负责处理孤儿。当有孤儿进程需要处理的时候,系统就把孤儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。因此孤儿进程并不会有什么危害。
僵尸进程的处理:
如果父进程一直不调用wait/waitpid函数接收子进程,那么子进程就一直保存在系统里,占用系统资源,因此如果僵尸进程数量太多,那么就会导致系统空间爆满,无法创建新的进程,严重系统工作,因此僵尸进程需要好好处理。
正确的处理方式可以这样子:系统规定,子进程退出后,父进程会自动收到SIGCHLD信号。因此我们需要在父进程里重置signal函数。每当子进程退出,父进程都会收到SIGCHLD信号,故通过signal函数,重置信号响应函数。void* handler(int sig) { int status; if(waitpid(-1, &status, WNOHANG) >= 0) { printf("child is die\n"); } } int main() { signal(SIGCHLD, handler);//信号捕捉函数 int pid = fork(); if(pid > 0) //父进程循环等待 { while(1) { sleep(2); } }else if(0 == pid){ //子进程说自己die后就结束生命周期,之后父进程就收到SIGCHLD //信号调用handler函数接收结束子进程,打印child is die。 printf("i am child, i die\n"); } }
注意:handler函数里不能使用wait()函数,比如同一时间有5个子进程都要结束了,向父进程发送SIGCHLD信号,但父进程此时就在处理其中一个,在处理结束前,收到的其他SIGCHLD信号会忽略,导致漏掉部分子进程没有处理结束。
C与C++高频面试题
new和malloc的区别⭐⭐⭐⭐⭐
1)new、delete是C++中独有的操作符,而malloc和free是C/C++中的标准库函数。
2)使用new创建对象在分配内存的时候会自动调用构造函数,同时也可以完成对对象的初始化,同理要记得delete也能自动调用析构函数。因为malloc和 free是库函数而不是运算符,不在编译器控制范围之内,所以不能够自动调用构造函数和析构函数。也就是mallloc只是单纯地为变量分配内存,free也只是释放变量的内存。
3)new返回的是指定类型的指针,并且可以自动计算所申请内存的大小。而malloc返回的是void*类型,我们需要强行将其转换为实际类型的指针,并且需要指定好要申请内存的大小,malloc不会自动计算的。
4)C++允许重载new/delete操作符,而malloc和free是一个函数,并不能重载。
5)new内存分配失败时,会抛出bad_alloc异常。malloc分配内存失败时返回NULL。
6)内存区域:先了解自由存储区和堆,两者不相等于的。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配。new操作符从自由存储区上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。malloc的底层实现⭐⭐⭐⭐
进程的空间模型:
malloc函数的实质是它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。 调用malloc()函数时,它沿着连接表寻找一个大到足以满足用户请求所需要的内存块。 然后,将该内存块一分为二(一块的大小与用户申请的大小相等,另一块的大小就是剩下来的字节)。 接下来,将分配给用户的那块内存存储区域传给用户,并将剩下的那块(如果有的话)返回到连接表上。 调用free函数时,它将用户释放的内存块连接到空闲链表上。 到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段, 那么空闲链表上可能没有可以满足用户要求的片段了。于是,malloc()函数请求延时,并开始在空闲链表上检查各内存片段,对它们进行内存整理,将相邻的小空闲块合并成较大的内存块。指针与引用的相同和区别;如何相互转换?⭐⭐⭐⭐⭐
区别:指针是一个实体,而引用仅是个别名
指针和引用的自增(++)运算意义不一样,指针是对内存地址的自增,引用是对值的自增;量或对象的地址)的大小;
引用使用时无需解引用(*),指针需要解引用;
引用只能在定义时被初始化一次,之后不可变;指针可变;
引用不能为空,指针可以为空;
引用没有const,指针有const;(本人当初看到这句话表示疑问,这里解释一下:指针有“指针常量”即int * const a,但是引用没有int& const a,不过引用有“常引用”即const int &a = 1)
“sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的 大小,在32位系统指针变量一般占用4字节内存。64位的指针是8字节
重写memcpy()函数需要注意哪些问题⭐⭐
void *Memcpy(void *dst, const void *src, size_t1 size) { char *psrc; //源地址 char *pdst; //目标地址 if(NULL == dst || NULL == src) { return NULL; } if((src < dst) && (char *)src + size > (char *)dst) //源地址在前,对应上述情况2,需要自后//向前拷贝 { psrc = (char *)src + size - 1; pdst = (char *)dst + size - 1; while(size--) { *pdst-- = *psrc--; } } else //源地址在后,对应上述第一种情况,直接逐个拷贝*pdst++ = *psrc++即可 { psrc = (char *)src; pdst = (char *)dst; while(size--) { *pdst++ = *psrc++; } } return dst; }
数组到底存放在哪里⭐⭐⭐
1、固定数组在函数体内分配(不带static)是在栈中的
2、固定数组是全局变量和带static前缀的局部数组是在全局数据的
3、固定数组在类中分配是在堆中的
4、动态数组(通过malloc或者new出来的空间)不管在函数体中、类中、全局变量都是在堆中static的用法(定义和用途)⭐⭐⭐⭐⭐
在C语言中,static作用:“改变生命周期” 或者 “改变作用域”。
1)static局部变量:局部变量在函数执行完之后,变量内存不会被释放。
2)static全局变量:变量在本文件内可见,在其他文件中不可见。
3)static函数:改变函数的作用域,函数在本文件可见可连接。
C++,多出两个用法:
1)static类成员变量:成员为类共有,对类的对象只有一份拷贝,可以借助类名直接访问。
2)static类成员函数:函数为类共有,只能访问static类成员变量,并且函数不接受this指针。cosnt的用法(定义和用途)⭐⭐⭐⭐⭐
const修饰主要用来修饰变量、函数形参和类成员函数:
1)const常量:定义时就初始化,以后不能更改。
2)const形参:func(const int a){};该形参在函数里不能改变
3)const修饰类成员函数:该函数对成员变量只能进行只读操作,就是const类成员函数是不能修改成员变量的数值的。const常量和#define的区别(编译阶段、安全性、内存占用等) ⭐⭐⭐⭐
1)#define 定义常量无类型 在预处理阶段的时候进行替换。
2)const 定义常量有类型 在编译阶段
3)define 定义的常量不可以使用指针指向,const 定义的常量可以用指针指向。
4)define 与 const 默认都是在当前文件生效,但是const 可以是用extern 实现多个文件共享,但是define不行。volatile作用和用法 ⭐⭐⭐⭐⭐
A:
volatile关键词的作用是影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错。
用法:
(1)中断服务程序中修改的供其它程序检测的变量需要加volatile;
(2)多任务环境下(如多线程)各任务间共享的标志应该加volatile;
(3)存储器映射的硬件寄存器通常也要加voliate,因为每次对它的读写都可能有不同意义。c++中类型转换机制?各适用什么环境?dynamic_cast转换失败时,会出现什么情况?⭐⭐⭐
https://blog.nowcoder.net/n/0e63cb499c5947908912e16ec77c143d
继承与多态
多态的类,内存布局是怎么样的 ⭐⭐⭐⭐⭐
解析:类的内存布局主要是考某个类所占用的内存大小
1)虚继承:如果是虚继承,那么就会为这个类创建一个虚表指针,占用4个字节#include <stdio.h> class A { public: int a; }; //sizeof(A)=4,因为a是整形,占用4字节 class B : virtual public A { public: int b; };//sizeof(B)=4(A副本)+4(虚表指针占用4字节)+4(变量b占用4字节)=12 class C : virtual public B { };//sizeof(c)= 12(B副本)+4(虚表指针) = 16,如果这里改为直接继承, //那么sizeof(c)=12,因为此时就没有虚表指针了
2)多重继承:如果是以虚继承实现多重继承,记得减掉基类的副本
#include <stdio.h> class A { public: int a; };//sizeof(A) = 4 class B : virtual public A { };// sizeof(B) =4+4=8 class C : virtual public A { };//sizeof(C) =4+4=8 class D : public B, public C{ }; //sizeof(D)=8+8-4=12这里需要注意要减去4,因为B和C同时继承A, //只需要保存一个A的副本就好了,sizeof(D)=4(A的副本)+4(B的虚表)+4(C的虚表)=12, //也可以是8(B的副本)+8(c的副本)-4(A的副本)=12
3)普通继承(含有:空类、虚函数)
class A //result=1 空类所占空间的大小为1 { }; class B //result=8 1+4 字节对齐后为 8 { char ch; virtual void func0() { } }; class C //result=8 1+1+4 字节对齐后为 8,没有继承的,此时类里即使出现多个虚函数,也只有一个虚指针 { char ch1; char ch2; virtual void func() { } //也只有一个虚指针 virtual void func1() { } //也只有一个虚指针 }; class D: public A, public C //result=12 8(C的副本)+4(整形变量d占用4字节)=12 { int d; virtual void func() { } //继承了C,C里已经有一个虚指针,此时D自己有虚函数, virtual void func1() { } //也不会创建另一个虚指针,所以D本身就变量d需要4字节 }; class E: public B, public C //result=20 8( B的副本)+8(C的副本)+4(E本身)=20 { int e; virtual void func0() { } //同理,E不会创建另一个虚指针,所以E本身就变量e需 virtual void func1() { } //要4字节 };
4)虚继承(多重继承和虚函数)
class CommonBase { int co; };// size = 4 class Base1: virtual public CommonBase { public: virtual void print1() { } virtual void print2() { } private: int b1; };//4(父类副本)+4(自己有虚函数,加1个虚指针空间)+4(自身变量b1)+4(虚继承再加1个虚指针空间)=16 class Base2: virtual public CommonBase { public: virtual void dump1() { } virtual void dump2() { } private: int b2; };//同理16 class Derived: public Base1, public Base2 { public: void print2() { } void dump2() { } private: int d; };//16+16-4+4=32
被隐藏的基类函数如何调用或者子类调用父类的同名函数和父类成员变量 ⭐⭐⭐⭐⭐
父类的同名函数和父类成员变量被隐藏不代表其不存在,只是藏起来而已,C++有两种方法可以调用被隐藏的函数
1)用using关键字:使用using后,父类的同名函数就不再隐藏,可以直接调用,如下:class Child:public Parent { public: Child(){}; using Parent::add; int add(void){}; };
2)用域操作符,可以调用基类中被隐藏的所有成员函数和变量。
如子类child和父类father都有add()函数,可以通过下面代码实现子类对象调用父类的add()函数:Child c; c.Parent::add(10);
多态实现的三个条件、实现的原理 ⭐⭐⭐⭐⭐
条件:
存在继承
有虚函数重写
父类指针指向子类对象
原理:
编译器发现一个类中有虚函数,便会立即为此类生成虚函数表vtable。虚函数表的各表项为指向类里面的虚函数的指针。编译器还会在此类中隐含插入一个指针vptr(对 vc 编译器来说,它插在类的内存地址的第一个位置上)指向虚函数表。调用此类的构造函数时,在类的构造函数中,编译器会隐含执行vptr 与 vtable 的关联代码,即将vptr 指向对应的 vtable,将类与此类的vtable 联系了起来。
另外在调用类的构造函数时,指向基础类的指针此时已经变成指向具体的类的this 指针,这样依靠此 this 指针即可得到正确的 vtable,如此才能真正与函数体进行连接,这就是动态联编,实现多态的基本原理。#include "stdafx.h" #include <iostream> #include <stdlib.h> using namespace std; class Father { public: void Face() { cout << "Father's face" << endl; } virtual void Say() { cout << "Father say hello" << endl; } }; class Son:public Father { public: void Say() { cout << "Son say hello" << endl; } }; void main() { Son son; Father *pFather=&son; //隐式类型转换 pFather->Say(); }
对拷贝构造函数 深浅拷贝 的理解 拷贝构造函数作用及用途?什么时候需要自定义拷贝构造函数?⭐⭐⭐
解析:简单的来说,浅拷贝是增加了一个指针,指向原来已经存在的内存。浅拷贝在多个对象指向一块空间的时候,释放一个空间会导致其他对象所使用的空间也被释放了,再次释放便会出现错误。
而深拷贝是增加了一个指针,并新开辟了一块空间让指针指向这块新开辟的空间。深拷贝和浅拷贝的不同之处,仅仅在于修改了下拷贝构造函数,以及赋值运算符的重载。就类对象而言,相同类型的类对象是通过拷贝构造函数来完成整个复制过程的。
提问:什么时候需要自定义拷贝构造函数?
答:
默认拷贝构造函数执行的是浅拷贝,对于凡是包含动态分配成员或包含指针成员的类都应该提供拷贝构造函数;在提供拷贝构造函数的同时,还应该考虑重载"="赋值操作符号。析构函数可以抛出异常吗?为什么不能抛出异常?除了资源泄露,还有其他需考虑的因素吗?⭐⭐⭐
答:析构函数不能抛出异常,除了资源泄露还可能造成程序崩溃。什么情况下会调用拷贝构造函数(三种情况)⭐⭐⭐
1、一个对象以值传递的方式传入函数体
2、一个对象以值传递的方式从函数返回
3、一个对象需要通过另外一个对象进行初始化。析构函数一般写成虚函数的原因⭐⭐⭐⭐⭐
防止内存泄露构造函数为什么一般不定义为虚函数⭐⭐⭐⭐⭐
1)因为创建一个对象时需要确定对象的类型,而虚函数是在运行时确定其类型的。而在构造一个对象时,由于对象还未创建成功,编译器无法知道对象的实际类型,是类本身还是类的派生类等等。
2)虚函数的调用需要虚函数表指针,而该指针存放在对象的内存空间中;若构造函数声明为虚函数,那么由于对象还未创建,还没有内存空间,更没有虚函数表地址用来调用虚函数即构造函数了。什么是纯虚函数⭐⭐⭐⭐⭐
- 静态绑定和动态绑定的介绍⭐⭐⭐⭐
解析:
静态类型:对象在声明时采用的类型,在编译期既已确定;
动态类型:通常是指一个指针或引用目前所指对象的类型,是在运行期决定的;
静态绑定:绑定的是静态类型,所对应的函数或属性依赖于对象的静态类型,发生在编译期;
动态绑定:绑定的是动态类型,所对应的函数或属性依赖于对象的动态类型,发生在运行期;
非虚函数一般都是静态绑定,而虚函数都是动态绑定(如此才可实现多态性)。
问题:引用是否能实现动态绑定,为什么引用可以实现
答:只有指定为虚函数的成员函数才能进行动态绑定,且必须通过基类类型的引用或指针进行函数调用,因为每个派生类对象中都拥有基类部分,所以可以使用基类类型的指针或引用来引用派生类对象。而指针或引用是在运行期根据他们绑定的具体对象确定。
C++所有的构造函数 ⭐⭐⭐
1、构造函数的名子必须和类名相同,不能任意命名;
2、构造函数没有返回值;
3、构造函数可以被重载,但是每次对象创建时只会调用其中的一个;重写(覆盖)、重载的区别⭐⭐⭐⭐⭐
(1)重写和重载主要有以下几点不同。
范围的区别:被重写的和重写的函数在两个类中,而重载和被重载的函数在同一个类中。
参数的区别:被重写函数和重写函数的参数列表一定相同,而被重载函数和重载函数的参数列表一定不同。
virtual的区别:重写的基类中被重写的函数必须要有virtual 修饰,而重载函数和被重载函数可以被virtual修饰,也可以没有。
(2)隐藏和重写、重载有以下几点不同。
与重载的范围不同:和重写一样,隐藏函数和被隐藏函数不在同一个类中。
参数的区别:隐藏函数和被隐藏的函数的参数列表可以相同,也可不同,但是函数名肯定要相同。
当参数不相同时,无论基类中的参数是否被virtual修饰,基类的函数都是被隐藏,而不是被重写。
说明:虽然重载和覆盖都是实现多态的基础,但是两者实现的技术完全不相同,达到的目的也是完全不同的,覆盖是动态态绑定的多态,而重载是静态绑定的多态。
网络基础
TCP、UDP的区别 ⭐⭐⭐⭐⭐
TCP---传输控制协议,提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。
UDP---用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。
总结:
1)TCP是面向连接的,UDP是面向无连接的
2)UDP程序结构较简单
3)TCP是面向字节流的,UDP是基于数据报的
4)TCP保证数据正确性,UDP可能丢包
5)TCP保证数据顺序,UDP不保证TCP、UDP的优缺点⭐⭐⭐
TCP:
优点:可靠稳定
缺点:慢,效率低,占有系统资源高,易被攻击;
UDP:
优点:快速、比TCP安全
缺点:不可靠不稳定。TCP UDP适用场景⭐⭐⭐
TCP:当对网络质量有要求时,比如HTTP,HTTPS,FTP等传输文件的协议;POP,SMTP等邮件传输的协议
UDP:对网络通讯质量要求不高时,要求网络通讯速度要快的场景QQ/微信TCP为什么是可靠连接⭐⭐⭐⭐
各种保证可靠传输的措施典型网络模型,简单说说有哪些;⭐⭐⭐
TCP/IP四层、TCP/IP五层、OSI七层模型Http1.1和Http1.0的区别⭐⭐⭐
keep-alive长连接,不用传输一次消息就断开链接(减少三次握手的次数)URI(统一资源标识符)和URL(统一资源定位符)之间的区别⭐⭐
URL是URI的子集。为什么三次握手中客户端还要发送一次确认呢?可以二次握手吗?⭐⭐⭐⭐
主要为了防止已失效的连接请求报文段突然又传送到了B,因而产生错误为什么服务端易受到SYN攻击?⭐⭐⭐⭐
答:服务器端的资源分配是在二次握手时分配的,而客户端的资源是在完成三次握手时分配的,所以服务器容易受到SYN洪泛攻击,SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server则回复确认包,并等待Client确认,由于源地址不存在,因此Server需要不断重发直至超时,这些伪造的SYN包将长时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。
防范SYN攻击措施:降低主机的等待时间使主机尽快的释放半连接的占用,短时间受到某IP的重复SYN则丢弃后续请求。为什么客户端最后还要等待2MSL?⭐⭐⭐⭐
解析:MSL(Maximum Segment Lifetime),TCP允许不同的实现可以设置不同的MSL值。
第一,保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。
第二,防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。为什么建立连接是三次握手,关闭连接确是四次挥手呢?⭐⭐⭐⭐
连接是双工的,断开也应该是双工的。