使用asio实现一个单线程异步的socket服务程序
文章目录
- 前言
- 代码
前言
之前,我使用epoll
实现过一个C++的后端服务程序,见:从头开始实现一个留言板-README_c++做一个留言板_大1234草的博客-CSDN博客
但是它不够简便,无法轻松的合并到其他代码中。并且,由于程序中使用epoll
函数,代码无法运行在windows上。如果选用select
,倒是可以同时在windows和linux上运行。
所以,本文重写一个C++后端服务程序,它需要便捷的嵌入到不同的项目中,需要可以跨平台运行。
网上可能有很多这样的程序,比如:【C++】HTTP Server 开源库(汇总级别整理)' | 像我这样的人。但是,处于菜鸟阶段,我得多敲代码(我暂时也没有去对比不同的实现,因为拖着拖着,就会不想做了)。
这里,我们看下有哪些技术路线。第一种是套接字编程。 socket
在windows和linux上的使用,略有区别。所以,这里不选择直接进行套接字编程。第二种是C++的网络库。C++的标准库没有提供网络功能,所以这里选择使用GitHub - boostorg/asio: Boost.org asio module。
初次使用boost::asio
,会有点难上手。但是,本文不介绍boost::asio
的具体使用,因为太冗长。这里推荐下参考资料。
-
《Boost程序完全开发指南》12.3 asio – 入门级介绍,可以有个初步认知
-
GitHub - dongzj1997/Web-Server: A simple and fast HTTP server implemented using C++17 and Boost.Asio. – 实战教程,可以上手写asio网络代码
-
C++11 Examples - 1.81.0– 官方教程中的HTTP Server小节,质量不错的代码
本文代码,修改自上面的链接。
详细代码见仓库。
代码
本文实现的是一个,单线程异步的socket服务程序。
程序的功能是,接收从客户端发送过来的字符串,返回相同的字符串后,关闭连接。
代码的基本结构:
-
封装一个server类,负责功能的启动,停止,信号处理,以及监听(listen)和接收连接(accept)功能。
-
封装一个connect类,围绕已经建立连接的socket,处理读写操作。(因为不同的连接需要不同的存储缓冲区空间,所以每个连接都有个connect对象)
-
封装一个connect_manager类,管理所有的connect对象。
有很多东西,我还没有完全搞清楚。
主要分为五个部分:网络编程中基础知识;C++的基本语法;boost::asio的基本使用;代码的结构设计;不同操作系统的API的使用,以实现相同的功能。
-
socket中优雅的关闭。
-
端口复用与地址复用:socket 端口复用 SO_REUSEPORT 与 SO_REUSEADDR - schips - 博客园
-
C++中的左值,右值,移动语义,错误码等。
-
asio中单线程异步的基本原理,asio的多线程编程。
-
代码的结构设计。(本文的代码参考自之前的链接。代码结构中,很好的一点是使用connect_manager去管理connect。当connect释放的时候,connect调用的是connect_managet中的方法。从而,避免了在server中做这件事。这样的结构很好。)
-
Linux中信号,windows中信号,windows中事件,这三者的区别。
下面是具体代码。
首先是main函数代码。
#include "server.h"int main(int argc, char *argv[])
{server s("127.0.0.1","6666");s.run();return 0;
}
下面是server类的封装。
#pragma once
#include "connection.h"
#include <boost/asio.hpp>class server {
public:server(const std::string& address, const std::string& port);void run();void stop();
private:void do_accept();
private:boost::asio::io_context m_io_context;boost::asio::ip::tcp::acceptor m_acceptor;connection_manager m_connection_manager;boost::asio::signal_set m_signals;
};
#include "server.h"
#include <iostream>
#include <boost/asio/ip/tcp.hpp>
#include <boost/bind/bind.hpp>
#include <fstream>server::server(const std::string& address, const std::string& port): m_io_context(1), m_acceptor(m_io_context),m_connection_manager(),m_signals(m_io_context)
{// 在win下,使用taskkill发送信号,会让进程直接退出,并没有执行这里的信号处理。// 目前不清楚,可参考:https://stackoverflow.com/questions/26404907/gracefully-terminate-a-boost-asio-based-windows-console-applicationm_signals.add(SIGINT);m_signals.add(SIGTERM);m_signals.async_wait([this](boost::system::error_code ec, int signo){if(signo == SIGINT || signo == SIGTERM) {stop();}});boost::asio::ip::tcp::resolver resolver(m_io_context);boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(address, port).begin();m_acceptor.open(endpoint.protocol());m_acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));m_acceptor.bind(endpoint);m_acceptor.listen();do_accept();
}void server::do_accept()
{// Move accept handler requirementsm_acceptor.async_accept([this](boost::system::error_code ec, boost::asio::ip::tcp::socket socket){// Check whether the server was stopped by a signal before this// completion handler had a chance to run.if (!m_acceptor.is_open()) {return;}if (!ec) {m_connection_manager.start(std::make_shared<connection>(std::move(socket), m_connection_manager));}do_accept();});
}void server::run()
{m_io_context.run();
}void server::stop()
{// 服务器停止是通过取消所有未完成的异步操作来实现的。// 一旦所有操作都完成,io_context::run() 函数将退出。m_acceptor.close();m_connection_manager.stop_all();
}
接下来是connect类。我把connect_manager类,也放在同一个文件里面了。
#pragma once
#include <memory>
#include <set>
#include <boost/asio/ip/tcp.hpp>class connection;
typedef std::shared_ptr<connection> connection_ptr;class connection_manager {
public:connection_manager() = default;connection_manager(const connection_manager&) = delete;connection_manager& operator=(const connection_manager&) = delete;void start(connection_ptr c);void stop(connection_ptr c);void stop_all();private:std::set<connection_ptr> m_connections;
};class connection : public std::enable_shared_from_this<connection> {
public:connection(const connection&) = delete;connection& operator=(const connection&) = delete;connection(boost::asio::ip::tcp::socket socket, connection_manager& manager);void start();void stop();private:void do_read();void do_write();void handle_read(const boost::system::error_code& ec, size_t bytes_transferred);void handle_write(const boost::system::error_code& ec, size_t bytes_transferred);private:boost::asio::ip::tcp::socket m_socket;int m_write_size = 0;std::array<char, 4096> m_read_buffer;std::array<char, 4096> m_write_buffer;connection_manager& m_connection_manager;};
#include "connection.h"
#include <boost/bind/bind.hpp>
#include <boost/asio/placeholders.hpp>void connection_manager::start(connection_ptr c)
{m_connections.insert(c);c->start();
}void connection_manager::stop(connection_ptr c)
{m_connections.erase(c);c->stop();
}void connection_manager::stop_all()
{for (auto c: m_connections)c->stop();m_connections.clear();
}connection::connection(boost::asio::ip::tcp::socket socket,connection_manager& manager): m_socket(std::move(socket)),m_connection_manager(manager)
{
}void connection::start()
{do_read();
}void connection::do_read()
{m_socket.async_read_some(boost::asio::buffer(m_read_buffer),boost::bind(&connection::handle_read, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}void connection::handle_read(const boost::system::error_code& ec, size_t bytes_transferred)
{if(!ec) {// 检查是否接受到完整的信息;(这里假定收到的信息完整)// 单线程的异步程序,这里会存在问题麻?m_write_buffer.fill('\0');m_write_buffer = m_read_buffer;m_write_size = bytes_transferred;do_write();}else if(ec != boost::asio::error::operation_aborted){m_connection_manager.stop(shared_from_this());}
}void connection::do_write()
{m_socket.async_write_some(boost::asio::buffer(m_write_buffer.data(), m_write_size),boost::bind(&connection::handle_write, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}void connection::handle_write(const boost::system::error_code& ec, size_t bytes_transferred)
{if(!ec) {// 发送后断开连接m_connection_manager.stop(shared_from_this()); // 这里的写法比较神奇.调用管理者来释放自己.// 直接调用connection::stop,会导致connection_manager中该对象的智能指针没有删除(虽然在对象释放后这个智能指针可能指向为空) }else if(ec != boost::asio::error::operation_aborted) {m_connection_manager.stop(shared_from_this());}
}void connection::stop()
{// m_socket.close();// 优雅的关闭:发送缓冲区中的内容发送完毕后再完全关闭boost::system::error_code ignored_ec;m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec);
}
相关文章:

使用asio实现一个单线程异步的socket服务程序
文章目录前言代码前言 之前,我使用epoll实现过一个C的后端服务程序,见:从头开始实现一个留言板-README_c做一个留言板_大1234草的博客-CSDN博客 但是它不够简便,无法轻松的合并到其他代码中。并且,由于程序中使用epo…...

大型JAVA版云HIS医院管理系统源码 Saas应用+前后端分离+B/S架构
SaaS运维平台多集团多医院入驻强大的电子病历完整文档 有源码,有演示! 云HIS系统技术栈: 1、前端框架:AngularNginx 2、后台框架:JavaSpring,SpringBoot,SpringMVC,SpringSecurity&…...

1 网关介绍
网关介绍 在微服务架构中,一个系统会被拆分为很多个微服务。那么作为客户端要如何去调用这么多的微服务呢?如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去调用。这样的话会产生很多问题,例如&a…...

Java中Scanner用法
Java中Scanner用法 Scanner可以实现程序和人的交互,用户可以利用键盘进行输入。 不同类型的输入: String ssc.next(); //接受字符串数据 System.out.println(s);int s1 sc.nextInt();//接受整型数据 System.out.println(s1);double s2 sc.nextDouble…...

malloc实现原理探究
2021年末面试蔚来汽车,面试官考察了malloc/free的实现机制。当时看过相关的文章,有一点印象,稍微说了一点东西,不过自己感到不满意。今天尝试研究malloc的实现细节,看了几篇博文,发现众说纷纭,且…...

Spring——整合junit4、junit5使用方法
spring需要创建spring容器,每次创建容器单元测试是测试单元代码junit4依赖<?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-i…...

计算机网络的一些思考(待完善)
文章目录概念1. 缓存2. 备份(副本)3. 硬件和软件:4.端口5. 二进制协议vs文本协议6. 虚拟7.分布式8.广播域和冲突域的区别9本地地址协议1.CSMA/CD协议2.IP协议3.路由算法协议(RIP,OSPF,BGP)4.ARP…...

【第一章】谭浩强C语言课后习题答案
1.什么是程序?什么是程序设计? 程序:就是一组能识别和执行的指令,每一条指令使计算机执行特定的操作 程序设计:是指从确定任务到得到结果、写出文档的全过程 2.为什么需要计算机语言?高级语言有哪些特点? 为什么需要计算机语言:计算机语言解决了人和计算机交流是的…...

最新版本vue3+vite重构尚品汇(解决接口问题)第21-50集
第21集,第22集:照敲就行,引入概念。 第23集:防抖概念:前面所有的触发被取消,最后一次执行在规定的时间之后才会触发,只会执行一次。Lodash插件里面封装了函数的防抖和节流的业务。用到lodash确实…...

【超级猜图案例上半部分的实现 Objective-C语言】
一、超级猜图这么一个案例: 1.实现之后的效果是这样的: 1)中间有一个图片,点一下,能放大,背景变半透明的黑色: 2)再点一下图片,或者点周围黑色的阴影,图片回归原状, 3)右边有一个“大图”按钮,点一下,实现跟点图片一样的效果, 4)左边有一个“提示”按钮,点…...

刷题笔记4 | 24. 两两交换链表中的节点、19. 删除链表的倒数第N个节点、面试题 02.07. 链表相交、142.环形链表II
24. 两两交换链表中的节点 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。 输入:head [1,2,3,4] 输出:…...

15、正则表达式
目录 一、元字符 二、限定修饰符 一、元字符 正则表达式通常被用于判断语句中,用来检查某一字符串是否满足某一格式。正则表达式是含有一些具有特殊意义字符的字符串,这些特殊字符称为正则表达式的元字符。例如,“\\d”表示数字0~9中的任何…...

javaWeb核心01-HTTPTomcatServlet
文章目录HTTP&Tomcat&Servlet1,Web概述1.1 Web和JavaWeb的概念1.2 JavaWeb技术栈1.2.1 B/S架构1.2.2 静态资源1.2.3 动态资源1.2.4 数据库1.2.5 HTTP协议1.2.6 Web服务器1.3 Web核心课程安排2, HTTP2.1 简介2.2 请求数据格式2.2.1 格式介绍2.2.2 实例演示2.…...

深圳大学计软《面向对象的程序设计》实验16 期末复习
A. 一、会员积分(期末模拟) 题目描述 某电商网站的会员分为:普通、贵宾两个级别 普通会员类Member,包含编号、姓名、积分三个属性,编号和积分是整数,姓名是字符串 操作包括构造、打印、积分累加、积分兑…...

Linux基础命令(一)
文章目录1、时间命令:date2、日历命令:cal3、计算器程序:bc4、基础组合键5、正确的关机指令使用5.1 将数据同步写入硬盘中的指令: sync5.2 惯用的关机指令: shutdown5.3 重新开机,关机: reboot,…...

RocketMQ Broker消息处理流程剩余源码解析
🍊 Java学习:Java从入门到精通总结 🍊 深入浅出RocketMQ设计思想:深入浅出RocketMQ设计思想 🍊 绝对不一样的职场干货:大厂最佳实践经验指南 📆 最近更新:2023年3月4日 …...

JQuery入门基础
目录 1.初识 下载 使用 JQuery(核心)对象 2.选择器 基础选择器 层次选择器 后代选择器 子代选择器 兄弟选择器 相邻选择器 3.JQuery DOM操作 创建元素 插入元素 删除元素 遍历元素 属性操作 获取属性 设置属性 删除属性 样式操作 …...

kafka 构建双向SSL认证
kafka 安装 以下内容均已完成测试,按照教程搭建你会得到一个双向ssl认证的kafka broker,并能通过ip以及域名访问,笔者能力有限如果文章内容存在问题烦请各位指出。 搭建单机Kafka 需求 centos 7kafka_2.12-2.6.0jdk8(文档中统…...

推荐一个.Net Core开发的Websocket群聊、私聊的开源项目
更多开源项目请查看:一个专注推荐.Net开源项目的榜单 今天给大家推荐一个使用Websocket协议实现的、高性能即时聊天组件,可用于群聊、好友聊天、游戏直播等场景。 项目简介 这是一个基于.Net Core开发的、简单、高性能的通讯组件,支持点对点…...

华为OD机试Golang解题 - 事件推送 | 含思路
华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典文章目录 华为Od必看系列使用说明本期题目…...

将微信小程序页面转为图片
最近做项目遇到一个需求,那就是要将某个页面转为图片然后传给后端,我仔细找了一圈,发现官方那个Api也就是wx.canvasToTempFilePath生成的图片很有可能为空,太坑了,于是我放弃用它了,选择了用wxml2canvas。 安装wxml2canvas npm init npm install wxml2canvas --save --…...

LINE、SDNE和struc2vec图嵌入算法学习笔记
引言 在cs224w课程中,我先后总结了deepwalk、node2vec,这两种算是最经典也是最主流的做法,而在 图节点嵌入相关算法学习笔记 中,从头至尾,将一些经典算法用wiki的数据集复现了一下,所以本篇博文࿰…...

Buuctf Younger-drive 题解
目录 一.查壳 二.运行缺少dll 三.主函数 四.hObject线程 五.Thread线程 六.judge函数 七.解题脚本 这题的关键在于了解一定的线程相关知识 一.查壳 32位带壳,用upx脱壳 二.运行缺少dll 后续尝试了各种方法修复dll但是还是运行不了 值得一提的是脱壳后的程序不能动态调试…...

数据结构与算法:二叉树专题
数据结构与算法:二叉树专题前言前提条件基础知识二叉树链式存储结构二叉树中序遍历二叉树层序遍历常见编程题把一个有序整数数组放到二叉树中逐层打印二叉树结点数据求一棵二叉树的最大子树和判断两棵二叉树是否相等把二叉树转换为双向链表判断一个数组是否是二元查…...

Cadence Allegro 导出Cadence Schematic Feedback Report详解
⏪《上一篇》 🏡《总目录》 ⏩《下一篇》 目录 1,概述2,Cadence Schematic Feedback Report作用3,Cadence Schematic Feedback Report示例4,Cadence Schematic Feedback Report导出方法4.1,方法1,4.2,方法2,...

《计算机系统基础》—— 运算
文章目录《计算机系统基础》——运算整数按位运算作用操作位移运算作用操作乘法运算除法运算浮点数加减运算乘除运算《计算机系统基础》——运算 🚀🚀本章我们需要介绍的是有关C语言里面的运算,当然了,我们不会是介绍简单的运算&…...

MSTP多进程讲解与实验配置
目录 MSTP多进程 专业术语 MSTP多进程配置 在MSTP域配置 MSTP多进程 多进程的作用 将设备上的端口绑定到不同的进程中,以进程为单位进行MSTP计算,不在同一进程内的端口不参与此进程中的MSTP协议计算,实现各个进程之间的生成树计算相互独立…...

【Python】软件测试必备:了解 fixture 在自动化测试中的重要作用
在自动化软件测试中,fixture 是一种确保测试在一致且受控条件下运行的重要方法。简单来说,fixture 就是一组先决条件或固定状态,必须在运行一组测试之前建立。在测试框架中,fixture 提供了一种方便的方法,用于在每个测…...

DevExpress皮肤引用的办法
1.引用Dll皮肤文件Typeprocedure SetSkin(skinnam:string);procedure TFrmMain.SetSkin(skinnam:string);varHinst:THANDLE;RStream:TResourceStream;beginHinst:Loadlibrary(ALLSK.dll);If Hinst0 ThenExitelsebeginRstream:TResourceStream.Create(Hinst,skinnam,MYSKIN);dxS…...

2023-03-04 区分纳米颗粒核壳原子
声明:未经允许,不得擅自复制、转载。欢迎引用:Laser-Assisted Synthesis of Bi-Decorated Pt Aerogel for Efficient Methanol Oxidation ElectrocatalysisApplied Surface Science ( IF 6.707 ) Pub Date : 2022-04-01 , DOI: 10.1016/j.aps…...