Linux网络编程常见面试题
1. 为什么用户态需要读写缓冲区?
答:int n = read(fd, buf, sz); 内核态网络缓冲区的数据拷贝到用户态buf中, sz预期读取长度,n实际读取大小。read完成后需要界定数据包
int n = write(fd, buf, sz); 用户态buf中的数据拷贝到内核态网络中, sz预期写入长度,n实际写入大小。
可以用过iotcl获取读写缓冲区可读可写的大小,但是会多一次系统调用。
一次read系统调用不一定构成一个完整的数据包,需要先缓存在用户态读缓冲区中,等构成完整的数据包后再进行业务操作。
在进行write操作的时候内核态写缓冲区不一定能全部接收用户态发送的数据,需要暂时缓存在用户态写缓冲区
省流:从内核读取的数据不一定构成完整数据包,需要在用户态读缓冲区缓存,写入内核的数据,内核缓冲区不一定能全部接收,没接受的暂时缓存在用户态写缓冲区
2. 接收客户端连接有哪些方式?
答:前提条件:服务端已经创建了socket,并且绑定在了某个地址上,还被监听了
阻塞的IO网络模型: int fd = accept(socket, &addr, sizeof(addr))
非阻塞的IO网络模型:int fd = accept(socket, &addr, sizeof(addr));接收到了同上,接收不到会立刻返回-1,错误返回的errno为EWOULDBLOCK
reactor网络模型:需要使用listenfd注册读事件,如果事件循环中检测到listenfd的读事件,说明建立的io已经就绪
再次调用非阻塞io accept函数,得到连接的fd和ip地址,其实就是把连接抽象成一个事件。
proactor网络模型:投递accept请求,调用AcceptEx函数,传递一个重叠结构;在工作线程中调用GetQueueCompletionStatus获取IO完成结构,如果有IO完成事件,通过这个函数可以获取重叠结构,从而知道具体的请求。它是个异步IO处理,reactor只是通知IO就绪,但是proactor模型中内核会完成IO
3. Proactor和Reactor的区别
答:reactor是同步IO网络模型,具体IO操作通过非阻塞IO完成,具体IO是否就绪由IO多路复用完成。
proactor是异步IO网络模型, 具体的IO操作和IO检测都是用内核完成。windows下的iocp就是用的proactor模型。
本质区别是IO操作的方法不同,ractor注册事件,proactor投递请求
4. 阻塞和非阻塞,异步和同步的区别
答:同步:IO函数调用后立刻获得执行结果
阻塞:如果IO未就绪,立刻返回就是非阻塞,等待就是阻塞
5. IOCP工作流程
答:
(1)createlocompletionport 创建完成一个端口
(2)创建socket、bind、listen,将该socket绑定在完成端口上
(3)根据CPU核心数创建工作线程,将完成端口传递到工作线程
(4)投递IO请求到完成端口
6. 简述Reactor网络编程模型
答:reactor是一个事件处理模型,把对IO的处理转换为对事件的处理。由非阻塞IO和IO多路复用构成。非阻塞IO操作就绪的IO,IO多路复用检测多路IO是否就绪。
工作流程:
(1)注册事件:accept[注册读事件]--connect[注册写事件]--read/write--被动断开连接
(2)处理事件:事件触发后说明IO就绪了,处理对应的IO
可以把它封装成三个模块,事件对象[连接、监听,回调函数],事件控制[注销/注册接口],事件循环[检测后同步处理若干个事件]。
7.连接断开有几种判定方式?
答:宏观上说两种。
主动断开:主动close
被动断开:客户端主动close,或者直接退出
IO网络模型:read=0 || write=-1 && errno=EPIPE。前者读端关闭,后者写端关闭。
IO多路复用模型:EPOLLRDHUP、EPOLLHUP。前者关闭读,后者都关闭
reactor:非阻塞IO+IO多路复用,主要是在非阻塞IO的时候进行IO操作
proactor: 先投递IO请求,接着在工作线程中等待IO完成的消息。
8. 边缘触发和水平触发的区别
答:水平触发:只要某个文件描述符仍然处于就绪状态(例如,套接字缓冲区中还有未读数据或可写空间),每次调用epoll_wait()时,epoll都会返回该文件描述符。这意味着即便应用程序没有完全处理完所有可用数据,下次调用epoll_wait()时仍会收到相同的事件通知。
边缘触发:水平触发意味着只要事件保持激活状态(如文件描述符就绪),每次检查时都会通知处理器。以 epoll 为例,如果一个文件描述符被设置为水平触发,并且一旦变为就绪状态,那么每次调用 epoll_wait 都会返回该文件描述符,直到它被处理并不再就绪。
一般来说边缘触发的效率更高一些。理想情况就是可读事件采用水平触发,可写事件使用边缘出发。
select、poll只有水平触发。epoll默认水平触发,可以设置边缘触发。
9.可读可写事件的触发条件?
答:读缓冲区有数据,就触发读事件;全连接队列中有数据,有连接没有处理,就会触发读事件;对端写端关闭,当前段触发读事件
内核网络只要有可写空间就会触发写事件,connect连接成功触发可写事件。
10. CLOSE_WAIT和TIME_WAIT是什么?
答:是四次挥手中服务器和客户端的连接状态
CLOSE_WAIT:被动断开方收到主动断开方的FIN后进入的状态。
TIME_WAIT:主动断开方收到被动断开方FIN后进入的状态,等待2MSL后会回收socket资源,并且转入CLOSED状态。防止前一个连接丢失重传的数据包被错误的接收。之所以要等待一段时间是为了避免最后的ACK数据没有发送,有重传的事件。
11. 什么是socket?
答:是个五元组,包括两个IP,两个port和一个类型(TCP/UDP)。
12. 大量的close_wait说明存在什么问题?怎么解决?
答:被动断开方没有正常关闭连接,可能是业务逻辑忘记close;cpu繁忙造成的,可能是有死锁、或者其他阻塞IO的操作;检查断开流程是否调用了close,cpu繁忙异步处理收尾耗时操作。
13. 大量的time_wait说明存在什么问题?怎么解决?
答:说明此时耗费了大量的内存和cpu,大量的socket未释放,所以耗费cpu查找下一个可用的随机端口;可以使用SO_REUSEPORT和SO_REUSEADDR复用端口,避免不断的更换新的端口。或者修改内核time_wait等待值;设置SO_LINGER未0,调用close后立刻发送RST标志给对端,强行关闭,尚未发送的数据都会丢失
14. 介绍一下TCP连接
答:TCP是一种面向连接的、可靠的、基于字节流的全双工传输层通信协议。能够实现数据的有序传输和流量控制。
15.三次握手的流程
答:第一次:客户端发送TCP报文到服务器,包含一个随机初始序列号seq=x,和SYN=1的标志位。客户端此时处于SYN_SENT
第二次:服务器收到客户端的SYN报文后,如果同意建立连接,则会向客户端回应一个SYN+ACK报文。这个报文包含确认号(ACK,即ack=x+1),表示收到了客户端的序列号x,并期待收到序列号为x+1的数据段。服务器进入SYN_RECV状态。
第三次:客户端收到服务器的SYN+ACK报文后,会检查确认号是否正确(即确认号是否为x+1),并发送一个确认报文给服务器。这个报文包含确认号(ack=y+1),表示收到了服务器的序列号y,并期待收到序列号为y+1的数据段。此外,这个报文的SYN标志位为0,因为客户端已经在第一次握手中发送过SYN,而ACK标志位为1,确认了服务器的SYN报文。客户端发送这个报文后,进入ESTABLISHED状态,表示连接已建立。服务器接收到客户端的ACK报文后,也会进入ESTABLISHED状态
15. 三次握手的作用
答:确保双方具有收发数据包的能力。确定双端的起始序列号。避免旧的重复的连接初始化。
16. 为什么不能只进行两次握手?
答:无法避免旧的重复的连接初始化,从而造成资源的浪费。无法同步双端的起始序列号,保证可靠传输
17. TCP四次挥手的过程
答:第一次挥手:客户端像服务器发送FIN报文,报文中包含一个seq=x序列号。
第二次挥手:服务端收到FIN报文后,向客户端发送ACK报文,ack=x+1,还有自己的序列号seq=y。
第三次挥手:当服务器准备好关闭连接时,会向客户端发送一个FIN报文,序列号(seq=y+1)是服务器数据的最后一个字节的序列号。
第四次挥手:客户端收到服务器的FIN报文后,会发送一个ACK报文作为响应,ACK标志置为1,确认号(ack)设置为y+1,表示收到了服务器的FIN报文
18. 为什么需要四次挥手
答: TCP是一个全双工的协议,关闭时需要分别关闭两个方向上的通道。确保数据已经被完全传输。
19. 四次挥手过程中数据丢失会发生什么?
答:第一次挥手:多次尝试重传,一直不成功直接进入close
第二次挥手:ACK数据包不会重传,因此客户端会认为服务端没有接受到FIN数据包,也会重传FIN数据包。
第三次挥手:多次超时重试FIN包,重试失败后直接进入CLOSED状态
第四次挥手:服务器没有收到ACK会重复发送之前的FIN数据包,重试次数达到上限后会处在半关闭状态
20. 什么是半打开、半关闭的状态
答: 客户端收到SYN+ACK数据包后不回复ACK----SYN泛洪(DDOS)攻击 ;或者全连接队列已经满了,即使三次握手完成,服务器也无法将连接移入全连接队列,可能选择忽略客户端的第三次ACK报文,这也会造成连接停留在半连接状态。
增大半连接队列,减少SYN+ACK重传次数,开启tcp_syncookies可以有效防御DDOS攻击
21. UDP和TCP的区别?
答: 相同点:都是传输层的协议,都是为了应用层程序提供服务。
不同点:
TCP是面向连接的协议,需要三次握手建立连接端到端的连接,是全双工通信允许双方同时收发数据。TCP基于字节流进行传输。完整的用户消息可能被拆分成多个tcp报文,不会保留报文边界,对于接收方需要粘包的问题。TCP可以对数据进行分段,根据网络情况对数据包的大小进行限制。数据包都有序列号,可以对序列号进行排序从而保证数据完整性,丢失的数据会进行重传。有流量控制和拥塞控制以及确认应答机制、超时重传机制。TCP头20字节,传输效率低一些
UDP是面向无连接的协议,不需要三次握手四次挥手,支持1对1、1对多、多对1、多对多的连接。UDP基于报文传输数据。UDP每次收发都是整个报文,读取一个udp报文就能获取全部信息。不能保证消息交付。不保证交付顺序、没有拥塞控制、流量控制。udp头8字节,传输效率高一些
22. 描述输入网址到网页显示的整个流程
答:用户在浏览器输入网址后, http会解析URL,生成http数据包,查询域名生成的IP地址。建立TCP连接,TCP会对数据进行分端,每段数据都会加上TCP的
头部信息。会加入IP地址和端口以及mac地址信息,这个和IP地址一样,也有目的地址和源地址。接下来建立TCP连接,浏览器会发送HTTP请求到服务器。服务器接收到请求后,会处理请求并返回相应的HTTP响应。浏览器接收到响应后,会根据响应的内容进行解析,并将网页渲染出来显示给用户
23. Linux接受网络数据包的流程
答:1. 网卡接受到数据包,通过DMA将数据写入内存(ringbuffer)结构
2. 网卡向CPU发起硬件中断,cpu收到中断请求,根据中断表查找中断处理函数,调用中断给处理函数
3. 中断处理函数将屏蔽硬件中断,发起软件中断。避免CPU频繁被网卡中断,使用软中断处理耗时操作,避免执行时间过长,导致cpu无法响应其他硬件中断
4. 内核ksoftirqd线程负责软中断处理,该线程从ringbuffer中逐个取出数据帧到sk_buffer
5. 从帧头取出IP协议,判断是ipv4还是ipv6,去掉帧头帧尾
6. 从IP头看上一层协议是tcp还是UDP,根据五元组找到socket并将数据提取出来后放到socket的接受缓冲区
7. 应用程序通过系统调用将socket的接受缓冲区数据拷贝到应用缓冲区
24. Linux发送网络数据包的流程
答:1. 应用程序通过系统调用吧用户数据拷贝sk_buff到socket的发送缓冲区
2. 网络协议栈从socket的发送缓冲区取出sk_buff并且复制出一个新的sk_buff,
3. 向下传递依次增加TCP/UDP头部、IP头部、帧头(MAC)头部、帧尾
4. 触发软中断通知网卡驱动程序,有新的网络包需要发送
5. 从发送队列中依次取出sk_buff写入ringbuffer(内存DMA区域)
6. 触发网卡发送,发送成功,触发硬件中断,释放sk_buff和ringbuffer内存
7. 当收到tcp报文的ack应答时,将释放原始的sk_buff
25. TCP是如何保证可靠性的
答:
(1)应答机制:TCP使用确认应答机制来确保数据的可靠传输。发送方发送数据后,会等待接收方发送确认应答,如果一定时间内未收到确认,则会重传数据。接收方在收到数据后会发送确认应答,同时对收到的数据进行排序和去重处理。
(2)序列号和确认号:TCP使用序列号和确认号来保证数据的顺序和完整性。每个数据包都有一个序列号,接收方使用确认号来告知发送方下一个期望接收的数据包序列号。这样可以确保数据的顺序和完整性。
(3)超时重传:如果发送方在一定时间内未收到确认应答,就会认为数据丢失,触发超时重传机制,重新发送数据包。
(4)流量控制:TCP使用滑动窗口机制来进行流量控制,确保发送方和接收方之间的数据传输速率适应网络状况。接收方可以告知发送方自己的可接收窗口大小,以防止发送方发送过多数据导致接收方缓冲区溢出。
(5)拥塞控制:TCP使用拥塞控制算法来避免网络拥塞,通过动态调整发送窗口大小和传输速率来保证网络的稳定性和公平性
本博客由挨踢零声赞助