[UNP]:TCP客户服务器_迭代式
编写一个完整的 TCP 客户端/服务器程序:
- 客户从标准输入读入一行文本,并写给服务器
- 服务器从网络输入读入这行文本,并回射给客户
- 客户从网络输入读入这行回射文本,并显示在标准输出上
1. 函数准备
1. TCP 回射服务器程序
原型:
1 | |
2. TCP 回射客户端程序
原型:
1 | |
2. 代码实现
1. TCP客户端
1 | |
2. TCP服务端
1 | |
3. 问题分析
僵尸进程与处理
产生:
- 子进程终止时发送给父进程一个 SIGCHLD 信号,若父进程没有捕获改信号处理,则子进程变为僵尸进程。
POSIX 信号处理知识:
父进程通过调用
sigaction()函数捕获信号,捕获到信号有三种处理方式:提供一个函数,只要特定信号发生,就调用该函数。SIGKILL 和 SIGSTOP 不能被捕获
信号处理函数原型:
1
void handler(int signo);// 无返回值, 形参为 信号把信号的处置设置为 SIG_NGN 忽略信号。SIGKILL 和 SIGSTOP 不能忽略
把信号处置设置为 SIG_DEF 启动默认处置
POSIX 信号处理总结
- 一旦安装了信号处理函数,便一直有效
- 在一个信号处理函数运行期间,正被递交的函数是阻塞的
- 如果一个信号在被阻塞期间产生了一次或多次,那么该信号被解除阻塞后只递交一次,Unix 信号默认是不排队的
使用 waitpid() 函数代替 wait() 函数,因为
Unix信号是不排队的,当同时出现多个子进程的 SIGCHLD 信号时,wait()可能不能全部处理所有信号1
2
3
4
5
6
7
8
9
10#include "unp.h"
void sig_child(int signo) {
pid_t pid;
int stat;
while ((pid = waitpid(-1, &stat, WHOHANG)) > 0) {
printf("child %d terminated\n", pid);
}
}accept()中必须处理中断- 当 SIGCHLD 信号递交时,父进程阻塞于
accept()系统调用,内核会使accept()返回一个 EINTR 错误( 被中断的系统调用 ),所以必须在程序 accept() 中处理该错误,重新启动accept()系统调用 - 对于
accept()以及诸如read()、write()、select()和open()之类的函数来说,重启被中断的系统调用时合适的,但是 connect() 除外,重启connect()将返回错误,因为原来的套接字已经无效
- 当 SIGCHLD 信号递交时,父进程阻塞于
- 总结
网络编程时可能会遇到的三种情况:
- 当 fork 子进程时,必须捕获 SIGCHLD 信号;
- 当捕获信号时,必须处理被中断的系统调用;
- SIGCHLD 的信号处理函数必须正确编写,应使用 waitpid 函数以免留下僵尸进程
服务器进程终止
服务器进程终止,发送
FIN给客户端客户端阻塞在
fget()上不能立即响应该 FIN这就是引入
select()和poll()的原因之一,客户端不能单纯阻塞在某个特定套接字描述符上,而应该阻塞在任意输入套接字描述等待用户输入文本后,
str_cli()函数调用writen()把数据发送给服务器服务器接收到数据,响应
RST客户端此时阻塞在
readline()上,看不到这个 RST,并且由于第 1 步中的FIN,readline()立即返回 0 ,所以str_cli()第 9 行,打印 “str_cli:server terminated prematurely”客户端终止,关闭所有打开的描述符
SIGPIPE 信号
- 产生:
如上第 5 步,客户端内核收到RST,而客户端进程并未及时处理,假如此时进程继续向对端服务器发送数据时(调用write()), 函数客户端内核将向该进程发送SIGPIPE信号 - 处理
SIGPIPE信号默认行为是终止进程,因此进程必须捕获它以免不情愿的被终止 - 无论进程有没有捕获
SIGPIPE信号,write()返回EPIPE错误- 写一个接收了
FIN的套接字正确(CLOSE_WAIT状态); - 写一个接收了
RST的套接字EPIPE错误
- 写一个接收了
服务器主机崩溃
- 过程
- 服务器崩溃时,客户端不会收到任何通知
- 客户端调用
wtrten()时,客户端TCP持续重传数据,试图从服务器接收ACK - 重传过程结束还是没收到服务器
ACK,此时客户端阻塞在readline()上,返回错误 ETIMEDOUT
- 处理
- 对
readline()调用设置超时,提前得知服务器崩溃信息,不必等待重传机制完成 - 设置 SO_KEEPALIVE 心跳保活选项
- 对
服务器主机崩溃后重启
- 服务器崩溃重启后丢失之前的所有
TCP连接,因此服务器收到客户端的消息直接返回RST - 客户端收到
RST返回 ECONNRESET 错误
服务器主机关机
Unix系统关机时,init进程给所有进程发送 SIGTERM 信号- 内核等待(5-20 秒),留给程序小段时间来清除和终止,然后给所有仍在运行的进程发送 SIGKILL 杀死进程
- 若进程不捕获 SIGTERM ,服务器进程由 SIGKILL 终止,随后发生的步骤如上: <服务器进程终止>
总结
必须在客户端程序中使用 select 或 poll 函数,使服务器进程终止一经发生,立刻检测到
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!