【高性能服务器】多进程并发模型

 🔥博客主页: 我要成为C++领域大神
🎥系列专栏:【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】
❤️感谢大家点赞👍收藏⭐评论✍️

本博客致力于知识分享,与更多的人进行学习交流

对于常见的C/S模型,一个服务端通常需要服务多个客户端。如果使用单行的处理模型,当新的客户端请求服务端的服务时,就必须等待比它先到的客户端的请求全部完成。

因此引入多进程并发服务器模型。多进程并发服务器模型的简单流程图如下所示。父进程创建一个套接字,然后与自己的IP地址、端口号进行绑定。之后调用开始监听来自客户端的敲门,当有客户端来敲门时,accept()接收客户端的连接并创建一个新套接字用于与客户端通信。接下来调用fork()函数,当调用fork()函数时,操作系统会复制当前进程的一个副本,包括进程的代码、数据和状态等信息。如果其返回值为负数,表示创建子进程失败。否则他在父子进程中有不同的返回值:如果返回值为0,表示当前代码正在子进程中执行。如果返回值大于0,表示当前代码正在父进程中执行,返回的值是子进程的进程ID。因此可以使用if-else语句来编写子进程的处理代码。

在子进程中,先关闭从父进程中复制下来监听套接字,这个套接字在子进程中没有用了,纯属浪费资源,之后再进行与客户端的通信。而在父进程中,同理关闭accept()创建的新套接字,然后继续监听客户端的连接请求。

多进程

父进程处理连接请求,子进程处理消息收发

缺点:

1、子进程与客户端高度绑定。如果客户端连接断开频繁,服务器会频繁创建销毁子进程,系统开销较大,资源浪费

2、并发数量取决于进程数量,如果系统可创建的进程较少,那表示服务器并发数受进程数量限制

3、进程模型本身有较大内存开销

使用服务器测试业务:

客户端向标准输入发送小写字符串,服务端响应回复对应大写字符,"abcAS"->"ABCAS"

客户端向服务端发送关键字localtime,服务端响应回复系统时间、

服务端:

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>
#include <time.h>

#define _SERVER_IP "xxx.xxx.xxx.xxx"
#define _PORT 8080
#define _BACKLOG 128
#define _SHUTDOWN 1
#define _TRUE 1
#define _FALSE 0
#define _IPSIZE 16
#define _RECVLEN 1500


int main()
{
    int recvlen;
        struct sockaddr_in serverAddr,clientAddr;
        int server_fd;
        int client_fd;
        char Result[_RECVLEN];
        char client_ip[_IPSIZE];
    pid_t pid;
    socklen_t Addrlen;
        serverAddr.sin_family=AF_INET;
        serverAddr.sin_port=htons(_PORT);
        serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
        server_fd=socket(AF_INET,SOCK_STREAM,0);
        bind(server_fd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
        listen(server_fd,_BACKLOG);
        printf("Test TCP Server Version 1.1.0 is Running...\n");
        time_t tp;
    char time_buf[100];//存放当前系统时间
    int toupper_flag;
    while(_SHUTDOWN)
        {
        Addrlen=sizeof(clientAddr);
        if((client_fd=accept(server_fd,(struct sockaddr*)&clientAddr,&Addrlen))>0)
        {
       //Socket通信
           bzero(Result,sizeof(Result));
           bzero(client_ip,sizeof(client_ip));
           inet_ntop(AF_INET,&clientAddr.sin_addr.s_addr,client_ip,_IPSIZE);
           printf("Connection From :IP[%s],PORT[%d]\n",client_ip,ntohs(clientAddr.sin_port));
           sprintf(Result,"Hi [%s] Welcome to my TCP test server!service version 1.1.0...",client_ip);
           send(client_fd,Result,strlen(Result),0);
       bzero(Result,sizeof(Result));
       pid=fork();//创建子进程处理通信,父进程只用于处理连接请求
       if(pid>0)
       {

       }
       else if(pid==0)
       {

       //读取用户数据,如果用户发的是普通小写字符字符串,转换为大写,如果发送的是local关键字,响应时间
       //持续响应,循环读写
       while((recvlen=recv(client_fd,Result,sizeof(Result),0))>0)
       {//处理客户端业务
        printf("Client Say:%s\n",Result);
        if(strcmp(Result,"localtime")==0)
        {
        tp=time(NULL);//获取时间种子
        ctime_r(&tp,time_buf);
        time_buf[strcspn(time_buf,"\n")]='\0';
        printf("[%s]Response SysTime Successfully!\n",client_ip);
        send(client_fd,time_buf,strlen(time_buf)+1,0);
        }
        else
        {
        toupper_flag=0;
        while(recvlen>toupper_flag)
        {
            Result[toupper_flag]=toupper(Result[toupper_flag]);
            ++toupper_flag;
        }
        printf("[%s]Response Toupper Successfully!\n",client_ip);
        send(client_fd,Result,recvlen,0);
        }
       }
       if(recvlen==0)//客户端退出
       {
           close(client_fd);
           printf("[%s] is Exiting,Kill Child\n",client_ip);
           exit(0);
       }
       }
       else
       {
        perror("fork call failed");
        exit(0);
       }
       close(client_fd);
        }
        else
        {
        perror("accpet failed");
        close(server_fd);
        exit(0);
        }
        }
        close(server_fd);
        return 0;
}

客户端:

#ifndef _MYSOCK_H_
#define _MYSOCK_H_

#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

int SOCKET(int domain, int type, int protocol);
int BIND(int sockfd, struct sockaddr* addr, socklen_t addrlen);
ssize_t RECV(int sockfd, void* buf, size_t len, int flags);
ssize_t SEND(int sockfd, void* buf, size_t len, int flags);
int CONNECT(int sockfd, struct sockaddr* addr, socklen_t addrlen);
int ACCEPT(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
int LISTEN(int sockfd, int backlog);
char* FGETS(char* s, int size, FILE* stream);
int SELECT(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds,
           struct timeval* timeout);
int socket_init();
int return_response(int clientfd, const char* clientip);
//void strDeal(int *client_fd);

// 全局变量声明
char recv_buf[1024];
char time_buf[64];
int serverFd, clientFd;
struct sockaddr_in clientAddr;
fd_set set, oset;
int client_array[1020];
int maxfd, ready;
socklen_t addrlen;
char clientip[16];
time_t tp;
ssize_t recvlen;
int toupper_flag;
#define SHUTDOWN 1
#endif
#include "MySock.h"


//客户端源码编写,连接服务器成功,服务器反馈信息

#define _IP "xxx.xxx.xxx.xxx"
#define _PORT 8080
int main()
{
    struct sockaddr_in ServerAddr;
    bzero(&ServerAddr,sizeof(ServerAddr));
    ServerAddr.sin_family=AF_INET;
	ServerAddr.sin_port=htons(_PORT);
    inet_pton(AF_INET,_IP,&ServerAddr.sin_addr.s_addr);
    
    int Myfd=SOCKET(AF_INET,SOCK_STREAM,0);
    //看需求决定是否要绑定
    char Response[1024];//存放服务端反馈信息
    ssize_t recvlen;
    bzero(Response,sizeof(Response));
    char sendbuf[1024];
    
    if((CONNECT(Myfd,(struct sockaddr *)&ServerAddr,sizeof(ServerAddr)))==0)
    {
    while(1)
    {    
     if((recvlen=RECV(Myfd,Response,sizeof(Response),0))>0)
     {
         printf("%s\n",Response);
     }
    
    printf("Please Type Some text:");//读取标准输入发送给服务端
    FGETS(sendbuf,sizeof(sendbuf),stdin);    
    sendbuf[strcspn(sendbuf,"\n")]='\0';
    SEND(Myfd,sendbuf,sizeof(sendbuf),0);
    }
    }
    close(Myfd);
    printf("Client is Over\n");
    return 0;
}

运行结果:可以同时运行多个客户端

通过命令查看服务端运行的子进程:

当客户端退出后,杀死子进程

缺点:频繁创建子进程开销大,并且对回收考虑欠佳,会产生僵尸进程

多进程+多线程

在父进程下创建两个线程,一个线程负责处理连接请求,另外一个线程处理子进程的回收。

负责回收的线程通过捕捉SIGCHLD信号触发捕捉函数杀死子进程

如果基于信号数量决定回收操作次数,一定会漏回收,所以回收方式应该是产生一次SIGCHLD信号,尽可能将当前可回收的所有僵尸回收完毕。

由于信号处理行为是共享的,如果产生回收信号,主线程也会尝试捕捉并执行捕捉函数,如果调用了捕捉函数,那么会强制中断主线程中的阻塞函数,影响连接。所以主线程需要设置对SIGCHLD屏蔽,屏蔽在线程创建后,继承给普通线程,普通线程完成捕捉设定,而后解除信号屏蔽。

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include <signal.h>

#define _SERVER_IP "xxx.xxx.xxx.xxx"
#define _PORT 8080
#define _BACKLOG 128
#define _SHUTDOWN 1
#define _TRUE 1
#define _FALSE 0
#define _IPSIZE 16
#define _RECVLEN 1500

void sig_wait(int n)
{
    pid_t zpid;
    while((zpid=waitpid(-1,NULL,WNOHANG))>0)
    {
        printf("wait Thread Tid [0x%x] Wait Successfully,Zombie %d\n",(unsigned int)pthread_self(),zpid);
    }
}

void * thread_wait(void *arg)
{
    //设定信号捕捉
    pthread_detach(pthread_self());
    struct sigaction act,oact;
    act.sa_handler=sig_wait;
    act.sa_flags=0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGCHLD,&act,&oact);
    //解除屏蔽
    sigprocmask(SIG_SETMASK,&act.sa_mask,NULL);
    printf("wait Thread [0x%x] is Waiting...\n",(unsigned int)pthread_self());
    while(1)
     sleep(1);
    pthread_exit(NULL);
}

int main()
{
    int recvlen;
  	struct sockaddr_in serverAddr,clientAddr;
	int server_fd;
	int client_fd;
	char Result[_RECVLEN];
	char client_ip[_IPSIZE];
    pid_t pid;
    
    //主线程设置屏蔽
    sigset_t set,oldset;
    sigemptyset(&set);
    sigaddset(&set,SIGCHLD);
    sigprocmask(SIG_SETMASK,&set,&oldset);
    pthread_t tid;
    pthread_create(&tid,NULL,thread_wait,NULL);//创建回收线程
    

    socklen_t Addrlen;
	serverAddr.sin_family=AF_INET;
	serverAddr.sin_port=htons(_PORT);
	serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
	server_fd=socket(AF_INET,SOCK_STREAM,0);
	bind(server_fd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
	listen(server_fd,_BACKLOG);
	printf("Test TCP Server Version 1.1.0 is Running...\n");
	time_t tp;
    char time_buf[100];//存放当前系统时间
    int toupper_flag;
    while(_SHUTDOWN)
	{
	Addrlen=sizeof(clientAddr);
	if((client_fd=accept(server_fd,(struct sockaddr*)&clientAddr,&Addrlen))>0)
	{
       //Socket通信
	   bzero(Result,sizeof(Result));
	   bzero(client_ip,sizeof(client_ip));
	   inet_ntop(AF_INET,&clientAddr.sin_addr.s_addr,client_ip,_IPSIZE);
	   printf("Connection From :IP[%s],PORT[%d]\n",client_ip,ntohs(clientAddr.sin_port));
	   sprintf(Result,"Hi [%s] Welcome to my TCP test server!service version 1.1.0...",client_ip);
	   send(client_fd,Result,strlen(Result),0);
       bzero(Result,sizeof(Result));
       pid=fork();//创建子进程处理通信,父进程只用于处理连接请求
       if(pid>0)
       {

       }
       else if(pid==0)
       {

       //读取用户数据,如果用户发的是普通小写字符字符串,转换为大写,如果发送的是local关键字,响应时间
       //持续响应,循环读写
       while((recvlen=recv(client_fd,Result,sizeof(Result),0))>0)
       {//处理客户端业务
        printf("Client Say:%s\n",Result);
        if(strcmp(Result,"localtime")==0)
        {
        tp=time(NULL);//获取时间种子
        ctime_r(&tp,time_buf);
        time_buf[strcspn(time_buf,"\n")]='\0';
        printf("[%s]Response SysTime Successfully!\n",client_ip);
        send(client_fd,time_buf,strlen(time_buf)+1,0);
        }
        else
        {
        toupper_flag=0;
        while(recvlen>toupper_flag)
        {
            Result[toupper_flag]=toupper(Result[toupper_flag]);
            ++toupper_flag;
        }
        printf("[%s]Response Toupper Successfully!\n",client_ip);
        send(client_fd,Result,recvlen,0);
        }
       }
       if(recvlen==0)//客户端退出
       {
           close(client_fd);
           printf("[%s] is Exiting,Kill Child\n",client_ip);
           exit(0);
       }
       }
       else
       {
        perror("fork call failed");
        exit(0);
       }
       close(client_fd);	
	}
	else
	{
	perror("accpet failed");
	close(server_fd);
	exit(0);
	}
	}
	close(server_fd);
	return 0;
}

当客户端进程退出后,成功杀死了僵尸进程

服务端下有两个线程,一个负责创建通信进程,一个负责回收僵尸进程

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL 9.0创新版发布!功能又进化了!

作者&#xff1a;IT邦德 中国DBA联盟(ACDU)成员&#xff0c;10余年DBA工作经验&#xff0c; Oracle、PostgreSQL ACE CSDN博客专家及B站知名UP主&#xff0c;全网粉丝10万 擅长主流Oracle、MySQL、PG、高斯及Greenplum备份恢复&#xff0c; 安装迁移&#xff0c;性能优化、故障…

【ARM系列】1of N SPI

1 of N模式 SPI 概述配置流程 概述 GIC-600AE支持1 of N模式SPI。在此模式下可以将SPI target到多个core&#xff0c;并且GIC-600AE可以选择哪些内核接收SPI。 GIC-600AE只向处于powered up 并且使能中断组的core发送SPI。 GIC-600AE会优先考虑那些被认为是active的核&#xf…

如何利用Stable Diffusion在AI绘画领域赚钱,(附详细教程)小白兼职必看!

前言 AIGC 现已成为内容生产的引擎&#xff0c;正为内容创作领域带来前所未有的变革。它不仅能够在文本、图像、视频、音频等单一模态上生成内容&#xff0c;更能实现跨模态的生成&#xff0c;打通了多模态间的壁垒。 对于“普通人”来说&#xff0c;理解并有效的学会利用 AI…

方法重载与重写的区别

1.方法重载和重写都是实现多态的方式&#xff0c;区别在于重载是编译时多态&#xff0c;重写是运行时多态。 2.重载是在同一个类中&#xff0c;两个方法的方法名相同&#xff0c;参数列表不同&#xff08;参数类型、顺序、个数&#xff09;&#xff0c;与方法返回值无关&#x…

电路里电源不仅仅是电源

电源往往被认为是直流控制电路中重要的考虑因素之一——但我们也不能忽视其他关键因素&#xff1a;电源滤波器、转换器和备用电源模块。 输入电源是任何电气控制系统的基本配置。没有电源&#xff0c;就没有传感器、控制器、负载设备&#xff0c;什么都没有。因此&#xff0c;…

windows下搭建python+jupyter notebook

一.下载python 下面网址下载python3 https://www.python.org/ 二. 安装jupyter notebook 三. 修改配置 四. 检测是否正常运行

夸克网盘拉新暑期大涨价!官方授权渠道流程揭秘

夸克网盘拉新暑期活动来袭&#xff0c;价格大涨&#xff01;从7月1日开始持续两个月&#xff0c;在这两个月里夸克网盘拉新的移动端用户&#xff0c;一个从原来的5元涨到了10元。这对做夸克网盘拉新的朋友来说&#xff0c;真的是福利的。趁着暑期时间多&#xff0c;如果有想做夸…

【工具推荐】Clink

文章目录 Clink介绍Clink安装删除版权信息 Clink介绍 Clink 是一个工具&#xff0c;它将 GNU Readline 库的强大命令行编辑功能与 Windows 原生的 cmd.exe 命令提示符结合在一起。Readline 是 Bash shell 中众所周知的库&#xff0c;Bash 是许多 Linux 发行版的标准 shell。通…

【高性能服务器】多线程并发模型

&#x1f525;博客主页&#xff1a; 我要成为C领域大神&#x1f3a5;系列专栏&#xff1a;【C核心编程】 【计算机网络】 【Linux编程】 【操作系统】 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 ​​ 对于常见的C/…

C语言使用先序遍历创建二叉树

#include<stdio.h> #include<stdlib.h>typedef struct node {int data;struct node * left;struct node * right; } Node;Node * createNode(int val); Node * createTree(); void freeTree(Node * node);void preOrder(Node * node);// 先序创建二叉树 int main()…

猫头虎博主全栈前沿AI技术领域矩阵社群

猫头虎博主全栈前沿AI技术领域矩阵社群 &#x1f44b;大家好&#xff0c;我是猫头虎&#xff01;今天我要向大家介绍一个非常重要的社群矩阵——专为全栈前沿AI技术领域的朋友们打造的各种技术交流和资源互助的社群。这些社群不仅能帮助大家快速提升技术水平&#xff0c;还能拓…

【数据结构与算法】堆排序算法原理与实现:基于堆实现的高效排序算法

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法》 期待您的关注 ​ 目录 一、引言 堆排序的简介 堆排序的特点 二、堆的概念 三、堆排序算法的原理 四、堆…

MySQL 9.0 悄悄上线,支持面向AI的向量数据库

MySQL狂热粉丝群已经发现MySQL官网上MySQL9.0这两天悄然上线&#xff0c;已经可以下载体验了&#xff0c;目前被定义为创新版本&#xff08;Innovation&#xff09;。 下载地址&#xff1a;https://dev.mysql.com/downloads/mysql/ 支持主流的操作系统&#xff0c;安装后可以直…

H5漂流瓶交友源码_社交漂流瓶H5源码

简介&#xff1a; 一种流行的娱乐性社交新潮流&#xff0c;年轻人玩得比较多。和盲盒有点类似 社交漂流瓶搭建教程 环境&#xff1a;Nginx 1.20.1-MySQL 5.6.50-PHP-7.3 上传源码至网站根目录&#xff0c;创建并导入数据库 数据库信息修改&#xff1a;/config/database.ph…

TCP 的安全可靠

TCP的安全可靠 重传机制往返时间测量快速重传 流量控制拥塞控制 重传机制 T C P确认从另一端收到的数据以提供可靠的运输层&#xff0c;但数据和确认都有可能会丢失。 T C P通过在发送时设置一个定时器来解决这种问题。如果当定时器溢出时还没有收到确认&#xff0c;它就重传该…

7.2.SQL注入-基于函数报错extractvalue(),floor()

注入基于函数报错extractvalue(),floor()-字符型 基于extractvalue() 爆出数据库版本payload语句&#xff1a; kobe and extractvalue(0,concat(0x7e,version()))#爆出数据库版本 基于floor() floor()函数就是取整数 爆出数据版本信息 kobe and (select 2 from (select …

深度解密Spark性能优化之道

课程介绍 课程通过实战案例解析和性能调优技巧的讲解&#xff0c;帮助学员提升大数据处理系统的性能和效率。课程内容涵盖了Spark性能调优的各个方面&#xff0c;包括内存管理、并行度设置、数据倾斜处理、Shuffle调优、资源配置等关键技术和策略。学员将通过实际案例的演示和…

【Altium】如何处理PCB上所有焊盘被误盖油

【更多软件使用问题请点击亿道电子官方网站】 1、 文档目标 解决焊盘被误盖油的操作 2、 问题场景 所有焊盘都可以设置为盖油或不盖油&#xff0c;由于焊盘需要用来焊接元器件&#xff0c;所以都不会设置盖油。由于误操作或者创建封装时设置错误&#xff0c;造成一定数量的焊盘…

web基础及http协议

一、WEB&#xff1a;就是我们所说的页面&#xff0c;点开的每个页面都是web。&#xff08;全球广域网、万维网&#xff09; 分布式图形信息系统&#xff1a;同一个服务&#xff0c;但是部署在不同的机器上且提供的服务和内容全部一致&#xff0c;集群就是建立在分布式的基础上。…

爬虫逆向实战(42)-某巢登陆(AES、MD5、RSA、滑块验证码)

一、数据接口分析 主页地址&#xff1a;某巢 1、抓包 通过抓包可以发现在登录时&#xff0c;网站首先请求captcha/querySlideImage/来获取滑块验证码的图片&#xff0c;然后请求captcha/checkCode/接口来验证滑块验证码。滑块验证码校验成功后&#xff0c;请求noshiro/getPu…