网络编程实战
盛延敏
前大众点评云平台首席架构师
44207 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 40 讲
网络编程实战
15
15
1.0x
00:00/00:00
登录|注册

25 | 使用阻塞I/O和进程模型:最传统的方式

SIGCHLD信号处理
wait和waitpid函数
僵尸进程问题
实现原理
返回值
为每个连接创建一个独立的进程
注意事项
使用阻塞I/O和进程模型的优缺点
客户端交互
启动服务器
套接字关闭
子进程处理逻辑
信号处理函数
父进程和子进程处理连接
子进程退出回收
fork函数
解决方法
思考题
总结
实验
程序讲解
阻塞I/O的进程模型
父子进程
C10K问题
网络编程实战第25讲

该思维导图由 AI 生成,仅供参考

你好,我是盛延敏,这里是网络编程实战第 25 讲,欢迎回来。
上一讲中,我们讲到了 C10K 问题,并引入了解决 C10K 问题的各种解法。其中,最简单也是最有效的一种解决方法就是为每个连接创建一个独立的进程去服务。那么,到底如何为每个连接客户创建一个进程来服务呢?在这其中,又需要特别注意什么呢?今天我们就围绕这部分内容展开,期望经过今天的学习,你对父子进程、僵尸进程、使用进程处理连接等有一个比较直观的理解。

父进程和子进程

我们知道,进程是程序执行的最小单位,一个进程有完整的地址空间、程序计数器等,如果想创建一个新的进程,使用函数 fork 就可以。
pid_t fork(void)
返回:在子进程中为0,在父进程中为子进程ID,若出错则为-1
如果你是第一次使用这个函数,你会觉得难以理解的地方在于,虽然我们的程序调用 fork 一次,它却在父、子进程里各返回一次。在调用该函数的进程(即为父进程)中返回的是新派生的进程 ID 号,在子进程中返回的值为 0。想要知道当前执行的进程到底是父进程,还是子进程,只能通过返回值来进行判断。
fork 函数实现的时候,实际上会把当前父进程的所有相关值都克隆一份,包括地址空间、打开的文件描述符、程序计数器等,就连执行代码也会拷贝一份,新派生的进程的表现行为和父进程近乎一样,就好像是派生进程调用过 fork 函数一样。为了区别两个不同的进程,实现者可以通过改变 fork 函数的栈空间值来判断,对应到程序中就是返回值的不同。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

文章介绍了使用阻塞I/O和进程模型来解决C10K问题的方法。通过讲解父子进程的概念和fork函数的使用,以及处理子进程的回收和避免僵尸进程的产生,文章详细讲解了阻塞I/O的进程模型,并给出了一个基于进程模型的服务器端程序,并进行了实验验证。使用阻塞I/O和进程模型为每个连接创建一个独立的子进程来进行服务是一种简单有效的实现方式,尽管可能难以满足高性能程序的需求,但在实现时需要注意对套接字的关闭和对子进程的回收,以避免产生不必要的僵尸进程。同时,文章提出了两道思考题,分别是寻找使用类似模式构建的著名程序以及解释为什么在处理SIGCHLD信号时需要使用循环回收终止的子进程。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《网络编程实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(18)

  • 最新
  • 精选
  • rrbbt
    文章开头部分,应该是线程是程序执行的最小单位吧。进程是cpu分配资源的最小单位。还有,每个线程有一个程序计数器,不是每个进程。

    作者回复: 在Linux里面,线程其实就是轻量级进程(light-weighted process),它们都使用同样的task_struct结构,无论用户进程(process), 用户线程(thread),就只是在task_struct的填法不一样而已。 所以,从内核角度来说,确实是线程(task_struct)是最小的,但是从用户角度来说,确实是进程是最小执行的单元,因为不可能没有进程,凭空产生出线程来。 线程和进程都有各自的PC计数器,实际上,无论是这里的fork,还是后面讲到的线程pthread_create,最终到内核中都是调用do_fork来产生task_struct结构的。

    2019-11-08
    4
    18
  • Kepler
    这种古老的多进程方式来处理io应该是apache服务器了吧,新型的redis, nginx都是事件驱动epoll来实现的; 采用while循环是为了回收所有已退出子进程的状态。

    作者回复: 我知道有apache服务器,其他的大家可以补充。

    2019-10-11
    3
    14
  • 刘丹
    请问主进程结束的时候没有关闭listener_fd,子进程结束的时候没有关闭fd,是让操作系统在关闭进程时自动回收资源吗?

    作者回复: 是我疏忽了,不过系统内核确实可以回收,最好还是自己close一下。

    2019-10-04
    13
  • Simple life
    这种阻塞进程模型与单进程accept+处理有什么区别?仅仅为了环境隔离?效率上并没有带来提升,还增加了上下文切换的操作,感觉有点得不偿失

    作者回复: 利用了cpu多核的能力。

    2020-08-03
    1
  • 菜鸡
    这个代码我运行了一下,当有一个客户端关闭时,服务端会出现accept failed: Interrupted system call (4),然后其它客户端就无法再进行交互了,直到被强制关闭

    作者回复: 你可以不放过这个问题,仔细研究一下,评论区等你......

    2022-05-05
  • supermouse
    请问老师,文稿中的代码child_run函数里面的recv是一次只接收一个字符吗?为什么不一次接收一个字符串呢?

    作者回复: 因为我需要对字符串进行解析,这里只是为了判断一下回车符的出现,你完全可以改为先读字节流再解析。

    2020-02-24
  • 传说中的成大大
    读书少的人只能直接回答第二问 第一问回答不了哈哈哈 是因为如果有多个子进程同时结束的话内核只会产生一次SIGCHLD信号至少信号处理函数只会唤醒一次,如果不循环就无法取得所有已终止的子进程数据
    2019-10-10
    41
  • Geek_sky
    一个发出而没有被接收的信号叫做待处理信号(pending signal)。在任何时刻,一种类型至多只会有一个待处理信号。如果一个进程有一个类型为k的待处理信号,那么任何接下来发送到这个进程的类型为k的信号都不会排队等待;它们只是被简单地丢弃。一个进程可以有选择性地阻塞接收某种信号。当一种信号被阻塞时,它仍可以被发送,但是产生的待处理信号不会被接收,直到进程取消对这种信号的阻塞。 一个待处理信号最多只能被接收一次。内核为每个进程在pending位向量中维护着待处理信号的集合,而在blocked位向量中维护着被阻塞的信号集合。只要传送了一个类型为k的信号,内核就会设置pending中的第k位,而只要接收了一个类型为k的信号,内核就会清除pending中的第k位。——深入理解计算机系统
    2019-12-06
    10
  • 传说中的成大大
    上面说错了 信号会产生多次 信号处理函数只会调用一次
    2019-10-10
    5
  • yang
    1. 按老师说的去搜了一下,也看到评论区其他同学的留言,发现redis的bgsave使用了fork创建子进程来保存数据。 2. 不使用while循环 在子进程退出时,会错过在子进程的资源回收叭
    2019-10-08
    4
收起评论
显示
设置
留言
18
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部