面试题答案
一键面试1. 进程划分
- 主进程(Master Process):负责初始化服务器,监听端口,接受客户端连接,并将新连接分发给子进程处理。使用
socket()
、bind()
、listen()
等函数进行网络相关初始化。 - 工作进程(Worker Processes):
- 业务类型划分:根据不同业务(文件上传下载、数据库查询更新、实时数据推送)分别创建对应的工作进程。例如,对于文件上传下载业务,创建专门的文件处理进程;数据库相关业务,创建数据库处理进程;实时数据推送业务,创建推送进程。
- 数量动态调整:可以根据系统资源和负载情况动态调整工作进程数量。通过
fork()
函数创建子进程,子进程继承父进程的部分资源,如打开的文件描述符(包括监听套接字),在子进程中通过execve()
系列函数执行具体业务逻辑的程序。
2. 任务调度策略
- 基于事件驱动:使用
epoll
机制(在Linux下)来实现高效的事件驱动模型。主进程将监听套接字添加到epoll
实例中,当有新连接到来时,epoll_wait
函数返回,主进程将连接分配给空闲的工作进程。工作进程同样使用epoll
监听其负责处理的套接字事件,如数据可读、可写等。 - 负载均衡:采用简单的轮询(Round - Robin)策略将新连接均匀分配给工作进程。主进程维护一个工作进程列表,每次有新连接时,按顺序选择一个工作进程来处理该连接。代码实现上,可以使用一个全局变量记录当前分配到的工作进程索引,每次分配后递增并取模。
3. 故障处理机制
- 进程监控:主进程定期检查工作进程的状态。可以通过
waitpid()
函数(设置WNOHANG
标志,非阻塞等待)来检查子进程是否退出。如果某个工作进程异常退出(通过检查waitpid()
返回值和获取子进程退出状态),主进程可以重新启动一个新的工作进程来替代它。 - 错误处理:在各个进程的业务逻辑处理中,对系统调用(如
read()
、write()
、connect()
等)返回的错误进行及时处理。例如,如果read()
函数返回错误,根据错误码判断是临时错误(如EAGAIN
)还是永久性错误,临时错误可重试,永久性错误则关闭连接并记录日志。
4. 利用C语言特性实现
- 指针和内存管理:在处理网络数据、文件操作等时,合理使用指针来高效地管理内存。例如,在接收网络数据时,使用
malloc()
分配合适大小的内存缓冲区,接收完成后再根据实际数据长度调整内存(可使用realloc()
),处理完毕后使用free()
释放内存,避免内存泄漏。 - 函数指针:可以将不同业务处理逻辑封装成函数,通过函数指针来实现灵活的任务调度。例如,在工作进程中,根据接收到的业务类型,调用相应的函数指针指向的处理函数。
- 多文件编程:将不同功能模块(如网络模块、业务处理模块、日志模块等)分别放在不同的源文件中,通过头文件进行函数声明和数据结构定义,提高代码的可维护性和可扩展性。