2. 从服务器的主接口入手

Webserver 的主函数 main.cpp,完成了哪些功能?

#include "config.h"int main(int argc, char *argv[])
{string user = "";string passwd = "";string databasename = "";Config config;config.parse_arg(argc, argv);WebServer server;server.init(config.PORT, user, passwd, databasename, config.LOGWrite, config.OPT_LINGER, config.TRIGMode,  config.sql_num,  config.thread_num, config.close_log, config.actor_model);server.log_write();server.sql_pool();server.thread_pool();server.trig_mode();server.eventListen();server.eventLoop();return 0;
}

上面是服务器的主函数,初始化并启动服务器。它的主要功能包括解析命令行参数初始化服务器设置日志数据库连接池线程池、触发模式等。

数据库信息设置

这里是数据库连接的用户名、密码和数据库名称,根据实际需要修改为正确的数据库配置信息。

string user = "";
string passwd = "";
string databasename = "";

命令行参数解析

创建 Config 类对象 config 并调用 parse_arg 函数解析命令行参数,例如端口号、日志模式、触发模式、线程池线程数等设置。

Config config;	// 创建 Config 类对象 config
config.parse_arg(argc, argv);	// 调用 parse_arg 函数解析命令行参数

服务器初始化

初始化 WebServer 对象,传入多个配置信息,包括:

  • 端口号
  • 数据库信息:1 数据库信息设置 中设置的数据库用户名、密码和数据库名称
  • 日志配置
  • 触发模式
  • 数据库连接池大小
  • 线程池线程数
  • 日志关闭选项
  • 并发模型(actor_model)。

这一步会完成服务器的基本配置。

WebServer server;server.init(config.PORT, user, passwd, databasename, config.LOGWrite, config.OPT_LINGER, config.TRIGMode,  config.sql_num,  config.thread_num, config.close_log, config.actor_model);

日志系统

配置并初始化日志系统,用于记录服务器运行时的事件、错误、请求信息等,便于调试和维护。

server.log_write();

数据库连接池

初始化数据库连接池,管理多个数据库连接,提高数据库访问效率。连接池可以避免频繁的数据库连接开销。

server.sql_pool();

线程池

初始化线程池,创建一定数量的工作线程,用于并发处理客户端请求,提升服务器的并发能力

server.thread_pool();

触发模式

设置触发模式,配置事件监听的触发方式( LT 模式和 ET 模式),用于控制 I/O 多路复用的触发行为。

server.trig_mode();

监听端口

监听端口,准备接受客户端的连接请求。

server.eventListen();

事件循环

进入事件循环,开始处理客户端连接请求及其他事件。eventLoop 通常会运行在一个循环中,处理网络事件、请求解析、响应生成等操作。

server.eventLoop();

Config 类

Config 类,用于配置服务器项目中的一些参数,并通过命令行参数来动态调整这些配置。

class Config
{
public:Config();~Config(){};void parse_arg(int argc, char*argv[]);	// 使用 getopt 函数来解析命令行参数。// 成员函数int PORT;           // 端口号 (默认为9006)int LOGWrite;       // 日志写入方式 (默认同步写入且不关闭)int TRIGMode;       // 触发组合模式int LISTENTrigmode; // listenfd触发模式int CONNTrigmode;   // connfd触发模式int OPT_LINGER;     // 优雅关闭链接int sql_num;        // 数据库连接池数量int thread_num;     // 线程池内的线程数量int close_log;      // 是否关闭日志int actor_model;    // 并发模型选择 (默认使用 Proactor 模式)
};

构造函数

/*构造函数*/
Config::Config(){PORT = 9006;        // 端口号,默认9006LOGWrite = 0;       // 日志写入方式,默认同步TRIGMode = 0;       // 触发组合模式,默认listenfd LT + connfd LTLISTENTrigmode = 0; // listenfd触发模式,默认LTCONNTrigmode = 0;   // connfd触发模式,默认LTOPT_LINGER = 0;     // 优雅关闭链接,默认不使用sql_num = 8;        // 数据库连接池数量,默认8thread_num = 8;     // 线程池内的线程数量,默认8close_log = 0;      // 关闭日志,默认不关闭actor_model = 0;    // 并发模型,默认是proactor
}
void Config::parse_arg(int argc, char*argv[]){int opt;const char *str = "p:l:m:o:s:t:c:a:";while ((opt = getopt(argc, argv, str)) != -1){switch (opt){case 'p':PORT = atoi(optarg);break;case 'l':LOGWrite = atoi(optarg);break;case 'm':TRIGMode = atoi(optarg);break;case 'o':OPT_LINGER = atoi(optarg);break;case 's':sql_num = atoi(optarg);break;case 't':thread_num = atoi(optarg);break;case 'c':close_log = atoi(optarg);break;case 'a':actor_model = atoi(optarg);break;default:break;}}
}

使用 parse_arg 方法解析命令行参数

选项字符串 str"p:l:m:o:s:t:c:a:" 定义了可接受的选项,每个字母代表一个选项,后面的冒号 : 表示该选项需要一个参数。

解析循环:使用 while 循环调用 getopt,逐个解析命令行参数。

选项处理:根据解析到的选项字符,使用 atoi(optarg) 将字符串转换为整数,并赋值给对应的配置变量。

如果不传入任何命令行参数,程序将会使用在 Config 类构造函数中设置的默认值。因为在 parse_arg 函数中,如果没有提供任何参数,getopt 函数会立即返回 -1,因此不会对配置对象的成员变量进行任何修改。

/* 命令行参数解析 */
void Config::parse_arg(int argc, char*argv[]){int opt;const char *str = "p:l:m:o:s:t:c:a:";while ((opt = getopt(argc, argv, str)) != -1){switch (opt){case 'p':{PORT = atoi(optarg);break;}case 'l':{LOGWrite = atoi(optarg);break;}case 'm':{TRIGMode = atoi(optarg);break;}case 'o':{OPT_LINGER = atoi(optarg);break;}case 's':{sql_num = atoi(optarg);break;}case 't':{thread_num = atoi(optarg);break;}case 'c':{close_log = atoi(optarg);break;}case 'a':{actor_model = atoi(optarg);break;}default:break;}}
}

getopt 函数

getopt 函数是用于解析命令行参数的函数,通常在 C/C++ 程序中使用。它可以方便地处理带有选项的命令行参数,使程序支持类似于 Unix 风格的命令行接口。

#include <unistd.h>int getopt(int argc, char * const argv[], const char *optstring);

参数说明

  • argc:命令行参数的数量。
  • argv:命令行参数的数组。
  • optstring:选项字符,定义了可接受的选项字符及其是否需要参数。

返回值

  • 成功时,返回解析到的选项字符。
  • 如果所有选项解析完毕,返回 -1
  • 如果遇到未定义的选项字符,返回 ?

optstring格式

  • 每个字符代表一个可接受的选项。例如,"abc" 表示接受 -a-b-c 选项。
  • 如果一个选项需要参数,在其后加上冒号 :。例如,"a:b" 表示 -a 需要参数,-b 不需要参数。
  • 如果一个选项的参数是可选的,在其后加上两个冒号 ::。这种用法是 GNU 扩展,不是标准 C 库的一部分。

使用步骤

  1. 定义选项字符串:根据程序需要,定义可接受的选项及其参数要求。
  2. 循环调用getopt:使用 while 循环,不断调用 getopt,直到返回 -1
  3. 处理选项:在循环内,使用 switch 语句,根据返回的选项字符执行相应的操作。
  4. 处理非选项参数:在所有选项解析完毕后,optind 指向第一个非选项参数,可以继续处理剩余的命令行参数。

示例代码

以下面的代码为例:

#include <iostream>
#include <unistd.h>int main(int argc, char *argv[]) {int opt;/* a: 需要参数b: 可选参数c: 不需要参数*/std::string optString = "a:b::c";// 禁止 getopt 自动打印错误信息opterr = 0;while ((opt = getopt(argc, argv, optString.c_str())) != -1) {switch (opt) {case 'a':std::cout << "选项 -a,参数值:" << optarg << std::endl;break;case 'b':if (optarg)std::cout << "选项 -b,参数值:" << optarg << std::endl;elsestd::cout << "选项 -b,没有提供参数" << std::endl;break;case 'c':std::cout << "选项 -c,不需要参数" << std::endl;break;case '?':std::cerr << "未知选项:" << char(optopt) << std::endl;break;default:break;}}// 处理非选项参数for (int index = optind; index < argc; index++)std::cout << "非选项参数:" << argv[index] << std::endl;return 0;
}

运行:./getopt -a value1 -b value2 -c arg1 arg2

结果如下:

运行:./getopt -a value1 -bvalue2 -c arg1 arg2

结果如下:

在使用 getopt 函数并且定义了可选参数(使用 ::)的选项时,如果选项参数与选项之间有空格(例如 -b value2),那么 getopt 不会将 value2 视为 -b 的参数,而是将其作为非选项参数处理。

函数优化

原来的代码,只是实现了一个很简单的命令行参数解析逻辑,在一些情况下,如:

  • 提供了未知选项。改进方法:?分支中 处理 getopt 返回的 ?,提示用户输入了未知选项。
  • 提供了选项但是没有提供对应的参数。改进方法:在optstring 的开头加入冒号

optstring(选项字符串)中,开头的冒号 : 非常重要。它改变了 getopt 函数的错误处理方式。
当选项需要参数但未提供参数时:
如果 optstring 以冒号开头(如 :p:l:m:o:s:t:c:a:h),getopt 将返回 :,并将 optopt 设置为缺少参数的选项字符。
如果 optstring 不以冒号开头(如 p:l:m:o:s:t:c:a:h),getopt 将返回 ?,并将 optopt 设置为出错的选项字符。

  • -p 提供的端口号非法(端口号小于 0 或者大于 65535)。改进方法:PORT <= 0 || PORT > 65535,限制 -p 提供的参数范围
  • 参数格式非法(传入的字符串不是数字字符串):atoi 对非数字字符串会返回 0,无法检测输入是否为有效数字。如果用户输入非数字字符(如 -p abc),程序可能会继续运行,但使用了错误的参数值。 改进方法:使用 strtol

这些情况下,可能导致无法正确解析参数。

此外,还可以加入一个帮助选项-h,用于打印帮助信息:

/* 显示帮助信息 */
void Config::display_usage() {printf("用法: server [选项]...\n""选项列表:\n""  -p <端口号>           设置服务器监听端口号 (默认: 9006)\n""  -l <日志写入方式>     设置日志写入方式 (0: 同步, 1: 异步, 默认: 0)\n""  -m <触发模式>         设置触发模式 (0~3, 默认: 0)\n""                         0: listenfd LT + connfd LT\n""                         1: listenfd LT + connfd ET\n""                         2: listenfd ET + connfd LT\n""                         3: listenfd ET + connfd ET\n""  -o <优雅关闭连接>     是否使用优雅关闭连接 (0: 不使用, 1: 使用, 默认: 0)\n""  -s <数据库连接数>     设置数据库连接池连接数 (默认: 8)\n""  -t <线程数>           设置线程池内线程数量 (默认: 8)\n""  -c <关闭日志>         是否关闭日志 (0: 不关闭, 1: 关闭, 默认: 0)\n""  -a <并发模型>         选择并发模型 (0: Proactor, 1: Reactor, 默认: 0)\n""  -h                    显示此帮助信息\n""示例:\n""  server -p 8080 -t 16 -c 1\n");
}

改进后的完整代码如下:

#include "config.h"/* 构造函数 */
Config::Config(){PORT = 9006;        // 端口号,默认9006LOGWrite = 0;       // 日志写入方式,默认同步TRIGMode = 0;       // 触发组合模式,默认listenfd LT + connfd LTLISTENTrigmode = 0; // listenfd触发模式,默认LTCONNTrigmode = 0;   // connfd触发模式,默认LTOPT_LINGER = 0;     // 优雅关闭链接,默认不使用sql_num = 8;        // 数据库连接池数量,默认8thread_num = 8;     // 线程池内的线程数量,默认8close_log = 0;      // 关闭日志,默认不关闭actor_model = 0;    // 并发模型,默认是proactor
}/* 显示帮助信息 */
void Config::display_usage() {printf("用法: server [选项]...\n""选项列表:\n""  -p <端口号>           设置服务器监听端口号 (默认: 9006)\n""  -l <日志写入方式>     设置日志写入方式 (0: 同步, 1: 异步, 默认: 0)\n""  -m <触发模式>         设置触发模式 (0~3, 默认: 0)\n""                         0: listenfd LT + connfd LT\n""                         1: listenfd LT + connfd ET\n""                         2: listenfd ET + connfd LT\n""                         3: listenfd ET + connfd ET\n""  -o <优雅关闭连接>     是否使用优雅关闭连接 (0: 不使用, 1: 使用, 默认: 0)\n""  -s <数据库连接数>     设置数据库连接池连接数 (默认: 8)\n""  -t <线程数>           设置线程池内线程数量 (默认: 8)\n""  -c <关闭日志>         是否关闭日志 (0: 不关闭, 1: 关闭, 默认: 0)\n""  -a <并发模型>         选择并发模型 (0: Proactor, 1: Reactor, 默认: 0)\n""  -h                    显示此帮助信息\n""示例:\n""  server -p 8080 -t 16 -c 1\n");
}void Config::parse_arg(int argc, char* argv[]) {int opt;// 设置 optstring:选项字符const char *str = ":p:l:m:o:s:t:c:a:h";while ((opt = getopt(argc, argv, str)) != -1) {switch (opt) {case 'p': // 设置端口号,范围应在 1 到 65535 之间。{char *endptr;PORT = strtol(optarg, &endptr, 10);if (*endptr != '\0' || PORT <= 0 || PORT > 65535) {fprintf(stderr, "无效的端口号:%s\n", optarg);exit(EXIT_FAILURE);}}break;case 'l':{char *endptr;LOGWrite = strtol(optarg, &endptr, 10);if (*endptr != '\0' ) {fprintf(stderr, "无效的日志写入方式:%s\n", optarg);exit(EXIT_FAILURE);}}break;case 'm':{char *endptr;TRIGMode = strtol(optarg, &endptr, 10);if (*endptr != '\0' ) {fprintf(stderr, "无效的触发模式:%s\n", optarg);exit(EXIT_FAILURE);}}break;case 'o':{char *endptr;OPT_LINGER = strtol(optarg, &endptr, 10);if (*endptr != '\0' ) {fprintf(stderr, "无效的优雅关闭选项:%s\n", optarg);exit(EXIT_FAILURE);}}break;case 's':{char *endptr;sql_num = strtol(optarg, &endptr, 10);if (*endptr != '\0' || sql_num <= 0) {fprintf(stderr, "无效的数据库连接池数量:%s,应为正整数\n", optarg);exit(EXIT_FAILURE);}}break;case 't':{char *endptr;thread_num = strtol(optarg, &endptr, 10);if (*endptr != '\0' || thread_num <= 0) {fprintf(stderr, "无效的线程数量:%s,应为正整数\n", optarg);exit(EXIT_FAILURE);}}break;case 'c':{char *endptr;close_log = strtol(optarg, &endptr, 10);if (*endptr != '\0') {fprintf(stderr, "无效的日志关闭选项:%s\n", optarg);exit(EXIT_FAILURE);}}break;case 'a':{char *endptr;actor_model = strtol(optarg, &endptr, 10);if (*endptr != '\0' ) {fprintf(stderr, "无效的并发模型选项:%s\n", optarg);exit(EXIT_FAILURE);}}break;case 'h': // 显示帮助信息display_usage();exit(EXIT_SUCCESS);case ':': // 缺少参数fprintf(stderr, "选项 -%c 缺少一个参数。\n", optopt);exit(EXIT_FAILURE);case '?':fprintf(stderr, "输入了未定义的选项:-%c\n", optopt);exit(EXIT_FAILURE);default:fprintf(stderr, "解析选项时遇到未知错误\n");exit(EXIT_FAILURE);}}// 检查是否有非选项参数if (optind < argc) {fprintf(stderr, "存在多余的非选项参数:\n");for (int i = optind; i < argc; i++) {fprintf(stderr, "  %s\n", argv[i]);}exit(EXIT_FAILURE);}
}

优化效果

优化前,输入一个非法端口号启动服务器,如:./server -p -1

终端没有报错,但是服务器拒绝连接。

优化后,针对错误情况都提供了报错和退出处理:

优化后,输入 ./server -h 显示帮助信息:

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

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

相关文章

四、Prompt工程——简单应用

Prompt工程——简单应用 一、提示工程&#xff08;Prompt Engineering&#xff09;二、Prompt基本法则三、Prompt 调优四、简单的例子文本总结文本判断文本提取文本转化——翻译文本转化——语气 更多结语 一、提示工程&#xff08;Prompt Engineering&#xff09; 提示工程也…

5G RedCap工业路由器赋能电力物联网应用

随着5G轻量化技术应用的推进&#xff0c;5G RedCap旨在提供低功耗、低成本、广覆盖等功能特点赋能电力智能化升级。特别适用于工业物联网、低空经济、车联网、消费电子和轻量级5G的需求。 5G RedCap工业路由器的特点 低功耗&#xff1a;5G RedCap工业路由器通过节能技术&#…

Flume采集Kafka数据到Hive

版本&#xff1a; Kafka&#xff1a;2.4.1 Flume&#xff1a;1.9.0 Hive&#xff1a;3.1.0 Kafka主题准备&#xff1a; Hive表准备&#xff1a;确保hive表为&#xff1a;分区分桶、orc存储、开启事务 Flume准备&#xff1a; 配置flume文件&#xff1a; /opt/datasophon/flume-1…

react18中react-thunk实现公共数据仓库的异步操作

redux及react-redux都只能实现数据的同步修改更新&#xff0c;有点类似于vue中的mutation&#xff0c;只能做同步操作&#xff0c;异步的话不用actions来实现。由于在项目始终不可避免要实现的异步数据的更新&#xff0c;这明显不够用了。是时候引入我们的异步中间件redux-thun…

开源一款前后端分离的企业级网站内容管理系统,支持站群管理、多平台静态化,多语言、全文检索的源码

大家好&#xff0c;我是一颗甜苞谷&#xff0c;今天分享一款前后端分离的企业级网站内容管理系统&#xff0c;支持站群管理、多平台静态化&#xff0c;多语言、全文检索的源码。 前言 在当今的数字化时代&#xff0c;企业网站和个人博客已成为信息传播和品牌建设的重要渠道。…

Docker-常用命令大全(附命令详解)

文章目录 Docker 基础命令查看docker 运行状态关闭docker启动docker重启dockerdocker设置随服务启动而自启动查看docker 版本号信息docker 帮助命令 docker 镜像命令查看自己服务器中docker 镜像列表搜索镜像拉取镜像运行镜像保存镜像删除镜像加载镜像镜像标签 Docker 容器命令…

【ComfyUI】手动安装部署ComfyUI的运行环境

如果不喜欢已有的一键启动包&#xff0c;我们可以手动的安装和部署ComfyUI的运行环境&#xff0c;相比一键安装包&#xff0c;自己部署ComfyUI 环境具有相当大的灵活性&#xff0c;其实部署ComfyUI 环境非常简单&#xff0c;不像网上说的那么复杂。下面我们就按照顺序给大家分享…

Golang | Leetcode Golang题解之第520题检测大写字母

题目&#xff1a; 题解&#xff1a; func detectCapitalUse(word string) bool {// 若第 1 个字母为小写&#xff0c;则需额外判断第 2 个字母是否为小写if len(word) > 2 && unicode.IsLower(rune(word[0])) && unicode.IsUpper(rune(word[1])) {return f…

【Cri-Dockerd】安装cri-dockerd

cri-dockerd的作用&#xff1a; 在k8s1.24之前。k8s会通过dockershim来调用docker进行容器运行时containerd&#xff0c;并且会自动安装dockershim&#xff0c;但是从1.24版本之前k8s为了降低容器运行时的调用的复杂度和效率&#xff0c;直接调用containerd了&#xff0c;并且…

git下载和配置

git是什么&#xff1f; Git是一种分布式版本控制系统&#xff0c;用于跟踪文件的变化&#xff0c;尤其是源代码。它允许多个开发者在同一项目上进行协作&#xff0c;同时保持代码的历史记录。Git的主要特点包括&#xff1a; 分布式&#xff1a;每个开发者都有项目的完整副本&a…

GPT避坑指南:如何辨别逆向、AZ、OpenAI官转

市面上有些说自己是官转&#xff0c;一刀只需要1块甚至几毛钱&#xff0c;并声称官方倍率的&#xff0c;很大可能就是使用的是 逆向或Azure。 如何鉴别逆向 逆向的种类很多&#xff0c;主要分为3类 逆向不知名A| 镜像站或偷的 key。成本约等于0&#xff0c;调用聊天数据可能在…

postgresql增量备份系列一

简介 在一些大容量得数据库应用中&#xff0c;采用全量备份得方式&#xff0c;会带来大量时间浪费和开销&#xff0c;此时定期的增量备份可以使得数据存储周期变长。本文讲解几个增量备份工具 pg_basebackup pg_receivewal&#xff08;异地归档模式&#xff09; 使用pg_bas…

arcgis pro 3.3.1安装教程

一、获取方式&#xff1a; http://dt4.8tupian.net/2/29913a61b1500.pg3二、软件目录&#xff1a; 三、安装步骤&#xff1a; &#xff08;1&#xff09;安装软件运行环境windowsdesktop-runtime 8.0.4; &#xff08;2&#xff09;选中安装文件arcgispro_33zh_cn_190127.exe&…

LabVIEW汽车状态监测系统

LabVIEW汽车状态监测系统通过模拟车辆运行状态&#xff0c;有效地辅助工程师进行故障预测和维护计划优化&#xff0c;从而提高汽车的可靠性和安全性。 项目背景&#xff1a; 现代汽车工业面临着日益增长的安全要求和客户对于车辆性能的高期望。汽车状态监测系统旨在实时监控汽…

GiantPandaCVARM Neon Intrinsics 学习指北:从入门、进阶到学个通透

【GiantPandaCV导语】Neon是手机普遍支持的计算加速指令集&#xff0c;是AI落地的工程利器。Neon Intrinsics 的出现&#xff0c;缓解了汇编语言难学难写的难题&#xff0c;值得工程师们开发利用。 前言 Neon是ARM平台的向量化计算指令集&#xff0c;通过一条指令完成多个数据…

python爬虫抓取豆瓣数据教程

环境准备 在开始之前&#xff0c;你需要确保你的Python环境已经安装了以下库&#xff1a; requests&#xff1a;用于发送HTTP请求。BeautifulSoup&#xff1a;用于解析HTML文档。 如果你还没有安装这些库&#xff0c;可以通过以下命令安装&#xff1a; pip install requests…

SD-WAN分布式组网:构建高效、灵活的企业网络架构

随着企业数字化转型的深入&#xff0c;分布式组网逐渐成为企业网络架构中的核心需求。无论是跨区域的分支机构互联&#xff0c;还是企业与云服务的连接&#xff0c;如何在不同区域实现高效、低延迟的网络传输&#xff0c;已成为业务成功的关键。SD-WAN&#xff08;软件定义广域…

使用Python和OpenCV实现火焰检测

使用Python和OpenCV实现火焰检测 项目解释&#xff1a; 此 Python 代码是使用 OpenCV、线程、声音和电子邮件功能的火灾探测系统的简单示例。 以下是它的功能的简单描述&#xff1a; 导入库&#xff1a;代码首先导入必要的库&#xff1a; cv2&#xff1a;用于图像和视频处理…

一篇文章入门傅里叶变换

文章目录 傅里叶变换欧拉公式傅里叶变换绕圈记录法质心记录法傅里叶变换公式第一步&#xff1a;旋转的表示第二步&#xff1a;缠绕的表示第三步&#xff1a;质心的表示最终步&#xff1a;整理积分限和系数 参考文献 傅里叶变换 在学习傅里叶变换之前&#xff0c;我们先来了解一…

基于uniapp微信小程序的校园二手书交易系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…