一、多进程服务器
-
read()
阻塞的话就是【单客户端】响应的服务器 - 多进程服务器是【多客户端响应的服务器】
之所以需要多客户端
响应的原因是——
三次握手的时候是阻塞在accept()那里,然后accept()返回成功后,到read()那里阻塞
这时候如果来一个新连接
,因为还是阻塞在read()中(一直在等待),而不在accept()中
(所以三次握手不能及时的响应),所以【新连接请求】会缓存在accept队列中
- 如果将read
和accept
都设置成非阻塞的,那么就是【非阻塞】轮询模型,这样的坏处:每来一个客户端链接,都有一个read()
,这样后面会有越来越多的read()
(也会有越来越多的【套接字】)。而且如果没数据读,会导致CPU空转(CPU使用率很高)
一、多进程服务器
多进程模型,在accept()和read()中间打断,加一个fork()
,fork()出来的子进程来读数据,再关闭子进程的fd
父进程则是回到accept()
那里继续监听,最后关闭父进程的fd
父进程阻塞在accept()那里,子进程会阻塞在read()那里,二者互相不影响,从而实现多进程并发
fork()之后,套接字的
引用计数
会加1,但是file结构体
没有复制过来,只是【复制过来的指针】也指向了file结构体,文件描述符表
复制了过来子进程的
clntfd
和父进程的clntfd
指向的是同一个套接字,套接字的引用计数
加1
exit(-1)
退出进程的函数
父进程则是continue回到accept()
继续阻塞
- pid为
-1
意味着创建进程失败
如果在连接过程中,如果客户端先关闭,那么
客户端的进程
就会变成僵尸进程
,所以要处理僵尸进程
- 如何
回收进程
——wait()【阻塞等待】,waitpid()【非阻塞接收】 - 多进程服务器模型中的
accept()
和read()
是【阻塞的】,所以不能使用wait()阻塞等待
二、使用信号回收进程
使用信号的方式回收,因为父进程一直阻塞在accept()
那里等待连接,所以均不能设置成阻塞等待和非阻塞轮询,因此使用信号
- SIGCHID(子进程退出的时候,会给父进程发送一个信号SIGCHID)
- sig_handler()使用
waitpid()来非阻塞的处理信号
因为可能有
多个子进程同时退出
,所以while()来保证可以接收到所有子进程退出的信号
三、关闭多余的fd
因为fork()是在accept()后面,所以fork()之前就已经有2个fd
了——lstnfd和clntfd,但是子进程又用不到lstnfd,所以子进程中需要关闭lstnfd
如果子进程这里不关闭的话,那么因为lstnfd对应套接字的引用计数是2,所以父进程要关闭2次fd
父进程也要关闭clntfd,不然子进程的close(clntfd)并没有真正的关掉,只是引用计数-1
(不关闭就意味着不释放资源,会导致内存泄露
)
如果服务器要把数据写回去,那么就是在子进程中读完数据(
read
)后通过write(clntfd, buf)
将数据写回去因为
write()
是在子进程中,如果写的时候读端关闭了,就会有一个信号SIGPIPE(管道破裂)
发送给子进程,可以默认不处理,也可以处理
ulimit -a 可以查看总共可以创建的进程数量
可以去系统改这个数量,也要考虑物理内存的大小,如果物理内存不够,那么就会将一部分的进程交换到交换分区中(比较慢),再需要使用的话则是从交换分区中恢复
如果不想去交换分区,则需要设置一下【粘住位】
fork()后子进程和父进程【接收到的数据】不一样,所以会
重新申请物理内存(内存页)
比如buf就是不一样的,原则是【读时共享,写时复制】`