腾讯公司后台服务器经典面试题

时间:2022-09-13 22:26:51

前些时间去了腾讯面试, 可惜现场没回答好。
' Q- R6 Q# V! {% `  A是一些基础问题,同时也比较深入的问题。 在此列出来, 欢迎大家讨论交流。
: S# p' H+ J+ y7 v- E8 O
. W6 I: j$ u+ q: o, @% S+ Z

0 S1 i! _+ J! i提问(不按时间顺序):
" B  B( Y# h4 y2 ~
1 [* }5 T: c1 T, ]9 W; u1, 使用
Linux epoll模型,水平触发模式(Level-Triggered);当socket可写时,会不停的触发socket可写的事件,如何处理?
4 S/ V* `8 G$ g4 w
5 N& V, x7 d2 a# s
2, 从socket读数据时,socket缓存里的数据,可能超过用户缓存的长度,如何处理? 例如,socket缓存有8kB的数据,而你的缓存只有2kB空间。
9 [# e! ?! e2 b
) F' f4 D8 d( Z; E! y3, 向socket发送数据时, 可能只发送了用户缓存里的一半,如何处理?例如,需要向socket发送8kB数据,返回值只有2kB发送成功。
# Y0 Q. Y3 C2 q* Z& J9 k1 _9 {7 b- v3 o

, U- M: a2 J$ M/ i  i; F* G& T  B. l* f* o% S
4, C++的虚函数是怎么实现的?# N3 G& o) d' r/ M% h( @

* i; B2 M; r. Q/ Q7 j1 k5, C++的虚函数有什么作用?
( @5 G: e; L; a5 ^% E2 d8 {' ]
3 A' ]- ]; t" ]) t' r  p: j6 B
6, 非阻塞connect()如何实现?
/ K( ]+ g, v! i# e: {
& P: F6 v8 p7 t; ~
7,sizeof()问题; s! o, Z/ x- L# S% P" _0 /( E% z

& [0 x7 A' n$ E& A! G/ k2 xclass A4 O  }7 K9 w+ k! |; K
{" l, M6 {4 m+ e- w8 O
  char c;8 `7 M+ o( H' s0 [
  int   val;
9 r3 x' q$ ^; p8 @) o: H/ {  short sh;
1 h% n5 `# m6 r
}# G/ A) D& ?" o0 G% q% `
5 p  E: |5 D; |* c
+ ^$ b- a. K. Z$ r% E/ P
class B0 i: X! h" W8 f7 S0 v
{# v+ I/ k( Y6 D$ F) R# o- [. ]( [
    char c;; g, k& ~/ A( W: ~0 v& V" r9 n  p
    int   val;
7 f$ @- f3 ?. }% R. t- w7 E% x    short sh;
* k; a. ?6 N1 m, L3 p. F+ n6 J/ j
    void func1(void);
) l$ z* G1 c! A( f/ Q9 i) /, r    virtual func2(void);
6 G0 m. {7 g7 g6 v2 g" Z, N" e
}
) s1 [) O0 c) u
# ?( Y2 z# X! r7 M; K
sizeof(A), sizeof(B) 分别是多少?
; R$ Y  d$ k6 p+ Q6 L' E* C/ b0 E& T
6 }/ U. p! N! q: `0 o
8, 实现字符串比较函数  strcmp(char *src, char * sub)
6 U1 u6 S, {$ ^$ T$ N# b' b
% _2 |& [7 B. t# i1 Q! U1 z
9, 实现内存拷贝函数  strcpy(void*dst, char * src, size_t len)7 T) [9 j* Q; |, t
3 {/ t% [3 ]6 f- ?$ g3 f
10,条件变量的如何使用? 你使用的线程函数是什么?
' q4 L% q! z2 m; q8 x4 P9 t
( m: J; q# p( c& b8 N& D11, deamon进程如何实现?
8 @& [* @9 N& Q
. i. l/ [$ q1 W# `8 j
12, HTTP和CGI是什么?
) D! ?" o& R% U
1 e; z" z/ E/ `: ]
13, TCP的三次握手, TIME_WAIT和CLOSE_WAIT状态是什么?9 x* P! C" P; o. ^; `8 G9 ?4 h

! J8 M6 e% i+ R( l: w  c+ I/ D- x9 u! ?' [! B3 R
因为第7题之后的属于客观题,不打算在此写答案。 朋友们如有好的答案也欢迎跟贴。
. f/ `. l( D1 e
* Z$ ]  P+ c% R6 /$ Z
本人在此写出自己对前6个问题的回答:. {& y, G  c8 d" J

& M% s% R& N* S% L2 n1, 使用linux epoll模型,水平触发模式(Level-Triggered);当socket可写时,会不停的触发socket可写的事件,如何处理?
1 D) s1 `( {/ w
# r9 B6 u/ T4 X
第一种最普通的方式:  4 ~( s4 }8 `: w1 C
    当需要向socket写数据时,将该socket加入到epoll模型(epoll_ctl);等待可写事件。
7 W* l. J: /) k    接收到socket可写事件后,调用write()或send()发送数据。。。
; a& X& r, g7 O# @/ r4 n
    当数据全部写完后, 将socket描述符移出epoll模型。
* F" ~& q2 _4 M) g   
+ @8 Y8 h' ~/ w  S% V& ^, _  @
    这种方式的缺点是:  即使发送很少的数据,也要将socket加入、移出epoll模型。有一定的操作代价。, x9 r% n$ }5 [1 r8 X# b
% o; d( k9 y+ M4 i6 s
第二种方式,(是本人的改进方案, 叫做directly-write)3 ?5 X9 p& R3 e4 q* D4 [3 ], e
; l6 @$ h, y1 z! Z/ B
    向socket写数据时,不将socket加入到epoll模型;而是直接调用send()发送;" n4 a) ~  Z( r% C+ c
    只有当或send()返回错误码EAGAIN(系统缓存满),才将socket加入到epoll模型,等待可写事件后,再发送数据。2 q# S  V8 E  E( ~& Q1 H
    全部数据发送完毕,再移出epoll模型。
& y) q+ f8 n: /3 l# x
8 d% }4 E+ A0 Y) g/ q+ T! r# U
     这种方案的优点:   当用户数据比较少时,不需要epool的事件处理。
4 H/ z; S5 v. o0 c+ Q& ~3 H     在高压力的情况下,性能怎么样呢?   
0 g) O5 c. m' O) |0 L, L      对一次性直接写成功、失败的次数进行统计。如果成功次数远大于失败的次数, 说明性能良好。(如果失败次数远大于成功的次数,则关闭这种直接写的操作,改用第一种方案。同时在日志里记录警告)
: U+ v2 S2 Y5 ?9 E# f+ f
     在我自己的应用系统中,实验结果数据证明该方案的性能良好。
7 y0 u. q% c6 h     
9 a# J" j) ~5 L    事实上,网络数据可分为两种到达/发送情况:
0 h  C. w  {- v; |8 }5 r' j$ ]     一是分散的数据包, 例如每间隔40ms左右,发送/接收3-5个 MTU(或更小,这样就没超过默认的8K系统缓存)。
8 n% w6 I: I/ N6 F& k3 F5 Y
     二是连续的数据包, 例如每间隔1s左右,连续发送/接收 20个 MTU(或更多)。6 ~, [9 ?! g- B) ?/ Z# N" /5 q

( C9 P5 v0 ]/ p8 D% O( M& c回来查了资料,发现以下两种方式:
) D, n+ A( Z0 Z8 S$ N+ m
$ c4 ?: Z1 N; K' /; [1 y1 A/ m    第三种方式:  使用Edge-Triggered(边沿触发),这样socket有可写事件,只会触发一次。
3 k6 x" @" j2 g4 p& E             可以在应用层做好标记。以避免频繁的调用 epoll_ctl( EPOLL_CTL_ADD, EPOLL_CTL_MOD)。  这种方式是epoll 的 man 手册里推荐的方式, 性能最高。但如果处理不当容易出错,事件驱动停止。
4 r8 Y: Z6 R1 j. c: E# F
1 G) u- F2 @. ]6 a+ B$ U6 J第四种方式:  在epoll_ctl()使用EPOLLONESHOT标志,当事件触发以后,socket会被禁止再次触发。
6 M* Q, Z8 f/ X
             需要再次调用epoll_ctl(EPOLL_CTL_MOD),才会接收下一次事件。   这种方式可以禁止socket可写事件,应该也会同时禁止可读事件。会带来不便,同时并没有性能优势,因为epoll_ctl()有一定的操作代价。. q& /1 p% T/ K# h
9 i& W8 }* I, ]* r2 S+ D
! h+ a3 L. `4 d( |2 r) _0 s* Y
2, 从socket读数据时,socket缓存里的数据,可能超过用户缓存的长度,如果处理?/ G$ ~& m+ /, r6 {% r
       可以调用realloc(),扩大原有的缓存块尺寸。/ ]- O; M0 E# N( K8 h# F
       但是临时申请内存的有一定性能损失。# z9 /, F5 z# _9 r

9 y/ F( c, {, T9 m3 r      这种情况要看接收缓存的方式。
! |" H& y# U( K* O) v& N! r第一种方式:  使用100k的大接收缓存为例。
8 S( H2 z5 _1 M& e               如果要等待数据,并进行解析。可能发生缓存不够的情况。此时只能扩充缓存,或先处理100k的数据,再接收新的数据。
/ C: W  x: [/ }4 f$ G' u1 r第二种方式: 使用缓存队列,分成8K大小的队列。
; g" D$ q% F( Y
               不存在接收缓存不够的情况。 除非用户解析已出错,使用数据接收、使用脱勾。 这种方式的代价是,可能需要将缓存队列再次拷贝、拼接成一块大的缓存,再进行解析。 而在本人的系统中,只需要将socket接收的数据再次原样分发给客户, 所以这种方案是最佳方案。1 |, n6 /. E8 F" m! n

; L! c6 /- [6 X: M3, 向socket发送数据时, 可能只发送了用户缓存里的一半,然后失败,如何处理?( l1 U; s. k8 w% D1 I, J
1 `- }3 i( ~9 Y; X
      记录缓存的偏移量。 下一次socket写事件时, 再从偏移的位置接着发送。
3 j5 O; T0 f  E: Y$ |      
& [/ X- l# j. p. Q1 b: k+ O
       那个面试官居然对这个问题问了我两次, 看来我解释的不够清晰。。。。。。 郁闷。
* {' |: w% M! B$ _- E
' H# d- }% w! k; b, M( `& [3 ?, M* C
4, C++的虚函数是怎么实现的?( l: u4 ~" {( m' i  A
       使用虚函数表。
: ]! y* J' R3 G0 j6 T       回来查下资料:  C++对象使用虚表, 如果是基类的实例,对应位置存放的是基类的函数指针;如果是继承类,对应位置存放的是继承类的函数指针(如果在继承类有实现)。所以,当使用基类指针调用对象方法时,也会根据具体的实例,调用到继承类的方法。
/ V6 u2 y  d# ~2 G! ?9 L

; y6 u' r7 m7 ], Q0 S5 J) i5, C++的虚函数有什么作用?0 I0 u8 I4 U8 K, K6 k3 y) g
# _( |7 }7 ]$ b+ R6 |8 Q
        虚函数作用是实现多态, 很多人都能理解这一点。但却不会回答下面这一点。+ b. K( e3 w0 z! J% U$ Y+ m9 p

9 i" y. ]7 ~$ J! l* y       更重要的,虚函数其实是实现封装,使得使用者不需要关心实现的细节。在很多设计模式中都是这样用法,例如Factory、Bridge、Strategy模式。 前两天在书上刚好看到这个问题,但在面试的时候却没想起来。
' d" F- `, p% h& x4 _3 j        个人觉得这个问题可以很好的区分C++的理解水平。
: H0 O$ m0 M. l( A

1 I6 z: h  c; H7 }6, 非阻塞connect()如何实现?
1 X( H9 b% ?6 @' ~% j       将socket设置成non-blocking,操作方法同非阻塞read()、write();
; m5 z# g' K3 ]8 /4 L1 J! E3 G
       面试官是在听到我介绍之后,才问我这个问题。可惜还是问我两遍。
7 S: z6 b# H6 g+ A
) f1 v1 j$ u6 R: t1 @: q

1 G# F0 w, e# x2 O3 G% ~* j/ ?, o/ y$ W, v
这次面试, 总的来说准备不够充足, 所以这次机会没有青睐我!
8 t8 C$ D- B, [也有其它一些问题:
1 G  F  N7 U3 j3 q' v+ n1, 对于一般的面试提问, 总是想很简要的回答完。因为对方可能本来就很清楚,所以自己就想一两句话说完。 但是有时候这样行不通。需要适当的回答清晰、完整一些。/ A; L' f$ q+ Y1 @) L0 E7 A# `- v  t
2, 对TCP/UDP的问题本来是很熟悉的,但因为长时间没复习,忘的差不多了。+ ~& x9 c% L- P  z  a; v, N
3, 以前已经对RTSP进行了仔细的学习。 HTTP、SIP属于同一类协议。而我却回答不了HTTP的问题。努力学习啊................
; l0 m: J5 H1 l; ?& W4, 有些问题要问我两遍,说明我的表达确实不够清晰。有的问题可能面试官自己并不清晰,所以除了表达清晰之外,完全有必要适当的回答稍完整些。否则很难让人满意。+ c, t+ A8 i: p; `: p4 G
5, 精神状态不太好,思维有些慢了。 因为总是睡的晚。% ^: W4 /% C  k8 Y
/ ^$ J( d' Z$ [4 l# S+ W' j* d
接下来打算继续研究 lighttpd源码, 这样对我自己的水平提高会有很大帮助。
9 r4 g$ u/ Y/ r: P6 F5 l/ g+ n1 Z  y3 U. U' H7 X9 Q
机会总是青睐有准备的人! 期待下次。