当前位置:网站首页>UNP前六章 回射服务模型 解析
UNP前六章 回射服务模型 解析
2022-07-28 15:28:00 【saddlesad】
本文试对UNP一书中截止到第六章的回射服务模型进行剖析,重点在于讲解代码为什么怎么做。下面的代码分别对应书中的5-12 tcpserv04.c,6-13 strcliselect02.c。
服务器代码:
#include <unp.h>
void sig_chld(int signo) {
pid_t pid;
int stat;
while ((pid = waitpid(-1, &stat, WNOHANG)) > 0) {
printf("child %d terminated\n", pid);
}
return;
}
void str_echo(int sockfd)
{
ssize_t n;
char buf[MAXLINE];
again:
while ( (n = read(sockfd, buf, MAXLINE)) > 0)
Writen(sockfd, buf, n);
if (n < 0 && errno == EINTR)
goto again;
else if (n < 0)
err_sys("str_echo: read error");
}
int main(int argc, char **argv)
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(listenfd, (SA *)&servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
signal(SIGCHLD, sig_chld);
for (;;)
{
clilen = sizeof(cliaddr);
if ((connfd = Accept(listenfd, (SA *)&cliaddr, &clilen)) < 0)
{
if (errno == EINTR)
continue;
else
err_sys("accept error");
}
// 子进程退出会导致信号SIGCHLD到来,它会引起sig_chld的异步执行,当从sig_chld()返回时,accept系统调用被打断
// bsd4.4会自动重启accept系统调用,否则就会触发一个
if ((childpid = Fork()) == 0)
{
Close(listenfd);
str_echo(connfd);
exit(0);
}
Close(connfd);
}
}
客户端代码:
#include <unp.h>
void str_cli(FILE *fp, int sockfd)
{
int maxfdp1, stdineof = 0;
fd_set rset;
char buf[MAXLINE];
int n;
FD_ZERO(&rset);
for (;;)
{
if (!stdineof)
FD_SET(fileno(fp), &rset);
FD_SET(sockfd, &rset);
maxfdp1 = max(fileno(fp), sockfd) + 1;
Select(maxfdp1, &rset, NULL, NULL, NULL);
if (FD_ISSET(sockfd, &rset))
{
if ( (n = Read(sockfd, buf, MAXLINE)) == 0)
{
if (stdineof == 1)
return;
else
err_quit("str_cli: server terminated prematurely");
}
Write(fileno(stdout), buf, n);
}
if (FD_ISSET(fileno(fp), &rset))
{
if ((n = read(fileno(fp), buf, MAXLINE)) == 0) {
stdineof = 1;
Shutdown(sockfd, SHUT_WR);
FD_CLR(fileno(fp), &rset);
continue;
}
Writen(sockfd, buf, n);
}
}
}
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
if (argc != 2)
err_quit("usage: tcpcli <IPaddress>");
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
Connect(sockfd, &servaddr, sizeof(servaddr));
str_cli(stdin, sockfd);
exit(0);
}
运行逻辑
服务器
- 创建监听套接字,监听本主机任意地址上的SERV_PORT(9877)端口,设置对SIGCHLD的handler。
- 调用阻塞式accept,等待已完成连接的到来(TCP连接的三次握手是在内核中完成的)。
- 服务器得到一个TCP连接以后,就调用fork()派生子进程,同时关闭子进程中的监听套接字,然后调用str_echo()函数。
- 在str_echo()函数中,循环阻塞式read并回射给客户端,直到收到FIN结束子进程。
客户端
- 创建主动套接字,向指定IP地址的SERV_PORT端口发起connect连接。
- 运行str_cli()函数,同时设置fd_set初值为输入文件描述符
fileno(fp)和套接字sockfd。调用阻塞式select监听二者。 - sockfd对应TCP连接,当服务器端传回数据时其变为可读;fileno(fp)对应标准输入,当用户输入时变为可读。二者任何之一变为可读状态都会是select设置相应fd_set并返回。
- if判断是哪个fd触发的select返回,然后读取TCP连接数据或者读取用户输入并发送TCP数据。
要点
服务器
服务器必须捕获子进程的终止信号(第41行),并在其中循环调用非阻塞式waitpid(第6行)。如果不捕获子进程的终止信号,那么子进程的数据将一直保留在内存中占用系统资源(除非父进程退出)。另外因为可能有多个子进程同时结束,那么服务器进程将收到多个连续的SIGCHLD,而UNIX信号默认是不排队的,所以有可能只能触发一次sig_chld()调用,我们需要在这一次sig_chld()调用中将所有已经结束的子进程全部回收(未结束的子进程不需要考虑,因为它们的终止将会引起新的SIGCHLD信号和下一次sig_chld()调用)。
waitpid(-1, &stat, WNOHANG)将等待任意一个已终止的子进程,如果没有已终止的待回收子进程那么就立即返回0,这就实现了一次sig_chld()调用收集所有待回收子进程。**服务器需要处理满系统调用accept(第47行)和read(第19行)被中断的问题。**因为当阻塞于慢系统调用的一个进程捕获某个信号后,当信号处理函数返回时,此系统调用被中断并返回一个EINTR错误(尽管有些系统的内核可以自动重启这个被中断的系统调用)。所以我们要在识别出errno为EINTR时重启此系统调用。
**服务器在fork()后需要分别关闭父进程的已连接套接字(第60行)和子进程的监听套接字(第56行)。**因为fork()后文件父进程的文件描述符会被复制到子进程中,而且我们要知道每个文件或套接字都有一个引用计数,只有计数到达0时相关资源才会被清理。如果我们不及时关闭本进程不用的套接字,那么另一个进程close掉这个套接字后清理工作就不能及时进行(要等到本进程退出时自动关闭所有套接字时才会清理)。
客户端
**客户端必须使用select轮询sockfd和fileno(fp)(第18行)。**如果采用简单的线性模型(gets->write->read),即阻塞于用户输入,然后将用户输入发送给TCP接收方,等待回射数据后输出到显示器。那么当服务器异常终止时,此时服务器发送FIN并被客户所接受,那么客户接下来对sockfd的读写都不被允许,但问题在于客户进程往往被阻塞在等待用户输入上,只有在用户输入后客户write发送数据后(服务器将返回RST,但时间原因客户进程将在收到RST前执行read()),再尝试调用read()函数时才会得到EOF错误,这是一种用户体验并不好的延迟错误告知。 而是用select轮询后,在用户输入结束前,客户将立即得到FIN的通知,并在第26行退出。
**客户端必须使用stdineof标志来标记用户输入是否结束(第13、23、33行),并只有在从sockfd读取数据结束时才退出进程(第21行)。**首先,用户输入结束并不能立即退出程序,因为在大批量输入时(尤见于从文件输入时),客户端发送数据后还要等待服务器回射然后再输出到显示器上,如果在回射前就退出程序,那么就会导致回射数据丢失。讽刺的是,在非select的线性模型中并不会发生这个问题,在那里,客户进程一定等到write->read后再退出。
**客户端应使用shutdown(sockfd)来代替close(sockfd)。**主要的原因是close只有在将sockfd的引用计数减到0时才开始四次挥手,而shutdown立即发送FIN给服务器。另一个原因是shutdown可以只关闭写端或读端,而调用close后将关闭读写两端即无法对sockfd进行读写操作,我们需要的只是半关闭写这一半,故用shutdown。
边栏推荐
- PHP gets the applet code, and the applet jumps with parameters
- KubeEdge发布云原生边缘计算威胁模型及安全防护技术白皮书
- PHP image upload
- CoDeSys realizes bubble sorting
- I'll show you a little chat! Summary of single merchant function modules
- 使用js直传oss阿里云存储文件,解决大文件上传服务器限制
- 关于web对接针式打印机问题,Lodop使用
- el-input限制只能输入规定的数
- Vm501 development kit development version single vibrating wire sensor acquisition module geotechnical engineering monitoring
- R语言使用fs包的file_delete函数删除指定文件夹下的指定文件、举一反三、dir_delete函数、link_delete函数可以用来删除文件夹和超链接
猜你喜欢

解决电脑恶意广告弹窗的思路

IT远程运维是什么意思?远程运维软件哪个好?

Deeply understand the fusing configuration of istio traffic management

Redis series 4: sentinel (sentinel mode) with high availability

Kubeedge releases white paper on cloud native edge computing threat model and security protection technology

Installation points and precautions of split angle probe

LabVIEW LINX Toolkit控制Arduino设备(拓展篇—1)

Stm32cube infrared remote control: input capture

Dynamic programming -- digital statistics DP

What does it remote operation and maintenance mean? Which is the best remote operation and maintenance software?
随机推荐
自动打包压缩备份下载及删除 bat脚本命令
Why do most people who learn programming go to Shenzhen and Beijing?
在vs code上配置Hypermesh二次开发环境
PHP gets the applet code, and the applet jumps with parameters
解决uniapp等富文本图片宽度溢出
Detectron2 installation and testing
QT packaging
排序3-选择排序与归并排序(递归实现+非递归实现)
I'll show you a little chat! Summary of single merchant function modules
Rosen's QT journey 102 listmodel
mysql查询 limit 1000,10 和limit 10 速度一样快吗?如果我要分页,我该怎么办?
R语言ggplot2可视化绘制线图(line plot)、使用gghighlight包突出高亮线图中满足组合判断条件的线图(satisfies both condition A and B)
PHP image upload
Huada chip hc32f4a0 realizes RS485 communication DMA transceiver
I can only sell the company after the capital has been "cut off" for two years
C language exception handling mechanism: jump function jump function setjmp/sigsetjmp and longjmp/siglongjmp
curl无输出返回空白或者null问题解决
c语言编程当中两个!!的作用
The epidemic dividend disappeared, and the "home fitness" foam dissipated
Pop up layer prompt in the background