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

devtools/2024/11/14 21:11:01/

 🔥博客主页: 我要成为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 1500int 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 1500void 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.ppmy.cn/devtools/56818.html

相关文章

【flutter】点击底部不同的item实现切换Scaffold

要实现底部导航栏的不同项目切换不同的 Scaffold&#xff0c;您可以使用 IndexedStack 和 BottomNavigationBar 的组合。IndexedStack 可以根据当前所选的底部导航栏项目来切换不同的 Scaffold。以下是一个示例代码&#xff1a; import package:flutter/material.dart;void ma…

大二暑假 + 大三上

希望&#xff0c;暑假能早睡早起&#xff0c;胸围达到 95&#xff0c;腰围保持 72&#xff0c;大臂 36&#xff0c;小臂 32&#xff0c;小腿 38&#x1f36d;&#x1f36d; 目录 &#x1f348;暑假计划 &#x1f339;每周进度 &#x1f923;寒假每日进度&#x1f602; &…

Windows页面错误(Page Fault)写几种c++会导致,此问题的例子

在C中&#xff0c;直接导致Windows页面错误&#xff08;Page Fault&#xff09;的情景较少直接由编程错误引发&#xff0c;页面错误更多是由操作系统在内存管理和虚拟内存机制中处理的。不过&#xff0c;某些编程错误可能导致访问违规&#xff0c;进而间接引起操作系统报告页面…

学生党蓝牙耳机推荐有哪些?四款学生党必入的性价比蓝牙耳机推荐

在数字化和移动设备普及的今天&#xff0c;蓝牙耳机已经成为了我们日常生活中不可或缺的一部分&#xff0c;尤其是对于学生群体来说&#xff0c;一款性价比高的蓝牙耳机都能极大地提升他们的生活质量&#xff0c;那么学生党蓝牙耳机推荐有哪些&#xff1f;市面上的蓝牙耳机品牌…

数据恢复篇:如何在 Android 手机上恢复未保存/删除的 Word 文档

在 Android 手机上访问 Word 文档通常很简单&#xff0c;但是当这些重要文件被删除或未保存时会发生什么&#xff1f;这种情况虽然令人痛苦&#xff0c;但并非毫无希望。到 2024 年&#xff0c;有几种强大的方法来处理此类数据丢失。本指南重点介绍如何在Android手机上恢复已删…

laravel对接百度智能云 实现智能机器人

创建API Key和 Secret Key进入网址&#xff1a;百度智能云千帆大模型平台 如下图操作&#xff1a; 填写完毕点击确认后&#xff0c;即可得到sk和ak 后端接口实现代码&#xff1a; //调用百度智能云第三方机器人接口public function run($text) {$curl curl_init();curl_setop…

Django靓号管理系统:实现Home页面

在本文中,我们将详细介绍如何在Django靓号管理系统中实现Home页面。Home页面是用户成功登录后看到的第一个页面,它应该提供一个友好的欢迎信息和系统的基本布局。 1. 更新 ​​urls.py​​ 首先,我们需要确保 ​​urls.py​​ 文件中包含了 Home 页面的 URL 配置: # url…

2024 年江西省研究生数学建模竞赛A题:交通信号灯管理问题分析、实现代码及参考论文

2024 年江西省研究生数学建模竞赛题目交通信号灯管理 1 题目 交通信号灯是指挥车辆通行的重要标志&#xff0c;由红灯、绿灯、 黄灯组成。红灯停、绿灯行&#xff0c;而黄灯则起到警示作用。交通 信号灯分为机动车信号灯、非机动车信号灯、人行横道信号 灯、方向指示灯等。 一…