【linux 多进程并发】0302 Linux下多进程模型的网络服务器架构设计,实时响应多客户端请求

0302 多进程网络服务器架构

专栏内容

  • postgresql使用入门基础
  • 手写数据库toadb
  • 并发编程

个人主页:我的主页
管理社区:开源数据库
座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物.

一、概述


在大规模数据处理中,会有大量的客户端接入同一台服务器,每个客户端都需要长时间提供服务。

服务器采用中心化的部署,而客户端往往分散在不同机器上,服务器与客户端之间跨网络通信,一般采用C/S架构。

而服务端的架构需要能应对大量并发客户端,同时可以给每个客户端独占的服务,这就用到了多任务的网络模型架构,下面我们来看看用多进程如何实现。

二、多路复用的网络模型


C/S架构中,处理大量的网络请求,需要一套基于多路复用的网络处理模型。

  • 可以同时处理网络连接请求和网络数据传递;
  • 减少程序的阻塞时间,避免无效的CPU消耗;
  • 适应不同的并发规模;

以此为目标实现如下网络模型。

2.1 服务端网络监听

多路复用模型这里采用了epoll方式,如果自己的平台不支持,可以换为select或者poll的方式。

在这里插入图片描述

代码如下:

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <arpa/inet.h>  
#include <sys/epoll.h>  
#include <fcntl.h>  
#include <errno.h>  #define MAX_EVENTS 10  
#define BUFFER_SIZE 1024  
#define PORT 8080  // 设置文件描述符为非阻塞模式  
int set_nonblocking(int fd) {  int flags, s;  flags = fcntl(fd, F_GETFL, 0);  if (flags == -1) {  perror("fcntl F_GETFL");  return -1;  }  flags |= O_NONBLOCK;  s = fcntl(fd, F_SETFL, flags);  if (s == -1) {  perror("fcntl F_SETFL");  return -1;  }  return 0;  
}  int main() {  int listen_fd, conn_fd, nfds, epoll_fd;  struct sockaddr_in server_addr;  struct epoll_event ev, events[MAX_EVENTS];  char buffer[BUFFER_SIZE];  ssize_t count;  // 创建监听socket  listen_fd = socket(AF_INET, SOCK_STREAM, 0);  if (listen_fd == -1) {  perror("socket");  exit(EXIT_FAILURE);  }  // 设置非阻塞模式  if (set_nonblocking(listen_fd) == -1) {  close(listen_fd);  exit(EXIT_FAILURE);  }  // 绑定地址和端口  server_addr.sin_family = AF_INET;  server_addr.sin_addr.s_addr = INADDR_ANY;  server_addr.sin_port = htons(PORT);  if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {  perror("bind");  close(listen_fd);  exit(EXIT_FAILURE);  }  // 开始监听  if (listen(listen_fd, SOMAXCONN) == -1) {  perror("listen");  close(listen_fd);  exit(EXIT_FAILURE);  }  // 创建epoll实例  epoll_fd = epoll_create1(0);  if (epoll_fd == -1) {  perror("epoll_create1");  close(listen_fd);  exit(EXIT_FAILURE);  }  // 添加监听socket到epoll实例  ev.events = EPOLLIN;  ev.data.fd = listen_fd;  if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev) == -1) {  perror("epoll_ctl: listen_fd");  close(listen_fd);  close(epoll_fd);  exit(EXIT_FAILURE);  }  // 主循环  while (1) {  nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);  if (nfds == -1) {  perror("epoll_wait");  exit(EXIT_FAILURE);  }  for (int n = 0; n < nfds; ++n) {  if (events[n].data.fd == listen_fd) {  // 新的连接  conn_fd = accept(listen_fd, NULL, NULL);  if (conn_fd == -1) {  perror("accept");  continue;  }  // 设置非阻塞模式  if (set_nonblocking(conn_fd) == -1) {  close(conn_fd);  continue;  }  // 添加新的连接socket到epoll实例  ev.events = EPOLLIN | EPOLLET;  ev.data.fd = conn_fd;  if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn_fd, &ev) == -1) {  perror("epoll_ctl: conn_fd");  close(conn_fd);  }  } else {  // 处理读事件  conn_fd = events[n].data.fd;  while ((count = read(conn_fd, buffer, BUFFER_SIZE)) > 0) {  // 处理接收到的数据(这里简单回显)  write(conn_fd, buffer, count);  }  if (count == -1 && errno != EAGAIN) {  // 出现错误或连接关闭  close(conn_fd);  } else if (count == 0) {  // 连接关闭  close(conn_fd);  }  // 从epoll实例中移除已关闭的socket  if (count <= 0 && errno != EAGAIN) {  ev.events = 0;  epoll_ctl(epoll_fd, EPOLL_CTL_DEL, conn_fd, &ev);  }}  }  }  close(listen_fd);  close(epoll_fd);  return 0;  
}

说明

  • TCP服务端的基本步骤创建socket,设置为非阻塞模式,绑定IP与端口,开启监听;
  • 这里服务端的socket需要设置为非阻塞模式,因为我们是在单进程中处理多个连接,每个连接不能阻塞等待;
  • 然后加入到epoll监听池中,开始epoll事件的等待;这里只处理接收事件;
  • 如果有服务端socket的接收事件,那么说明有客户端连接消息,进行accep,创建客户端连接的socket;
  • 同样将客户端连接的socket设置为非阻塞,理由同上;加入epoll临听池中,同样也只处理接收事件;
  • 如果有客户端连接的socket上的接收事件,那么说明客户端正在给服务端发消息;
  • 收到客户端消息后,这里只是简单处理,原样再发给客户端;
  • 如果客户端关闭或出错,将客户端连接关闭,并从epoll临听池中移除;

2.2 客户端测试

现在我们来编写一个简单的客户端模拟程序,测试一下多路复用的网络框架。

/** ex020302_client.c*/#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <arpa/inet.h>  #define SERVER_IP "127.0.0.1"  
#define SERVER_PORT 4808  
#define BUFFER_SIZE 1024  #define CLIENT_SEND_CNT 20int main() 
{  int sockfd;  struct sockaddr_in server_addr;  char buffer[BUFFER_SIZE] = {0};  const char *message = "Hello, Server!";  // 创建套接字  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {  perror("socket creation failed");  exit(EXIT_FAILURE);  }  // 配置服务器地址信息  server_addr.sin_family = AF_INET;  server_addr.sin_port = htons(SERVER_PORT);  // 将IP地址从字符串转换为二进制形式  if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {  perror("Invalid address/ Address not supported");  close(sockfd);  exit(EXIT_FAILURE);  }  // 连接到服务器  if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {  perror("Connection Failed");  close(sockfd);  exit(EXIT_FAILURE);  }  for(int i = 0; i < CLIENT_SEND_CNT; i++){// 发送消息到服务器  send(sockfd, message, strlen(message), 0);  printf("Message sent: %s\n", message);  // 接收服务器的响应  int bytes_received = recv(sockfd, buffer, BUFFER_SIZE - 1, 0);  if (bytes_received < 0) {  perror("Error in receiving");  } else if (bytes_received == 0) {  printf("Server closed the connection\n");  } else {  buffer[bytes_received] = '\0'; // 确保字符串以空字符结尾  printf("Message received from server: %s\n", buffer);  }  sleep(1);}// 关闭套接字  close(sockfd);  return 0;  
}

说明

  • TCP客户端建立的基本步骤,创建socket,初始化服务端地址,连接服务器;
  • 然后向服务端发送相同的消息;
  • 每次发送完成后,等待接收消息;

2.3 客户端测试

可以看到,服务端处理客户端的请求时,都是按照接收到的顺序进行串行处理;

当客户端的数量达到成百上千时,对客户端的响应时间就会出现非常明显的延迟,

这种延迟会随着业务的复杂度而放大。

这时就需要充分利用多核CPU硬件资源,来进行并发任务的处理。

三、多进程服务处理


上面是在单个任务进程中处理了监听和大量任务连接的网络处理,各客户端连接的服务会相互影响,实际是串行化处理的。

要让大量的客户端能同时被响应,需要采用多任务的方式,那么在上面的网络模型基础上加入多进程,服务端为每个客户端连接准备一个独立的进程,这样就可以及时响应。

3.1 多进程架构

首先我们利用前面几个章节的介绍,来搭建一个多进程的代码架构,由主进程根据需要进行创建子进程,并且由主进程进行全局的控制。

在这里插入图片描述

/** ex020302_netprocess.c*/
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <arpa/inet.h>  
#include <sys/epoll.h>  
#include <fcntl.h>  
#include <errno.h>  #define MAX_EVENTS 10  
#define BUFFER_SIZE 1024  
#define PORT 4808  void daemon_fork()
{int pid = -1;pid = fork();if(pid < 0){printf("fork error[%s]\n",strerror(errno));exit(-1);}else if(pid > 0){// parent exit.exit(0);}else {// child daemonreturn;}
}void subprocess(int sock)
{int pid = -1;pid = fork();if(pid < 0){printf("fork error[%s]\n",strerror(errno));exit(-1);}else if(pid > 0){// parent.close(sock);return;}else {// child close(listen_fd);processMsg(sock);exit(0);}
}

说明

  • daemon服务程序函数,这个前一章节已经介绍过了,服务端以后台进程的方式运行;
  • 子进程任务处理函数;这里创建的是任务子进程,并在子进程中调用消息处理函数;
  • 这里需要注意的是,在子进程中要关闭服务端的socket,同时在父进程中要关闭客户端连接的socket; 因为父子进程会复制内存空间,但是在各自的进程中,已经不再需要;

3.2 并发网络处理模型

现在就可以将上面的多路复用网络处理放入多进程架构中,处理逻辑进行如下切分:

  • 服务端监听socket初始化,多路复用器的初始化等,都放在主进程中,作为服务端网络初始化的一部分;
  • 每个客户端连接的socket,以及它的读写消息处理逻辑,放在子进程中;这样每个客户端连接对应一个后台服务子进程;
  • 创建子进程的时机,也就是在主进程中接收到新连接时,创建新连接成功后,就可以新建子进程进行处理;
  • 而子进程的退出时间,就是客户端断开连接,或者处理出错时;
void initializeServerNet()
{struct sockaddr_in server_addr;  // 创建监听socket  listen_fd = socket(AF_INET, SOCK_STREAM, 0);  if (listen_fd == -1) {  perror("socket");  exit(EXIT_FAILURE);  }  // 绑定地址和端口  server_addr.sin_family = AF_INET;  server_addr.sin_addr.s_addr = INADDR_ANY;  server_addr.sin_port = htons(PORT);  if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {  perror("bind");  close(listen_fd);  exit(EXIT_FAILURE);  }// 开始监听if (listen(listen_fd, SOMAXCONN) == -1){perror("listen");close(listen_fd);exit(EXIT_FAILURE);}
}void closeServerFd()
{close(listen_fd);  
}void dispatchLoop()
{int conn_fd;  // 主循环  while (1) {  // 新的连接  conn_fd = accept(listen_fd, NULL, NULL);  if (conn_fd == -1) {  perror("accept");  sleep(1);continue;  }subprocess(conn_fd);}  
}void processMsg(int sock)
{char buffer[BUFFER_SIZE];  ssize_t count;  printf("serv-process:%d start.\n");while ((count = read(sock, buffer, BUFFER_SIZE)) > 0){// 处理接收到的数据(这里简单回显)write(sock, buffer, count);}if (count == -1 && errno != EAGAIN){// 出现错误或连接关闭close(sock);}else if (count == 0){// 连接关闭close(sock);}printf("serv-process:%d exit.\n");
}

那么主程序实现如下:

void daemon_fork();
void subprocess(int sock);
void processMsg(int sock);void initializeServerNet();
void closeServerFd();
void dispatchLoop();int listen_fd;int main(int argc ,char *argv[])
{daemon_fork();initializeServerNet();dispatchLoop();closeServerFd();return 0;
}
  • 在主进程中先进程服务端初始化;
  • 然后就可以开始监听,并接收客户端的连接;
  • 当有客户端连接时,就创建客户端连接,并启动子进程与该客户端进行网络通信;
  • 子进程在客户端断开连接或出错时,就会退出;

2.3 客户端测试

可以看到将客户端发送次数调大后,开启的客户端越多,服务端启动的子进程也就会越多;

此时,可以看到服务端每个进程的CPU使用率并不是很高;

但是随着客户端数量越来越多,服务端进程数量超过CPU核数时,就会增加系统的负担;

四、总结


本文主要介绍了基于多进程架构的网络服务器的设计与实现,在多进程架构中每个客户端会有一个服务端的进程专门处理通信,增加了对客户端消息的响应效率,提升了并发处理能力。

结尾


非常感谢大家的支持,在浏览的同时别忘了留下您宝贵的评论,如果觉得值得鼓励,请点赞,收藏,我会更加努力!

作者邮箱:study@senllang.onaliyun.com
如有错误或者疏漏欢迎指出,互相学习。

注:未经同意,不得转载!

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

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

相关文章

SQLI LABS | Less-26 GET-Error Based-All Your SPACES And COMMENTS Belong To Us

关注这个靶场的其它相关笔记&#xff1a;SQLI LABS —— 靶场笔记合集-CSDN博客 0x01&#xff1a;过关流程 输入下面的链接进入靶场&#xff08;如果你的地址和我不一样&#xff0c;按照你本地的环境来&#xff09;&#xff1a; http://localhost/sqli-labs/Less-25/ 本关考察…

XML标记语言

最近在学XXE-XML外部实体注入漏洞时候&#xff0c;浅浅学习了一下XML&#xff0c;谨做此学习笔记。 目录 一&#xff1a;XML概述 二&#xff1a;XML语法 XML中的CDATA 三&#xff1a;使用PHP解析XML文档 添加节点 四&#xff1a;Xpath语言 绝对查找 相对查找 使用*匹配…

详解:模板设计模式

模板设计模式&#xff08;Template Pattern&#xff09;是一种行为设计模式&#xff0c;在软件设计中有着广泛的应用&#xff0c;旨在提高代码的可维护性和可复用性。 一、定义与特点 定义&#xff1a; 模板设计模式定义了一个算法的骨架&#xff0c;将某些步骤推迟到子类中实…

《向量数据库指南》——RAG质量评估与监控,打造高效AI引擎

嘿,各位向量数据库的小伙伴们,我是你们的老朋友王帅旭,大禹智库的向量数据库高级研究员,也是那本被大家津津乐道的《向量数据库指南》的作者。今天咱们来聊聊一个特别重要但又经常被忽视的话题——质量评估与监控,这可是确保咱们RAG(检索增强生成)系统稳定高效运行的关键…

Verilog HDL基础

模块的基本结构 module 模块名(端口列表); // 模块声明// 端口定义input [数据类型] [位宽] 输入端口列表; output [数据类型] [位宽] 输出端口列表; inout [数据类型] [位宽] 双向端口列表; // 数据类型定义wire [位宽] 线网名,线网名&#xff0c;…; …

HarmonyOS NEXT 应用开发实战(九、知乎日报项目详情页实现详细介绍)

在本篇博文中&#xff0c;我们将探讨如何使用 HarmonyOS Next 框架开发一个知乎日报的详情页&#xff0c;逐步介绍所用到的组件及代码实现。知乎日报是个小巧完整的小项目&#xff0c;这是一个循序渐进的过程&#xff0c;适合初学者和有一定开发经验的工程师参考。 1. 项目背景…

SpringBoot+FileBeat+ELK8.x版本收集日志

一、准备环境 1、ElasticSearch&#xff1a;8.1.0 2、FileBeat&#xff1a;8.1.0 3、Kibana&#xff1a;8.1.0 4、logstach&#xff1a;8.1.0 本次统一版本&#xff1a;8.1.0,4个组件&#xff0c;划分目录&#xff0c;保持版本一致。 说明&#xff1a;elasticsearch和kib…

UML介绍-不同类间关系

目录 不同的类间关系有着各自独特的体现方式。 继承关系 实现关系 关联关系 聚合关系 组合关系 数据库建模PowerDesigner UML&#xff08;Unified Modeling Language&#xff09;即统一建模语言&#xff0c;是一种用于软件系统分析、 设计和文档编制的可视化建模语言。…

C语言 | Leetcode C语言题解之第528题按权重随机选择

题目&#xff1a; 题解&#xff1a; typedef struct {int* pre;int preSize;int total; } Solution;Solution* solutionCreate(int* w, int wSize) {Solution* obj malloc(sizeof(Solution));obj->pre malloc(sizeof(int) * wSize);obj->preSize wSize;obj->total…

Yocto 项目中 layer.conf 文件的基本框架与模版

一个在layer.conf文件的基本框架&#xff1a; # 层的名称&#xff0c;可自定义&#xff0c;用于标识该层 LCONF_VERSION "7" LAYERNAME "my_layer"# 层的优先级&#xff0c;整数&#xff0c;用于确定层在构建顺序中的位置 # 数字越大&#xff0c;优先级…

计算机毕业设计Hadoop+大模型地震预测系统 地震数据分析可视化 地震爬虫 大数据毕业设计 Spark 机器学习 深度学习 Flink 大数据

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

移植 AWTK 到 纯血鸿蒙 (HarmonyOS NEXT) 系统 (1) - 让 OpenGLES 应用跑起来

在正式移植 AWTK 之前&#xff0c;我们先尝试在 HarmonyOS 上运行一个简单的 OpenGLES 应用程序。虽然可以确定这一步肯定是可行的&#xff0c;但是作为一个 HarmonyOS NEXT 新手&#xff0c;还是先从简单的应用程序开始&#xff0c;把坑都踩一遍&#xff0c;遇到问题比较好解决…

Node.js:内置模块

Node.js&#xff1a;内置模块 Node.jsfs模块读取文件写入文件__dirname path模块路径拼接文件名解析 http模块创建服务 Node.js 传统的JavaScript是运行在浏览器的&#xff0c;浏览器就是其运行环境。 浏览器提供了JavaScript的API&#xff0c;以及解析JavaScript的解析引擎&a…

《Web性能权威指南》-WebRTC-读书笔记

本文是《Web性能权威指南》第四部分——WebRTC的读书笔记。 第一部分——网络技术概览&#xff0c;请参考网络技术概览&#xff1b; 第二部分——无线网络性能&#xff0c;请参考无线网络性能&#xff1b; 第三部分——HTTP&#xff0c;请参考HTTP&#xff1b; 第四部分——浏览…

vscode ssh连接autodl失败

autodl服务器已开启&#xff0c;vscode弹窗显示连接失败 0. 检查状态 这里的端口和主机根据自己的连接更改 ssh -p 52165 rootregion-45.autodl.pro1. 修改config权限 按返回的路径找到config文件 右键--属性--安全--高级--禁用继承--从此对象中删除所有已继承的权限--添加…

HTML 基础标签——多媒体标签<img>、<object> 与 <embed>

文章目录 1. `<img>` 标签主要属性示例注意事项2. `<object>` 标签概述主要属性示例注意事项3. `<embed>` 标签概述主要属性示例注意事项小结在现代网页设计中,多媒体内容的使用变得越来越重要,因为它能够有效增强用户体验、吸引注意力并传达信息。HTML 提…

Android——横屏竖屏

系统配置变更的处理机制 为了避免横竖屏切换时重新加载界面的情况&#xff0c;Android设计了一中配置变更机制&#xff0c;在指定的环境配置发生变更之时&#xff0c;无需重启活动页面&#xff0c;只需执行特定的变更行为。该机制的视线过程分为两步&#xff1a; 修改 Androi…

宝塔使用clickhouse踩坑

前言 最近有个物联网项目&#xff0c;需要存储物联网终端发送过来的信息&#xff08;类似log日志&#xff0c;但又要存储在数据库里&#xff0c;方便后期聚合统计&#xff09;&#xff0c;本来想写文件的奈何客户要求聚合统计&#xff0c;所以只能用数据库才能达到更高的计算效…

探索Python新境界:Buzhug库的神秘面纱

文章目录 探索Python新境界&#xff1a;Buzhug库的神秘面纱第一部分&#xff1a;背景介绍第二部分&#xff1a;Buzhug库是什么&#xff1f;第三部分&#xff1a;如何安装Buzhug库&#xff1f;第四部分&#xff1a;Buzhug库函数使用方法第五部分&#xff1a;Buzhug库使用场景第六…

跨越科技与文化的桥梁——ROSCon China 2024 即将盛大开幕

在全球机器人技术飞速发展的浪潮中&#xff0c;ROS&#xff08;Robot Operating System&#xff09;作为一款开源的机器人操作系统&#xff0c;已成为无数开发者、研究人员和企业的首选工具。为了进一步推动ROS的应用与发展&#xff0c;全球知名的机器人操作系统会议——ROSCon…