后端网络编程知识点总结

  • Post author:
  • Post category:其他


这一篇是网络编程的面试知识点的总结。不打算从计算机网络的基础讲起,只是摘要性的总结一下知识点,更多的内容请从经典的书籍中获取。

1、tcp与udp的区别(必问)


  • 可靠性

    首先,TCP是一个

    面向连接

    的协议,需要三次握手。具有可靠性,但是并不是百分百的可靠,它通过序列号确认,超时重传等机制提供数据的可靠传送或者故障的可靠通知,但并不能保障数据一定会被对方接收。

    UDP是一个

    无连接

    的协议。UDP不保证数据报会到达最终的目的地,不保证数据报的先后顺序不变,不保证每个数据报只到达一次。不会自动的重传以及报告错误。


  • 协议类型

    每个UDP数据报都有一个长度,改数据报的长度随数据一道传递给接收端应用程序,而TCP是一个字节流协议,没有任何的记录边界。TCP和UDP都可以IPv4和IPv6。


  • 流量控制

    TCP提供流量控制,TCP总是告知对端在任何时刻它一次能够从对端接收多少字节的数据。从而保证发送端的发送的数据不会使接收缓冲区溢出。接收端的窗口大小会动态的变化。UDP不提供流量控制。


  • 全双工

    TCP连接是全双工的,意味着连接上的应用可以在任何时刻既发送数据又接收数据。需要的话也可转化成单工的连接。UDP**可以**是全双工的。

    比较来看,UDP似乎不是很好的选择,但是它胜在速度快(不需要建立连接),占用资源少(不需要状态维护)。报文的首部开销小,8个字节,TCP首部20个字节。从实际应用来说,也会选择UDP模拟实现TCP,或者使用上层协议保证传输可靠。

2、udp调用connect有什么作用?

UDP的connect和TCP的connect不是一个概念,没有三次握手的过程。对于使用connect的已经连接的UDP套接字,与默认的未连接的套接字相比:

  1. 再也不能给输出操作指定目的地的IP和端口号。写到已连接UDP套接字上的任何内容都会自动发送到connect指定的协议地址上。使用write或send代替sendto。

  2. 不必使用recvfrom获悉数据报发送者,因为从内核返回的数据报只有那些指定协议地址的数据报。改用read,recv,recvmsg。

  3. 由已连接UDP套接字引发的异步错误会返回给它们所在的进程。而未连接套接字不接收任何异步错误。

    总的来说,connect启动一个声明和限定通信方的作用。同时,更少次数的连接和断开可以提高数据的传输效率。

    如果对一个已连接的UDP套接字进程再次调用connect,可以:

    1.指定新的IP地址和端口。

    2.断开套接字。

3、tcp连接中时序图,状态图,必须非常非常熟练

这个没什么好说的,看图。

这里写图片描述

这里写图片描述

4、socket服务端的实现,select和epoll的区别(必问)

基本的socket服务端实现就在上面的图上。

epoll的出现比较晚了,epoll是linux独有的高效的IO复用的机制。先简单提一下poll和select,poll和select很相似,但是poll没有select的文件描述符的限制(默认1024),poll使用pollfd结构代替描述符fd。

而epoll与poll的不同在于:poll每次返回整个文件描述符的数组,用户代码需要遍历数组找到哪些文件描述符上有IO事件。而epoll_wait返回的是活动fd的列表,比遍历的数组小的多。epoll使用的是一种注册回调的机制,epoll_ctl为每一个fd指定一个回调函数,当设备就绪的时候,会唤醒等待队列中的等候者并调用其回调函数,回调函数会把对应的fd加入到就绪队列,epoll _wait就负责查询这个就绪队列,同时epoll也没有文件描述符数目的限制。

select和poll就相当于轮询,需要遍历整个的fd集合来确定IO就绪,而epoll只需要判断一下就绪链表是否为空就可以了。

struct pollfd{
    int fd;
    short events;
    short revents;
};

5、epoll哪些触发模式,有啥区别?(必须非常详尽的解释水平触发和边缘触发的区别,以及边缘触发在编程中要做哪些更多的确认)

如果稍微有点电子方面经验的人,相信对水平触发和边沿触发的概念就比较了解了。如果是边缘触发模式,就是当状态变化的时候才通知,对应电子中的高低电平变化的上升沿和下降沿。而水平触发则是你等待的状态到了,就一直通知。

这里写图片描述

注意因为边沿触发在被监控的文件描述符上有可读写事件发生时,epoll_wait会通知处理程序去读写,但是它只会通知一次。如果没有把数据全部读写完,下次调用epoll_wait时并不会通知你。因此需要去确认缓冲区中的数据读写完毕,例如在非阻塞模式下读取数据,使用循环。根据返回的error,EAGIN和EWOULDBLOCK来判断数据是否全部读取完毕。

6、大规模连接上来,并发模型怎么设计

我本身并没有做过大规模连接的项目,说并发模型设计属于纸上谈兵。

如果没有这方面概念的,先看一看C10K问题。


http://www.kegel.com/c10k.html#frameworks


比较通俗易懂的知乎专栏:


https://zhuanlan.zhihu.com/p/20311080


《unix网络编程》第30章有关于服务器设计的范式,虽然内容稍显过时。

现在应用比较广泛的应该是多进程/多线程+“non-blocking IO+IO multiplexing(对应Java的NIO概念)”的异步IO模型,使用Linux的epoll或者kqueue,或者使用libevent封装的统一接口。基本结构就是一个事件循环(event loop),以事件驱动(event-driven)和事件回调方式实现业务逻辑。另外对于CPU密集型的操作,可以开线程池作为补充。

相关代码和实现可以参考nignx。


http://tengine.taobao.org/book/chapter_02.html#id1

7、tcp结束连接怎么握手,time_wait状态是什么,为什么会有time_wait状态?哪一方会有time_wait状态,如何避免time_wait状态占用资源(必须回答的详细)

这里问的应该是怎么挥手。以下部分摘自《unix网络编程》和《TCP/IP详解卷一》。首先TCP终止连接需要4个分节。因为TCP是一个全双工的连接,每个方向都必须单独关闭。

– 某个应用进程首先调动close,主动关闭,发送一个FIN(意味着这一个方向没有数据的流动)字节。

– 接收到这个FIN的对端被动关闭。这个FIN由TCP确认(ACK),并作为一个文件结束符传递给接收端应用程序。

– 一段时间后,接收到这个结束符的应用程序调用close关闭它的套接字。这导致它的TCP也发送一个FIN。

– 接收这个最终FIN的原发送端TCP确认这个FIN。

这里写图片描述

注意,主动关闭方会有一个TIME_WAIT状态,也称为2MSL状态。主要是防止新建立的连接使用原来的socket,迟到的报文到达后被错误的接收。MSL(maximum Segment Lifetime)是任何报文段被丢弃在网络内的最长时间。在2MSL时间内,本次连接的socket(客户IP和端口,服务器IP和端口)不能再被使用。迟到的报文会被丢弃。

TIME_WAIT的另一个存在理由是可靠的实现TCP全双工连接的终止。假设最终的ACK丢失了,服务器需要重新发送一个FIN,客户必须能够重新发送ACK,不然它将响应以一个RST,这将被服务器解释为一个错误。

大量的TCP的短连接引发的TIME_WAIT会占用很多资源,其默认等待时间比较保守。

– 可以通过修改操作系统设置例如tcp_fin_timeout减少TIME_WAIT时间;

– 设置tcp_tw_recycle=1开启socket的快速回收;

– 使用比较激进的socket的SO_LINGER选项,设置为0,取消关闭等待。

– 或者选择使用tcp_tw_reuse开启socket的重用。

8、tcp头多少字节?哪些字段?(必问)

这里写图片描述

TCP头有固定的20字节,并且有可扩充的选项。每个TCP段都包含用于寻找发送端和接收端的源端口和目的端口。这两个值加上IP首部的源IP地址和目的IP地址能够确定唯一的一个TCP连接。

序号表示这个数据报段的第一个数据字节。用来标识从TCP发送端到接收端的数据字节流。

确认号包含发送确认的一端期望接收的下一个序号。为上次接收成功的数据字节序加1。ACK标志位1时确认号的字段才有效。一旦连接建立,ACK标志字段总是设置为1。

这里数据偏移表示有误,实际上就是TCP首部的长度。以32位为长度单位,偏移的意思是数据开始的地方离TCP段的起始位置的距离。

下面是6个标志比特位:

URG:紧急指针有效,用以通知另一端紧急数据已经放置在普通的数据流中,仅仅是通知而已,具体如何处理由接收端决定。注意后面16位的紧急指针是一个正的偏移量,通过与TCP首部序号字段相加,得出紧急数据

最后一个字节

的序号。但是没有办法指明紧急数据从数据流的何处

开始

。TCP通过连接传送的唯一信息时紧急方式已经开始,以及结束位置。接收方当前读取位置到紧急指针之间的数据会使应用程序处于紧急方式,通过紧急指针后,恢复正常方式。

ACK:确认序列号有效。

PSH:接收方应该尽快将这个报文段交给应用层。意思是当TCP向服务器发送一个报文段时,不要因等待额外数据而使已提交的数据在缓存中滞留。TCP的一些算法实现会推迟数据的交付,例如等待缓冲区填满。

RST:重建连接。

SYN:同步序号用来发起一个连接。既三次握手的同步。

FIN:发送端完成发送任务,常见于连接断开,四次挥手。

校验和覆盖整个TCP报文段,首部加数据。由发送端计算和存储,由接收端验证。

具体的实现由RFC793定义。

首先,把伪首部、TCP报头、TCP数据分为16位的字,如果总长度为奇数个字节,则在最后增添一个位都为0的字节。把TCP报头中的校验和字段置为0。

其次,用反码相加法累加所有的16位字(进位也要累加)。

最后,对计算结果取反,作为TCP的校验和。

既发送方的原码相加,将高位叠加到低位,取反放入校验和段中。

接收方将所有数据原码相加,高位叠加带低位,如果全为1,则正确。

选项在后面有详述。

9、什么是滑动窗口(必问)

滑动窗口协议是应流量控制方法。该协议允许发送方在停止并等待确认前可以连续发送多个分组。加速数据的传输。发送方不必发送一个全窗口大小的数据,具体如图:

这里写图片描述

如上图所示,对于TCP的发送方,在发送缓存中的数据可分为4个部分。已发送并被确认的,既已经收到对端ACK的数据,已发送但是未被确认的数据,未发送到但对端允许发送的数据,以及最后不可发送的数据。

滑动窗口由接收方通告,限制发送方的发送速度。其主要有两个作用:一是提供TCP的可靠性,二是控制流量。TCP的首部中有相关的字段window,上面已经介绍过了。

注意ACK是期望收到的下一个字节的序号n,并表示接收方已经收到了前n-1个字节,假如接收端收到1-1024个字节,它会发送一个1025的确认号,如果接下来收到的是2049-3072,它是不会接着发送3073的ACK的,而是接着发送1025。

通过窗口大小m和发送方接收到的ACK序列号n可以确认还可以发送多少数据,假设发送到第x字节,既可发送和不可发送的交界。则可以发送的字节数位y=m-(x-n)。通过调整窗口大小就可以做到流量的控制。

当数据被发送和确认时,滑动窗口的左边沿向右移动,窗口合拢。当接收端读取已经确认的数据并释放TCP缓存时,窗口的右边沿向右移动,窗口张开。

10、connect会阻塞,怎么解决?(必考必问,提示:设置非阻塞,返回之后用select检测状态)

这个问题已经把答案说出来了。通过设置TCP套接字为非阻塞,并调用connect,connect会立即返回一个EINPROGRESS错误,但是已经发起的TCP三路握手继续进行。然后接着使用select检测连接的成功或者失败。

使用select和非阻塞connect,当连接成功建立的时候,描述符变为可写。当建立连接遇到错误的时候,描述符变为既可读又可写。下一个问题会更多的讲到select的描述符问题。

使用非阻塞的connect有三个用途。

  1. 可以把三路握手叠加在其他处理上。既完成connect的时间内执行其他工作。
  2. 同时建立多个连接。
  3. 使用select等待连接建立,可以设置时间限制。缩短connect的超时。

11、如果select返回可读,结果只读到0字节,什么情况?

select函数的细节问题。先说答案吧,读到0表示该连接的读半部关闭(接收了FIN的TCP连接)。

对于select函数返回套接字就绪的情况。

可读:

1. 该套接字接收缓冲区的数据大于等于接收缓冲区低水位标记的当前大小。对此套接字执行读操作将不阻塞并返回一个>0的值。使用SO_REVLOWAT套接字选项设置低水位标记。默认为1。

2. 该连接读半部关闭,对此套接字读将不阻塞并返回0。(EOF)

3. 其上有一个错误待处理。对此套接字读不阻塞返回-1,并把errno设置成确切的错误条件。

4. 该套接字是一个监听套接字并已完成的连接数不为0。

可写:

1. 发送缓冲区可用空间字节数大于等于低水位标记,且改套接字已连接,或不需要连接(UDP)。写操作不阻塞返回正值。默认标记2048。

2 . 该连接写半部关闭,写操作将产生SIGPIPE信号。

3 . 使用非阻塞connect套接字连接已建立,或者已失败。

4 . 其上有一个错误待处理。同读。

12、keepalive 是什么东东?如何使用?

在《TCP/IP详解卷一》二十三章有对保活概念的描述。许多时候服务器希望知道客户是否崩溃关机或者崩溃重新启动。以防止服务器永远等待一个消失的客户。

TCP的套接字中有SO_KEEPALIVE选项,设置后,如果两小时内该套接字的任一方向都没有数据交换,TCP就会自动给对端发送一个探测分节。这时对端必须响应一个TCP分节。有以下三种情况:

1.对端返回以期望的ACK,应用进程因为一切正常得不到通知。在又经过无动静的2小时后,TCP再发另一个探测分节。

2.对端以RST响应,告知本端,对端已崩溃并重启。改套接字待处理错误被置为ECONNRESET,套接字被关闭。

3.对端无任何响应,将另外发送若干探测分节(Berkeley为8),两两相隔75秒。在发送第一个分节11分15后无任何响应则放弃。待处理错误为ETIMEOUT。套接字被关闭。若收到ICMP错误的响应,则返回相应的错误。套接字被关闭。

KeepAlive并不是默认开启的,在Linux系统上没有一个全局的选项去开启TCP的KeepAlive。需要开启KeepAlive的应用必须在TCP的socket中单独开启。Linux Kernel有三个选项影响到KeepAlive的行为:

1.net.ipv4.tcpkeepaliveintvl = 75

2.net.ipv4.tcpkeepaliveprobes = 9

3.net.ipv4.tcpkeepalivetime = 7200

tcpkeepalivetime的单位是秒,表示TCP链接在多少秒之后没有数据报文传输启动探测报文; tcpkeepaliveintvl单位是也秒,表示前一个探测报文和后一个探测报文之间的时间间隔,tcpkeepaliveprobes表示探测的次数。

TCP socket也有三个选项和内核对应,通过setsockopt系统调用针对单独的socket进行设置:

1.TCPKEEPCNT: 覆盖 tcpkeepaliveprobes

2.TCPKEEPIDLE: 覆盖 tcpkeepalivetime

3.TCPKEEPINTVL: 覆盖 tcpkeepalive_intvl

注意HTTP上也有keep-alive的概念,它与TCP上的完全不是一回事儿。HTTP上的keep-alive是为了复用TCP连接,以免每一个HTTP请求都新建并断开一个TCP,提高传输效率。

13、列举你所知道的tcp选项,并说明其作用。

这里写图片描述

kind表示类型,len为总长度。常见选项7种。

kind0:选项表结束。表示首部无更多消息。

kind1:无操作,用来填充字段为4字节的倍数。

kind2:MSS最大报文长度。

kind3:窗口扩大因子。窗口大小为原始的N*(1<<移位数)。

kind4:SACK,正常某个报文段丢失后会将其后所有报文重传,设置SACK后只重传丢失部分。

kind5:SACK具体部分,发送端据此重传。左边沿为不连续块的第一个数据序号,右边沿为最后一个不连续块数据序号的下一个序号。

kind8:时间戳。经常打log的应该都了解。

14、socket什么情况下可读?

见11.



版权声明:本文为lijun538原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。