【牛客网linux服务器开发项目】面试八股问答2

[21]Select

Poll优点:解决了select的1024限制(定义一个结构体数组,可以自己设大小),可以无数个;解决了fd复用,因为结构体除了有events,还有revents;

对于select和poll来说,所有文件描述符都是在用户态被加入其文件描述符集合的,每次调用都需要将整个集合拷贝到内核态;epoll则将整个文件描述符集合维护在内核态,每次添加文件描述符的时候都需要执行一个系统调用。

select使用线性表描述文件描述符集合,文件描述符有上限;poll使用链表来描述;epoll底层通过红黑树来描述,并且维护一个ready list,将事件表中已经就绪的事件添加到这里,在使用epoll_wait调用时,仅观察这个list中有没有数据即可。

select和poll的最大开销来自内核判断是否有文件描述符就绪这一过程:每次执行select或poll调用时,它们会采用遍历的方式,遍历整个文件描述符集合去判断各个文件描述符是否有活动;epoll则不需要去以这种方式检查,当有活动产生时,会自动触发epoll回调函数通知epoll文件描述符,然后内核将这些就绪的文件描述符放到之前提到的ready list中等待epoll_wait调用后被处理。

select和poll都只能工作在相对低效的LT模式下,而epoll同时支持LT和ET模式。

当监测的fd数量较小,且各个fd都很活跃的情况下,建议使用select和poll;当监听的fd数量较多,且单位时间仅部分fd活跃的情况下,使用epoll会明显提升性能。

Select可以跨平台,epoll不可以

ET配合非阻塞使用,防止read读完后处于阻塞状态,难以执行下面代码以及难以检测其他文件描述符是否有事件到来(单进程)

监听文件描述符监听到变化的事件数,然后遍历这些事件解析事件的fd和event如果fd是listen文件描述符,那么处理监听(accept()返回读写的fd,添加到定时器中(堆没有这个fd,直接放,设置超时时间,有这个fd则设置超时时间后上下调整)将fd加入epoller,设置非阻塞)如果读事件变化,处理读如果写事件变化,处理写

处理监听事件:

处理读事件:1.定时器内部延长该事件的超时时间2.加到任务队列中

子线程负责读数据到缓冲区,解析http请求

Read()的返回值为小于等于0,代表已经读完,返回break

Readfd是分散读,调用readv(),代表数据已经存放在两个缓冲区里了定义2个vector数组buffer:初始化大小1024 65536 vector<char>

每个数组里面设置两个坐标:一个是存放数组可以读的起始位置base,一个是存放可以读的长度writeable

如果readv()返回值小于0,报错如果第一个数组可存放的长度writeable大于读到的长度,写坐标右移否则,就先把第一个填满,然后在第二个大buffer里存放剩余的

Append函数中:先创造空间,(如果第一个放不下,resize第一个空间)(如果第一个经过挪动后可以放,就直接放在第一个了)然后把第二个里数据放到第一个里

处理写事件:1.定时器内部延长该客户端的超时时间2.加到任务队列中

业务逻辑处理:解析http请求,封装成响应的数据如果完成一次解析响应,则注册该fd写事件,写给客户端否则继续注册fd为读事件。

解析请求数据:请求首行、请求头、请求体

请求首行:请求方法 请求路径 请求协议版本

请求首行:

请求头: 往writebuff里写响应数据

响应行和响应头在writebuff里,响应体在内存映射里

处理写事件dealwrite分散写:因为响应数据分为两部分,一块在writebuff,一块在内存映射里,现在把他们都写到TCP的写缓冲区里

[22]HTTP报文解析相关

• 用了状态机啊,为什么要用状态机?

每个状态都有一系列的转移,每个转移与输入和另一状态相关。当输入进来,如果它与当前状态的某个转移相匹配,机器转换为所指的状态,然后执行相应的代码。

传统应用程序的控制流程基本是按顺序执行的:遵循事先设定的逻辑,从头到尾地执行。简单来说如果想在不同状态下实现代码跳转时,就需要破坏一些代码,这样就会造成代码逻辑混乱,代码显得十分复杂。

• 状态机的转移图画一下

• https协议为什么安全?

https=http+TLS/SSL

TLS/SSL协议位于应用层协议和TCP之间,构建在TCP之上,由TCP协议保证数据传输版的可靠性,任何数据到权达TCP之前,都经过TLS/SSL协议处理。

https是加密传输协议,可以保障客户端到服务器端的传输数据安全。用户通过http协议访问网站时,浏览器和服务器之间是明文传输,这就意味着用户填写的密码、帐号、交易记录等机密信息都是明文,随时可能被泄露、窃取、篡改,被第三者加以利用。安装SSL证书后,使用https加密协议访问网站,可激活客户端浏览器到网站服务器之间的"SSL加密通道"(SSL协议),实现高强度双向加密传输,防止传输数据被泄露或篡改。

https的ssl连接过程

1. 客户端提交https请求

2. 服务器响应客户,并把证书公钥发给客户端

3. 客户端验证证书公钥的有效性

4. 有效后,会生成一个会话密钥

5. 用证书公钥加密这个会话密钥后,发送给服务器

6. 服务器收到公钥加密的会话密钥后,用私钥解密,回去会话密钥

7. 客户端与服务器双方利用这个会话密钥加密要传输的数据进行通信

• GET和POST的区别

get请求和post请求区别

// 1、get请求一般用来请求获取数据

// post请求一般作为发送数据到后台,传递数据,创建数据

// 2、get请求也可以传参到后台,但是传递的参数则显示在地址栏,安全性低,且参数的长度也有限制(2048字符)

// post请求则是将传递的参数放在request body中,不会在地址栏显示,安全性比get请求高,参数没有长度限制

// 3、get请求刷新浏览器或者回退没有影响

// post请求则会重新请求一遍

// 4、get请求可以被缓存,也会保留在浏览器的历史记录中

// post请求不会被缓存,也不好保留在浏览器的历史记录中

// 5、get请求通常是通过url地址请求

// post常见的则是form表单请求

[23]数据库登录注册相关

设置一个固定大小的数据库连接池,利用信号量和互斥锁控制放入的连接数量• 登录说一下?数据库登录分为:1.载入数据表 2.提取用户名和密码 3.注册和登录校验 4.页面跳转

1.载入数据表就是把数据库的数据通过通过map容器传到服务器上。

2.当从浏览器上输入用户的用户名和密码后,浏览器会一个post请求报文,服务器通过解析请求报文的消息体,解析出账号密码。

3.根据解析出的账号密码,与map容器中保存账号密码进行对比校验,相符则成功登陆,就将浏览器跳转到对应的界面。注册账号时,同样将输入的账号密码与数据库已经存储的账号名进行对比校验,防止出现相同的账号名。如果不相同就加入数据库。

4.当输入的账号密码与数据库的数据成功匹配,就将浏览器跳转到对应的界面。数据库用单例模式

设置一个队列queue存放数据库连接信息设置信号量代表队列里可用的连接数量设置互斥锁

初始化为信号量为最大连接数,用一个-1,释放一个+1

RAII机制:利用;lock_guard实现自动的状态管理,构造加锁和析构释放锁

你这个保存状态了吗?如果要保存,你会怎么做?(cookie和session)Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容

Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了

登录中的用户名和密码你是load到本地,然后使用map匹配的,如果有10亿数据,即使load到本地后hash,也是很耗时的,你要怎么优化?

用的mysql啊,redis了解吗?用过吗?

1.mysql和redis的数据库类型mysql是关系型数据库,主要用于存放持久化数据,将数据存储在硬盘中,读取速度较慢。redis是NOSQL,即非关系型数据库,也是缓存数据库,即将数据存储在缓存中,缓存的读取速度快,能够大大的提高运行效率,但是保存时间有限。

2.mysql的运行机制mysql作为持久化存储的关系型数据库,相对薄弱的地方在于每次请求访问数据库时,都存在着I/O操作,如果反复频繁的访问数据库。第一:会在反复链接数据库上花费大量时间,从而导致运行效率过慢;第二:反复的访问数据库也会导致数据库的负载过高,那么此时缓存的概念就衍生了出来。

3.缓存缓存就是数据交换的缓冲区(cache),当浏览器执行请求时,首先会对在缓存中进行查找,如果存在,就获取;否则就访问数据库。缓存的好处就是读取速度快

4.redis数据库redis数据库就是一款缓存数据库,用于存储使用频繁的数据,这样减少访问数据库的次数,提高运行效率。

5.redis和mysql的区别总结(1)类型上从类型上来说,mysql是关系型数据库,redis是缓存数据库(2)作用上mysql用于持久化的存储数据到硬盘,功能强大,但是速度较慢redis用于存储使用较为频繁的数据到缓存中,读取速度快(3)需求上mysql和redis因为需求的不同,一般都是配合使用

[24]定时器相关

为什么要用定时器?方便释放那些超时的非活动连接,关闭被占用的文件描述符,才使用定时器•

说一下定时器的工作原理(小顶堆)将所有定时器中超时时间最小的一个定时器的超时值作为alarm函数的定时值。这样,一旦定时任务处理函数tick()被调用,超时时间最小的定时器必然到期,我们就可以在tick 函数中处理该定时器。然后,再次从剩余的定时器中找出超时时间最小的一个(堆),并将这段最小时间设置为下一次alarm函数的定时值。如此反复,就实现了较为精确的定时。

添加:O(lgn)删除:O(lgn)定时器代码:

1.设置超时时间为1min

2.获取下一个即将要超时的时间(先把堆中已超时的剔除(下树,关闭文件描述符,usercount--),用下一个即将超时的节点时刻减去当前时刻)

3.吧这个值设为epoll_wait()的阻塞时间

4.在这个时间里,如果有事件发生,epoll_wait返回变化文件描述符数量,继续执行,否则直到这个时间过了后才返回

5. 如果timeMS==-1,则永久阻塞,就没法吧上面的超时时间的给剔除;如果不设置超时时间,调用次数太多,效率低

小顶堆heap用vector实现,默认大小64,不是红黑数,小顶堆放的是即将要超时的时间里面存放timernode:主要是超时时间,文件描述符

Uordered_map<fd,index>ref二者通过索引i进行关联

往小跟堆添加元素1.如果ref没有存:heap进行push_back,并增超时时间,ref存2.如果ref有存:找到对应heap节点,直接增加超时

往小跟堆删除元素:1将要删除的结点换到队尾(一次交换就行),然后调整堆2. 队尾元素删除(ref进行erase,heap进行pop_back)

交换节点:1交换heap值2交换ref索引

向上调整:找到当前节点与父节点去比

向下调整:找到左右节点中最大的那个,和父节点去比如果父节点大,则交换父节点和当前节点

双向链表啊,删除和添加的时间复杂度说一下?还可以优化吗?

[25]日志相关

针对下面进行阻塞,上面直接pushback

说下你的日志系统的运行机制

1.单例模式(局部静态变量懒汉方法)获取实例

2:主程序一开始Log::get_instance()->init()初始化实例。初始化后:服务器启动。按当前时刻创建日志(前缀为时间,后缀为自定义log文件名,并记录创建日志的时间day和行数count)。如果是异步(通过是否设置队列大小判断是否异步,0为同步。当异步时候,子线程可以慢慢从队列里取出日志信息,进行写日志),工作线程将要写的内容放进阻塞队列,还创建了写线程用于在阻塞队列里取出一个内容(指针),写入日志。

3:其他功能模块调用write_log()函数写日志。(write_log:实现日志分级、分文件、超行分类的格式化输出内容。)里面会根据异步、同步实现不同的写方式。

阻塞队列是自定义的一个类,里面用互斥锁和条件变量,实现生产者消费者模型

为什么要异步?和同步的区别是什么?

因为同步日志的,日志写入函数与工作线程串行执行,由于涉及到I/O操作,在单条日志比较大的时候,同步模式会阻塞整个处理流程,服务器所能处理的并发能力将有所下降,尤其是在峰值的时候,写日志可能成为系统的瓶颈。

而异步日志采用生产者-消费者模型,工作线程将所写的日志内容先存入缓冲区,写线程从缓冲区中取出内容,写入日志。并发能力比较高

阻塞队列:Deque(里面放的是字符串,要写的日志信息)、互斥锁、条件变量(生产者消费者)

阻塞体现在生产者消费者模型上(用的是条件变量,没用信号量),当队列里没有日志可写,就阻塞等待,有日志,就唤醒线程进行写日志

数据库连接池用到信号量,线程池和日志阻塞队列用到条件变量,三者都用到互斥锁日志实现分级、分天、分文件(每个日志文件50000行)日志级别:LOG_DEBUG LOG_INFO LOG_WARN LOG_ERROR写日志:

1.单例模式(局部静态变量懒汉方法)获取实例2.主线程将日志信息放到队列里

3.子线程写日志,从队列里取出来写到文件

现在你要监控一台服务器的状态,输出监控日志,请问如何将该日志分发到不同的机器上?(消息队列)• 同一个机器:使用观察者模式(有的叫发布订阅模式)• 但是多机器,借助redis数据库的消息队列的发布订阅模式。实现分布式日志系统。如果有上次未写完的数据,文件指针开着,先flush写完数据,关闭文件指针,在重新打开文件指针

[26]压测相关

• 服务器并发量测试过吗?怎么测试的?

webbench是什么?介绍一下原理父进程fork若干个子进程,每个子进程在用户要求时间或默认的时间内对目标web循环发出实际访问请求,父子进程通过管道进行通信,子进程通过管道写端向父进程传递在若干次请求访问完毕后记录到的总信息,父进程通过管道读端读取子进程发来的相关信息,子进程在时间到后结束,父进程在所有子进程退出后统计并给用户显示最后的测试结果,然后退出。• 测试的时候有没有遇到问题?在虚拟机上创建子进程,同时也在虚拟机上运行服务器(创建子进程会消耗资源,可以在另一机器上fork,来访问此服务器)

开启两个终端:一个是服务器,一个是webbench连接端

过了一会

访问太多的结果:fork failed:resource temporarily unavailable

服务器参数超时时间:1min数据库连接池数量12 线程池数量6 日志异步队列容量1024字节

Epoll设置最大检测事件为512,epollevent的vector里存放最多1024个事件//异步日志:开启一个子线程,子线程负责写日志,主线程继续执行其余代码//同步日志:主线程负责写日志,主线程继续执行其余代码

端口初始化:创建套接字(监听文件描述符)、设置端口复用,绑定,监听,监听文件描述符填入epoll中,设置读、边沿触发、非阻塞

代码: 2300行Buffer194http:httpconn 200 httprequest 360 httpresponse235log:450pool: threadpool 70 sqlpool 140server: epoller 85 webserver 400time: heaptimer 185

[21]LINUX命令

linux光标移动到行尾的指令

跳到文本最后一行 G跳到文本第一行第一个字符 gg跳到文本第一行第一个字符 G$

Gerp有几种模式 3种 模糊匹配 精确匹配 正则表达式

链接

grep 命令用于查找文件里符合条件的字符串

网络配置和状态:ifconfig查看⽹⼝ eth0 的配置等信息:Ifconfig eth0查看 socket、⽹络协议栈、⽹⼝以及路由表的信息:netstat -ap

协议栈的统计信息,netstat -s

⽹络吞吐率和 PPS包转发查看:

带宽查看:ethtool eth0 | grep Speed连通性和延时:⽤ ping 命令,它是基于 ICMP 协议的,⼯作在⽹络 层。显示的内容主要包含 icmp_seq (ICMP 序列号)、 TTL (⽣存时间,或者跳数)以及 time (往返延 时),⽽且最后会汇总本次测试的情况,如果⽹络没有丢包, packet loss 的百分⽐就是 0

ping 不通服务器并不代表 HTTP 请求也不通,因为有的服务器的防⽕墙是会禁⽤ ICMP 协议的查看进程:ps -ef查看文件大小: stat 文件名wc -c 文件名du -b 文件名ls -l 文件名查看文件类型: ls -l 文件名file 文件名stat 文件名查看路由表

修改环境变量法1:

法2,法3:

linux业务访问量,耗时的统计

查看哪些端口被打开 netstat -anp(查看是否打开23端口):netstat -an | grep 23关闭端口号: iptables -A OUTPUT -p tcp --dport 端口号 -j DROP打开端口号:iptables -A INPUT -p tcp --dport 端口号 -j ACCEPT保存设置:service iptables save

面试常考linux命令链接

查看当前目录下的文件个数:ls -l | grep "^-" | wc -l

查看指定目录下的文件个数 ls -l specified_dir | grep "^-" | wc -l

递归查询当前目录下的文件个数 ls -lR | grep "^-" | wc -l递归查询指定目录下的文件个数 ls -lR specified_dir | grep "^-" | wc -l

查看tcp连接 netstat -at查看udp连接 netstat -au

查看tcp连接(带进程) netstat -napt

Ps -ef 主要是查看服务器的进程信息

Ps -aux 查看进程占用CPU、占用内存信息

Top 查看系统 CPU 使用情况,也可以查看系统内存使用信息。

Df -h 查看硬盘使用情况

Fdisk -l 查看硬盘及分区

获取cpu详情 cat /proc/cpuinfo

获取系统内存,虚拟内存(交换空间)的大小 free -h

负载 uptime

Vim 文本编辑器命令模式 插入模式 末行模式

统计某个目录下文件的个数

统计某个目录下及其子目录中文件的个数

Awk

AWK是一个优良的文本处理工具awk是以文件的一行为处理单位的。awk每接收文件的一行,然后执行相应的命令,来处理文本。Awk ‘模式 {动作}’ 文件

(-F “:”)指定分隔符 $0代表打印所有这行内容 $1代表第一列内容

统计大小之和

ls -l | awk 'NR>1 {total+=$5} BEGIN{total=0} END{print total} '

grep 、sed、awk被称为linux中的"三剑客"。  grep 更适合单纯的查找或匹配文本  sed 更适合编辑匹配到的文本  awk 更适合格式化文本,对文本进行较复杂格式处理

#我的求职思考##如何判断面试是否凉了##23届找工作求助阵地##我的实习求职记录##实习,投递多份简历没人回复怎么办#

涵盖数据结构与算法、编程语言、数据库、操作系统、计算机网络、设计模式、项目经验、综合智力题

全部评论
大佬
1 回复 分享
发布于 2023-07-27 11:52 北京
大佬
点赞 回复 分享
发布于 2023-08-23 19:24 湖北
谢谢大佬!😋
点赞 回复 分享
发布于 2023-10-19 21:02 四川

相关推荐

#一人分享一句让你在秋招振作起来的话#“火车是朝前开的,去哪并不重要,重要的是沿途的风景”——《爱情公寓》吕子乔在牛客的交流群里我写过这段话:“大四那会儿,觉得自己要是读不了研,这辈子就完了,所以不敢睡觉同时抓考研保研;研一的时候,觉得要是导师的某个项目要是做不出来就完了,所以加班加点调机器人改bug;研二的时候觉得再发不出来小论文就完了,憋着一股气写了三篇……现在想起来其实,这心态跟小学的时候,忘了写某一门作业,上课前觉得完蛋要被老师骂,是一样的。”你说这些事情完不成会怎样呢,可能读不了研就找个班上,看身边已经就业了的同学一个个都过得很巴适,没有谁会因为没有读研而一直苦恼;可能做不出来项目挨顿骂,下个项目从一开始就努力去做,或者导师发现我其实没那么靠谱就不再给我安排Dirty&nbsp;Work,反而现在会过得更好;可能后面多写几个专利,或者等灵光乍现就有论文思路了……可能老师那天不检查作业,或者扯过来同桌的一顿抄就好了我们人生的容错率真的大得离谱,短时间找不到工作考不上研也没啥其实,能走的路多着呢,我见过找到工作后不喜欢工作内容、辞职拿着前几个月的工资去周游世界的,我见过放弃保研去大厂结果被优化,转而出国读研的;还有人一路跌跌撞撞换了好几个方向,最后在一个意想不到的领域找到了自己的归属。人生从来不是一条直线,偶尔走错路、绕点弯,甚至停下来喘口气,都没什么大不了的。其实很多时候,我们以为的“走投无路”,只不过是没找到下一个出口而已。试错和选择是成长的必修课,而所谓“xxx就完了”不过是自己吓唬自己的幻觉。重要的是,在迷茫的时候别太急着下结论,给自己多一些耐心和弹性。你可以暂停,可以换轨,也可以走慢一点,甚至可以不走和别人一样的路——世界这么大,总有一条路适合你。错误是积累的垫脚石,而不是判决人生的锤子。所以啊,人生没必要时时刻刻都逼自己上满发条,放轻松点,少一点焦虑,与其紧紧盯着终点,不如偶尔看看窗外,说不定那些不经意的风景,反而会成为你最珍贵的记忆。
点赞 评论 收藏
分享
29 291 评论
分享
牛客网
牛客企业服务