《TCP/IP网络编程》阅读笔记--并发多进程服务端的使用

目录

1--并发服务器端

2--进程

2-1--进程的相关概念

2-2--fork()创建进程

2-3--僵尸进程

2-4--wait()和waitpid()销毁僵尸进程

3--信号处理

3-1--signal()函数

3-2--sigaction()函数

3--3--利用信号处理技术消灭僵尸进程

4--基于多任务的并发服务器

5--分割 TCP 的 I/O 程序


1--并发服务器端

并发服务器端主要有以下三类:

        ① 多进程服务器:通过创建多个进程提供服务;

        ② 多路复用服务器:通过捆绑并统一管理I/O对象提供服务;

        ③ 多线程服务器:通过生成与客户端等量的线程提供服务;

2--进程

2-1--进程的相关概念

进程的相关概念:

        ① 进程的定义如下:占用内存空间的正在运行的程序;

        ② 从操作系统的角度看,进程是程序流的基本单位,若创建多个进程,则操作系统将同时运行;

        ③ 对于 CPU 而言,核的个数与可同时运行的进程数相同;若进程数超过核数,进程将分时使用 CPU 资源;

        ④ 无论进程是如何创建的,所有进程都会从操作系统中分配到进程 ID,其值为大于 2 的整数;

2-2--fork()创建进程

#include <unistd.h>
pid_t fork(void);// 成功时返回进程 ID,失败时返回 -1

        fork() 函数会复制父进程的进程副本给创建的子进程,两个进程都将执行 fork() 函数调用后的语句;具体执行的内容可根据 fork() 函数的返回值进行区分,对于父进程 fork() 函数返回子进程的进程 ID,对于子进程 fork() 函数返回 0;

// gcc fork.c -o fork
// ./fork
#include <stdio.h>
#include <unistd.h>int gval = 10;
int main(int argc, char *argv[]){__pid_t pid;int lval = 20;gval++, lval += 5;pid = fork();if(pid == 0){ // 对于子进程,fork返回0,因此执行以下内容gval += 2, lval += 2;}else{ // 对于父进程,执行以下内容gval -= 2, lval -= 2;}if(pid == 0){// 对于子进程,复制父进程的进程副本,则最初 gval = 11, lval = 25;// 执行 += 2 后,gval = 13, lval = 27;printf("Child Proc: [%d, %d] \n", gval, lval);}else{// 对于父进程,执行 -= 2后,gval = 9, lval = 23;printf("Parent Proc: [%d %d] \n", gval, lval);}return 0;
}

2-3--僵尸进程

        一般进程完成工作后都应被立即销毁,但部分进行由于各种原因导致不能及时销毁,就成为了僵尸进程,其会占用系统中的重要资源;

        终止僵尸进程的两种方式:① 传递参数给 exit 函数并调用 exit 函数;② main 函数中执行 return 语句并返回值;

        向 exit 函数传递的参数值和 main 函数 return 语句的返回值都会传递给操作系统,而操作系统不会立即销毁子进程,直到把这些返回值传递给父进程;这种不会被立即销毁的子进程就是僵尸进程

        此外,操作系统不会主动将返回值传递给父进程;只有父进程主动发起请求时,操作系统才会将子进程的返回值传递给父进程;因此,如果父进程未主动要求获得子进程的结束状态值,操作系统就不会销毁子进程,子进程就一直处于僵尸进程状态;

// gcc zombie.c -o zombie
// ./zombie
#include <stdio.h>
#include <unistd.h>int main(int argc, char *argv[]){__pid_t pid = fork();if(pid == 0){puts("Hi, I am a child process");}else{// 父进程终止时,子进程也会被同时销毁// 本案例通过延缓父进程的终止时间,来让子进程进入僵尸进程状态printf("Child Process ID: %d \n", pid);sleep(30);}if(pid == 0){puts("End child process");}else{puts("End parent process");}return 0;
}

        通过 ps au 可以观测到在父进程睡眠的时间里,子进程成为了僵尸进程(Z+状态);

2-4--wait()和waitpid()销毁僵尸进程

        为了销毁僵尸子进程,父进程必须主动请求获取子进程的返回值;

        父进程调用 wait() 函数 和 waitpid() 函数可以主动获取子进程的返回值;

#include <sys/wait.h>pid_t wait(int* statloc);
// 成功时返回终止的子进程 ID, 失败时返回 -1;
// 子进程的返回值会保存到 statloc 所指的内存空间// WIFEXITED() 子进程正常终止时返回 true
// WEXITSTATUS() 返回子进程的返回值

        父进程调用 wait() 函数时,如果没有已终止的子进程,则父进程的程序将会阻塞,直至有子进程终止来返回值;

// gcc wait.c -o wait
// ./wait
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>int main(int argc, char* argv[]){int status;__pid_t pid = fork();if(pid == 0){return 3; // 第一个子进程返回3}else{printf("Child PID: %d \n", pid); // 第一个子进程的 IDpid = fork(); // 创建第二个子进程if(pid == 0){exit(7); // 第二个子进程返回7}else{printf("Child PID : %d \n", pid); // 第二个子进程的 IDwait(&status); // 主动请求获取子进程的返回值if(WIFEXITED(status)){printf("Chile send one: %d \n", WEXITSTATUS(status));}wait(&status); // 主动请求获取子进程的返回值if(WIFEXITED(status)){printf("Child send two: %d \n", WEXITSTATUS(status));}sleep(30); // 这时候父进程选择睡眠,子进程也不会成为僵尸进程}}return 0;
}

        wait() 函数会引起程序阻塞,而 waitpid() 函数不会引起阻塞;

#include <sys/wait.h>pid_t waitpid(pid_t pid, int* statloc, int options);
// 成功时返回终止的子进程的ID(或0),失败时返回-1
// pid 表示等待终止的目标子进程的 ID,传递 -1 时与 wait() 相同,即可以等待任意子进程终止
// statloc 存放子进程返回结果的地址空间
// options 设置为 WNOHANG 时,即使没有终止的子进程,父进程也不会进入阻塞状态,而是返回 0 并结束函数
// gcc waitpid.c -o waitpid
// ./waitpid
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>int main(int argc, char *argv[]){int status;__pid_t pid = fork();if(pid == 0){sleep(15);return 24;}else{// 没有终止的子进程时,返回0,则一直循环调用waitpid()// 直到有终止的子进程来跳出循环while(!waitpid(-1, &status, WNOHANG)){sleep(3);puts("sleep 3sec.");}if(WIFEXITED(status)){printf("Child send %d \n", WEXITSTATUS(status));}return 0;}
}

3--信号处理

        上述父进程调用 wait() 函数会阻塞,而调用 waitpid() 函数也必须不断调用(因为不知道子进程何时终止),这也同样会影响父进程的工作效率;

        通过信号处理机制,可以解决上述问题;信号表示在特定事件发生时由操作系统向进程发送(通知)的消息

        因此可以通过注册信号,当子进程终止时,让操作系统将子进程终止的消息发送给父进程,这时候父进程才请求获取子进程的返回值;

3-1--signal()函数

#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);// 第一个参数 signo 表示特殊情况信息
// 第二个参数表示特殊情况发生后,要调用的函数的地址值(指针)// 常见特殊情况
// 1. SIGALRM 表示已到调用 alarm 函数注册的时间
// 2. SIGINT 表示遇到了 CTRL+C 的情况
// 3. SIGCHLD 表示遇到了子进程终止的情况#include <unistd.h>
unsigned int alarm(unsigned int seconds);
// 返回 0 或以秒为单位的距离 SIGALRM 信号发生的所剩时间(即还剩下多长时间就会发生 SIGALRM 信号时间)
// 经过 seconds 秒后会发生 SIGALRM 信号事件

        发生信号事件时,将会唤醒由于调用 sleep 函数而进入阻塞状态的进程;即:即使还没到 sleep 函数规定的事件也会被强制唤醒,而进程一旦唤醒后就不会再进入睡眠状态;

// gcc signal.c -o signal
// ./signal#include <stdio.h>
#include <unistd.h>
#include <signal.h>void timeout(int sig){if(sig == SIGALRM){puts("Time out!");}alarm(2);
}void keycontrol(int sig){if(sig == SIGINT){puts("CTRL+C pressed");}
}int main(int argc, char *argv[]){int i;signal(SIGALRM, timeout);signal(SIGINT, keycontrol);alarm(2);for(i = 0; i < 3; i++){puts("wait...");sleep(100); // 不会真的睡眠 100s,因为alarm函数会产生SIGALRM信号事件,从而唤醒进程}return 0;
}

3-2--sigaction()函数

        sigaction() 函数的功能类似于 signal() 函数,但 sigaction() 更稳定;因为 signal() 函数在不同操作系统中可能存在区别,但 sigaction() 在不同 UNIX 系统中完全相同;

#include <signal.h>int sigaction(int signo, const struct sigaction* act, struct sigaction* oldact);
// 成功时返回0,失败时返回 -1
// signo 用于传递信号信息
// act 对应于 signo 的信号处理函数
// oldact 获取之前注册的信号处理函数的指针,不用时传递0struct sigaction{void (*sa_handler)(int); // 信号处理函数的指针sigset_t sa_mask; // 初始化为0int sa_flags; // 初始化为0
}
// gcc sigaction.c -o sigaction
// ./sigaction#include <stdio.h>
#include <unistd.h>
#include <signal.h>void timeout(int sig){if(sig == SIGALRM){puts("Time out!");}alarm(2);
}int main(int argc, char* argv[]){int i;struct sigaction act;act.sa_handler = timeout;sigemptyset(&act.sa_mask); // 调用sigemptyset()将sa_mask的所有位初始化为0act.sa_flags = 0; // sa_flags也初始化为0sigaction(SIGALRM, &act, 0);alarm(2);for(int i = 0; i < 3; i++){puts("wait...");sleep(100);}return 0;
}

3--3--利用信号处理技术消灭僵尸进程

// gcc remove_zombie.c -o remove_zombie
// ./remove_zombie#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>void read_childproc(int sig){int status;pid_t id = waitpid(-1, &status, WNOHANG); // 等待任意子线程结束if(WIFEXITED(status)){ // 判断子线程是否正常终止printf("Remove proc id: %d \n", id);printf("Child send: %d \n", WEXITSTATUS(status)); // 打印子线程的返回值}
}int main(int argc, char *argv[]){pid_t pid;struct sigaction act;act.sa_handler = read_childproc; // 设置信号处理函数sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGCHLD, &act, 0); // 调用sigaction(),当遇到子线程结束的信号时,调用信号处理函数pid = fork();if(pid == 0){ // 子线程执行区域puts("Hi! I'm child process");sleep(10);return 12;}else{printf("Child proc id: %d \n", pid);pid = fork();if(pid == 0){ // 另一个子线程执行区域puts("Hi! I'm child process");sleep(10);return 24;}else{int i;printf("Child proc id: %d \n", pid);for(int i = 0; i < 5; i++){puts("wait...");sleep(5);}}}return 0;
}

4--基于多任务的并发服务器

        每当有客户端请求服务时,回声服务器端都创建子进程以提供服务;

        使用 fork() 创建进程时,子进程会复制父进程拥有的所有资源,因此无需额外传递文件描述符;

// gcc echo_mpserv.c -o echo_mpserv
// ./echo_mpserv 9190#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/socket.h>#define BUF_SIZE 30void error_handling(char *message){fputs(message, stderr);fputc('\n', stderr);exit(1);
}void read_childproc(int sig){__pid_t pid;int status;pid = waitpid(-1, &status, WNOHANG);printf("remove proc id: %d \n", pid);
}int main(int argc, char* argv[]){int serv_sock, clnt_sock;struct sockaddr_in serv_adr, clnt_adr;__pid_t pid;struct sigaction act; // 信号socklen_t adr_sz;int str_len, state;char buf[BUF_SIZE];if(argc != 2){printf("Usage : %s <port>\n", argv[0]);exit(1);}// 防止僵尸进程act.sa_handler = read_childproc; //设置信号处理函数sigemptyset(&act.sa_mask);act.sa_flags = 0;state = sigaction(SIGCHLD, &act, 0);serv_sock = socket(PF_INET, SOCK_STREAM, 0); // 创建 tcp socketmemset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = htons(atoi(argv[1]));if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr)) == -1){error_handling("bind() error"); } if(listen(serv_sock, 5) == -1){error_handling("listen() error");}while(1){adr_sz = sizeof(clnt_adr);clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);if(clnt_sock == -1){continue;}else{puts("new client connected...");}// 每当有客户端请求服务时,clnt_sock 不为 -1// 因此会执行 fork() 函数创建子进程,并由子进程向客户端提供服务pid = fork(); if(pid == -1){close(clnt_sock);continue;}if(pid == 0){ // 子进程运行区域close(serv_sock); // 将复制过来的父进程中的服务器文件描述符销毁while((str_len = read(clnt_sock, buf, BUF_SIZE)) != 0){write(clnt_sock, buf, str_len);}close(clnt_sock);puts("client disconnected...");return 0;}else{ // 父进程运行区域// 因为客户端的文件描述符已经复制到子进程中// 由子进程处理客户端的内容,因此父进程需要销毁客户端的文件描述符close(clnt_sock); }}close(serv_sock);return 0;
}

5--分割 TCP 的 I/O 程序

        客户端通过 fork() 创建子进程,将 I/O 分割;客户端的父进程负责接收数据,额外创建的子进程负责发送数据;

        分割后,不同进程分别负责输入和输出,因此客户端是否从服务器端接收完数据都可以进行传输;

// gcc echo_mpclient.c -o echo_mpclient
// ./echo_mpclient 127.0.0.1 9190#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>#define BUF_SIZE 30void error_handling(char *message){fputs(message, stderr);fputc('\n', stderr);exit(1);
}void read_routine(int sock, char *buf){while(1){int str_len = read(sock, buf, BUF_SIZE);if(str_len == 0) return;buf[str_len] = 0;printf("Message from server: %s", buf);}
}void write_routine(int sock, char* buf){while(1){fgets(buf, BUF_SIZE, stdin);if(!strcmp(buf, "q\n") || !strcmp(buf, "Q\n")){shutdown(sock, SHUT_WR); // 调用 shutdown 函数向服务器端传递 EOFreturn;}write(sock, buf, strlen(buf));}
}int main(int argc, char *argv[]){int sock;pid_t pid;char buf[BUF_SIZE];struct sockaddr_in serv_adr;if(argc != 3){printf("Usage : %s <IP> <port>\n", argv[0]);exit(1);}sock = socket(PF_INET, SOCK_STREAM, 0); // 创建 tcp socketmemset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = inet_addr(argv[1]);serv_adr.sin_port = htons(atoi(argv[2]));if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1){error_handling("connect() error!");}pid = fork(); // 创建子进程实现 I/O 分离if(pid == 0){ // 子进程写数据到服务器端write_routine(sock, buf);}else{ // 父进程从服务器端中读取数据read_routine(sock, buf);}close(sock);return 0;
}   

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/129734.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

webrtc-m79-测试peerconnectionserver的webclient-p2p-demo

1 背景 webrtc的代码中有peerconnectionclient和peerconnectionserver的例子&#xff0c;但是没有对应的web端的例子&#xff0c;这里简单的写了一个测试例子&#xff0c;具体如下&#xff1a; 2 具体操作 2.1 操作流程 2.2 测试效果 使用webclient与peerconnectionclient的…

day34 Set

概述 Set也是集合Collection接口的子接口 Set也是集合Collection接口的子接口 特点&#xff1a;不保证元素有顺序&#xff0c;数组元素不可以重复 HashSet: 底层是基于HashMap的。元素是无序的。元素不可重复&#xff0c;去重机制是依据hashCode()和equals()方法 LinkedHas…

Ubuntu22.04 安装 MongoDB 7.0

稍微查了一些文章发现普遍比较过时。有的是使用旧版本的Ubuntu&#xff0c;或者安装的旧版本的MongoDB。英语可以的朋友可以移步Install MongoDB Community Edition on Ubuntu — MongoDB Manual&#xff0c;按照官方安装文档操作。伸手党或者英语略差的朋友可以按照本文一步步…

使用Mybatis实现基本的增删改查------数据输入

创建一个空的Maven项目,删去src,用作存储pom的父项目 pom中存放下列依赖: <dependencies><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.11</version></dependency><de…

【MongoDB】Ubuntu22.04 下安装 MongoDB | 用户权限认证 | skynet.db.mongo 模块使用

文章目录 Ubuntu 22.04 安装 MongoDB后台启动 MongoDBshell 连入 MongoDB 服务 MongoDB 用户权限认证创建 root 用户开启认证重启 MongoDB 服务创建其他用户查看用户信息验证用户权限删除用户 skynet.db.mongo 模块使用authensureIndexfind、findOneinsert、safe_insertdelete、…

在自定义数据集上实现OpenAI CLIP

在2021年1月&#xff0c;OpenAI宣布了两个新模型:DALL-E和CLIP&#xff0c;它们都是以某种方式连接文本和图像的多模态模型。CLIP全称是Contrastive Language–Image Pre-training&#xff0c;一种基于对比文本-图像对的预训练方法。为什么要介绍CLIP呢&#xff1f;因为现在大火…

【C#】关于Array.Copy 和 GC

关于Array.Copy 和 GC //一个简单的 数组copy 什么情况下会触发GC呢[ReliabilityContract(Consistency.MayCorruptInstance, Cer.MayFail)]public static void Copy(Array sourceArray,long sourceIndex,Array destinationArray,long destinationIndex,long length);当源和目…

浅析Open vSwitch数据结构:哈希表hmap/smap/shash

文章目录 概述hmaphmap数据结构初始化hmap插入节点扩展hmap空间resize函数 删除节点遍历所有节点辅助函数hmap_first辅助函数hmap_next smapsmap数据结构插入节点删除节点查找节点遍历所有节点 shashshash数据结构插入节点删除节点查找节点遍历所有节点 概述 在OVS软件中&…

【网络安全带你练爬虫-100练】第23练:文件内容的删除+写入

目录 0x00 前言&#xff1a; 0x02 解决&#xff1a; 0x00 前言&#xff1a; 本篇博文可能会有一点点的超级呆 0x02 解决&#xff1a; 你是不是也会想&#xff1a; 使用pyrhon将指定文件夹位置里面的1.txt中数据全部删除以后---->然后再将参数req_text的值写入到1.txt …

rsa加密解密java和C#互通

前言 因为第三方项目是java的案例&#xff0c;但是原来的项目使用的是java&#xff0c;故需要将java代码转化为C#代码&#xff0c;其中核心代码就是RSA加密以及加签和验签&#xff0c;其他的都是api接口请求难度不大。 遇到的问题 java和c#密钥格式不一致&#xff0c;java使…

LeetCode(力扣)491. 递增子序列Python

LeetCode491. 递增子序列 题目链接代码 题目链接 https://leetcode.cn/problems/non-decreasing-subsequences/ 代码 class Solution:def backtracking(self, nums, index, result, path):if len(path) > 1:result.append(path[:])uset set()for i in range(index, len…

分类预测 | Matlab实现基于LFDA-SVM局部费歇尔判别数据降维结合支持向量机的多输入分类预测

分类预测 | Matlab实现基于LFDA-SVM局部费歇尔判别数据降维结合支持向量机的多输入分类预测 目录 分类预测 | Matlab实现基于LFDA-SVM局部费歇尔判别数据降维结合支持向量机的多输入分类预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 基于局部费歇尔判别数据降维的L…

日常开发小汇总(5)元素跟随鼠标移动(在视口下移动)

<div class"mark"><h1>title</h1><div><p>title 鼠标移动 盒子整体移动</p><p>test</p><p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Modi, porro.</p></div></div>cons…

springboot MongoDB 主从 多数据源

上一篇&#xff0c;我写了关于用一个map管理mongodb多个数据源&#xff08;每个数据源&#xff0c;只有单例&#xff09;的内容。 springboot mongodb 配置多数据源 临到部署到阿里云的测试环境&#xff0c;发现还需要考虑一下主从的问题&#xff0c;阿里云买的数据库&#x…

入门力扣自学笔记279 C++ (题目编号:1123)

1123. 最深叶节点的最近公共祖先 题目&#xff1a; 给你一个有根节点 root 的二叉树&#xff0c;返回它 最深的叶节点的最近公共祖先 。 回想一下&#xff1a; 叶节点 是二叉树中没有子节点的节点树的根节点的 深度 为 0&#xff0c;如果某一节点的深度为 d&#xff0c;那它…

【Flutter】引入网络图片时,提示:Failed host lookup: ‘[图片host]‘

在使用 NetworkImage 组件加载外部图片时&#xff0c;提示 Failed host lookup: [图片host] 错误。 排查方向 1、清理缓存 解决方案&#xff1a; 尝试flutter clean清空缓存后重新安装依赖flutter pub get重新构建项目flutter create . 走完上述三个步骤后&#xff0c;再次…

MySql学习笔记11——DBA命令介绍

DBA命令 数据导入 要进入Mysql 创建数据库 create database database_name;使用数据库 use database_name;初始化数据库 source .sql文件地址&#xff0c;不能加双引号&#xff1b;数据导出 要在windows的dos环境下进行 导出数据库 mysqldump database_name > 存放…

Android 文字转语音播放实现

1&#xff0c;TextToSpeech类是android自带的&#xff0c;但是部分设备需要支持TTS需要增加语音库&#xff0c;我使用的是讯飞语音&#xff08;离线的哦&#xff09;。请自行下载并安装讯飞语音APK&#xff0c;然后到系统设置中设置TTS功能默认使用该选项。有自带TTS库的可以省…

《存储IO路径》专题:块设备层多队列blk-mq架构

我们想象一下&#xff0c;你是一个餐厅的厨师&#xff0c;你要准备很多不同的菜肴&#xff0c;而每种菜肴需要不同的食材和烹饪时间。如果每道菜都按照需要的顺序来准备&#xff0c;那么你的工作效率一定会非常低。为了提高效率&#xff0c;你会怎么做呢&#xff1f; 在linux架…

基于SSM的家政服务网站

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…