德州哪家网站优化公司专业,谷歌平台推广,我要表白网app,论坛程序目录
1. 项目介绍
2. 技术选型
3. 开发环境和环境搭建
Ubuntu-22.04环境搭建
1. 安装 wget#xff08;一般情况下默认会自带#xff09;
2. 更换国内软件源
① 备份原始 /etc/apt/sources.list 文件
② 编辑软件源文件
③ 更新软件包列表
3. 安装常用工具
3.1 安装…目录
1. 项目介绍
2. 技术选型
3. 开发环境和环境搭建
Ubuntu-22.04环境搭建
1. 安装 wget一般情况下默认会自带
2. 更换国内软件源
① 备份原始 /etc/apt/sources.list 文件
② 编辑软件源文件
③ 更新软件包列表
3. 安装常用工具
3.1 安装 lrzsz 传输工具
3.2 安装编译器 gcc/g
3.3 安装项目构建工具 make
3.4 安装调试器 gdb
3.5 安装版本控制系统 git
3.6 安装 cmake
3.7 安装 jsoncpp
4. 安装 Muduo 库
4. 第三方库使用介绍
4.1 JsonCpp库
4.1.1 Json的数据格式
4.1.2 JsonCpp介绍
4.1.3 JsonCpp使用
4.2 Muduo库
4.2.1 Muduo库是什么
4.2.2 Muduo库常见接口介绍
1. TcpServer 类
2. EventLoop 类
3. TcpConnection 类
4. TcpClient 类
5. Buffer 类
4.2.3 Muduo库的使用
1. 实现英译汉 TCP 服务器
2. 实现英译汉 TCP 客户端
3. Makefile 编译指令
运行结果
回调函数
1. 解耦网络事件处理逻辑
2. 事件驱动的核心机制
3. 提高代码复用性与可维护性
4. 非阻塞与多线程安全
5. 动态响应不同需求 1. 项目介绍
1
RPCRemote Procedure Call远程过程调用是⼀种通过网络从远程计算机上请求服务而不需要了解底层网络通信细节。RPC可以使用多种网络协议进行通信 如HTTP、TCP、UDP等 并且在TCP/IP网络四层模型中跨越了传输层和应用层。简言之 RPC就是像调用本地方法⼀样调用远程方法。 此过程可以理解为业务处理、计算任务更直白的说就是程序/方法/函数等就是像调用本地放法⼀样调用远程放法。 2
举个形象点谈恋爱的例子
本地过程调用恋爱对象在你的身边 可以随时约对象吃饭、看电影、约会等等远端过程调用好像异地恋⼀样 隔着千山万水 如果想约会 需要先和对象进行约定在坐火车/飞机赶到约定的地点 3
⼀个完整RPC通信框架大概包含以下内容
序列化协议。通信协议。连接复用。服务注册。服务发现。服务订阅和通知。负载均衡。服务监控。同步调用。异步调用。
本项目是基于C、JsonCpp、muduo网络库实现⼀个简单、易用的RPC通信框架
实现了 同步调用、异步callback调用、异步futrue调用、服务注册/发现/上线/下线主题发布订阅等功能设计。 2. 技术选型
1目前RPC的实现方案有两种
第一种是 client和server继承公共接口
根据IDL接口描述语言定义公共接口。编写代码生成器根据IDL语言生成相关的C、Java代码然后我们的客户端和服务器程序 共同向上继承公共接口即可
比如我们常用的Protobuf、json可以定义IDL接口并生成RPC相关的代码
缺点使用pb因为生成⼀部分代码 所以对理解不够友好如果是json定义IDL语言需要自己编写代码⽣成器难度较大一点 暂不考虑这种方案 第二种实现⼀个远程调用接口call然后通过传入函数名参数来调用RPC接口。本项目采用这种实现方案。 2网络传输的参数和返回值 怎么映射到对应的RPC 接口上 使用protobuf的反射机制。使用C模板、类型萃取、函数萃取等机制。使用更通用的类型 比如 JSON类型 设计好参数和返回值协议即可。
前两种技术难度和学习成本较高 本项目使用第三种方式。 3网络传输怎么做 原生socket - 实现难度较⼤ 暂不考虑。Boost asio库的异步通信 - 需要扩展boost库。使用muduo库学习开发成本较低。 4序列化和反序列化 Protobuf可选。JSON因为项⽬需要使⽤JSON来定义函数参数和返回值所以本项目中 直接采用JSON进行序列化和反序列化。 3. 开发环境和环境搭建
LinuxCentos-7.6 / Ubuntu-22.04。VSCode/Vim。g/gdb。Makefile。
Ubuntu-22.04环境搭建
1. 安装 wget一般情况下默认会自带
在Ubuntu 22.04中wget通常已经预安装。如果需要确保它已安装可以运行以下命令
sudo apt-get update
sudo apt-get install -y wget
2. 更换国内软件源
为了加速下载速度和提高稳定性可以将官方的APT源更换为国内镜像源。
① 备份原始 /etc/apt/sources.list 文件
首先备份原来的软件源文件以防出现问题时可以恢复
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
② 编辑软件源文件
接下来使用文本编辑器打开并编辑/etc/apt/sources.list文件以添加新的源。推荐使用nano或您喜欢的其他编辑器
sudo nano /etc/apt/sources.list
然后在文件中添加或替换以下内容这里使用了阿里云和清华大学的镜像源
deb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse# 添加清华源
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-updates main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-backports main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-security main restricted universe multiverse
请注意这里的focal是Ubuntu 20.04 LTS的代号。对于Ubuntu 22.04请根据实际情况更改为jammy。
③ 更新软件包列表
保存更改后通过以下命令更新APT软件包列表
sudo apt-get update
3. 安装常用工具
现在可以安装一些常用的开发工具和库。
3.1 安装 lrzsz 传输工具
用于从远程服务器上传和下载文件
sudo apt-get install lrzsz
3.2 安装编译器 gcc/g
安装GNU编译器集合包括C和C编译器
sudo apt-get install gcc g
3.3 安装项目构建工具 make
用于管理和构建项目
sudo apt-get install make
3.4 安装调试器 gdb
用于调试程序
sudo apt-get install gdb
3.5 安装版本控制系统 git
用于代码版本管理
sudo apt-get install git
3.6 安装 cmake
用于跨平台的项目构建系统
sudo apt-get install cmake
3.7 安装 jsoncpp
用于解析JSON数据格式
sudo apt-get install libjsoncpp-dev 4. 安装 Muduo 库
1. 下载源码
可以通过 git 命令从 GitHub 克隆 Muduo 的源码到本地
# 使用 git 克隆源码
git clone https://github.com/chenshuo/muduo.git
2. 安装依赖环境
Muduo 依赖于以下库需要通过包管理工具安装相关依赖
# 安装 zlib 和 Boost 库
sudo apt-get install libz-dev libboost-all-dev
3. 编译与安装
执行以下步骤完成编译和安装
解压源码如适用 如果下载的是压缩包如 muduo-master.zip需要先解压。 unzip muduo-master.zip
cd muduo-master 运行编译脚本 使用 build.sh 脚本编译 Muduo 库。 ./build.sh 安装到系统目录 运行安装脚本将编译生成的库文件安装到系统路径。
./build.sh install
安装成功 4. 第三方库使用介绍
4.1 JsonCpp库
4.1.1 Json的数据格式
Json简介
JsonJavaScript Object Notation是一种轻量级的数据交换格式
它采用完全独立于编程语言的文本格式来存储和表示数据。这种格式易于人阅读和编写同时也易于机器解析和生成。
示例学生信息表示
C/C代码表示
char *name xx;
int age 18;
float score[3] {88.5, 99, 58};
Json 表示
{姓名 : xx,年龄 : 18,成绩 : [88.5, 99, 58],爱好 :{书籍 : 西游记,运动 : 打篮球}
}
Json 的数据类型
对象使用花括号 {} 括起来表示一个对象。数组使用中括号 [] 括起来表示一个数组。字符串使用常规双引号 括起来表示一个字符串。数字包括整形和浮点型直接使用。 4.1.2 JsonCpp介绍
Jsoncpp库概述
Jsoncpp库主要用于实现Json格式数据的序列化和反序列化即它可以将多个数据对象组织成Json格式字符串并且可以从Json格式字符串解析得到多个数据对象。
⭕Json 数据对象类 Value
class Json::Value
{Value operator(const Value other); // Value重载了[]和因此所有的赋值和获取数据都可以通过简单的方式完成Value operator[](const std::string key); // val[name] xx;Value operator[](const char* key);Value removeMember(const char* key); // 移除元素const Value operator[](ArrayIndex index) const; // val[score][0]Value append(const Value value); // 添加数组元素 val[score].append(88);ArrayIndex size() const; // 获取数组元素个数 val[score].size();std::string asString() const; // 转换为 string string name val[name].asString();const char* asCString() const; // 转换为 char* char *name val[name].asCString();Int asInt() const; // 转换为 int int age val[age].asInt();float asFloat() const; // 转换为 float float weight val[weight].asFloat();bool asBool() const; // 转换为 bool bool ok val[ok].asBool();
};
序列化接口
class JSON_API StreamWriter
{virtual int write(Value const root, std::ostream* sout) 0;
};class JSON_API StreamWriterBuilder : public StreamWriter::Factory
{virtual StreamWriter* newStreamWriter() const;
};
反序列化接口
class JSON_API CharReader
{virtual bool parse(char const* beginDoc, char const* endDoc, Value* root, std::string* errs) 0;
};class JSON_API CharReaderBuilder : public CharReader::Factory
{virtual CharReader* newCharReader() const;
};
序列化和反序列化都是需要通过工厂类来进行实例化。 4.1.3 JsonCpp使用
代码编写示例
下面是一个简单的例子展示了如何使用Jsoncpp库进行序列化和反序列化操作。
#include iostream
#include string
#include jsoncpp/json/json.h
#include sstream
#include memory// 序列化函数
bool serialize(const Json::Value val, std::string body)
{std::stringstream ss;Json::StreamWriterBuilder swb;std::unique_ptrJson::StreamWriter w(swb.newStreamWriter());bool ret w-write(val, ss);if (!ret) return false;body ss.str();return true;
}// 反序列化函数
bool deserialize(const std::string body, Json::Value val)
{Json::CharReaderBuilder crb;std::unique_ptrJson::CharReader r(crb.newCharReader());std::string errs;bool ret r-parse(body.c_str(), body.c_str() body.size(), val, errs);if (!ret) {std::cout json unserialize failed : errs std::endl;return false;}return true;
}int main()
{const char* sex 男;const char* name 小明;int age 18;int score[4] {70, 80, 90};Json::Value stu1;stu1[姓名] name;stu1[性别] sex;stu1[年龄] age;for (auto s : score) {stu1[分数].append(s);}Json::Value fav;fav[书籍] 西游记;fav[运动] 打篮球;stu1[爱好] fav;std::string body;bool ret1 serialize(stu1, body);std::cout body std::endl;std::string str R({姓名:小黑, 年龄: 19, 成绩:[32, 45, 56]});Json::Value stu2;bool ret2 deserialize(str, stu2);if (!ret2) return -1;std::cout 姓名: stu2[姓名].asString() std::endl;std::cout 年龄: stu2[年龄].asInt() std::endl;for (int i 0; i static_castint(stu2[成绩].size()); i) {std::cout 成绩: stu2[成绩][i].asFloat() std::endl;}return 0;
}
⭕注意对于头文件的包含调用 运行 4.2 Muduo库
4.2.1 Muduo库是什么
Muduo是由陈硕老师开发的 基于非阻塞IO和事件驱动的C高并发TCP网络编程库。它采用 主从Reactor模型 和 one loop per thread 线程模型。 ⭕one loop per thread 的含义 一个线程只能有一个事件循环EventLoop用于响应计时器和IO事件。一个 文件描述符只能由一个线程进行读写即一个TCP连接必须归属于某个EventLoop管理。 从而避免了例如一万个请求就创建一万个线程的等待浪费主从的结构也将 对于网络连接的接收和对于信息的处理 进行了拆分。 4.2.2 Muduo库常见接口介绍
1. TcpServer 类
TcpServer 用于服务端管理和维护 TCP 连接。核心接口包括
构造函数
TcpServer(EventLoop* loop, const InetAddress listenAddr, const string nameArg, Option option kNoReusePort); 参数 loop主EventLoop用于事件循环。listenAddr服务器监听地址。nameArg服务器实例名称。option是否复用端口。
线程设置与启动
void setThreadNum(int numThreads); // 设置IO线程数量
void start(); // 启动服务端监听
回调函数设置
void setConnectionCallback(const ConnectionCallback cb); // 连接建立/断开回调
void setMessageCallback(const MessageCallback cb); // 消息接收回调
2. EventLoop 类
EventLoop 是事件循环的核心类。
循环控制
void loop(); // 启动事件循环
void quit(); // 退出事件循环
定时器接口
TimerId runAt(Timestamp time, TimerCallback cb); // 在指定时间运行回调
TimerId runAfter(double delay, TimerCallback cb); // 指定延迟后运行回调
TimerId runEvery(double interval, TimerCallback cb); // 周期性运行回调
void cancel(TimerId timerId); // 取消定时器
3. TcpConnection 类
TcpConnection 负责维护单个TCP连接。
核心功能
void send(const void* message, int len); // 发送数据
void shutdown(); // 关闭连接
上下文管理
void setContext(const boost::any context); // 设置上下文
const boost::any getContext() const; // 获取上下文
回调函数
void setConnectionCallback(const ConnectionCallback cb); // 设置连接回调
void setMessageCallback(const MessageCallback cb); // 设置消息回调
4. TcpClient 类
TcpClient 用于客户端管理 TCP 连接。
核心方法
void connect(); // 发起连接
void disconnect(); // 断开连接
void stop(); // 停止客户端
回调函数
void setConnectionCallback(ConnectionCallback cb); // 设置连接回调
void setMessageCallback(MessageCallback cb); // 设置消息回调
注意事项 由于TcpClient操作是异步的在连接建立之前尝试发送数据是不被允许的可通过CountDownLatch进行同步。
5. Buffer 类
Buffer 用于管理数据缓冲区。
核心方法
size_t readableBytes() const; // 可读数据大小
size_t writableBytes() const; // 可写数据大小
string retrieveAllAsString(); // 取出所有数据并转为字符串
void append(const char* data, size_t len); // 添加数据
void prepend(const void* data, size_t len); // 在缓冲区头部追加数据
内部实现 使用std::vectorchar作为缓冲区支持动态扩容。 4.2.3 Muduo库的使用
1. 实现英译汉 TCP 服务器
代码示例
核心逻辑 使用TcpServer管理服务器。注册onConnection和onMessage回调函数。在onMessage中通过字典查找翻译单词并返回给客户端。
代码实现
class DictServer {
public:DictServer(int port):_server(_baseloop, InetAddress(0.0.0.0, port), DictServer, TcpServer::kReusePort){_server.setConnectionCallback(std::bind(DictServer::onConnection, this, std::placeholders::_1));_server.setMessageCallback(std::bind(DictServer::onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));}void Start() {_server.start();_baseloop.loop();}private:void onConnection(const TcpConnectionPtr conn) {if (conn-connected()) std::cout 连接建立\n;else std::cout 连接断开\n;}void onMessage(const TcpConnectionPtr conn, Buffer *buf, Timestamp) {std::unordered_mapstd::string, std::string dict {{hello, 你好}, {world, 世界}};std::string msg buf-retrieveAllAsString();conn-send(dict.count(msg) ? dict[msg] : 未知单词);}private:EventLoop _baseloop;TcpServer _server;
};int main() {DictServer server(8080);server.Start();return 0;
}
2. 实现英译汉 TCP 客户端
代码示例
核心逻辑 使用TcpClient连接服务器。注册回调函数onConnection和onMessage。在建立连接后通过send方法发送单词。
代码实现
class DictClient {
public:DictClient(const std::string ip, int port):_baseloop(_loopthread.startLoop()), _latch(1), _client(_baseloop, InetAddress(ip, port), DictClient){_client.setConnectionCallback(std::bind(DictClient::onConnection, this, std::placeholders::_1));_client.setMessageCallback(std::bind(DictClient::onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));_client.connect();_latch.wait();}void send(const std::string msg) {if (_conn-connected()) _conn-send(msg);else std::cout 连接断开发送失败\n;}private:void onConnection(const TcpConnectionPtr conn) {if (conn-connected()) {_conn conn;_latch.countDown();} else {_conn.reset();}}void onMessage(const TcpConnectionPtr conn, Buffer *buf, Timestamp) {std::cout buf-retrieveAllAsString() std::endl;}private:EventLoop *_baseloop;EventLoopThread _loopthread;TcpClient _client;TcpConnectionPtr _conn;CountDownLatch _latch;
};int main() {DictClient client(127.0.0.1, 8080);std::string msg;while (std::cin msg) client.send(msg);return 0;
}
3. Makefile 编译指令
CFLAG -stdc11 -I ../build/release-install-cpp11/include/
LFLAG -L../build/release-install-cpp11/lib -lmuduo_net -lmuduo_base -pthreadall: server clientserver: server.cppg $(CFLAG) $^ -o $ $(LFLAG)client: client.cppg $(CFLAG) $^ -o $ $(LFLAG)clean:rm -f server client 运行结果
通过简单输入单词实现实时翻译服务器返回翻译结果或提示“未知单词”。 客户端 回调函数
在 Muduo 中使用回调函数如 setConnectionCallback 和 setMessageCallback是为了实现网络库的 事件驱动编程模型这符合非阻塞 I/O 和高并发网络编程的设计理念。 为什么要借助回调函数呢 1. 解耦网络事件处理逻辑
Muduo 库本身只负责处理网络事件的底层逻辑比如连接建立、断开、消息接收等但具体的业务逻辑是用户的需求如数据如何处理、何时响应客户端等而这些需求是 多变且不可预测的。
问题如果将业务逻辑直接嵌入到 Muduo 内部不仅会导致耦合度过高还会限制灵活性。解决通过 回调函数用户可以将自己的业务逻辑注册给 Muduo在事件发生时 Muduo 自动调用注册的回调函数。
示例
连接建立或断开时用户可以通过 setConnectionCallback 注册逻辑来处理事件如记录日志、初始化资源等。消息到达时通过 setMessageCallback 注册回调处理消息如解析请求、响应客户端。 2. 事件驱动的核心机制
Muduo 的设计基于 事件驱动模型回调函数是这种模型的核心
在事件循环EventLoop中Muduo 会监听文件描述符如 socket上的事件。当检测到事件如新连接、可读数据等会触发相应的回调函数。
使用回调函数的好处
异步非阻塞Muduo 不会阻塞等待事件完成而是注册回调函数后立即返回继续处理其他事件。灵活性高用户可以根据业务需要提供任意逻辑来响应事件而无需修改框架本身。
示例
void onConnection(const TcpConnectionPtr conn) {if (conn-connected()) {std::cout 新连接建立 conn-peerAddress().toIpPort() std::endl;} else {std::cout 连接断开 std::endl;}
}
用户通过 setConnectionCallback 将 onConnection 注册给 TcpServer当连接建立或断开时TcpServer 自动调用用户定义的逻辑。 3. 提高代码复用性与可维护性
回调函数机制使得
复用性高Muduo 的底层逻辑和用户的业务逻辑是解耦的可以分别复用。例如Muduo 可用于不同类型的服务器如 HTTP 服务器、聊天服务器而无需修改核心代码。易于维护用户只需关心自己的回调函数不需要深入理解网络库内部的实现。框架和业务逻辑的分离使得代码更清晰易维护。 4. 非阻塞与多线程安全
在高并发场景中回调函数的设计可以帮助 Muduo
避免阻塞通过异步回调实现逻辑处理避免阻塞事件循环线程EventLoop。线程安全Muduo 使用 one loop per thread 模型每个线程一个 EventLoop回调函数在绑定的线程中执行不需要担心多线程竞态问题。 5. 动态响应不同需求
回调函数的动态绑定特性允许用户在运行时根据需求改变逻辑
用户可以在程序启动时或运行时调用 setConnectionCallback 或 setMessageCallback 设置不同的回调函数动态调整行为。
示例
// 设置新的消息回调函数
server.setMessageCallback([](const TcpConnectionPtr conn, Buffer* buf, Timestamp) {std::string msg buf-retrieveAllAsString();conn-send(Echo: msg);
});
通过新的回调函数程序逻辑可以轻松从英译汉服务器切换为回声服务器而无需修改其他部分的代码。
总结
使用回调函数的原因总结如下
解耦网络库与业务逻辑增强灵活性。事件驱动模型的核心机制支持异步非阻塞操作。提高复用性与可维护性框架和业务逻辑分离。适应高并发需求避免阻塞并提供线程安全保证。支持动态逻辑调整满足运行时多样化需求。
回调函数是事件驱动编程中不可或缺的设计模式Muduo 通过它实现了高性能、高并发的网络框架设计理念。