一、线程池的设计及作用
二、线程和进程的区别
三、C++ 设计模式
四、进程间的通讯方式
五、引用和指针的区别
六、构造函数和析构函数
七、深拷贝和浅拷贝
八、对多态的理解
九、数据结构有哪些
十、socket包安全
十一、new和malloc的区别
十二、epoll的 LT 和 ET 模式的理解
十三、TCP/UPD的区别
十四、三次握手和四次挥手
十五、socket的概念和特点
十六、gdb调试
十七、面向对象的理解
十八、虚函数的作用
十九、类和对象的关系
二十、IO复用,epoll和select的区别,opoll和selete的特点
二十一、客户端与服务器如何通信
二十二、堆和栈的区别
二十三、结构体和类的区别
二十四、STL中的vector如何实现
二十五、如何保证线程的安全
二十六、数组和链表的区别
二十七、boost库是否有了解
二十八、socket的工作模式,为什么选择socket
二十九、长连接和短连接
三十、socket中阻塞和非阻塞的区别
线程池的作用:
线程池是为了解决线程在程序中因频繁创建和销毁而消耗大量时间而存在的,即在程序开始正式任务之前,先创建出一些线程,这些线程在程序不会被销毁,而且程序在运行中也不会再去创建线程。这样在程序的运行期间就提高了效率。
线程池的设计思路:
先创建出一组线程,当有新任务进来时就从线程池中取出空闲线程处理任务,任务完成之后又重新放回去,当线程池中的所有线程都在任务时,只能等待有线程结束任务才能继续执行。
根本区别:进程是操作系统资源分配的基本单位,而线程是CPU任务调度和执行的基本单位
在开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)
内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。
包含关系:一个线程只能属于一个进程,但是一个进程可以拥有多个线程。多线程处理就是允许一个进程中在同一时刻执行多个任务。所以线程也被称为轻权进程或者轻量级进程。
单例模式
概念:是指在内存中只会创建且仅创建一次对象的设计模式
优点:
1.保证一个类只有一个实例,并提供一个访问它的全局访问点,使得系统中只有唯一的一个对象实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例
2.由于在系统内存中只存在一个对象,因此可以节约系统资源、提高系统的性能。
缺点:
就是不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
实现要点:
在类中,要构造一个实例,就必须调用类的构造函数,并且为了保证全局只有一个实例,
需防止在外部调用类的构造函数而构造实例,需要将构造函数的访问权限标记为private,
同时阻止拷贝创建对象时赋值拷贝对象,因此也将它们声明并权限标记为private;
另外,需要提供一个全局访问点,就需要在类中定义一个static函数,返回在类内部唯一构造的实例。
观察者模式
观察者模式定义了对象间的一对多依赖关系,让一个或多个观察者对象观察一个主题对象。当主题对象的状态发生变化时,系统能通知所有的依赖于此对象的观察者对象,从而使得观察者对象能够自动更新。
实现方式:
a) 角色抽象类(提供对观察者的添加,删除和通知功能)。
b) 角色具体类,实现a,维护一个c的集合(对角色抽象类的实现)。
c) 观察者抽象类(被角色通知后实现的方法)。
d) 观察者实现类,实现c(多个)。
工厂模式
工厂模式包括三种:简单工厂模式、工厂方法模式、抽象工厂模式。
工厂模式的主要作用是封装对象的创建,分离对象的创建和操作过程,用于批量管理对象的创建过程,便于程序的维护和扩展。
简单工厂是工厂模式最简单的一种实现,对于不同产品的创建定义一个工厂类,将产品的类型作为参数传入到工厂的创建函数,根据类型分支选择不同的产品构造函数。
管道:数据只能单向流动,速度慢,容量有限,只有父子进程能通讯
消息队列:就是一个消息的链表,是一系列保存在内核中消息的列表。用户进程可以向消息队列添加消息,也可以向消息队列读取消息。
共享内存:映射一段能被其他进程访问的内存,这段内存由一个进程创建,但多个进程都可以访问;
信号量:是一个计数器,用于控制多个进程间对共享资源的访问;
套接字:用于不同计算机之间的不同进程间通信。
本质:引用是别名,指针是地址
相同点:都是地址的概念;
指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名。
不同点:
1.引用不可以为空,但指针可以为空 ,故定义一个引用的时候,必须初始化
2.引用不可以改变指向,对一个对象"至死不渝";但是指针可以改变指向,而指向其它对象,虽然引用不可以改变指向,但是可以改变初始化对象的内容
3.引用的大小是所指向的变量的大小,因为引用只是一个别名而已;指针是指针本身的大小,4个字节
4.引用比指针更安全。由于不存在空引用,并且引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用,因此引用很安全。对于指针来说,它可以随时指向别的对象,并且可以不被初始化,或为NULL,所以不安全。
5.引用仅在声明时带有引用运算符“&”,以后像普通变量一样使用,不能再带“&”,其它场合使用的“&”都是地址操作符。
构造函数的作用:用于新建对象的初始化工作。(一个类可以有多个构造函数,构造函数可以重载,不可以加虚函数)
析构函数的作用:用于在撤销对象前,完成一些清理工作,比如:释放内存等。(一个类只能有一个析构函数,不可以重载)
拷贝构造函数:拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它的唯一的一个参数是本类型的一个引用变量,该参数是const类型,不可变的。
浅拷贝:浅拷贝是对指针进行拷贝,拷贝后两个指针指向同一块内存空间。C++中如果不定义类的赋值构造函数,就会调用类的默认赋值构造函数,而类的赋值构造函数是浅拷贝。
深拷贝:深拷贝是对指针进行拷贝而且还对内容进行拷贝,拷贝完成后,指针指向的地址不一样,但是值是一样的。
浅拷贝和深拷贝的区别:前者就是使用编译器提供的默认拷贝构造函数或者默认赋值构造函数。后者是自己显示实现的拷贝/赋值构造函数。
C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类类,别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。
多态的实现主要分为静态多态和动态多态,静态多态主要是重载,在编译的时候就已经确定;动态多态是用虚函数机制实现的,在运行期间动态绑定。
多态的条件:
1.必须有继承
2.要有虚函数重写
3.用父类指针(引用)指向子类对象
多态的基础理论:
联编:一个程序模块,代码之间互相关联的过程
动态联编:把程序联编的过程,推迟到运行时进行
静态联编
多态的实现效果:
同样的调用语句,不同的表现形态
多态的意义:设计模式的基础、编写框架的基础、函数指针做函数参数
什么是数据结构:
数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成 。
常用的数据结构有:数组,栈,链表,队列,树,图,堆,散列表等。
数组:数组是可以在内存中连续存储多个元素的结构,在内存中的分配也是连续的,数组中的元素通过数组下标进行访问
栈:栈是一种特殊的线性表,仅能在线性表的一端操作,栈顶允许操作,栈底不允许操作。 栈的特点是:先进后出,或者说是后进先出。
队列:队列与栈一样,也是一种线性表,不同的是,队列可以在一端添加元素,在另一端取出元素,也就是:先进先出。
链表:链表是物理存储单元上非连续的、非顺序的存储结构,数据元素的逻辑顺序是通过链表的指针地址实现,每个元素包含两个结点,一个是存储元素的数据域 (内存空间),另一个是指向下一个结点地址的指针域。根据指针的指向,链表能形成不同的结构,例如单链表,双向链表,循环链表等。
树:树是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合。
TCP是一个基于字节流的传输服务,"流"意味着TCP所传输的数据是没有边界的。这不同于UDP提供基于消息的传输服务,其传输的数据是有边界的。TCP的发送方无法保证对等方每次接收到的是一个完整的数据包。就会出现分包或粘包的问题.
分包:传输数据不完整,一条信息被分成多次发送。
比如我们发送了一条信息:“你好”,我们可能只收到了“你”,却没有收到“好”,这样就会导致数据的不完整。
粘包:传输的多条数据粘在一起,比如我发了“你好”和“我是小胖”,我们可能会收到“你好我是小胖”,也可能收到“你好我是”“你好我”“你好我是小”,后几种情况是分包粘包同时发生,我们肯定不期望这种现象发生,所以我们就有必要对我们发送的数据进行编辑。
Socket只是一种通信手段它本身没有任何额外的安全措施,所以要用到加密技术 ,不然通信的数据非常容易初攻击者获取到,一般情况下,我们会使用CRC进行冗余验证,看数据包是否传输完整,然后自定义自己的加密方式,将数据包加密以后再发出,有的项目还会对数据包进行压缩,所以我们这里给出一种通用的结构:数据头(长度)+冗余验证(CRC)+是否压缩+包体(加密后)。
区别 |
New 和delete |
malloc和free |
属性 |
C++编译器支持 |
库函数、头文件支持C |
参数 |
申请内存无需指定内存大小 |
显式指定大小 |
返回值 |
对象类型的指针 |
泛型,void*类型,再转换为需要的类型 |
内存区域 |
自由存储区 |
堆上动态分配内存 |
epoll 对文件描述符的操作有两种模式:LT(level trigger)和 ET(edge trigger)。LT 模式是默认模式,LT 模式与 ET 模式的区别如下:
LT 模式:支持block和no-block socket。当 epoll_wait 检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用 epoll_wait 时,会再次响应应用程序并通知此事件。效率会低于ET触发,尤其在大并发,大流量的情况下。但是LT对代码编写要求比较低,不容易出现问题。LT模式服务编写上的表现是:只要有数据没有被获取,内核就不断通知你,因此不用担心事件丢失的情况。
ET 模式:只支持no-block socket。当 epoll_wait 检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用 epoll_wait 时,不会再次响应应用程序并通知此事件。该模式效率非常高,尤其在高并发,大流量的情况下,会比LT少很多epoll的系统调用。但是对编程要求高,需要细致的处理每个请求,否则容易发生丢失事件的情况。
区别:
1) TCP是面向连接的,可靠性高;UDP是基于非连接的,可靠性低
2) 由于TCP是连接的通信,需要有三次握手、重新确认等连接过程,会有延时,实时性差,同时过程复杂,也使其易于攻击;UDP没有建立连接的过程,因而实时性较强,也稍安全
3) 在传输相同大小的数据时,TCP首部开销20字节;UDP首部开销8字节,TCP报头比UDP复杂,故实际包含的用户数据较少。TCP在IP协议的基础上添加了序号机制、确认机制、超时重传机制等,保证了传输的可靠性,不会出现丢包或乱序,而UDP有丢包,故TCP开销大,UDP开销较小
4) 每条TCP连接只能是点到点的;UDP支持一对一、一对多、多对一、多对多的交互通信
应用场景选择:
三次握手:客户端和服务端建立连接需要三次握手
第一次:客户端向服务端发送报文,向服务器发送连接请求;
第二次:服务端向客户端返回ACK报文,通知客户端可以连接;
第三次:客户端收到服务端报文,正式连接服务端。
三次握手完成。
四次挥手:客户端要与服务器断开连接,需要四次挥手
第一次:客户端向服务端发送FIN报文,向服务器发送中断连接请求;
第二次:服务器收到客户端中断请求,向客户端发送已得知中断请求,但服务器还有资源未处理,需要等待;
第三次:服务器处理完数据后,再次向客户端发送报文,告诉客户端可以断开连接了;
第四次:客户端收到服务端断开连接的确认信息后,最后发送信息看是否真的断开连接了,如果服务器一段时间没有回应,则说明已经断开,中断过程完成;
四次挥手完成。
socket概念:
这是为了实现以上的通信过程而建立成来的通信管道,其真实的代表是客户端和服务器端的一个通信进程,双方进程通过socket进行通信,而通信的规则采用指定的协议。
socket只是一种连接模式,不是协议,socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。tcp、udp,简单的说(虽然不准确)是两个最基本的协议,很多其它协议都是基于这两个协议如,http就是基于tcp的,.用socket可以创建tcp连接,也可以创建udp连接。
Socket 传输的特点:
优点:
1) 传输数据为字节级,传输数据可自定义,数据量小(对于手机应用讲:费用低)
2)传输数据时间短,性能高
3)适合于客户端和服务器端之间信息实时交互
4)可以加密,数据安全性强
缺点:
1)需对传输的数据进行解析,转化成应用级的数据
2)对开发人员的开发水平要求高
3)相对于Http协议传输,增加了开发量
GDB是一个由GNU开源组织发布的、UNIX/LINUX操作系统下的、基于命令行的、功能强大的程序调试工具。
进入gdb后可直接在(gdb)后输入相应命令进行调试操作。
特点:被动的去实现,分解成一个个的对象
由现实的世界建立的软件模型
优点:效率高、易维护、易复用、易扩展
缺点:类调用时需要实例化,开销比较大
面向对象:面向对象编程就是把问题分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为。
面向对象就是高度实物抽象化(功能划分)、面向过程就是自顶向下的编程(步骤划分)
虚函数的作用:用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离;用形象的语言来解释就是实现以共同的方法,但因个体差异而采用不同的策略。
虚函数的实现:在有虚函数的类中,类的最开始部分是一个虚函数表的指针,这个指针指向一个虚函数表,表中放了虚函数的地址,实际的虚函数在代码段(.text)中。当子类继承了父类的时候也会继承其虚函数表,当子类重写父类中虚函数时候,会将其继承到的虚函数表中的地址替换为重新写的函数地址。使用了虚函数,会增加访问内存开销,降低效率。
类是对象的概括,对象是类的具体体现
1)对象:对象是运行期的基本实体,它是一个封装了数据和操作这些数据的代码的逻辑实体。
2)类:类是具有相同类型的对象的抽象。一个对象所包含的所有数据和代码可以通过类来构造。
I/O 多路复用是为了解决进程或线程阻塞到某个 I/O 系统调用而出现的技术,使进程或线程不阻塞于某个特定的 I/O 系统调用。
select(),poll(),epoll()都是I/O多路复用的机制。I/O多路复用通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪,就是这个文件描述符进行读写操作之前),能够通知程序进行相应的读写操作。
epoll:
epoll支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就需态,并且只会通知一次。还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知
epoll的优点:
1、没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口);
2、效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;即Epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。
3、 内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销。
Selete:
select 的核心功能是调用tcp文件系统的poll函数,不停的查询,如果没有想要的数据,主动执行一次调度(防止一直占用cpu),直到有一个连接有想要的消息为止。从这里可以看出select的执行方式基本就是不同的调用poll,直到有需要的消息为止。
缺点:
1、每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大;
2、同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大;
3、select支持的文件描述符数量太小了,默认是1024。
优点:
1、select的可移植性更好,在某些Unix系统上不支持poll()。
2、select对于超时值提供了更好的精度:微秒,而poll是毫秒。
1.服务器先用socket()函数来建立一个套接字,用这个套接字完成通信的监听和数据的收发
2.服务器用bind()函数来绑定一个端口号和IP地址,使套接字与指定的端口号和IP地址相关联
3.用服务器调用listen()函数,使服务器的这个端口和IP处于监听状态,等待网络中某一客户机的请求发送
4.客户机用socket()函数建立一个套接字,设定远程IP和端口
5.客户机调用connect()函数连接远程计算机指定的端口。
6.服务器调用accept()函数来接受远程计算机的请求,建立与客户机之间的通信连接
7.建立连接以后,客户机用write()函数或者close()函数向socket中写入数据,也可以用read()函数读区服务器发来的数据
8.服务器用read()函数读区客户机发来的数据,也可以用write()函数或者send()函数来发送数据
9.通信完成以后,使用close()函数关闭socket连接
栈由操作系统自动分配释放 ,用于存放函数的参数值、局部变量等,其操作方式类似于数据结构中的栈。
堆由开发人员分配和释放, 若开发人员不释放,程序结束时由 OS 回收,分配方式类似于链表
堆与栈实际上是操作系统对进程占用的内存空间的两种管理方式,主要有如下几种区别:
(1)管理方式不同。栈由操作系统自动分配释放,无需我们手动控制;堆的申请和释放工作由程序员控制,容易产生内存泄漏;
(2)空间大小不同。每个进程拥有的栈的大小要远远小于堆的大小。理论上,程序员可申请的堆大小为虚拟内存的大小,进程栈的大小 64bits 的 Windows 默认 1MB,64bits 的 Linux 默认 10MB;
(3)生长方向不同。堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。
(4)分配方式不同。堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是由操作系统完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由操作系统进行释放,无需我们手工实现。
(5)分配效率不同。栈由操作系统自动分配,会在硬件层级对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是由C/C++提供的库函数或运算符来完成申请与管理,实现机制较为复杂,频繁的内存申请容易产生内存碎片。显然,堆的效率比栈要低得多。
最本质的一个区别就是默认的访问控制:
默认的继承访问权限
struct是public的,class是private的。
STL(Standard Template Library),即标准模板库,是一个具有工业强度的,高效的C++程序库。它被容纳于C++标准程序库中,包括容器、算法、迭代器组件。
vector内部使用动态数组的方式实现的。如果动态数组的内存不够用,就要动态的重新分配,一般是当前大小的两倍,然后把原数组的内容拷贝过去。所以,在一般情况下,其访问速度同一般数组,只有在重新分配发生时,其性能才会下降。注意vector的size()和capacity()是不同的,前者表示数组中元素的多少,后者表示数组有多大的容量。由上面的分析可以看出,使用vector的时候需要注意内存的使用,如果频繁地进行内存的重新分配,会导致效率低下。它的内部使用allocator类进行内存管理,程序员不需要自己操作内存。
vector其中一个特点:内存空间只会增长,不会减小,援引C++ Primer:为了支持快速的随机访问,vector容器的元素以连续方式存放,每一个元素都紧挨着前一个元素存储。设想一下,当vector添加一个元素时,为了满足连续存放这个特性,都需要重新分配空间、拷贝元素、撤销旧空间,这样性能难以接受。因此STL实现者在对vector进行内存分配时,其实际分配的容量要比当前所需的空间多一些。就是说,vector容器预留了一些额外的存储区,用于存放新添加的元素,这样就不必为每个新元素重新分配整个容器的内存空间。
在大多数软件应用中,线程的数量都不止一个,多线程程序处在一个多变的环境中,可访问的全局变量和堆数据随时都可能被其他的线程改变,这就将“线程安全”的问题提上了议程。那么,如何确保线程的安全呢?
线程安全
一般说来,确保线程安全的方法有这几个:竞争与原子操作、同步与锁、可重入、过度优化。
竞争与原子操作
多个线程同时访问和修改一个数据,可能造成很严重的后果。出现严重后果的原因是很多操作被操作系统编译为汇编代码之后不止一条指令,因此在执行的时候可能执行了一半就被调度系统打断了而去执行别的代码了。一般将单指令的操作称为原子的(Atomic),因为不管怎样,单条指令的执行是不会被打断的。
因此,为了避免出现多线程操作数据的出现异常,Linux系统提供了一些常用操作的原子指令,确保了线程的安全。但是,它们只适用于比较简单的场合,在复杂的情况下就要选用其他的方法了。
同步与锁
为了避免多个线程同时读写一个数据而产生不可预料的后果,开发人员要将各个线程对同一个数据的访问同步,也就是说,在一个线程访问数据未结束的时候,其他线程不得对同一个数据进行访问。
同步的最常用的方法是使用锁(Lock),它是一种非强制机制,每个线程在访问数据或资源之前首先试图获取锁,并在访问结束之后释放锁;在锁已经被占用的时候试图获取锁时,线程会等待,直到锁重新可用。
二元信号量是最简单的一种锁,它只有两种状态:占用与非占用,它适合只能被唯一一个线程独占访问的资源。对于允许多个线程并发访问的资源,要使用多元信号量(简称信号量)。
可重入
一个函数被重入,表示这个函数没有执行完成,但由于外部因素或内部因素,又一次进入该函数执行。一个函数称为可重入的,表明该函数被重入之后不会产生任何不良后果。可重入是并发安全的强力保障,一个可重入的函数可以在多线程环境下放心使用。
过度优化
在很多情况下,即使我们合理地使用了锁,也不一定能够保证线程安全,因此,我们可能对代码进行过度的优化以确保线程安全。
数组
数组的特点:
1.在内存中,数组是一块连续的区域
2.数组需要预留空间
在使用前需要提前申请所占内存的大小,这样不知道需要多大的空间,就预先申请可能会浪费内存空间,即数组空间利用率低
ps:数组的空间在编译阶段就需要进行确定,所以需要提前给出数组空间的大小(在运行阶段是不允许改变的)
3.在数组起始位置处,插入数据和删除数据效率低。
插入数据时,待插入位置的的元素和它后面的所有元素都需要向后搬移
删除数据时,待删除位置后面的所有元素都需要向前搬移
4.随机访问效率很高,时间复杂度可以达到O(1)
因为数组的内存是连续的,想要访问那个元素,直接从数组的首地址处向后偏移就可以访问到了
5.数组开辟的空间,在不够使用的时候需要扩容,扩容的话,就会涉及到需要把旧数组中的所有元素向新数组中搬移
6.数组的空间是从栈分配的
数组的优点:
随机访问性强,查找速度快,时间复杂度为O(1)
数组的缺点:
1.头插和头删的效率低,时间复杂度为O(N)
2.空间利用率不高
3.内存空间要求高,必须有足够的连续的内存空间
4.数组空间的大小固定,不能动态拓展
链表
链表的特点:
1.在内存中,元素的空间可以在任意地方,空间是分散的,不需要连续
2.链表中的元素都会两个属性,一个是元素的值,另一个是指针,此指针标记了下一个元素的地址
每一个数据都会保存下一个数据的内存的地址,通过此地址可以找到下一个数据
3.查找数据时效率低,时间复杂度为O(N)
因为链表的空间是分散的,所以不具有随机访问性,如要需要访问某个位置的数据,需要从第一个数据开始找起,依次往后遍历,直到找到待查询的位置,故可能在查找某个元素时,时间复杂度达到O(N)
4.空间不需要提前指定大小,是动态申请的,根据需求动态的申请和删除内存空间,扩展方便,故空间的利用率较高
5.任意位置插入元素和删除元素效率较高,时间复杂度为O(1)
6.链表的空间是从堆中分配的
链表的优点:
1.任意位置插入元素和删除元素的速度快,时间复杂度为O(1)
2.内存利用率高,不会浪费内存
3.链表的空间大小不固定,可以动态拓展
链表的缺点:
随机访问效率低,时间复杂度为0(N)
boost库是一个优秀的。可移植,开源的C++库,它是由C++标准委员会库工作自成员发起,它是对STL的延续和扩充,设计理念和STL比较接近,都是利用泛型让复用达到最大化,其中有些内容经常成为下一代C++标准库内容,在C++社区影响很大,是不折不扣的“准”标准库。
socket是对tcp/ip协议的封装和应用,给我们提供了操作网络的接口
工作模式:
阻塞模式:是socket的缺省方式,也是最常用的方式,即函数阻塞直到调用完毕。
可能造成阻塞的函数有:connect()、accept()、读写函数、select()、poll()等。
非阻塞模式:非阻塞IO通过进程反复调用IO函数(多次系统调用,并马上返回);在数据拷贝的过程中,进程是阻塞的;
I/O多路复用(同步I/O模式):使用select()、poll()等函数实现对多个socket的同步I/O操作。它能同时等待多个socket描述符,而这些socket描述符其中的任意一个进入读就绪/写就绪/出错状态
为什么选择socket?
应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为套接字(Socket)的接口,区分不同应用程序进程间的网络通信和连接。
短连接
连接->传输数据->关闭连接
HTTP是无状态的,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束后就中断连接。短连接是指SOCKET连接后发送后接收完数据后马上断开连接。
长连接
连接->传输数据->保持连接->传输数据->....->关闭连接
长连接指建立SOCKET连接后不管是否使用都保持连接,但安全性较差。
非阻塞模式可以理解为,执行此套接字的网络调用时,不管是否执行成功,都会立即返回。
如调用recv( )函数读取网络缓冲区中的数据时,不管是否读到数据都立即返回,而不会一直挂在此函数的调用上。
而阻塞模式为只有接收到数据后才会返回,套接字默认的会创建堵塞模式。