深信服的面试总共有三轮,其中包括两轮技术面和一轮HR面。两轮技术面间隔时间不长。如果过了,会让你等着;如果没过,就直接让你走了。
面试问题
技术一面
1.手写strncpy函数的实现。
strncpy函数总是正好向dst写入len个字符。如果strlen(src)的值小于len,dst数组就用额外的'\0'字符填充到len长度,如果strlen(src)的值大于或等于len,那么只有len个字符被复制到dst中。注意:如果strlen(src)的值大于或等于len,那么复制到dst中的字符将不包括'\0'。char * strncpy(char *dst, const char *src, int len){ assert(dst != NULL && src != NULL); char *temp = dst; int i = 0; int src_len = strlen(src); if(src_len >= len) //src length大于或者等于len的时候,会拷贝len个有效字符(不包括'\0')到dst中 { while(i <= len) { *temp++ = *src++; i++; } } else //src length小于len的时候,先拷贝src中所有的字符,再填充len - strlen(src)个'\0' { while(i <= src_len) { *temp++ = *src++; i++; } while(i <= len) { *temp++ = '\0'; i++; } } return dst;}
2.strncpy函数的返回值是啥?
strncpy函数的返回值是dst。3.在一个字符串中找另一个字符串的子串。
(1)C语言的库函数strstr,函数原型函数原型:extern char *strstr(char *str1, const char *str2);
判断str2是否是str1的子串。 返回值:若str2是str1的子串,则返回str2在str1的首次出现的地址;如果str2不是str1的子串,则返回NULL。 (2)KMP算法 ps:KMP算法不是效率最高的算法,实际采用并不多。 (3)Boyer-Moore算法 (4)Sunday算法 4.Linux系统中的僵尸进程。
僵尸进程:在Linux系统中,一个进程结束了,但是它的父进程没有回收它,释放它占用的资源, 那么它将变成一个僵尸进程。 另外,如果该进程的父进程已经先结束了,那么该进程就不会变成僵尸进程。 因为每个进程结束的时候,系统都会扫描当前系统中所运行的所有进程, 看有没有哪个进程是刚刚结束的这个进程的子进程,如果是的话,就由Init 来接管子进程,成为子进程的父进程。 怎样来清除僵尸进程: (1)父进程通过wait和waitpid等函数等待子进程结束,这会导致父进程挂起。 (2)如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler,因为子进程结束后, 父进程会收到该信号,可以在handler中调用wait回收。 (3)如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD,SIG_IGN) 通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收, 并不再给父进程发送信号。 (4)还有一些技巧,就是fork两次,父进程fork一个子进程,然后继续工作,子进程fork一 个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过,子进程的回收还是要自己做。5.DHCP协议的交互过程。单个和多个DHCP server的区别。存在DHCP relay的情况。
DHCP交互过程分为四个阶段(最基本的情况): (1)Discover: Client在以广播的形式DHCP Discover报文(DstMAC和DstIP都是全1,SrcIP全0,SrcMAC是Client的MAC),广播域内的所有Server都会收到该报文。另外,Client可以在选项字段中加入“request paramter list” 选项,表明自己想要获得的各种参数。 (2)Offer:广播域内的Server收到DHCP Discover报文之后,会发送DHCP Offer报文,报文中包含了Server准备分配给Client的IP地址,以及一些选项字段。 (3)Request:如果广播域内有多个Server,那么Client会收到多个DHCP Offer报文。Client只能选择处理其中一个,一般的做法是选择最先收到的DHCP Offer报文。然后Client就以广播方式回答一个DHCP request请求信息,该信息中包含Client选中的DHCP Server的IP地址和Client想要的IP地址。之所以要以广播方式回答,是为了通知所有的Server,Client将选择某台Server所提供的IP地址。 (4)ACK:Server收到DHCP Request报文后,判断选项字段中的DHCP Server的IP地址是否与自己的地址相同。如果不相同就不回应ACK报文,并且判断自己是否给该Request报文的Client发送过DHCP Offer报文。如果发送过DHCP Offer报文,则清除相应IP地址记录。如果选项字段中的DHCP Server的IP地址与自己的IP地址相同,Server就会响应一个DHCP ACK报文,其内容同DHCP Offer类似并在选项字段中增加了IP地址使用租期选项。Client收到ACK报文后,如果Server分配的IP地址不跟其他设备的IP地址冲突,那么Client将使用它作为自己的IP。复杂一些的情况:
(1)Client收到Server发送的ACK报文之后,会先发送一个免费ARP报文(SrcIP和DstIP都是Server分配的IP)。如果Client收到ARP应答了,就说明Server分配的IP已被占用,那么Client会给Server发送一个DHCP Decline报文,在Request IP Address(option 50)字段填入Server分配的发生冲突的IP地址。发送完成后,等待一段时间再开始重新申请IP地址,直至申请到一个可用的IP地址。 (2)如果Client之前请求过IP地址,然后重启了,那么重启之后Client首先发送单播的DHCP Request报文到指定的Server,在Request IP Address(option 50)字段填入之前使用过的IP地址,等待Server的响应。如果收到了Server的ACK报文,那么Client确认该IP不冲突之后,将直接使用它。如果Server回应的NAK报文,那么Client将发送广播的DHCP Discover报文请求IP地址。 (3)当DHCP Server分配给Client的IP地址的使用租期到达50%时(T1),Client发送单播的DHCP Request报文到指定的Server,等待Server的响应。如果收到了Server的ACK报文,那么Client会刷新租期。如果没有收到Server的ACK报文,那么Client会继续使用该IP。 (4)当DHCP Server分配给Client的IP地址的使用租期到达87.5%时(T2),Client发送广播的DHCP Request报文,等待Server的响。如果收到了Server的ACK报文,那么Client会刷新租期。如果没有收到Server的ACK报文,那么Client会继续使用该IP,知道租期结束。 (5)当DHCP Server分配给Client的IP地址的使用租期结束时,Client发送广播的DHCP Discover报文,重新申请IP地址。 (6)已经动态获取到了IP地址的DHCP Client,如果需要向Server请求一些参数,那么它可以向Server发送DHCP Inform报文。Server收到DHCP Inform报文后,回应DHCP ACK报文。该ACK报文不包含分配的Client的IP地址,也不包含租期信息,但是在选项字段中包含了Client需要的参数。存在DHCP Relay的情况:
(1)如果DHCP Server和DHCP Client不在同一个网段,那么可以使用DHCP Relay中转。Relay收到Client发送的广播的DHCP Discover报文和DHCP Request报文后,在报文的giaddr字段填充自己的IP地址,并将报文转换成单播报文,然后发送给指定的Server(DHCP Relay上需要配置Server的IP地址)。Relay收到Server回应的Offer和ACK报文后,将其转发给Client。注意:DHCP Server给DHCP Client分配的IP地址与DHCP Relay添加的giaddr在同一网段。6.linux系统常用的命令,比如查看文件大小,查看磁盘分区及使用情况。
查看文件或者文件夹的大小:du -sh file_or_folder_name 查看磁盘分区及使用情况:df -h 或者 fdisk -l7.AVL树的特点。
1.AVL树本质上还是一棵二叉搜索树,它的特点是: (1)本身首先是一棵二叉搜索树。 (2)带有平衡条件:每个结点的左右子树的高度之差的绝对值(平衡因子)最多为1。 也就是说,AVL树,本质上是带了平衡功能的二叉查找树(二叉排序树,二叉搜索树)。 2.AVL树为了达到平衡条件,必须进行旋转: (1)左单旋:当前节点的右子树比左子树高(平衡因子为1),现在在它的右子树的右侧插入一个新节点,右子树高度增加1导致节点平衡因子从1变为变为2而不平衡。此时,将当前节点(不平衡的节点)逆时针旋转(左旋),使得它原先的右子节点变成它的父节点,它本身则变成原先右子节点的左子节点,原先右子节点的左子节点变成它新的右子节点。int left_rotate(avl_tree *tree, avl_node *node) /* 左单旋 */{ avl_node *p; p = node->right; /* 原先的右子节点 */ node->right = p->left; /* 原先右子节点的左子节点 -->它新的右子节点 */ if(p->left) { p->left->parent = node; } p->left = node; /* 它本身-->原先右子节点的左子节点 */ if(node == tree->root) { tree->root = p; } p->parent = node->parent; node->parent = p; /* 它原先的右子节点-->它的父节点 */ return 0;}
(2)右单旋:当前节点左子树比右子树高(平衡因子为-1),现在在左子树的左侧插入一个新节点,左子树的高度增加1,导致该节点的平衡因子从-1变为-2而不平衡。此时,将当前节点(不平衡的节点)顺时针旋转(右旋),使得它原先的左子节点变成它的父节点,它本身则变成原先左子节点的右子节点,原先左子节点的右子节点变成它新的左子节点。
struct avl_node{ struct avl_node *left; struct avl_node *right; struct avl_node *parent; int bf; //balance factor void *info;};struct avl_tree{ int (*compare_func)(void *data1, void *data2); /* 用来比较avl树的节点的值的大小 */ struct avl_node *root; int count;};int right_rotate(avl_tree *tree, avl_node *node) /* 右单旋 */{ avl_node *p; p = node->left; /* 原先的左子节点 */ node->left = p->right; /* 原先的左子节点的右子节点-->它新的左子节点 */ if(p->right) { p->right->parent = node; } p->right = node;/* 它本身-->原先的左子节点的右子节点 */ if(node == tree->root) { tree->root = p; } p->parent = node->parent; node->parent = p;/* 它原先的左子节点-->它的父节点 */ return 0; }
(3)左右双旋(先左旋,再右旋):当前节点的左子树比右子树高(平衡因子为-1),现在在它的左子树的右子树上插入一个新的节点,它的左子树的高度增加1,导致该节点的平衡因子从-1变为-2而不平衡。此时,将先当前节点(不平衡节点)的左子节点逆时针旋转(左旋),再将当前节点顺时针旋转(右旋),就可以使得AVL树恢复平衡。
int left_right_rotate(avl_tree *tree, avl_node *node) /*先左旋,再右旋*/{ left_rotate(tree, node->left); right_rotate(tree,node); return 0;}
(4)右左双旋(先右旋,再左旋):当前节点的右子树比左子树高(平衡因子为1),现在在它的右子树的左子树上插入一个新的节点,它的右子树的高度增加1,导致该节点的平衡因子从1变为2而不平衡。此时,将先当前节点(不平衡节点)的右子节点顺时针旋转(右旋),再将当前节点逆时针旋转(左旋),就可以使得AVL树恢复平衡。
int right_left_rotate(avl_tree *tree, avl_node *node) /*先右旋,再左旋*/{ right_rotate(tree, node->right); left_rotate(tree,node); return 0;}
8.报文检测系统(我简历中写的一个项目)的具体实现。
9.socket调用时可能出现的错误,以及解决方法。
10.如何判断链表是否有环?
判断链表是否有环可以用快慢指针,fast指针一次走两步,slow指针一次走一步。如果链表确实有环,那么fast和slow会相遇;否则不会相遇。 假设链表有环,环长度为r,fast指针走过的距离为s1,slow指针走过的距离为s2。 可以得到s1 = s2 + n*r,(n >= 1)typedef struct listnode{ struct listnode *next; struct listnode *prev; void *data;}NODE;typedef struct list{ struct listnode *head; struct listnode *tail; unsigned int count; int (*compare_func)(void *data1, void *data2); /* 用来比较链表元素的值的大小 */}LIST;NODE * have_loop_or_not(LIST *list) /* 判断链表是否有环 */{ NODE *fast, *slow; if(!list) return NULL; slow = fast = list->head; while(slow && fast && fast->next) { slow = slow->next; fast = fast->next->next; if(slow == fast) return slow; } return NULL;}
11.如何判断两个链表(没有环)是否相交?
如果两个没有环的链表相交,那么相交节点之后的所有节点必定是完全相同的。两个相交链表呈Y型。 假设这两个链表分别是listA和listB。现在让listA的最后一个节点指向它的head,如果listA和listB相交,那么listB会有环。判断listB是否有环就可以判断A,B是否相交。12.snmp相关的内容。比如trap上报了哪些信息?比如网元侧的AgentX如何处理snmp报文?
技术二面
1.双主控板(我简历中写的一个分布式路由器项目)的时候,主用和备用主控板之间如何保持通信?如何倒换(备用主控什么时候变成主用)?
2.假如某个时刻存在双主用,那么如何处理,怎么决策出一个主用主控板?
3.linux内核的收包过程。
4.Linux中的守护进程。
守护进程的概念: 守护进程(Daemon)是一种运行在后台的一种特殊的进程,它独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。由于在linux中,每个系统与用户进行交流的界面成为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端被称为这些进程的控制终端,当控制终端被关闭的时候,相应的进程都会自动关闭。但是守护进程却能突破这种限制,它脱离于终端并且在后台运行,并且它脱离终端的目的是为了避免进程在运行的过程中的信息在任何终端中显示并且进程也不会被任何终端所产生的终端信息所打断。它从被执行的时候开始运转,知道整个系统关闭才退出(当然可以认为的杀死相应的守护进程)。如果想让某个进程不因为用户或中断或其他变化而影响,那么就必须把这个进程变成一个守护进程。5.字符串的搜索,比如CLI命令行的快速匹配与搜索。
6.链表的插入操作。
NODE * listnode_add(LIST *list, void *val){/* 在链表尾部插入节点 */ NODE *node; if((!list) || (!val)) return NULL; node = (NODE *)calloc(1, sizeof(NODE)); if(!node) return NULL; node->prev = list->tail; node->data = val; if(list->head == NULL) list->head = node; else list->tail->next = node; list->tail = node; list->count++; return node;}NODE * listnode_add_sort(LIST *list, void *val){/* 往有序链表中插入元素 *(链表元素的值按照从小到大顺序排序) */ NODE *n, *node; if((!list) || (!val)) return NULL; node = (NODE *)calloc(1, sizeof(NODE)); if(!node) return NULL; node->data = val; if(list->compare_func) { for(n = list->tail; n; n = n->prev) { if((list->compare_func(val, n->data)) >= 0) {/* 在中间或者尾部插入元素 */ node->prev = n; node->next = n->next; if(n->next) n->next->prev = node; else list->tail = node; n->next = node; list->count++; return node; } } } /* 在头部插入元素 */ node->next = list->head; if(list->head) list->head->prev = node; else list->tail = node; list->head = node; list->count++; return node;}
7.如何处理广播风暴?
(1)什么是广播风暴:网络上的广播帧(由于被转发)数量急剧增加而影响正常的网络通讯的反常现象,广播风暴会占用相当可观的网络带宽,造成整个网络无法正常工作。 (2)广播风暴只会在二层出现,广播报文跨越不了网段。 (3)广播风暴的产生有几个可能的原因:网卡损坏、网络环路、网络病毒。 (4)广播风暴的避免/抑制/解决方法: a)开启交换机端口的广播风暴控制,当端口收到的广播帧累计到预定门限值时,端口将自动丢弃收到的广播帧。 b)vlan隔离:按照端口/MAC/子网划分vlan,可以将广播风暴的影响范围限制在vlan内。 c)交换机开启生成树协议(STP/RSTP/MSTP),可以避免网络环路造成的广播风暴。 d)开启安全防护软件,可以减少网络病毒造成的广播风暴。8.分布式的一致性问题。
9.平时有看过那些技术书籍?
10.平时有看过哪些开源的代码,或者参与过哪些开源的项目?
总的来说深信服的技术面会参考应聘者的简历,问一些跟简历相关的内容。
另外,他们还会让应聘者手写代码,实现基本的算法,考察应聘者数据结构与算法的功底。