面试题答案
一键面试常用系统调用
epoll
(在Linux系统上):- 原理:
epoll
是Linux内核为处理大批量文件描述符而作了改进的poll
,它通过一个文件描述符管理多个文件描述符。epoll
有两种工作模式:水平触发(LT)和边缘触发(ET)。在水平触发模式下,只要文件描述符对应的缓冲区还有数据可读或可写,就会一直触发事件;在边缘触发模式下,只有当文件描述符状态发生变化时才会触发事件,这种模式要求应用程序在事件触发后尽可能多地读写数据,以避免漏读。 - Go语言使用:在Go语言标准库的
net
包底层,当运行在Linux系统时,会使用epoll
相关的系统调用。例如,syscall.EpollCreate
用于创建一个epoll
实例,syscall.EpollCtl
用于控制epoll
实例所监听的文件描述符,syscall.EpollWait
用于等待所监听文件描述符上的事件发生。
- 原理:
kqueue
(在FreeBSD、macOS等系统上):- 原理:
kqueue
是FreeBSD等系统提供的一种高效的事件通知机制,类似于epoll
。它使用一个内核对象来管理多个文件描述符的事件监听。kqueue
通过kevent
结构体来表示事件,并且支持多种类型的事件,如文件描述符的读写事件、信号事件等。 - Go语言使用:在Go语言运行在支持
kqueue
的系统上时,net
包底层会使用kqueue
相关系统调用。例如,syscall.Kqueue
用于创建一个kqueue
实例,syscall.Kevent
用于控制kqueue
所监听的事件以及获取发生的事件。
- 原理:
常用数据结构
- 文件描述符(File Descriptor):
- 作用:在操作系统中,文件描述符是一个用于标识打开文件或网络套接字等I/O资源的整数。在Go语言中,
net.Conn
等网络连接对象内部封装了文件描述符。例如,net.TCPConn
的fd
字段(在底层实现中)保存了对应的文件描述符。网络轮询器需要监听这些文件描述符上的事件,如可读、可写事件,以实现对网络连接状态的感知。
- 作用:在操作系统中,文件描述符是一个用于标识打开文件或网络套接字等I/O资源的整数。在Go语言中,
epoll_event
结构体(对应epoll
)或kevent
结构体(对应kqueue
):- 作用:
- 在
epoll
中,epoll_event
结构体用于表示epoll
所监听的事件以及事件发生时的相关信息。它包含事件类型(如EPOLLIN
表示可读,EPOLLOUT
表示可写)和一个联合体,可用于传递文件描述符等附加信息。 - 在
kqueue
中,kevent
结构体同样用于表示事件。它包含事件类型(如EVFILT_READ
表示读事件,EVFILT_WRITE
表示写事件)、过滤器、标志位等信息,用于精确描述事件以及对事件的控制。
- 在
- 作用:
epoll
实例(epoll
相关数据结构)或kqueue
实例(kqueue
相关数据结构):- 作用:
- 在
epoll
中,epoll
实例是一个内核对象,通过epoll_create
系统调用创建。它维护了一个所监听文件描述符的列表,并在事件发生时通知应用程序。 - 在
kqueue
中,kqueue
实例也是一个内核对象,通过kqueue
系统调用创建。它管理着所监听的事件集合,并提供事件通知功能。
- 在
- 作用:
协同工作实现高效网络事件监听
- 初始化阶段:
- 创建轮询器实例:在Linux系统上,通过
syscall.EpollCreate
创建epoll
实例;在FreeBSD、macOS等系统上,通过syscall.Kqueue
创建kqueue
实例。 - 注册文件描述符:对于每个需要监听的网络连接(如
net.TCPConn
对应的文件描述符),使用syscall.EpollCtl
(epoll
)或syscall.Kevent
(kqueue
)将其注册到轮询器实例中,并指定需要监听的事件类型(如可读、可写事件)。例如,对于一个TCP连接,可能先注册EPOLLIN
(可读事件),以便在有数据到达时能够被通知。
- 创建轮询器实例:在Linux系统上,通过
- 运行阶段:
- 等待事件发生:调用
syscall.EpollWait
(epoll
)或syscall.Kevent
(kqueue
)进入阻塞等待状态,等待所监听的文件描述符上有事件发生。当有事件发生时,这些系统调用会返回发生事件的文件描述符列表以及对应的事件类型。 - 处理事件:Go语言的网络轮询器接收到事件通知后,根据事件类型(如可读事件则从对应的网络连接读取数据,可写事件则向网络连接写入数据),调用相应的处理函数。例如,对于可读事件,会调用
net.Conn
的Read
方法读取数据,并将数据传递给上层应用逻辑进行处理。处理完事件后,可能会根据需要再次注册文件描述符到轮询器中,以继续监听后续事件。例如,读取完数据后,再次注册EPOLLIN
事件,以便监听下一次数据到达。
- 等待事件发生:调用
通过上述系统调用和数据结构的协同工作,Go语言的网络轮询器能够高效地监听大量网络连接上的事件,实现高性能的网络编程。