腾讯一头总结-2014 软件开发 后台研发

腾讯一面总结--2014 软件开发 后台研发
真的很期待进入腾讯公司的。。。。现一面已结束,已料知结果惨痛。专业基础知识忘的差不多了,现场又紧张起来没有冷静下来按流程分析。。。不说了,一说满脸都是泪。。。暂且尝试记下面试经历吧,以此作为原创博客的开始。——2014.04.01

抛去自我介绍,项目介绍之类的,简单记录下当时我被问卡住的问题。
一、TCP协议有什么优点?
TCP协议是一种面向连接的协议,给用户进程提供可靠的全双工的字节流。
优点:面向链接,可靠性,有序性;面向字节流;拥塞控制;
面向链接:是指TCP客户端和服务器之间通信之前首先要建立一个虚拟的链接,这个链接由“源IP地址,源端口号ó目的IP地址,目的端口号”唯一确定;同时TCP层需要为每一个链接维护一个状态机;
可靠性:是指TCP为了保证数据正确的传到对端,引入了重传机制;TCP层发送的每一个数据包都有一个唯一的序号,对端收到后需要对收到的数据包进行确认;本端TCP层对发送的每一个TCP数据包都在缓存中保留一个副本,只有在收到对端的确认后才将其删除,否则在定时器超时后仍没收到确认就会重传;
有序性:因为TCP发送到每一个数据包都有一个唯一的序号,该序号是结合本次发送端字节数依次增长的,所以对端收到后很容易按照其发送顺序进行排序,然后依次交个应用层;
面向字节流:和UDP不同,应用层调用发送消息的接口发送一块数据时,TCP为了不让IP层发生分片,会对用户的数据块分片后分别加上TCP头部然后交给IP层,所以基于TCP的程序很难使IP层发生分片;这样一来用户的数据块就被分割了,到达对端TCP层也不会被合并,就直接交给应用层,所以对端应用层收到的可能是被分割后的数据块,而不是源端最初发送的整个数据块;如果应用层需要对源端发送的整个数据块进行处理则需要应用层自己负责将TCP送上来的分片合并起来。
此外UDP还具有如下特性:TCP中引入了拥塞控制;使用四个定时器;重传计时器、坚持计时器、保活计时器、时间等待计时器。
缺点:慢,效率低,占用系统资源高,易被攻击
TCP在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞控制机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的CPU、内存等硬件资源。
因为TCP有确认机制、三次握手机制,这些也导致TCP容易被人利用,实现DOS、DDOS、CC等攻击。

补充:UDP特点:
无连接,不可靠,无序的,基于应用层消息的;支持广播和多播;
因为IP层本身是无连接,不可靠,无序的,而UDP也没有做特殊处理,所以UDP仍然是无连接,不可靠,无序的;关于“基于应用层消息”是指应用层调用发送消息的接口(Sendto)发送一块数据时,UDP不对数据做任何处理,只是将其加上UDP头部,然后交给IP层;这样一来如果应用层每次发送的数据块比较大的话就很容易造成IP层分片;
此外UDP还具有如下特性:可以限制本地和远端IP地址;支持广播和多播;
参考:http://blog.****.net/minico/article/details/5706665

二、TCP协议如何保证面向连接的可靠性?
TCP通过下列方式来提供可靠性:
1、应用数据被分割成TCP认为最适合发送的数据块。这和UDP完全不同,应用程序产生的数据报长度将保持不变。
(将数据截断为合理的长度)
2、当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
(超时重发)
3、当TCP收到发自TCP连接另一端的数据,它将发送一个确认。这个确认不是立即发送,通常将推迟几分之一秒。
(对于收到的请求,给出确认响应,之所以推迟,可能是要对包做完整校验)
4、TCP将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP将丢弃这个报文段和不确认收到此报文段。
(校验出包有错,丢弃报文段,不给出响应,TCP发送数据端,超时时会重发数据)
5、既然TCP报文段作为IP数据报来传输,而IP数据报的到达可能会失序,因此TCP报文段的到达也可能会失序。如果必要,TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层。
(对失序数据进行重新排序,然后才交给应用层)
6、既然IP数据报会发生重复,TCP的接收端必须丢弃重复的数据。
(对于重复数据,能够丢弃重复数据)
7、TCP还能提供流量控制。TCP连接的每一方都有固定大小的缓冲空间。TCP的接收端只允许另一端发送接收端缓冲区所能接纳的数据。这将防止较快主机致使较慢主机的缓冲区溢出。
(TCP可以进行流量控制,防止较快主机致使较慢主机的缓冲区溢出)——TCP使用的流量控制协议是可变大小的滑动窗口协议。
参考:http://zh-shaochun.blog.163.com/blog/static/18322091201151354211756/

补充:关于TCP的可靠性,可靠性--是什么,不是什么?
IP是个不可靠的协议,那就应该很清楚,在数据传输路径上,第一个可以讨论确保可靠传输问题的地方就是应用程序B所在主机的TCP层。当一个段抵达应用程序B所在主机的TCP层时,唯一可以确定的就是这个段已经到达了,但它可能损坏了,可能是重复的数据,可能是错序的,或者是由于其他一些原因无法接受的。注意,发送端TCP无法对这些抵达接收端TCP的段做出任何保证。
另一个可以讨论确保可靠传输问题的地方是应用程序B。我们知道,无法保证应用程序A发送的所有数据都会到达。TCP能够向应用程序B保证的是所有到达的数据都是按序且未受损的。
一个更难解决的问题是如果服务器没有对收到的报文进行确认,客户端会怎么做?
参考:http://www.cnblogs.com/lwzz/archive/2011/07/03/2096967.html

三、TCP协议流量控制、TCP滑动窗口?
流量控制(滑动窗口协议)
1、流量控制是管理两端的流量,以免会产生发送过块导致收端溢出,或者因收端处理太快而浪费时间的状态。用的是:滑动窗口,以字节为单位。
2、窗口有3种动作:展开(右边向右),合拢(左边向右),收缩(右边向左)这三种动作受接收端的控制。
2.1合拢:表示已经收到相应字节的确认了
2.2展开:表示允许缓存发送更多的字节
2.3收缩(非常不希望出现的,某些实现是禁止的):表示本来可以发送的,现在不能发送;但是如果收缩的是那些已经发出的,就会有问题;为了避免,收端会等待到缓存中有更多缓存空间时才进行通信。
发端窗口的大小取决于收端的窗口大小rwnd(TCP报文的窗口大小字段)和拥塞窗口大小cwnd(见拥塞控制)。发端窗口大小=min{rwnd,cwnd};
3、关闭窗口:窗口缩回有个例外,就是发送rwnd=0表示暂时不愿意接收数据。这种情况下,发端不是把窗口收缩,二是停止发送数据。(为了比避免死锁,会用一些探测报定时发送试探,见定时器一节)
4、问题:某些时候,由于发端或收端的数据很慢,会引起大量的1字节数据痛惜,浪费很多资源。
4.1发端的进程产生数据很慢时候,时不时的来个1字节数据,那么TCP就会1字节1字节的发送,效率很低。
解决方法(Nagle算法):
a、将第一块数据发出去
b、然后等到发送缓存有足够多的数据(最大报文段长度),或者等到收端确认的ACK时再发送数据。
c、重复b的过程
4.2收端进程由于消耗数据很慢,所以可能会有这么一种情况,收端会发送其窗口大小为1的信息,然后有是1字节的传输
解决办法(2种)
a、Clark方法:在接收缓存的一半变空,或者有足够空间放最大报文长度之前,宣告接收窗口大小为0
b、推迟确认:在对收到的报文段确认之前等待到足够的接收缓存,或者等待到一个时间段(现在一般定义500ms)

滑动窗口机制
(1)窗口机制
滑动窗口协议的基本原理就是在任意时刻,发送方都维持了一个连续的允许发送的帧的序号,称为发送窗口;同时,接收方也维持了一个连续的允许接收的帧的序号,称为接收窗口。发送窗口和接收窗口的序号的上下界不一定要一样,甚至大小也可以不同。不同的滑动窗口协议窗口大小一般不同。发送方窗口内的序列号代表了那些已经被发送,但是还没有被确认的帧,或者是那些可以被发送的帧。下面举一个例子(假设发送窗口尺寸为2,接收窗口尺寸为1):
腾讯一头总结-2014 软件开发 后台研发
腾讯一头总结-2014 软件开发 后台研发
分析:
①初始态,发送方没有帧发出,发送窗口前后沿相重合。接收方0号窗口打开,等待接收0号帧;
②发送方打开0号窗口,表示已发出0帧但尚确认返回信息。此时接收窗口状态不变;
③发送方打开0、1号窗口,表示0、1号帧均在等待确认之列。至此,发送方打开的窗口数已达规定限度,在未收到新的确认返回帧之前,发送方将暂停发送新的数据帧。接收窗口此时状态仍未变;
④接收方已收到0号帧,0号窗口关闭,1号窗口打开,表示准备接收1号帧。此时发送窗口状态不变;
⑤发送方收到接收方发来的0号帧确认返回信息,关闭0号窗口,表示从重发表中删除0号帧。此时接收窗口状态仍不变;
⑥发送方继续发送2号帧,2号窗口打开,表示2号帧也纳入待确认之列。至此,发送方打开的窗口又已达规定限度,在未收到新的确认返回帧之前,发送方将暂停发送新的数据帧,此时接收窗口状态仍不变;
⑦接收方已收到1号帧,1号窗口关闭,2号窗口打开,表示准备接收2号帧。此时发送窗口状态不变;
⑧发送方收到接收方发来的1号帧收毕的确认信息,关闭1号窗口,表示从重发表中删除1号帧。此时接收窗口状态仍不变。
若从滑动窗口的观点来统一看待1比特滑动窗口、后退n及选择重传三种协议,它们的差别仅在于各自窗口尺寸的大小不同而已。1比特滑动窗口协议:发送窗口=1,接收窗口=1;后退n协议:发窗口>1,接收窗口>1;选择重传协议:发送窗口>1,接收窗口>1。

(2)1比特滑动窗口协议
当发送窗口和接收窗口的大小固定为1时,滑动窗口协议退化为停等协议(stop-and-wait)。该协议规定发送方每发送一帧后就要停下来,等待接收方已正确接收的确认(acknowledgement)返回后才能继续发送下一帧。由于接收方需要判断接收到的帧是新发的帧还是重新发送的帧,因此发送方要为每一个帧加一个序号。由于停等协议规定只有一帧完全发送成功后才能发送新的帧,因而只用一比特来编号就够了。其发送方和接收方运行的流程图如图所示。

(3).后退n协议
由于停等协议要为每一个帧进行确认后才继续发送下一帧,大大降低了信道利用率,因此又提出了后退n协议。后退n协议中,发送方在发完一个数据帧后,不停下来等待应答帧,而是连续发送若干个数据帧,即使在连续发送过程中收到了接收方发来的应答帧,也可以继续发送。且发送方在每发送完一个数据帧时都要设置超时定时器。只要在所设置的超时时间内仍收到确认帧,就要重发相应的数据帧。如:当发送方发送了N个帧后,若发现该N帧的前一个帧在计时器超时后仍未返回其确认信息,则该帧被判为出错或丢失,此时发送方就不得不重新发送出错帧及其后的N帧。
从这里不难看出,后退n协议一方面因连续发送数据帧而提高了效率,但另一方面,在重传时又必须把原来已正确传送过的数据帧进行重传(仅因这些数据帧之前有一个数据帧出了错),这种做法又使传送效率降低。由此可见,若传输信道的传输质量很差因而误码率较大时,连续测协议不一定优于停止等待协议。此协议中的发送窗口的大小为k,接收窗口仍是1。

(4).选择重传协议
在后退n协议中,接收方若发现错误帧就不再接收后续的帧,即使是正确到达的帧,这显然是一种浪费。另一种效率更高的策略是当接收方发现某帧出错后,其后继续送来的正确的帧虽然不能立即递交给接收方的高层,但接收方仍可收下来,存放在一个缓冲区中,同时要求发送方重新传送出错的那一帧。一旦收到重新传来的帧后,就可以原已存于缓冲区中的其余帧一并按正确的顺序递交高层。这种方法称为选择重发(SELECTICEREPEAT),其工作过程如图所示。显然,选择重发减少了浪费,但要求接收方有足够大的缓冲区空间。

TCP滑动窗口(SlidingWindow)
滑动窗口协议可以用图四来形象表示。

腾讯一头总结-2014 软件开发 后台研发
图中我们已经将字节进行了1到11的编号。由接收者通告的窗口称为提议窗口(offeredwindow),它覆盖了第4到第9个字节,意味着接收方已经确认了第3字节之前(包括第3字节)的数据,并且通告窗口的大小是6。窗口大小与确认的顺序号(acknowledgedsequencenumber)有关。发送者计算它的可用窗口(usablewindow),用以度量它可以立即发送多少数据。
随着接收者对收到数据的确认,滑动窗口随时向右移动。窗口两端的相关运动增加或减少着窗口大小。我们使用3个术语来描述窗口边缘(edge)的左右运动。
1.当窗口左边缘靠近右边缘时称窗口闭合(windowcloses)。窗口闭合发生在数据已经发送并被确认的情况下。
2.当窗口右边缘向右移动时称窗口打开(windowopens)。窗口打开发生在另一端的接收进程读取已确认数据的时候,它释放了TCP接收缓冲区的空间。
3.当窗口右边缘向左移动时称窗口收缩(windowshrinks)。HostRequirementRFC强烈不鼓励这种做法,但TCP必须能够在一端发生这种情况时进行处理。

图五表示了这三个术语。由于窗口的左边缘是受从连接另一端收到的确认号来控制的,因此它不会向左移动。如果收到一个ACK要求将左边缘向左移动,那么它是一个重复的(duplicate)的确认,并被丢弃。

腾讯一头总结-2014 软件开发 后台研发
如果窗口左边缘重合了右边缘,就称它为零窗口(zerowindow)。它将停止发送者传输任何数据。

图六显示了图一数据传输中TCP滑动窗口的动态变化
腾讯一头总结-2014 软件开发 后台研发
以此图为例,我们可以总结几个要点:
1.发送者不必传送满窗口大小的数据。
2.收到接收者对数据的确认后,窗口向右滑动。这是由于窗口大小与确认顺序号相关。
3.从段7到段8的变化,可以看出窗口可以减小,但窗口右边缘不能向左移动。
4.接收者不必等待窗口被填满才发送确认。在许多实现中,接收者每收到两个段发送一个确认。
参考:http://blog.****.net/wbw1985/article/details/4879224

四、在给定范围[2~10000]范围内查找质数的个数?如果使用双核CPU,如何提高效率?提高了多少?
代码很简单
#include <stdio.h>
#include <math.h>
#include <assert.h>

int find(int start,int end,int *k)
{
     int i=0,j=0,t=0;
     *k=0;
     assert(start&&end&&(start<end));
     for(i=start;i<=end;i++)
     {
          t=0;
          if(i==1)
               continue;
          if(i==2)
          {
               (*k)++;
               printf("%d ",i);
               continue;
          }
          for(j=1;j<=sqrt(i);j++)
          {
               if(i%j==0)
                    t++;
               if(t>1)
                    break;
          }
          if(t==1)
          {
               (*k)++;
               printf("%d ",i);
          }
     }
     return *k;
}

int main(void) {
     int a=1,b=9,k=0;
     find(a,b,&k);
     printf("k=%d\n",k);
     return 0;
}

当使用双核CPU时,从应用层面和微架构层面,对样例程序进行优化。对于应用层面的优化,将采用多线程和 CPU 亲和力技术;在微架构层面,采用 Cache 优化。
(1)多线程
a.计算过程多线程:
为什么多线程会比单线程更耗时呢?其原因就在于,线程启停以及线程上下文切换都会引起额外的开销,所以消耗的时间比单线程多。
为什么加锁后的三线程比两线程还慢呢?其原因也很简单,那把读写锁就是罪魁祸首。通过 Thread Viewer 也可以印证刚才的结果,实际情况并不是并行执行,反而成了串行执行,
b. 计算内容多线程
这个并不能是主要解决手段
注:线程并不是越多越好,软件线程的数量尽量能与硬件线程的数量相匹配。最好根据实际的需要,通过不断的调优,来确定线程数量的最佳值。
(2)CPU 亲和力
CPU 亲和力可分为两大类:软亲和力和硬亲和力。
Linux 内核进程调度器天生就具有被称为 CPU 软亲和力(affinity) 的特性,这意味着进程通常不会在处理器之间频繁迁移。这种状态正是我们希望的,因为进程迁移的频率小就意味着产生的负载小。但不代表不会进行小范围的迁移。
CPU 硬亲和力是指进程固定在某个处理器上运行,而不是在不同的处理器之间进行频繁的迁移。这样不仅改善了程序的性能,还提高了程序的可靠性。
从以上不难看出,在某种程度上硬亲和力比软亲和力具有一定的优势。但在内核开发者不断的努力下,2.6内核软亲和力的缺陷已经比2.4的内核有了很大的改善。
(3) Cache 优化
数据不仅在执行核和存储器之间移动,还会在执行核之间传输。根据数据相关性,其中有两种读写模式会涉及到数据的移动:写后读和写后写 ,因为这两种模式会引发数据的竞争,表面上是并行执行,但实际只能串行执行,进而影响到性能。
(4)并行程序设计
OpenMP是一个支持共享存储并行设计的库,特别适宜多核CPU上的并行程序设计。等等
参考:http://www.ibm.com/developerworks/cn/linux/l-cn-optimization/