父进程和子进程

进程是程序执行的最小单位,一个进程有完整的地址空间、程序计数器等,如果想创建一个新的进程,使用函数 fork 就可以

pid_t fork(void)
返回:在子进程中为0,在父进程中为子进程ID,若出错则为-1

fork函数实现的时候,实际上会把当前父进程的所有相关值都克隆一份,包括地址空间、打开的文件描述符、程序计数器等,连执行代码也会拷贝一份,新派生的进程的表现行为和父进程近乎一样。

为区别两个不同的进程,实现者可通过改变fork函数的栈空间值来判断,对应的程序中就是返回值的不同。


if(fork() == 0){
do_child_process(); //子进程执行代码
}else{
do_parent_process(); //父进程执行代码
}

当一个子进程退出时,系统内核还会保留该进程的若干信息,如退出状态,这样的进程如果不回收,就变成了僵尸进程。在Linux中,这样的“僵尸”进程被挂到进程号为1的init进程中,所以,由父进程派生出来的子进程,也必须由父进程负责回收,否则子进程就变成僵尸进程。僵尸进程会占用不必要的内存空间,如果量多到一定数量级,就会耗尽系统资源。

有两种方式可以在子进程退出后回收资源,分别是调用wait和waitpid函数

pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);

函数 wait 和 waitpid 都可以返回两个值,一个是函数返回值,表示已终止子进程的进程 ID 号,另一个则是通过 statloc 指针返回子进程终止的实际状态。这个状态可能的值为正常终止、被信号杀死、作业控制停止等。

如果没有已终止的子进程,而是有一个或多个子进程在正常运行,那么 wait 将阻塞,直到第一个子进程终止。

waitpid 可以认为是 wait 函数的升级版,它的参数更多,提供的控制权也更多。pid 参数允许我们指定任意想等待终止的进程 ID,值 -1 表示等待第一个终止的子进程。options 参数给了我们更多的控制选项。

处理子进程退出的方式一般是注册一个信号处理函数,捕捉信号 SIGCHILD 信号,然后再在信号处理函数里调用 waitpid 函数来完成子进程资源的回收。SIGCHLD 是子进程退出或者中断时由内核向父进程发出的信号,默认这个信号是忽略的。所以,如果想在子进程退出时能回收它,需要像下面一样,注册一个 SIGCHOLD 函数。

signal(SIGCHLD, sigchld_handler);  

阻塞I/O的进程模型

为说明使用阻塞 I/O 和进程模型,假设有两个客户端,服务器初始监听在套接字 lisnted_fd 上。当第一个客户端发起连接请求,连接建立后产生出连接套接字,此时,父进程派生出一个子进程,在子进程中,使用连接套接字和客户端通信,因此子进程不需要关心监听套接字,只需要关心连接套接字;父进程则相反,将客户服务交给子进程来处理,因此父进程不需要关心连接套接字,只需要关心监听套接字。

这张图描述了从连接请求到连接建立,父进程派生子进程为客户服务。

假设父进程之后又接收了新的连接请求,从 accept 调用返回新的已连接套接字,父进程又派生出另一个子进程,这个子进程用第二个已连接套接字为客户端服务。

这张图同样描述了这个过程。

现在,服务器端的父进程继续监听在套接字上,等待新的客户连接到来;两个子进程分别使用两个不同的连接套接字为两个客户服务。

代码实现

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h> #define SERV_PORT 43211
#define LISTENQ 1024
#define MAX_LINE 4096 int tcp_server_listen(int port)
{
int listen_fd;
listen_fd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); int on = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); int rt1 = bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if(rt1 < 0)
{
perror("bind failed");
return -1;
} int rt2 = listen(listen_fd, LISTENQ);
if(rt2 < 0)
{
perror("listen failed");
return -1;
} signal(SIGPIPE, SIG_IGN); return listen_fd;
}
char rot13_char(char c) {
if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
return c + 13;
else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
return c - 13;
else
return c;
}
void sigchld_handler(int sig)
{
while(waitpid(-1, 0, WNOHANG) > 0);
return;
}
void child_run(int fd)
{
char outbuf[MAX_LINE + 1];
size_t outbuf_used = 0;
ssize_t result; while(1)
{
char ch;
result = recv(fd, &ch, 1, 0);
if(result == 0)
{
break;
}
else if(result == -1)
{
perror("read failed");
return -1;
} if(outbuf_used < sizeof(outbuf))
{
outbuf[outbuf_used++] = rot13_char(ch);
}
if(ch == '\n')
{
send(fd, outbuf, outbuf_used, 0);
outbuf_used = 0;
continue;
}
}
} int main(int argc, char *argv[])
{
int listener_fd = tcp_server_listen(SERV_PORT);
signal(SIGCHLD, sigchld_handler);
while(1)
{
struct sockaddr_storage ss;
socklen_t slen = sizeof(ss);
int fd = accept(listener_fd, (struct sockaddr *)&ss, &slen);
if(fd < 0)
{
perror("accept failed");
return -1;
}
if(fork() == 0)
{
close(listener_fd);
child_run(fd);
exit(0);
}
else
{
close(fd);
}
}
return 0;
}

网络编程:阻塞I/O和进程模型的更多相关文章

  1. 《Unix 网络编程》13:守护进程和 inetd 超级服务器

    守护进程和 inetd 超级服务器 ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ...

  2. UNIX网络编程读书笔记:I/O模型(阻塞、非阻塞、I/O复用、信号驱动、异步)

    I/O模型 UNIX下可用的5种I/O模型: (1)阻塞I/O (2)非阻塞I/O (3)I/O复用(select和poll) (4)信号驱动I/O(SIGIO) (5)异步I/O 对于一个套接口上的 ...

  3. 网络编程中select模型和poll模型学习(linux)

    一.概述 并发的网络编程中不管是阻塞式IO还是非阻塞式IO,都不能很好的解决同时处理多个socket的问题.操作系统提供了复用IO模型:select和poll,帮助我们解决了这个问题.这两个函数都能够 ...

  4. c++ 网络编程(九)LINUX/windows-IOCP模型 多线程超详细教程及多线程实现服务端

    原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/9661012.html 先讲Linux下(windows下在后面可以直接跳到后面看): 一.线程 ...

  5. 【网络编程基础】Linux下进程通信方式(共享内存,管道,消息队列,Socket)

    在网络课程中,有讲到Socket编程,对于tcp讲解的环节,为了加深理解,自己写了Linux下进程Socket通信,在学习的过程中,又接触到了其它的几种方式.记录一下. 管道通信(匿名,有名) 管道通 ...

  6. python网络编程中互斥锁与进程之间的通信

    一.互斥锁 进程之间数据隔离,但是共享一套文件系统,因而可以通过文件来实现进程直接的通信,但问题是必须自己加锁处理. 注意:加锁的目的是为了保证多个进程修改同一块数据时,同一时间只能有一个修改,即串行 ...

  7. PHP7 网络编程(三)孤儿进程与僵尸进程

    基本概念 我们知道在unix/linux中,正常情况下,子进程是通过父进程创建的,子进程在创建新的进程.子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束. 当一个 ...

  8. Windows网络编程系列教程之四:Select模型

    讲一下套接字模式和套接字I/O模型的区别.先说明一下,只针对Winsock,如果你要骨头里挑鸡蛋把UNIX下的套接字概念来往这里套,那就不关我的事. 套接字模式:阻塞套接字和非阻塞套接字.或者叫同步套 ...

  9. [转载]Windows网络编程系列教程之四:Select模型

    原文:http://www.51see.com/asp/bbs/public/bp_show.asp?t_id=200308131152297103 讲一下套接字模式和套接字I/O模型的区别.先说明一 ...

  10. Linux网络编程学习(一) ----- 概论和Linux模型(第一章第二章)

    1.什么是计算机网络,通信方式是什么? 计算机网络就是通过通信线路相互连接的计算机的集合,主要通过双绞线.同轴电缆.电话线或者光缆等有形传输介质通信,还有就是通过激光.微波.卫星等实现无线通信 2.W ...

随机推荐

  1. 几乎全平台的C语言JSON解析工具cJSON[转载]

    最近在做一个外设管理平台,用PYTHON写了一个连接管理,兼容串口和套接字的连接,然后抽象为设备统一管理.使用套接字时JSON是一种很好的数据封装类型,假设我需要远程操控一个设备,发送一个JSON的数 ...

  2. SpringBoot - [00] 注解大全

    原文链接:https://mp.weixin.qq.com/s/DgNhohtJyEq4vMGEzqrP8A @SpringBootApplication 这个注解用于标识一个SpringBoot应用 ...

  3. vue - [02] 安装部署

    Vue.js 是一个流行的前端JavaScript框架,用于构建用户界面. 001 || 通过CND快速开始 只需要在HTML文件中引入VUE的CDN链接即可 (1)创建HTML文件 <!DOC ...

  4. 大数据之路Week08_day03 (Hive的动态分区和分桶)

    一.动态分区 先来说说我对动态分区的理解与一些感受吧. 由于我们通过hive去查询数据的时候,实际还是查询HDFS上的数据,一旦一个目录下有很多文件呢?而我们去查找的数据也没有那么多,全盘扫描就会浪费 ...

  5. 使用nodejs安装并使用vue操作步骤

    1.下载安装nodejs 官网下载nodejs并安装,我这边选择Windows的20版本 下载地址:https://nodejs.org/en/download/prebuilt-installer ...

  6. 终于写完轮子一部分:tcp代理 了,记录一下

    24年终自己立了flag: 25年做些轮子玩(用于浪费生命,赚不了钱) 所以25年就准备用c#写一个网络代理NZOrz(nginx知道吧,就那玩意儿干的事),包含 udp/tcp/http1 2 3, ...

  7. linux shell用expect实现在scp时自动输入密码

    文章目录 linux shell用expect自动输入密码 按行读取文件 expect 其他 linux shell用expect自动输入密码 最近有东西需要部署到很多服务器上去,一个服务器一个服务器 ...

  8. 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!

    前言 在人工智能技术日新月异的今天,DeepSeek-R1模型以其卓越的性能和广泛的应用场景,成为了众多用户心中的明星模型.它不仅具备强大的日常写作.翻译.问答等基础功能,更引入了独特的深度思考模式, ...

  9. windows本地认证

    windows本地认证 本地认证概述 本地认证最简单的例子就是我们的电脑上存储着自己的账号密码,无论电脑是否联网,只要能开机,就可以输入账号密码登录到电脑中,工作组就是采用本地认证. 那认证流程是什么 ...

  10. 程序员必看 Linux 常用命令(重要)

    文件操作命令 find find 用于在指定目录下查找文件或子目录,如果不指定查找目录,则在当前目录下查找 命令格式:find path -option [-print] [ -exec/-ok co ...