面试问题整理之后台问题

面试后台服务器开发之类的岗位的时候,除了前面几篇提到的基础问题,还有很多由此拓展的开放性问题,在此做一些记录。答案基本都是我自己的理解,不保证正确。

1.epoll中et和lt的区别与实现原理

LT:水平触发,效率会低于ET触发,尤其在大并发,大流量的情况下。但是LT对代码编写要求比较低,不容易出现问题。LT模式服务编写上的表现是:只要有数据没有被获取,内核就不断通知你,因此不用担心事件丢失的情况。
ET:边缘触发,效率非常高,在并发,大流量的情况下,会比LT少很多epoll的系统调用,因此效率高。但是对编程要求高,需要细致的处理每个请求,否则容易发生丢失事件的情况。


2.TCP有哪些定时器?

TCP使用四种定时器(Timer,也称为“计时器”):
重传计时器:Retransmission Timer
坚持计时器:Persistent Timer
保活计时器:Keeplive Timer
时间等待计时器:Time_Wait Timer。
(1)重传计时器:
重传定时器:为了控制丢失的报文段或丢弃的报文段,也就是对报文段确认的等待时间。当TCP发送报文段时,就创建这个特定报文段的重传计时器,可能发生两种情况:若在计时器超时之前收到对报文段的确认,则撤销计时器;若在收到对特定报文段的确认之前计时器超时,则重传该报文,并把计时器复位;
重传时间=2*RTT;
RTT的值应该动态计算。常用的公式是:RTT=previous RTT*i + (1-i)*current RTT。i的值通常取90%,即新的RTT是以前的RTT值的90%加上当前RTT值的10%.
Karn算法:对重传报文,在计算新的RTT时,不考虑重传报文的RTT。因为无法推理出:发送端所收到的确认是对上一次报文段的确认还是对重传报文段的确认。干脆不计入。
(2)坚持计时器:persistent timer
专门为对付零窗口通知而设立的。
当发送端收到零窗口的确认时,就启动坚持计时器,当坚持计时器截止期到时,发送端TCP就发送一个特殊的报文段,叫探测报文段,这个报文段只有一个字节的数据。探测报文段有序号,但序号永远不需要确认,甚至在计算对其他部分数据的确认时这个序号也被忽略。探测报文段提醒接收端TCP,确认已丢失,必须重传。
坚持计时器的截止期设置为重传时间的值,但若没有收到从接收端来的响应,则发送另一个探测报文段,并将坚持计时器的值加倍和并复位,发送端继续发送探测报文段,将坚持计时器的值加倍和复位,知道这个值增大到阈值为止(通常为60秒)。之后,发送端每隔60s就发送一个报文段,直到窗口重新打开为止;
(3)保活计时器:keeplive timer
每当服务器收到客户的信息,就将keeplive timer复位,超时通常设置2小时,若服务器超过2小时还没有收到来自客户的信息,就发送探测报文段,若发送了10个探测报文段(没75秒发送一个)还没收到响应,则终止连接。
(4)时间等待计时器:Time_Wait Timer
在连接终止期使用,当TCP关闭连接时,并不认为这个连接就真正关闭了,在时间等待期间,连接还处于一种中间过度状态。这样就可以时重复的fin报文段在到达终点后被丢弃,这个计时器的值通常设置为一格报文段寿命期望值的两倍。


3.TCP设置tcp_nodelay是不是就能做到TCP数据直接发出不经过缓冲区?

显然是不能的。
TCP/IP协议中,无论发送多少数据,总是要在数据前面加上协议头,同时,对方接收到数据,也需要发送ACK表示确认。为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据。(一个连接会设置MSS参数,因此,TCP/IP希望每次都能够以MSS尺寸的数据块来发送数据)。
Nagle算法就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。
Nagle算法的基本定义是任意时刻,最多只能有一个未被确认的小段。 所谓“小段”,指的是小于MSS尺寸的数据块,所谓“未被确认”,是指一个数据块发送出去后,没有收到对方发送的ACK确认该数据已收到。
举个例子,一开始client端调用socket的write操作将一个int型数据(称为A块)写入到网络中,由于此时连接是空闲的(也就是说还没有未被确认的小段),因此这个int型数据会被马上发送到server端,接着,client端又调用write操作写入一个int型数据(简称B块),这个时候,A块的ACK没有返回,所以可以认为已经存在了一个未被确认的小段,所以B块没有立即被发送,一直等待A块的ACK收到(大概40ms之后),B块才被发送。
TCP_NODELAY
简单地说,这个选项的作用就是禁用Nagle算法,禁止后当然就不会有它引起的一系列问题了。使用setsockopt可以做到。


4.初始化序列和构造函数谁先执行

初始化序列


5.timewait如何强制结束

setsocketopt


6.显示当前进程信息的命令以及显示网络连接的命令

ps 和 netstat


7.printf缓冲区的问题
以下代码输出几个?

#include <stdio.h>
 #include <unistd.h>
 #include <fcntl.h>

int main(void) 
{ 
    int i; 
    for(i = 0; i < 2; i++){ 
        fork(); 
        printf("-"); 
    } 

    return 0; 
}

结果应该是输出8个.第一次父进程进入之后fork子进程子1,同事要记得父子进程都会执行fork之后的代码,所以此时父子都会printf“-”,此时是两个“-” 然后第二次fork,父进程再创建一个子进程子2,同时两个都打印printf“-”, 子1进程fork孙子进程孙1,此时子1和孙1都打印“-”,此时共6个“-”; 这也是理论情况下的,但是由于printf有自己的缓冲区,所以会多打印两个,即8个。因为printf刷新缓冲区时不带’\n’时,是按全缓冲刷新的,即遇到文件结尾或缓冲区写满才会刷新,所以在fork时缓冲区作为一个进程PCB中的一部分也被子进程继承。


8.单链表删除和公共节点查找问题

单链表删除节点的思路在于用后续节点代替当前节点以实现删除
公共节点查找的思路在于两个链表如果有公共节点则一定是Y型,因此可以直接查看尾部是否相等,不相等则一定没有公共节点


9.32位机一个进程最多可以创建多少个线程?

首先要知道32位机最多可以分配2的32次方的空间给虚拟内存,因此有4GB的内存空间分配堆栈,然而其中还有内核空间,windows为2G,linux为1G。剩余空间在理想条件下可用于分配线程。而每个线程的保留堆栈空间为1M(也有的说2M,8M之类的),由此可计算出理论上的线程数上限。


10.word文档中插入、删除文字以及修改文字效果应该用什么样的数据结构实现?

对每个字单独做一个数据结构,存储文字的属性。为了方便遍历、方便查找、方便各个位置插入,这里跳跃表无疑是最好的选择之一。跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。
跳跃表支持平均O(logN)、最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点。
在大部分情况下,跳跃表的效率可以和平衡树相媲美,并且因为跳跃表的实现比平衡树要来得更为简单,所以有不少程序都使用跳跃表来代替平衡树。
Redis使用跳跃表作为有序集合键的底层实现之一,如果一个有序集合包含的元素数量比较多,又或者有序集合中元素的成员(member)是比较长的字符串时,Redis就会使用跳跃表来作为有序集合键的底层实现。
和链表、字典等数据结构被广泛地应用在Redis内部不同,Redis只在两个地方用到了跳跃表,一个是实现有序集合键,另一个是在集群节点中用作内部数据结构,除此之外,跳跃表在Redis里面没有其他用途。


猜你喜欢

转载自blog.csdn.net/u013354486/article/details/80602221