多进程网络服务端详细说明文档

多进程网络服务端详细说明文档

一、概述

本项目实现了一个基于多进程的 TCP 网络服务端,主要用于处理多个客户端的连接请求。为了提高代码的可维护性和可复用性,分成了头文件(.h)和多个源文件(.cpp)。具体包含 ctcpserver.hctcpserver.cppmulti_process_network_server.cpp 三个文件。

二、文件结构及功能
1. ctcpserver.h
  • 功能:该头文件主要用于声明 ctcpserver 类,定义了类的成员变量和成员函数接口,起到了对类的功能进行抽象和声明的作用,方便其他源文件引用该类。
  • 关键内容
    • 成员变量
      • m_listenfd:监听的 socket 描述符,-1 表示未初始化。
      • m_clientfd:客户端连接的 socket 描述符,-1 表示客户端未连接。
      • m_clientip:客户端的 IP 地址,以字符串形式存储。
      • m_port:服务端用于通信的端口号。
    • 成员函数声明:包含了构造函数、析构函数以及一系列用于初始化服务端、接受客户端连接、发送和接收数据、关闭 socket 等操作的函数声明。
class ctcpserver {
private:int m_listenfd;int m_clientfd;std::string m_clientip;unsigned short m_port;
public:ctcpserver();~ctcpserver();bool initserver(const unsigned short in_port);bool accept();const std::string & clientip() const;bool send(const std::string &buffer);bool recv(std::string &buffer, const size_t maxlen);bool closelisten();bool closeclient();
};
2. ctcpserver.cpp
  • 功能:该源文件实现了 ctcpserver.h 中声明的 ctcpserver 类的所有成员函数,是类的具体实现部分。
  • 关键函数及实现细节
    • 构造函数 ctcpserver::ctcpserver():初始化 m_listenfdm_clientfd-1,表示初始状态下监听 socket 和客户端 socket 未初始化。
    • 析构函数 ctcpserver::~ctcpserver():调用 closelisten()closeclient() 函数,确保在对象销毁时关闭监听 socket 和客户端 socket,释放资源。
    • initserver 函数
      • 使用 socket() 函数创建一个 TCP 套接字,使用 IPv4 协议(AF_INET)和面向连接的 TCP 协议(SOCK_STREAM)。
      • 填充 sockaddr_in 结构体,设置协议族、端口号(使用 htons() 进行字节序转换)和 IP 地址(INADDR_ANY 表示监听所有可用 IP)。
      • 使用 bind() 函数将套接字绑定到指定的 IP 和端口,若绑定失败则关闭套接字并返回 false
      • 使用 listen() 函数将套接字设置为监听状态,允许最多 5 个客户端连接请求进入队列,若设置失败则关闭套接字并返回 false
    • accept 函数
      • 使用 accept() 函数从监听队列中取出一个客户端连接请求,返回一个新的套接字描述符用于与该客户端通信。
      • 使用 inet_ntoa() 函数将客户端的 IP 地址从大端序转换为字符串格式并存储在 m_clientip 中。
    • send 函数:检查客户端 socket 是否有效,若有效则使用 send() 函数向客户端发送数据。
    • recv 函数
      • 清空接收缓冲区并调整其大小为指定的最大长度。
      • 使用 recv() 函数接收客户端发送的数据,若接收失败则清空缓冲区并返回 false,否则根据实际接收的字节数调整缓冲区大小并返回 true
    • closelisten 函数:关闭监听 socket 并将 m_listenfd 设置为 -1
    • closeclient 函数:关闭客户端 socket 并将 m_clientfd 设置为 -1
3. multi_process_network_server.cpp
  • 功能:该文件是整个服务端程序的入口,包含 main 函数和信号处理函数,负责初始化服务端、处理客户端连接和信号处理。
  • 关键流程及函数说明
    • main 函数
      • 参数检查:检查命令行参数是否正确,若不正确则输出使用说明并退出程序。
      • 信号处理设置
        • 使用 for 循环忽略所有信号,避免程序被不必要的信号干扰,同时解决僵尸进程问题。
        • 设置 SIGTERMSIGINT 信号的处理函数为 FathEXIT,允许通过 kill 命令或 Ctrl + C 正常终止程序。
      • 服务端初始化:调用 tcpserver.initserver() 函数初始化服务端监听 socket,若初始化失败则输出错误信息并退出。
      • 客户端连接处理
        • 使用 while 循环不断接受客户端连接请求,若接受失败则输出错误信息并退出。
        • 调用 fork() 函数创建子进程,父进程关闭客户端 socket 并继续等待下一个客户端连接;子进程关闭监听 socket,重新设置信号处理函数,并与客户端进行通信。
      • 子进程通信处理
        • 循环接收客户端发送的数据,若接收失败则退出循环。
        • 接收到数据后,向客户端发送 “ok” 作为响应,若发送失败则退出循环。
    • 信号处理函数
      • FathEXIT 函数:处理父进程接收到的 SIGTERMSIGINT 信号。首先屏蔽这两个信号,防止信号处理函数被中断;输出父进程退出信息,使用 kill(0, SIGTERM) 向所有子进程发送终止信号;关闭监听 socket 并退出父进程。
      • ChldEXIT 函数:处理子进程接收到的 SIGTERM 信号。屏蔽 SIGINTSIGTERM 信号,输出子进程退出信息,关闭客户端 socket 并退出子进程。
四、注意事项
  • 资源管理:每个子进程在处理完与客户端的通信后必须退出,否则会继续进入 accept 函数,导致错误。同时,要确保在程序结束时正确关闭所有 socket 资源,避免资源泄漏。
  • 信号处理:信号处理函数中屏蔽了 SIGINTSIGTERM 信号,防止信号处理函数被中断,确保程序的稳定性。
  • 高并发场景:该程序使用多进程处理客户端连接,每个客户端连接会创建一个新的子进程,可能会消耗较多的系统资源。在高并发场景下,可以考虑使用多线程或异步 I/O 技术进行优化。

1. ctcpserver.h 头文件

#ifndef CTCP_SERVER_H
#define CTCP_SERVER_H#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>class ctcpserver {
private:int m_listenfd;        // 监听的socket,-1表示未初始化int m_clientfd;        // 客户端连上来的socket,-1表示客户端未连接std::string m_clientip;     // 客户端字符串格式的IPunsigned short m_port; // 服务端用于通讯的端口
public:ctcpserver();~ctcpserver();// 初始化服务端用于监听的socketbool initserver(const unsigned short in_port);// 受理客户端的连接bool accept();// 获取客户端的IP(字符串格式)const std::string & clientip() const;// 向对端发送报文bool send(const std::string &buffer);// 接收对端的报文bool recv(std::string &buffer, const size_t maxlen);// 关闭监听的socketbool closelisten();// 关闭客户端连上来的socketbool closeclient();
};#endif

2. ctcpserver.cpp 源文件

#include "ctcpserver.h"// 构造函数,初始化监听socket和客户端socket为未初始化状态
ctcpserver::ctcpserver() : m_listenfd(-1), m_clientfd(-1) {}// 析构函数,关闭监听socket和客户端socket
ctcpserver::~ctcpserver() {closelisten();closeclient();
}// 初始化服务端用于监听的socket
bool ctcpserver::initserver(const unsigned short in_port) {// 第1步:创建服务端的socket// AF_INET表示使用IPv4协议,SOCK_STREAM表示使用面向连接的TCP协议if ((m_listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) return false;m_port = in_port;// 第2步:把服务端用于通信的IP和端口绑定到socket上struct sockaddr_in servaddr;  // 用于存放协议、端口和IP地址的结构体memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;  // 协议族,固定填AF_INETservaddr.sin_port = htons(m_port);  // 指定服务端的通信端口,htons用于将主机字节序转换为网络字节序servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  // 如果操作系统有多个IP,全部的IP都可以用于通讯// 绑定服务端的IP和端口(为socket分配IP和端口)if (bind(m_listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {close(m_listenfd);m_listenfd = -1;return false;}// 第3步:把socket设置为可连接(监听)的状态// 5表示监听队列的最大长度if (listen(m_listenfd, 5) == -1) {close(m_listenfd);m_listenfd = -1;return false;}return true;
}// 受理客户端的连接(从已连接的客户端中取出一个客户端)
// 如果没有已连接的客户端,accept()函数将阻塞等待
bool ctcpserver::accept() {struct sockaddr_in caddr;  // 客户端的地址信息socklen_t addrlen = sizeof(caddr);  // struct sockaddr_in的大小// 从监听队列中取出一个客户端连接if ((m_clientfd = ::accept(m_listenfd, (struct sockaddr *)&caddr, &addrlen)) == -1) return false;m_clientip = inet_ntoa(caddr.sin_addr);  // 把客户端的地址从大端序转换成字符串return true;
}// 获取客户端的IP(字符串格式)
const std::string & ctcpserver::clientip() const {return m_clientip;
}// 向对端发送报文,成功返回true,失败返回false
bool ctcpserver::send(const std::string &buffer) {if (m_clientfd == -1) return false;// 发送报文if ((::send(m_clientfd, buffer.data(), buffer.size(), 0)) <= 0) return false;return true;
}// 接收对端的报文,成功返回true,失败返回false
// buffer - 存放接收到的报文的内容,maxlen - 本次接收报文的最大长度
bool ctcpserver::recv(std::string &buffer, const size_t maxlen) {buffer.clear();  // 清空容器buffer.resize(maxlen);  // 设置容器的大小为maxlen// 直接操作buffer的内存接收数据int readn = ::recv(m_clientfd, &buffer[0], buffer.size(), 0);if (readn <= 0) {buffer.clear();return false;}buffer.resize(readn);  // 重置buffer的实际大小return true;
}// 关闭监听的socket
bool ctcpserver::closelisten() {if (m_listenfd == -1) return false;::close(m_listenfd);m_listenfd = -1;return true;
}// 关闭客户端连上来的socket
bool ctcpserver::closeclient() {if (m_clientfd == -1) return false;::close(m_clientfd);m_clientfd = -1;return true;
}

3. multi_process_network_server.cpp 主源文件

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <netdb.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include "ctcpserver.h"ctcpserver tcpserver;// 父进程的信号处理函数
void FathEXIT(int sig);
// 子进程的信号处理函数
void ChldEXIT(int sig);int main(int argc, char *argv[]) {// 检查命令行参数if (argc != 2) {std::cout << "Using:./multi_process_network_server 通讯端口\nExample:./multi_process_network_server 5005\n\n";std::cout << "注意:运行服务端程序的Linux系统的防火墙必须要开通5005端口。\n";std::cout << "      如果是云服务器,还要开通云平台的访问策略。\n\n";return -1;}// 忽略全部的信号,不希望被打扰。顺便解决了僵尸进程的问题for (int ii = 1; ii <= 64; ii++) signal(ii, SIG_IGN);// 设置信号,在shell状态下可用 "kill 进程号" 或 "Ctrl+c" 正常终止些进程// 但请不要用 "kill -9 +进程号" 强行终止signal(SIGTERM, FathEXIT);signal(SIGINT, FathEXIT);  // SIGTERM 15 SIGINT 2// 初始化服务端用于监听的socketif (tcpserver.initserver(atoi(argv[1])) == false) {perror("initserver()");return -1;}while (true) {// 受理客户端的连接(从已连接的客户端中取出一个客户端)// 如果没有已连接的客户端,accept()函数将阻塞等待if (tcpserver.accept() == false) {perror("accept()");return -1;}// 创建子进程处理客户端连接int pid = fork();if (pid == -1) {perror("fork()");return -1;}  // 系统资源不足if (pid > 0) {// 父进程tcpserver.closeclient();  // 父进程关闭客户端连接的socketcontinue;  // 父进程返回到循环开始的位置,继续受理客户端的连接}// 子进程tcpserver.closelisten();  // 子进程关闭监听的socket// 子进程需要重新设置信号signal(SIGTERM, ChldEXIT);  // 子进程的退出函数与父进程不一样signal(SIGINT, SIG_IGN);  // 子进程不需要捕获SIGINT信号// 子进程负责与客户端进行通讯std::cout << "客户端已连接(" << tcpserver.clientip() << ")。\n";std::string buffer;while (true) {// 接收对端的报文,如果对端没有发送报文,recv()函数将阻塞等待if (tcpserver.recv(buffer, 1024) == false) {perror("recv()");break;}std::cout << "接收:" << buffer << std::endl;buffer = "ok";if (tcpserver.send(buffer) == false) {  // 向对端发送报文perror("send");break;}std::cout << "发送:" << buffer << std::endl;}return 0;  // 子进程一定要退出,否则又会回到accept()函数的位置}
}// 父进程的信号处理函数
void FathEXIT(int sig) {// 以下代码是为了防止信号处理函数在执行的过程中再次被信号中断signal(SIGINT, SIG_IGN);signal(SIGTERM, SIG_IGN);std::cout << "父进程退出,sig=" << sig << std::endl;kill(0, SIGTERM);  // 向全部的子进程发送15的信号,通知它们退出// 在这里增加释放资源的代码(全局的资源)tcpserver.closelisten();  // 父进程关闭监听的socketexit(0);
}// 子进程的信号处理函数
void ChldEXIT(int sig) {// 以下代码是为了防止信号处理函数在执行的过程中再次被信号中断signal(SIGINT, SIG_IGN);signal(SIGTERM, SIG_IGN);std::cout << "子进程" << getpid() << "退出,sig=" << sig << std::endl;// 在这里增加释放资源的代码(只释放子进程的资源)tcpserver.closeclient();  // 子进程关闭客户端连上来的socketexit(0);
}

代码说明

  • ctcpserver.h:定义了 ctcpserver 类的接口,包括类的成员变量和成员函数的声明。使用了预处理器指令 #ifndef#define#endif 来防止头文件被重复包含。
  • ctcpserver.cpp:实现了 ctcpserver 类的成员函数,包括构造函数、析构函数以及各种操作函数。
  • multi_process_network_server.cpp:主源文件,包含了 main 函数和信号处理函数,使用 ctcpserver 类来实现多进程的网络服务端。

编译和运行

将上述三个文件放在同一目录下,使用以下命令进行编译:

g++ -o multi_process_network_server ctcpserver.cpp multi_process_network_server.cpp

编译成功后,使用以下命令运行服务端程序:

./multi_process_network_server 5005

其中 5005 是服务端监听的端口号,你可以根据需要修改。

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

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

相关文章

向量数据库milvus部署

官方文档 Milvus vector database documentationRun Milvus in Docker (Linux) | Milvus DocumentationMilvus vector database documentation 按部署比较简单&#xff0c;这里说一下遇到的问题 一&#xff1a;Docker Compose 方式部署 1、镜像无法拉取,(docker.io被禁) …

Java 基础面试题

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;…

清华大学《AIGC发展研究3.0》

大家好&#xff0c;我是吾鳴。 AIGC已经爆火好长一段时间了&#xff0c;特别是DeepSeek的爆火&#xff0c;直接让很多之前没有体会过推理模型的人可以免费的使用上推理模型&#xff0c;同时DeepSeek产品形态也是全球首创&#xff0c;就是直接把AI的思考过程展示给你看&#xff…

苹果CMS泛目录站群架构:无缓存刷新技术的SEO实战

一、技术背景与行业痛点 传统泛目录站群系统普遍依赖静态缓存机制&#xff0c;导致两个核心问题&#xff1a; 缓存臃肿&#xff1a;运行3-6个月后缓存文件可达数百GB量级&#xff0c;严重影响服务器性能内容僵化&#xff1a;缓存机制导致页面TDK&#xff08;标题/描述/关键词…

iview table组件中修改按钮时 要注意是否真的修改了值

如图所示&#xff0c; switch按钮的默认值用dj来控制&#xff0c;但是如果没有加事情去修改切换后的值的话&#xff0c;那么他只会修改本身的显示值&#xff0c;但是我们需要跟着修改的列表数据的dj值是不会修改的&#xff0c;所以要注意&#xff0c;一定要加上事情去修改确定的…

Go中slice和map引用传递误区

背景 关于slice和map是指传递还是引用传递&#xff0c;很多文章都分析得模棱两可&#xff0c;其实在Go中只有值传递&#xff0c;但是很多情况下是因为分不清slice和map的底层实现&#xff0c;所以导致很多人在这一块产生疑惑&#xff0c;下面通过代码案例分析slice和map到底是…

Linux网络基础(协议 TCP/IP 网络传输基本流程 IP VS Mac Socket编程UDP)

文章目录 一.前言二.协议协议分层分层的好处 OSI七层模型TCP/IP五层(或四层)模型为什么要有TCP/IP协议TCP/IP协议与操作系统的关系(宏观上是如何实现的)什么是协议 三.网络传输基本流程局域网(以太网为例)通信原理MAC地址令牌环网 封装与解包分用 四.IP地址IP VS Mac地址 五.So…

python-leetcode-乘积最大子数组

152. 乘积最大子数组 - 力扣&#xff08;LeetCode&#xff09; class Solution:def maxProduct(self, nums: List[int]) -> int:if not nums:return 0max_prod nums[0]min_prod nums[0]result nums[0]for i in range(1, len(nums)):if nums[i] < 0:max_prod, min_prod…

图像处理之图像边缘检测算法

目录 1 图像边缘检测算法简介 2 Sobel边缘检测 3 经典的Canny边缘检测算法 4 演示Demo 4.1 开发环境 4.2 功能介绍 4.3 下载地址 参考 1 图像边缘检测算法简介 图像边缘检测是计算机视觉和图像处理中的基本问题&#xff0c;主要目的是提取图像中明暗变化明显的边缘细节…

React 源码揭秘 | Effect更新流程

前面的文章介绍了 hooks和commit流程&#xff0c;算是前置知识&#xff0c;这篇来讨论一下useEffect的原理。 useEffect用来处理副作用&#xff0c;比如网络请求&#xff0c;dom操作等等, 其本质也是个hooks&#xff0c;包含hooks的memorizedState, updateQueue, next Effec…

【Linux】vim 设置

【Linux】vim 设置 零、起因 刚学Linux&#xff0c;有时候会重装Linux系统&#xff0c;然后默认的vi不太好用&#xff0c;需要进行一些设置&#xff0c;本文简述如何配置一个好用的vim。 壹、软件安装 sudo apt-get install vim贰、配置路径 对所有用户生效&#xff1a; …

qt-C++笔记之QtCreator新建项目即Create Project所提供模板的逐个尝试

qt-C笔记之QtCreator新建项目即Create Project所提供模板的逐个尝试 code review! 文章目录 qt-C笔记之QtCreator新建项目即Create Project所提供模板的逐个尝试1.Application(Qt):Qt Widgets Application1.1.qmake版本1.2.cmake版本 2.Application(Qt):Qt Console Applicati…

Vue 项目中配置代理的必要性与实现指南

Vue 项目中配置代理的必要性与实现指南 在 Vue 前端项目的开发过程中&#xff0c;前端与后端地址通常不同&#xff0c;可能引发跨域问题。为了在开发环境下顺畅地请求后端接口&#xff0c;常常会通过配置**代理&#xff08;proxy&#xff09;**来解决问题。这篇文章将详细解析…

Linux运维命令-三剑客(grep awk sed)

目录 1.简介 2.命令详解 2.1.grep命令 2.1.1.功能 2.1.2.常见的使用场景及命令 2.2.awk命令 2.2.1.功能 2.2.2.常见的使用场景及命令 2.3.sed命令 2.3.1.功能 2.&#xff13;.2.常见的使用场景及命令 3.总结 1.简介 在Linux中&#xff0c;grep、awk、sed 命令常被称…

浅析 Redis 分片集群 Cluster 原理、手动搭建、动态伸缩集群、故障转移

大家好&#xff0c;我是此林。 之前的文章中分享了 Redis 集群方案的一种&#xff1a;主从集群哨兵机制 浅谈 Redis 主从集群原理&#xff08;一&#xff09;-CSDN博客 浅谈 Redis 主从复制原理&#xff08;二&#xff09;-CSDN博客 这种模式有什么缺点呢&#xff1f; 1. 虽…

Javaweb后端数据库多表关系一对多,外键,一对一

多表关系 一对多 多的表里&#xff0c;要有一表里的主键 外键 多的表上&#xff0c;添加外键 一对一 多对多 案例

PhotoLine绿色版 v25.00:全能型图像处理软件的深度解析

在图像处理领域,PhotoLine以其强大的功能和紧凑的体积,赢得了国内外众多用户的喜爱。本文将为大家全面解析PhotoLine绿色版 v25.00的各项功能,帮助大家更好地了解这款全能型的图像处理软件。 一、迷你体积,强大功能 PhotoLine被誉为迷你版的Photoshop,其体积虽小,但功能却…

Windows 11【1001问】修改主题隐藏或删除Win11桌面“了解此图片”

在<Windows 11【1001问】如何安装Windows 11>篇幅中我们第一安装完成Windows 11还未开始其他操作的时候会发现桌面上有一个“了解此图片”的图标是之前没见过的&#xff1b;而在Windows 11中&#xff0c;“了解此图片”图标是微软引入的一项功能&#xff0c;旨在让用户通…

Spring MVC框架二:创建第一个MVC程序

精心整理了最新的面试资料&#xff0c;有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 有两种方式 利用配置 1、利用IDEA新建一个Maven项目&#xff0c;添加一个web支持 2、导入常用的依赖 <dependencies><dependency><groupId>junit&…

go基本语法

跟Java比较学习。 hello word 示例代码 test1.go文件&#xff1a; // 包路径 package main// 导入模块&#xff0c;下面两种都行 import ("fmt" ) import "log"// main方法 func main() {log.Print("hello word !!!")fmt.Print("hello …