一、信号打断read()函数
- read()函数如果阻塞地读,很容易就会被信号打断
如果被信号打断,返回的错误码errno则是EINTR
,然而无所谓,我们被信号打断并不影响什么,continue
并且取消关闭fd
,进入下一次循环即可——
二、信号打断accept()函数
- 先打开服务器后,不启动客户端,那么现在就阻塞在accept()这个地方
- 打开服务器后,客户端启动完,就进入到了read()函数中
被信号打断并不可怕,我们只要设置成,万一被打断了,重启一次就行,这样就没有事
我们可以让它continue
,另一种就是设置【信号中的RESTART
】——
-
read函数中的信号处理——
三、复习review
四、错误值封装
- 一行的结束标志是
/n
- 字符串的结束标志是
/0
先从缓存中读取数据,然后读取文件描述符(套接字)中的数据
五、多进程服务器
轮询的坏处:如果没有实际的数据进入,那么CPU就是一直空转,占用CPU的使用
现如今服务器的缺点,不能处理多个客户端的连接,因为accept()完成后,会进入read()函数,这个时候第二个客户端连接进来后只能进入accpe()的队列中,但是服务器还是在read()函数中,没有进入到accept()那个函数,那么就不能处理
另一个客户端的三次握手
。多进程服务器模型,原先的进程只负责accept(),至于读取数据则是fork()出一个子进程来处理,新来一个客户端的连接就fork()一次子进程——
fork()之后
文件描述符表
也会被复制,因为整个PCB都会被复制,PCB中包含文件描述符表。(fork()之后file结构体并没有复制过来,只是新的FD对应的新的指针指向了同一个file结构体
)
子进程中的client_fd是从父进程中拷贝过来的,使用这个复制过来的套接字client_fd并读取数据,并且和
父进程中的client_fd
指向的是同一个套接字socket。
- 将fork()放到
accept()函数
的下面,然后将之前的读取数据
都放在子进程中,父进程直接continue,进行下一次accept()即可。
- 但是上述代码会产生
僵尸进程
,我们没有去回收子进程
。 - 之前回收子进程的方法有wait()和waitpid(),但是现在父进程已经阻塞在accept()了(每接收完一个请求就回到accept()那里),不能再让它阻塞在wait()那里
接收子进程的退出
了。
所以除了
阻塞
和轮询
的方式,我们只能使用【信号
和waitpid()
】这种方式了——
这样就不会出现僵尸进程了
为什么使用SIGCHLD
这个信号呢?因为当子进程退出的时候会发给父进程信号SIGCHLD
,所以我们需要捕捉
的就是这个信号。
- 100个子进程同时退出,就会给父进程发100个信号
-
文件描述符表
通过fork()直接复制过来,是一个浅拷贝
,并没有生成新的file结构体
。
因为fork()后不论是
listen_fd
还是client_fd
都会被复制,但是listen_fd
并没有再被使用,所以要在一开始就关闭掉listen_fd
——
- 当然了,因为父进程中没有再用到client_fd,所以在fork()后,也要手动关闭client_fd,因为只有这样才不会造成内存泄漏,只靠子进程那里关闭只会让引用计数-1,并不会真的关闭,除非父进程也退出——
5.1 服务器不光读也可以写消息给客户端
- 服务器的代码(将接收到的字符串变成大写)——
- 客户端的代码——
多进程模型的好处:可以不断地创建子进程来处理连接请求,可以通过
ulimit -a
来查看可以创建多少个进程数量
- 也可以去系统文件改写这个
数值
虽然多进程都是fork出来的,但是比如
buf
这种缓存区中接收到的内容,因为读时共享,写时复制
的原则,buf区中接收到的数据一定是不一样,那么系统会新分配一个内存页
的。