当前位置: 首页 > news >正文

使用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服务程序

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

大型JAVA版云HIS医院管理系统源码 Saas应用+前后端分离+B/S架构

SaaS运维平台多集团多医院入驻强大的电子病历完整文档 有源码&#xff0c;有演示&#xff01; 云HIS系统技术栈&#xff1a; 1、前端框架&#xff1a;AngularNginx 2、后台框架&#xff1a;JavaSpring&#xff0c;SpringBoot&#xff0c;SpringMVC&#xff0c;SpringSecurity&…...

1 网关介绍

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

Java中Scanner用法

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

malloc实现原理探究

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

Spring——整合junit4、junit5使用方法

spring需要创建spring容器&#xff0c;每次创建容器单元测试是测试单元代码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. 备份&#xff08;副本&#xff09;3. 硬件和软件&#xff1a;4.端口5. 二进制协议vs文本协议6. 虚拟7.分布式8.广播域和冲突域的区别9本地地址协议1.CSMA/CD协议2.IP协议3.路由算法协议&#xff08;RIP&#xff0c;OSPF&#xff0c;BGP&#xff09;4.ARP…...

【第一章】谭浩强C语言课后习题答案

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

最新版本vue3+vite重构尚品汇(解决接口问题)第21-50集

第21集&#xff0c;第22集&#xff1a;照敲就行&#xff0c;引入概念。 第23集&#xff1a;防抖概念&#xff1a;前面所有的触发被取消&#xff0c;最后一次执行在规定的时间之后才会触发&#xff0c;只会执行一次。Lodash插件里面封装了函数的防抖和节流的业务。用到lodash确实…...

【超级猜图案例上半部分的实现 Objective-C语言】

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

刷题笔记4 | 24. 两两交换链表中的节点、19. 删除链表的倒数第N个节点、面试题 02.07. 链表相交、142.环形链表II

24. 两两交换链表中的节点 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 输入&#xff1a;head [1,2,3,4] 输出&#xff1a…...

15、正则表达式

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

javaWeb核心01-HTTPTomcatServlet

文章目录HTTP&Tomcat&Servlet1&#xff0c;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. 一、会员积分&#xff08;期末模拟&#xff09; 题目描述 某电商网站的会员分为&#xff1a;普通、贵宾两个级别 普通会员类Member&#xff0c;包含编号、姓名、积分三个属性&#xff0c;编号和积分是整数&#xff0c;姓名是字符串 操作包括构造、打印、积分累加、积分兑…...

Linux基础命令(一)

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

RocketMQ Broker消息处理流程剩余源码解析

&#x1f34a; Java学习&#xff1a;Java从入门到精通总结 &#x1f34a; 深入浅出RocketMQ设计思想&#xff1a;深入浅出RocketMQ设计思想 &#x1f34a; 绝对不一样的职场干货&#xff1a;大厂最佳实践经验指南 &#x1f4c6; 最近更新&#xff1a;2023年3月4日 &#x1…...

JQuery入门基础

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

kafka 构建双向SSL认证

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

推荐一个.Net Core开发的Websocket群聊、私聊的开源项目

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

华为OD机试Golang解题 - 事件推送 | 含思路

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

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误

HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误&#xff0c;它们的含义、原因和解决方法都有显著区别。以下是详细对比&#xff1a; 1. HTTP 406 (Not Acceptable) 含义&#xff1a; 客户端请求的内容类型与服务器支持的内容类型不匹…...

大话软工笔记—需求分析概述

需求分析&#xff0c;就是要对需求调研收集到的资料信息逐个地进行拆分、研究&#xff0c;从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要&#xff0c;后续设计的依据主要来自于需求分析的成果&#xff0c;包括: 项目的目的…...

DAY 47

三、通道注意力 3.1 通道注意力的定义 # 新增&#xff1a;通道注意力模块&#xff08;SE模块&#xff09; class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...

Objective-C常用命名规范总结

【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名&#xff08;Class Name)2.协议名&#xff08;Protocol Name)3.方法名&#xff08;Method Name)4.属性名&#xff08;Property Name&#xff09;5.局部变量/实例变量&#xff08;Local / Instance Variables&…...

dedecms 织梦自定义表单留言增加ajax验证码功能

增加ajax功能模块&#xff0c;用户不点击提交按钮&#xff0c;只要输入框失去焦点&#xff0c;就会提前提示验证码是否正确。 一&#xff0c;模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...

浅谈不同二分算法的查找情况

二分算法原理比较简单&#xff0c;但是实际的算法模板却有很多&#xff0c;这一切都源于二分查找问题中的复杂情况和二分算法的边界处理&#xff0c;以下是博主对一些二分算法查找的情况分析。 需要说明的是&#xff0c;以下二分算法都是基于有序序列为升序有序的情况&#xf…...