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

news2024/12/23 8:36:20

 🔥博客主页: 我要成为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;
}

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

恶意域名检测研究与应用综述

域名攻击发展 使用 DGA 的优势在于模糊了控制服务器的节点位置&#xff0c;该方法的灵活性还让网络安全管理员无法阻止所有可能的域名&#xff0c;并且注册一些域名对攻击者来说成本很低。利用 DGA 域名实施的攻击是网络安全中重要的攻击形式。因此&#xff0c;捕获由恶意软件生…

【CentOS7.6】yum 报错:Could not retrieve mirrorlist http://mirrorlist.centos.org

一、报错 1.报错内容如下 在使用 yum makecache 命令时报错&#xff0c;在 yum install -y xxx 的时候报错等等 [roothcss-ecs-a901 yum.repos.d]# yum makecache Loaded plugins: fastestmirror Determining fastest mirrors Could not retrieve mirrorlist http://mirrorl…

第十一章 Nest 创建动态模块

在 NestJS 中&#xff0c;动态模块允许在运行时动态添加和删除模块。这对于创建可扩展的和灵活的应用程序非常有用。 新建一个项目&#xff1a; nest new dynamic-module -p npm创建一个crud的模块&#xff1a; nest g resource test启动项目 浏览器访问 可以发现模块生效了 …

哪些品牌的充电宝牌子比较好用?性价比无线磁吸充电宝推荐

经常外出的朋友&#xff0c;不管是旅行聚会&#xff0c;或是商务出差&#xff0c;一旦手机电量告急&#xff0c;总会令人心生焦虑。共享充电宝不仅充电速度迟缓&#xff0c;而且价格高昂&#xff0c;有线充电宝又显得沉重&#xff0c;线缆还杂乱无章。现在随着科技的发展&#…

将exe文件添加到注册表中,实现开机时自动运行

目录 一、前言 二、代码 三、使用步骤 1.编译生成exe文件、 2.以管理员身份运行代码 3.打开注册表&#xff0c;验证结果 一、前言 在Windows操作系统中&#xff0c;将exe文件的路径添加到注册表下&#xff0c;主要用于实现程序的开机自动运行功能。 注册表路径为&#xf…

6.The hardest part about learing hard things(学一件难的事,难在哪里)

I’ve been recording a lot of podcast interviews for my upcoming book, Ultralearning.One of the reurring themes I’ve noticed in our conversations is that how people feel about learning is the overwhelming cause of the results they experience. 我为我的新书…

2024年6月总结及随笔之打卡网红点

1. 回头看 日更坚持了547天。 读《人工智能时代与人类未来》更新完成读《AI未来进行式》开更并更新完成读《AI新生&#xff1a;破解人机共存密码》开更并持续更新 2023年至2024年6月底累计码字1267912字&#xff0c;累计日均码字2317字。 2024年6月码字90659字&#xff0c;…

Git学习(常用的一些命令)

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;日常聊聊 ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 配置相关&#xff1a; 创建与克隆仓库&#xff1a; 基本操作&#xff1a; 分支操作&#xff1a; 远程仓库操作&#xff1a…

mac 安装nvm的教程

在macOS上切换Node.js版本&#xff0c;可以使用nvm&#xff08;Node Version Manager&#xff09;。以下是安装nvm和切换Node.js版本的步骤&#xff1a; 安装nvm 下载方式 终端复制输入&#xff1a; curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.…

第一后裔掉宝奖励有什么 怎么领取第一后裔掉宝奖励

第一后裔在今天就要正式上线了&#xff0c;虽然是一款刚刚上线的新游戏&#xff0c;但是其实很早就测试过了&#xff0c;而且测试过很多次&#xff0c;所以有很多已经体验过的小伙伴&#xff0c;这款游戏的玩法还是比较有意思的&#xff0c;第三人称刷宝射击加上MMORPG的玩法&a…

深入分析差分驱动器,4种情形帮您看透~

差分驱动器可以由单端或差分信号驱动&#xff0c;今天我们就利用无端接或端接信号源来分析这两种情况。 01 差分输入、无端接信号源 图1显示一个差分驱动器由一个平衡的无端接信号源驱动。这种情况通常是针对低阻抗信号源&#xff0c;信号源与驱动器之间的连接距离非常短。 图…

充电宝买哪个牌子好?2024年十大口碑最好充电宝推荐

在如今快节奏的生活中&#xff0c;充电宝已然成为我们出行必备的物品之一。然而&#xff0c;面对市场上琳琅满目的充电宝品牌和型号&#xff0c;我们常常陷入选择的困境&#xff1a;充电宝买哪个牌子好&#xff1f;2024 年&#xff0c;充电宝技术不断革新&#xff0c;新品牌层出…

重生之算法刷题之路之链表初探(三)

算法刷题之路之链表初探&#xff08;三&#xff09; 今天来学习的算法题是leecode2链表相加&#xff0c;是一道简单的入门题&#xff0c;但是原子在做的时候其实是有些抓耳挠腮&#xff0c;看了官解之后才恍然大悟&#xff01; 条件 项目解释 有题目可以知道&#xff0c;我们需…

论文题目:SpringBoot种草好物app13151

摘要 随着电子商务的快速发展和智能手机的普及&#xff0c;越来越多的用户选择通过移动应用程序进行商品浏览、购买和分享体验。种草好物App作为一个专注于商品推荐和购物体验的平台&#xff0c;具有广泛的应用前景和商业价值。本研究旨在构建一个功能丰富、性能稳定的种草好物…

第一后裔快速领取掉宝奖励礼包教程

7月2日第一后裔在steam正式上线&#xff0c;全新刷宝射击mmo玩法&#xff0c;角色的招式非常新颖 &#xff0c;画面冲击感十足&#xff0c;而且游戏人物的自定义功能非常丰富&#xff0c;超级细节真实的人物建模&#xff0c;加上超带感的服装自定义系统&#xff0c;让你能玩一整…

AI太火,今年更缺人了 (含实习)

AI太火了&#xff01;眼睛一睁一闭&#xff0c;一大堆新鲜出炉的前沿科技进展已经塞满未读列表。 许多公司更是开出了高薪&#xff0c; 读者福利&#xff1a;如果大家对大模型感兴趣&#xff0c;这套大模型学习资料一定对你有用 对于0基础小白入门&#xff1a; 如果你是零基础…

leetcode-21-回溯-全排列及其去重

一、[46]全排列 给定一个 没有重复 数字的序列&#xff0c;返回其所有可能的全排列。 示例: 输入: [1,2,3]输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ] 其中&#xff0c;不需要使用startIndex used数组&#xff0c;其实就是记录此时path里都有哪些元素…

TypeScript中,如何利用数组生成一个联合类型

本文由 ChatMoney团队出品 在开发中我们常常会遇到这样一个问题&#xff0c;代码如下&#xff1a; const arr ["a","b","c","d","e","f","g","h","i","j","k&quo…

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()…

在CentOS7云服务器下搭建MySQL网络服务详细教程

目录 0.说明 1.卸载不要的环境 1.1查看当前环境存在的服务mysql或者mariadb 1.2卸载不要的环境 1.2.1先关闭相关的服务 1.2.2查询曾经下载的安装包 1.2.3卸载安装包 1.2.4检查是否卸载干净 2.配置MySQLyum源 2.1获取mysql关外yum源 2.2 查看当前系统结合系统配置yum…