epoll的优点
- 高效的事件通知机制:epoll使用了内核与用户空间之间的事件回调机制,只有当有新的事件到达时才会通知应用程序,避免了轮询和阻塞等待导致的性能损耗
- 支持大规模并发连接:epoll采用基于事件驱动的方式处理IO,不受文件描述符数量限制,可以高效地管理大量连接
- 内存拷贝优化:epoll使用共享内存区域保存事件就绪列表,在出发事件时将就绪列表直接传递给应用程序,避免了数据的多次拷贝
- 精确控制事件类型:epoll支持边缘触发(ET)和水平触发(LT),可以根据需求选择合适的时间类型进行监听处理
- 能够处理更多类型的IO:除了普通文件和套接字外,epoll还支持管道、定时器和信号等其他类型的IO操作
水平触发 (Level-Triggered, LT):
- 在水平触发模式下,只要文件描述符处于可读或可写状态,epoll_wait()函数每次调用都会返回相应的事件。例如,只要读缓冲区里有数据可读,即使这次没读完,下一次调用仍然会报告可读事件,直到所有数据都被读取完毕。
- 这种模式相对简单易用,因为它允许应用程序按需读取数据,无需一次性全部读完。但缺点是在某些场景下如果不小心处理,可能会陷入“忙等”循环,即不断收到相同事件的通知却无法完全消耗掉缓冲区中的数据。
边缘触发 (Edge-Triggered, ET):
- 边缘触发模式下,epoll只会通知你一次,即当某个文件描述符状态从不可读/不可写变为可读/可写时。这意味着,一旦发生状态变化(比如新的数据到达使得读缓冲区从空变满),epoll_wait()会触发一次事件通知。
- 使用边缘触发时,应用程序必须确保在接收到事件通知后能够一次性读取或写入所有可用的数据,直到遇到EAGAIN/EWOULDBLOCK错误,表明当前没有更多数据可读或没有足够的缓冲区空间可写。若不这样操作,可能导致后续事件不再被触发,即使仍有数据在缓冲区中等待处理。
- 边缘触发可以降低不必要的系统调用次数,提高效率,但如果编程不当,可能会错过事件,因此实现起来通常更复杂,需要开发者具有更强的控制能力和理解力
epoll具体过程
- 创建epoll实例:通过调用epoll_create函数创建一个epoll实例,返回一个文件描述符
- 注册事件监听:使用epoll_ctl函数将需要监听的文件描述符和相关事件注册到epoll实例中。可以指定感兴趣的事件类型(读写等)以及是否使用边缘触发模式
- 等待时间就绪:调用epoll_wait函数开始等待时间就绪。该函数会一直阻塞,直到有文件描述符上的事件发生或者超时时间到达。在这个阶段,内核会监视已注册的文件描述符,并记录哪些文件描述符上有可读、可写、异常等事件发生
- 处理就绪事件:当epoll_wait函数返回时,表示有一个或多个文件描述符上的时间已经就绪。应用程序可以遍历就绪事件列表,对每个就绪的文件描述符进行相应操作
- 重复步骤2-4:如果需要持续监听事件,可以重复执行步骤2-4.在每次轮询时,只需修改注册的事件类型即可
IO中断发生后的行为有什么
- 中断服务例程(interrupt service routine,ISR)的执行:一旦IO设备触发了中断信号,操作系统将跳转到对应的ISR进行处理,ISR是预先定义好的代码段,用于处理特定设备或事件相关的中断
- 保存上下文:在进入ISR之前,操作系统需要保存当前被中断程序的上下文信息,如CPU寄存区、程序计数器等,便于中断结束后恢复
- 中断处理:ISR会根据具体情况执行适当的处理操作。例如,读取或写入数据,接收或发送数据
- 唤醒阻塞线程:如果有进程正在等待某个IO操作完成,而该IO操作触发了中断,则操作系统会将阻塞状态的进程唤醒,并让其继续执行
- 恢复上下文:在ISR执行完毕后,操作系统将恢复被中断的上下文信息,并重新开始执行被中断处的指令
介绍Reactor,怎么优化
Reactor是一种基于事件驱动的设计模式,用于构建高效的并发系统,有以下几个核心组件
- 事件处理器(Event Handler):负责处理特定类型的事件,并定义对应的回调函数
- 事件分派器(Event Dispatcher):负责将输入的事件分派给对应的事件处理器进行处理
- 多路复用器(Multiplexer):负责管理多个事件源,并监听这些事件源上是否有待处理的事件
- 同步/异步模式支持:Reactor可以以同步或异步的方式运行,具体取决于需求和编程语言/框架
优化可考虑: - 使用非阻塞IO:通过非阻塞IO操作来避免线程在等待IO完成时被阻塞,提高系统并发性能
- 使用多线程/线程池:将耗时较长且独立的任务交给线程池来处理,避免占用Reactor主循环线程
- 采用合适的数据结构和算法:在Reactor内部使用高效的数据结构和算法,如选择合适的哈希表、红黑树等数据结构、以及使用最佳匹配算法等
- 事件驱动的设计:合理划分事件处理器,使其能够专注于特定类型的事件处理,避免单个处理器负担过重
- 缓存优化:通过合理的缓存机制来减少IO操作和资源访问次数,提高系统效率
- 合理的配置系统参数:根据际需求和硬件性能,调整相关系统参数如线程数、最大连接数、内存大小等,以获得最佳性能
webserver线程池怎么对应大量连接的
- 合理配置线程池大小:根据系统的负载情况、硬件性能和预期的并发连接数等因素,设置适当的线程池大小。如果线程池过小,可能会导致阻塞;如果线程池过大,可能会占用过多系统资源
- 使用固定大小的线程池:使用固定大小的线程池可以确保每个请求都能够得到及时响应,并且控制了整体的并发数量。这样可以避免由于动态调整线程数而引起的性能波动
- 设置适当的任务队列长度:若任线程池中所有线程都在忙碌,可以将新连接的请求放入任务队列中等待处理
- 拒绝策略:当线程池和任务队列均无法接受更多任务时,可以选择不同的拒绝策略,如直接抛出异常并关闭连接、使用某种算法尝试替换已排队的任务、使用调用方提供的回调函数通知无法处理连接、忽略等
- 进行负载均衡:如果有多个服务器实例,可以使用负载均衡算法将连接分发到不同的服务器上,以平衡服务器负载
- 监控与动态调整:在运行时对线程池状态进行监控,必要时动态调整某些参数
C++的历史版本
- C++98(也称C++03)
- 最早的标准化版本
- 引入了许多面向编程的特性,如类、继承、多态等
- C++11
- 一次重大更新
- 引入了一系列新特性,如lambda表达式、智能指针、右值引用和移动语义等
- 增强了代码的可读性、间接性和性能
- 自动类型推导(auto关键字)
- 区间循环(range-based for循环)
- Lambda表达式
- 右值引用和移动语义
- 线程支持库(std::thread等)
- C++14
- 添加了一些新特性,如泛型lambda函数、二进制文字表示以及constexpr函数的增强等
- 泛型lambda表达式
- 返回类型推导
- 字面量操作符
- 类型别名模板(using关键字)
- C++17
- 引入了许多新特性,包括结构化绑定、if constexpr语句、折叠表达式等
- 结构化绑定(structured bindings)
- if constexpr语句
- 折叠表达式(fold expressions)
- 并行算法库
- C++20
- 引入了许多新特性,概念(concepts)、范围for循环初始化器、第三方运算符(spaceship operator)等
- 概念(concepts):用于模板参数约束的语法扩展
- 三路比较运算符(<=>):简化对象比较操作符的实现
- 模块化编程:引入模块化系统来替代头文件包含机制
- 异常规范化被弃用
单例模式的实现
- 将类的构造函数、拷贝构造函数和赋值构造函数和赋值运算符声明为私有,以防外部直接创建对象或复制对象
- 在类中定义一个静态成员变量,用于存储唯一实例的指针
- 提供一个公共的静态方法(通常命名为getInstance()),用于获取类的唯一实例。在该方法内部,会有如下操作
- 检查静态成员变量是否为空
- 如果为空,则创建一个新的实例并将其赋值给静态成员变量
- 返回静态成员变量作为实例指针
- 可选:添加线程安全措施,确保在多线程环境下单例对象能正确地被创建和访问。可以使用互斥锁、双重检查锁定等机制来解决线程安全问题
- 可选:根据需要定义虚构函数,并在其中释放资源或执行其他清理操作
为什么需要移动构造
移动构造函数可以在C++中的引入是为了提高程序的性能和效率。当需要通过值传递对象时,常规的拷贝构造函数会进行深拷贝操作,将原对象赋值到新对象中。但是在某些情况下,这种深拷贝操作可能会产生不必要的开销,特别是对于大型对象或者含有资源管理(例如动态内存分配)的对象
移动构造函数允许在没有复制数据的情况下,将资源从一个对象“移动”到另一个对象,从而避免了不必要的数据复制和额外的内存分配。移动构造函数通常通过右值引用来实现,并且在被调用后,原始对象将被置为有效但未定义状态
auto有什么缺点
- 可读性下降:使用auto关键字可以使代码更简洁,但是也降低了可读性。程序员无法直接看到变量的实际类型,需要通过山下文来确定其类型
- 隐藏错误:如果使用auto时没有正确初始化变量,编译器可能会根据上下文选择一个错误的类型,导致意外行为或错误。使用auto时如果忽略了隐式类型转换,也可能会引入潜在的问题
- 增加编译时间:编译器需要进行类型推导和分析上下文
- 不利于维护和调试:可读性下降导致不利于维护和调试
匿名函数有什么优点
- 简洁性:匿名函数在需要时直接定义,无需显示命名。使代码更加简洁明了,适用于一次性的使用
- 便捷性:由于不需要为匿名函数分配独立的名称,可以将它们传递给其他函数作为参数,或者用作回调函数等。这种灵活性使得代码编写更加方便和简单
- 闭包特性:匿名函数可以捕获并访问其外部环境中的变量。这以为这它们可以在定义时引用自己周围作用域中的变量,并且可以继续使用这些变量,即使在定义之后环境发生了改变。这种闭包特性非常有用,能够实现更强大和灵活的功能
如何理解HTTP是一个无状态的协议
HTTP的每个请求都是完全独立的,每个请求半酣了处理这个请求所需的完整的数据,发送请求不涉及到状态的变更。对比有状态的协议更加简单,实现起来也更容易,不需要使用状态机,只需要使用一个循环
http个版本的区别
- 1.0默认使用短连接,可以强制开启长连接
- 1.1默认长连接,分块传输编码,引入了管道机制
- 2.0完全多路复用
GET和POST的区别
- post更安全(不会作为url的一部分,不会被缓存、保存在服务器日志、以及浏览器浏览记录中)
- post发送的数据更大(get有url长度限制)
- post能发送更多的数据类型(get只能发送ASCII字符)
- post比get慢
- post用于修改和写入数据,get一般用于搜索排序和筛选之类的操作(淘宝,支付宝的搜索查询都是get提交),目的是资源的获取,读取数据
- get可被缓存、保留在浏览器历史记录中、可被收藏为标签、对数据长度有限制,post则相反
HTTP和HTTPS的区别
HTTP,超文本传输协议,是一个基于TCP/IP通信协议来传递 明文数据 的协议。HTTP会存在这几个问题:
- 请求信息是明文传输,容易被窃听截取。
- 没有验证对方身份,存在被冒充的风险
- 数据的完整性未校验,容易被中间人篡改
HTTPS超文本传输安全协议的出现解决了这些问题
HTTPS=HTTP+SSL/TLS - HTTP 明文传输,数据都是未加密的,安全性较差,HTTPS(SSL+HTTP) 数据传输过程是加密的,安全性较好。
- 使用 HTTPS 协议需要到 CA(Certificate Authority,数字证书认证机构) 申请证书,一般免费证书较少,因而需要一定费用。
- HTTP 页面响应速度比 HTTPS 快,主要是因为 HTTP 使用 TCP 三次握手建立连接,客户端和服务器需要交换 3 个包,而 HTTPS除了 TCP 的三个包,还要加上 ssl 握手需要的 9 个包,所以一共是 12 个包。
- http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。
- HTTPS 其实就是建构在 SSL/TLS 之上的 HTTP 协议,所以,要比较 HTTPS 比 HTTP 要更耗费服务器资源。
##HTTPS流程?
要点:公私钥、数字整证书、加密、对称加密、非对称加密
- HTTPS = HTTP + SSL/TLS,也就是用SSL/TLS对数据进行加密和解密,Http进行传输。
- SSL,即Secure Sockets Layer(安全套接层协议),是网络通信提供安全及数据完整性的一种安全协议。
- TLS,即Transport Layer Security(安全传输层协议),它是SSL3.0的后续版本。
- 客户端发起Https请求,连接到服务器的443端口。
- 服务器必须要有一套数字证书(证书内容有公钥、证书颁发机构、失效日期等)。
- 服务器将自己的数字证书发送给客户端(公钥在证书里面,私钥由服务器持有)。
- 客户端收到数字证书之后,会验证证书的合法性。如果证书验证通过,就会生成一个随机的对称密钥,用证书的公钥加密。
- 客户端将公钥加密后的密钥发送到服务器。
- 服务器接收到客户端发来的密文密钥之后,用自己之前保留的私钥对其进行非对称解密,解密之后就得到客户端的密钥,然后用客户端密钥对返回数据进行对称加密,如此传输的数据都是密文啦。
- 服务器将加密后的密文返回到客户端。
- 客户端收到后,用自己的密钥对其进行对称解密,得到服务器返回的数据。
什么是数字签名?什么是数字证书?
数字证书是指在互联网通讯中标志通讯各方身份信息的一个数字认证,人们可以在网上用它来识别对方的身份。它的出现,是为了避免身份被篡改冒充的。比如Https的数字证书,就是为了避免公钥被中间人冒充篡改
- 公钥和个人等信息,经过Hash摘要算法加密,形成消息摘要;将消息摘要拿到拥有公信力的认证中心(CA),用它的私钥对消息摘要加密,形成数字签名。
- 公钥和个人信息、数字签名共同构成数字证书
对称加密和非对称加密
- 对称加密:指加密和解密使用同一密钥,优点是运算速度较快,缺点是如何安全将密钥传输给另一方。常见的对称加密算法有:DES、AES等
- 非对称加密:指的是加密和解密使用不同的密钥(即公钥和私钥)。公钥与私钥是成对存在的,如果用公钥对数据进行加密,只有对应的私钥才能解密。常见的非对称加密算法有RSA。
DNS解析过程
DNS解析过程:
- 本地电脑检查浏览器缓存中有没有该域名对应的解析过的IP地址。域名被缓存的时间由TTL来设置,通常要求时间长度适中,太长IP地址可能会发生变化而导致无法解析到变化后的IP地址,太短会导致用户每次访问网站都要重新解析域名
- 浏览器会查找操作系统缓存中是否有这个域名对应的DNS解析结果。
- 需要用到网络配置中的“DNS服务器地址”。操作系统会把这个域名发送给这个本地DNS服务器。
- 如果本地DNS服务器仍然没有命中,就直接到根DNS服务器请求解析
- 根DNS服务器返回给本地DNS域名服务器一个顶级DNS服务器地址,它是国际顶级域名服务器,如.com .cn等
- 本地DNS服务器再向上一步获得的顶级DNS服务器发送解析请求。
- 接受请求的顶级DNS服务器查找并且返回此域名对应的Name Server域名服务器的地址。 这个Name Server服务器就是要访问的网站域名提供商的服务器,其实该域名的解析任务就是由域名提供商的服务器来完成。
- Name Server服务器会查询储存的域名和IP的映射关系表,再把查询出来的域名和IP地址等信息,连同一个TTL值返回给本地DNS服务器
- 本地DNS服务器返回给浏览器对应的IP和TTL值,本地DNS服务器会缓存在这个域名和IP的对应关系,缓存时间由TTL的值控制。
- 把解析的结果返回给本地电脑,本地电脑根据TTL值缓存在本地系统缓存中,域名解析过程结束在实际的DNS解析过程中,可能还不止这10步,如Name Server可能有很多级,或者有一个GTM来负载均衡控制,这都有可能会影响域名解析过程。
五层计算机网络体系结构中,每一层对应的网络协议有哪些?
Socket是计算机网络中用于实现网络通信的编程接口,不仅仅是IP+Port的封装,它还包含了协议和一些操作函数。Socket是一个抽象层,它提供了一种通用的接口,让应用程序可以使用不同的协议进行网络通信。Socket也不是一个方法,而是一个对象,它有自己的属性和方法。你可以创建一个Socket对象,然后调用它的方法来实现网络通信
HTTP的过程
HTTP是一个基于TCP/IP协议来传递数据的超文本传输协议,传输的数据类型有HTML,图片等
- 客户端进行DNS域名解析,得到对应的IP地址
- 根据这个IP,找到对应的服务器建立连接(三次握手)
- 建立TCP连接后发起HTTP请求(一个完整的http请求报文)
- 服务器响应HTTP请求,客户端得到html代码
- 客户端解析html代码,用html代码中的资源(如js,css,图片等等)渲染页面。
- 服务器关闭TCP连接(四次挥手)
ARP协议的工作过程
ARP地址解析协议,用于实现IP到MAC地址的映射
- 每台主机都会在自己的ARP缓冲区中建立一个ARP列表,以表示IP地址和MAC地址的对应关系
- 当源主机需要将一个数据包要发送到目的主机时,会首先检查自己的ARP列表,是否存在该IP地址对应的MAC地址;如果有﹐就直接将数据包发送到这个MAC地址;如果没有,就向本地网段发起一个ARP请求的广播包,查询此目的主机对应的MAC地址。此ARP请求的数据包里,包括源主机的IP地址、硬件地址、以及目的主机的IP地址。
3.网络中所有的主机收到这个ARP请求后,会检查数据包中的目的IP是否和自己的IP地址一致。如果不相同,就会忽略此数据包;如果相同,该主机首先将发送端的MAC地址和IP地址添加到自己的ARP列表中,如果ARP表中已经存在该IP的信息,则将其覆盖,然后给源主机发送一个 ARP响应数据包,告诉对方自己是它需要查找的MAC地址。 - 源主机收到这个ARP响应数据包后,将得到的目的主机的IP地址和MAC地址添加到自己的ARP列表中,并利用此信息开始数据的传输。如果源主机一直没有收到ARP响应数据包,表示ARP查询失败。
为什么有了IP地址还需要MAC地址?
如果让你来设计网络 (qq.com)
- IP地址和MAC地址都可以用来标识一台计算机,但是计算机的IP地址可以由用户自行更改,管理起来更加困难,而MAC地址不可更改,所有会把IP地址和MAC地址组合起来使用
- 早期先出现的是MAC地址,随着网络中的数量越来越多,整个路由过程越来越复杂,便出现了子网的概念。对于目的地址在其他子网的数据包,路由只需要将数据包送到那个子网即可
- 有了IP地址,对于同于一个子网上的设备,IP地址的前缀都是一样的,这样路由器通过IP地址的前缀就知道设备在哪个子网上了。如果只用MAC地址的话,路由器则需要记住每个MAC地址在哪个子网,这就需要路由器有极大的存储空间,难以实现
ICMP协议的作用
- ICMP是一种面向无连接的协议,用于传输出错报告控制信息
- 如果遇到IP数据报无法访问目标、IP路由器无法按当前的传输速率转发数据包等情况时,会自动发送ICMP消息
- PING:通过发送回送请求报文和回送回答报文来检测源主机到目的主机的链路是否有问题,目的地是否可达,以及通信的延迟情况
- traceroute:通过发送探测报文来获取链路地址信息
Ping的工作原理
ping,Packet Internet Groper,是一种因特网包探索器,用于测试网络连接量的程序。Ping是工作在TCP/IP网络体系结构中应用层的一个服务命令, 主要是向特定的目的主机发送ICMP(Internet Control Message Protocol 因特网报文控制协议) 请求报文,测试目的站是否可达及了解其有关状态
一般来说,ping可以用来检测网络通不通。它是基于ICMP协议工作的。假设机器A ping机器B,工作过程如下:
- ping通知系统,新建一个固定格式的ICMP请求数据包
- ICMP协议,将该数据包和目标机器B的IP地址打包,一起转交给IP协议层
- IP层协议将本机IP地址为源地址,机器B的IP地址为目标地址,加上一些其他的控制信息,构建一个IP数据包
- 先获取目标机器B的MAC地址。
- 数据链路层构建一个数据帧,目的地址是IP层传过来的MAC地址,源地址是本机的MAC地址
- 机器B收到后,对比目标地址,和自己本机的MAC地址是否一致,符合就处理返回,不符合就丢弃。
- 根据目的主机返回的ICMP回送回答报文中的时间戳,从而计算出往返时间
- 最终显示结果有这几项:发送到目的主机的IP地址、发送 & 收到 & 丢失的分组数、往返时间的最小、最大& 平均值
简述TCP和UDP的区别
TCP的流量控制和拥塞控制
区别
- 拥塞控制:拥塞控制是作用于网络的,它是防止过多的数据注入到网络中,避免出现网络负载过大的情况;发送发维持一个拥塞窗口cwnd的状态变量。拥塞窗口的大小取决于网络的拥塞能力,并且动态变化。维护一个慢开始门限ssthresh状态变量
- 常用的方法就是:( 1 )慢开始、拥塞避免( 2 )快重传、快恢复。
- 慢开始:初始cwnd=1,每经过一个往返时间RTT,拥塞窗口cwnd就加倍,当cwnd大于等于ssthresh时,停止慢开始改用拥塞避免
- 拥塞避免:每经过一个往返时间RTT,就把发送方的拥塞窗口cwnd加1,使得拥塞窗口cwnd按线性缓慢增长
- 快重传:接收方在收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方)而不要等到自己发送数据时捎带确认。快重传算法规定,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器时间到期
- 快恢复:当发送方连续收到三个重复确认时,就将慢开始门限ssthresh改为cwnd/2,然后执行拥塞避免算法
- 流量控制:流量控制是作用于接收者的,它是控制发送者的发送速度从而使接收者来得及接收,防止分组丢失的
- 在每次接收端回复确认报文ACK时,通知发送端已成功接收的数据量,并更新接收窗口rwnd的大小。
- 发送方根据接收窗口rwnd和拥塞窗口cwnd调整发送速率。
TCP是如何确保可靠性的
- 首先,TCP的连接是基于三次握手,而断开则是基于四次挥手。确保连接和断开的可靠性。
- 其次,TCP的可靠性,还体现在有状态;TCP会记录哪些数据发送了,哪些数据被接收了,哪些没有被接受,并且保证数据包按序到达,保证数据传输不出差错。
- 再次,TCP的可靠性,还体现在可控制。它有数据包校验、ACK应答、超时重传(发送方)、失序数据重传(接收方)、丢弃重复数据、流量控制(滑动窗口)和拥塞控制等机制。