Netty系列-2 NioServerSocketChannel和NioSocketChannel介绍
背景
本文介绍Netty的通道组件NioServerSocketChannel和NioSocketChannel,从源码的角度介绍其实现原理。
1.NioServerSocketChannel
Netty本质是对NIO的封装和增强,因此Netty框架中必然包含了对于ServerSocketChannel的构建、配置以及向选择器注册,如下所示:
// 创建ServerSocketChannel对象
ServerSocketChannel serverSocketChannel = SelectorProvider.provider().openServerSocketChannel();// ServerSocketChannel通道设置为非阻塞
serverSocketChannel.configureBlocking(false);// 将ServerSocketChannel通道注册至选择器
serverSocketChannel.register(Selector, opts, attachment);// 接收客户端连接得到SocketChannel通道
SocketChannel socketChannel = serverSocketChannel.accept();
其中的构建和配置过程发生在NioServerSocketChannel的实例化过程。
1.1 NioServerSocketChannel构造函数
NioServerSocketChannel实例化过程包含了对serverSocketChannel的创建以及配置
Netty启动时,通过反射调用NioServerSocketChannel的无参构造函数创建NioServerSocketChannel对象.
private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();public NioServerSocketChannel() {this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}public NioServerSocketChannel(ServerSocketChannel channel) {super(null, channel, SelectionKey.OP_ACCEPT);config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
DEFAULT_SELECTOR_PROVIDER是Provider对象,用于创建通道和选择器,newSocket方法返回一个ServerSocketChannel对象,如下所示:
private static ServerSocketChannel newSocket(SelectorProvider provider) {try {return provider.openServerSocketChannel();} catch (IOException e) {throw new ChannelException("Failed to open a server socket.", e);}
}
NioServerSocketChannel中还维护了一个config对象用于储存该通道相关的配置,后续通过通道对象的config()
方法获取该config对象。
继续调用父类的构造方法:
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {super(parent);this.ch = ch;this.readInterestOp = readInterestOp;try {ch.configureBlocking(false);} catch (IOException e) {try {ch.close();} catch (IOException e2) {logger.warn("Failed to close a partially initialized socket.", e2);}throw new ChannelException("Failed to enter non-blocking mode.", e);}
}// super(parent)内容如下:
protected AbstractChannel(Channel parent) {this.parent = parent;id = newId();unsafe = newUnsafe();pipeline = newChannelPipeline();
}
因此NioServerSocketChannel中包含如下属性:
[1] SelectableChannel ch:实际为ServerSocketChannel类型,即NIO中的服务端通道类型,并将其配置为非阻塞类型,以便后续向选择器注册;
[2] int readInterestOp: 值固定为SelectionKey.OP_ACCEPT,表示仅处理连接事件;
[3] pipeline: Netty的Pipeline组件,每个channel都有一个属于自己的Pipeline对象;
[4] unsafe: 对底层IO进行了封装,实际的读写操作在该类中进行处理;
[5] 其他: id唯一ID标识,parent固定为空。
1.2 NioServerSocketChannel注册
NioServerSocketChannel包含了ServerSocketChannel对象,向选择器注册NioServerSocketChannel本质是将ServerSocketChannel注册到选择器
在Netty启动流程流程中,依次构造ServerSocketChannel, 并注册到选择器上,具体逻辑为:
// NioServerSocketChannel的父类AbstractNioChannel中
// 删除try-catch异常逻辑
protected void doRegister() throws Exception {boolean selected = false;for (;;) {selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);return;}
}
其中: javaChannel()获取NioServerSocketChannel对象的ServerSocketChannel属性;eventLoop().unwrappedSelector()为NioEventLoop这个线程绑定的选择器;此处的this表明将ServerSocketChannel注册到选择器上时,将当前的NioServerSocketChannel对象作为attachment保存到SelectionKey中,并使用volatile SelectionKey selectionKey;
属性保存了注册结果。
说明:后续选择器会执行select而阻塞,当该选择器被IO事件唤醒时,可通过SelectionKey的attachment获取NioServerSocketChannel对象,从而可以获取包括ServerSocketChannel、Pipeline、Config等其他所有相关信息。
1.3 NioServerSocketChannel处理连接
章节1.1中提到了NioServerSocketChannel的unsafe属性,unsafe用于封装底层具体的IO行为,具体的实现类为NioMessageUnsafe.
当有连接请求到达NioServerSocketChannel后,进入NioMessageUnsafe的read()方法中(详细的调用流程和线程处理关系在后续Netty的消息处理流程中介绍, 这里仅对read方法实现逻辑进行说明),read方法省去内存分配优化策略以及异常处理逻辑后的主线逻辑如下:
private final class NioMessageUnsafe extends AbstractNioUnsafe {private final List<Object> readBuf = new ArrayList<Object>();@Overridepublic void read() {// ...final ChannelPipeline pipeline = pipeline();do {// ...doReadMessages(readBuf);} while (allocHandle.continueReading());int size = readBuf.size();for (int i = 0; i < size; i ++) {readPending = false;pipeline.fireChannelRead(readBuf.get(i));}readBuf.clear();pipeline.fireChannelReadComplete();}
}
readBuf是一个列表类型,用于存放解析后的消息对象,解析完成后,依次遍历readBuf,并调用pipeline.fireChannelRead将消息对象发送至Netty的Pipeline组件(后面单独介绍)。
解析逻辑在doReadMessages方法中:
protected int doReadMessages(List<Object> buf) throws Exception {SocketChannel ch = SocketUtils.accept(javaChannel());try {if (ch != null) {buf.add(new NioSocketChannel(this, ch));return 1;}} catch (Throwable t) {logger.warn("Failed to create a new channel from an accepted socket.", t);try {ch.close();} catch (Throwable t2) {logger.warn("Failed to close a socket.", t2);}}return 0;
}// SocketUtils.accept(javaChannel())代码逻辑:
public static SocketChannel accept(final ServerSocketChannel serverSocketChannel) throws IOException {// 删除try-catch异常逻辑return AccessController.doPrivileged(new PrivilegedExceptionAction<SocketChannel>() {@Overridepublic SocketChannel run() throws IOException {return serverSocketChannel.accept();}});
}
javaChannel()得到ServerSocketChannel对象,serverSocketChannel.accept()得到客户端通道对象SocketChannel。将当前服务端通道NioServerSocketChannel对象和得到的客户端通道对象SocketChannel作为参数构造NioSocketChannel对象。
2.NioSocketChannel
与NioServerSocketChannel相似,NioSocketChannel也是Netty对NIO中ServerSocketChannel的封装和增强。本章节内容将包含SocketChannel的构建、配置、向选择器注册以及读取数据,如下所示:
// 得到SocketChannel对象
SocketChannel socketChannel = serverSocketChannel.accept();// SocketChannel通道设置为非阻塞
socketChannel.configureBlocking(false);// 将SocketChannel通道注册至选择器
socketChannel.register(Selector, opts, attachment);// 从SocketChannel通道读取数据值缓冲区
socketChannel.read(ByteBuffer)
2.1 NioSocketChannel构造函数
每个客户端连接对应一个通道,即一个NioSocketChannel对象。
Netty收到客户端连接时,会调用NioSocketChannel构造函数创建通道对象,如下所示:
public NioSocketChannel(Channel parent, SocketChannel socket) {super(parent, socket);config = new NioSocketChannelConfig(this, socket.socket());
}
parent为NioServerSocketChannel对象,socket为NIO中SocketChannel对象。NioSocketChannel与NioServerSocketChannel相似,维持了一个config配置类用于存放和读取通道的配置信息。
继续沿着super调用父类的构造方法:
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {super(parent, ch, SelectionKey.OP_READ);
}protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {super(parent);this.ch = ch;this.readInterestOp = readInterestOp;try {ch.configureBlocking(false);} catch (IOException e) {try {ch.close();} catch (IOException e2) {logger.warn("Failed to close a partially initialized socket.", e2);}throw new ChannelException("Failed to enter non-blocking mode.", e);}
}protected AbstractChannel(Channel parent) {this.parent = parent;id = newId();unsafe = newUnsafe();pipeline = newChannelPipeline();
}
上述构造过程逻辑较为简单,为NioSocketChannel创建一个Unsafe对象和Pipeline对象;以及将ch属性即SocketChannel设置为非阻塞。
2.2 注册选择器
NioServerSocketChannel接收客户端连接构造出NioSocketChannel对象,并通过Pipeline.fireChannelRead触发Inbound读事件后,通过Pipiline进入ServerBootstrapAcceptor处理器的channelRead方法:
public void channelRead(ChannelHandlerContext ctx, Object msg) {final Channel child = (Channel) msg;// ...childGroup.register(child).addListener(new ChannelFutureListener() {//...});
}
由章节1可知msg消息为NioSocketChannel,childGroup为线程池NioEventLoopGroup对象(workgroup)。
childGroup.register(child)
表示将NioSocketChannel注册到workgroup的一个线程中,经过Unsafe对象最终会进入NioSocketChannel的doRegister方法:
@Override
protected void doRegister() throws Exception {// ...selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);// ...
}
javaChannel()为NioSocketChannel的ch属性,即SocketChannel通道对象;eventLoop().unwrappedSelector()为选择器;this为NioSocketChannel对象本身;返回的SelectionKey也作为属性保存在NioSocketChannel类中。
说明:后续选择器会执行select而阻塞,当有可读消息到达时被唤醒。可通过SelectionKey得到NioSocketChannel对象,从而得到相关的SocketChannel、Pipeline、Config等其他所有相关信息。
2.3 读取消息
当有可读时间到达时,NioEvetLoop会从阻塞中被唤醒,从而执行processSelectedKeys处理IO事件:
private void processSelectedKeys() {// ...processSelectedKeysOptimized();// ...
}private void processSelectedKeysOptimized() {for (int i = 0; i < selectedKeys.size; ++i) {final SelectionKey k = selectedKeys.keys[i];selectedKeys.keys[i] = null;final Object a = k.attachment();processSelectedKey(k, (AbstractNioChannel) a);}
}
遍历已就绪的IO事件,调用processSelectedKey方法处理,此时k为NIO的SelectionKey对象,而attachment为NioSocketChannel对象。
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();int readyOps = k.readyOps();//...if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {unsafe.read();}// ...
}
根据SelectionKey和NioSocketChannel对象的readyOps确定此时IO事件为可读消息,进入unsafe.read():
@Override
public final void read() {final ChannelConfig config = config();final ChannelPipeline pipeline = pipeline();final ByteBufAllocator allocator = config.getAllocator();ByteBuf byteBuf = null;boolean close = false;// ...do {// ...// 1.分配ButeBuf缓冲对象byteBuf = allocHandle.allocate(allocator);// 2.将数据读取到ButeBuf缓冲对象allocHandle.lastBytesRead(doReadBytes(byteBuf));if (allocHandle.lastBytesRead() <= 0) {byteBuf.release();byteBuf = null;break;}readPending = false;// 3.向Pipeline传递可读消息pipeline.fireChannelRead(byteBuf);byteBuf = null;// 直到读取完所有消息内容} while (allocHandle.continueReading());// ...// 触发消息读取完成事件pipeline.fireChannelReadComplete();// ...
}
代码较为清晰,重点包含3个步骤:创建ByteBuf缓冲对象(Netty自定义的,而非NIO的ByteBuffer); 将消息读取到ButeBuf对象,向Pipeline触发可读事件(在Pipeline的Handler中传递并处理消息);其中,核心逻辑在于doReadBytes(byteBuf):
@Override
protected int doReadBytes(ByteBuf byteBuf) throws Exception {// ...return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());
}
javaChannel()是NIO的SocketChannel对象,继续跟进ByteBuf的writeBytes方法进入:
@Override
public int writeBytes(ScatteringByteChannel in, int length) throws IOException {//...int writtenBytes = setBytes(writerIndex, in, length);//...return writtenBytes;
}@Override
public final int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {try {return in.read(internalNioBuffer(index, length));} catch (ClosedChannelException ignored) {return -1;}
}
可以看到底层逻辑在于in.read(internalNioBuffer(index, length))
, 返回一个ByteBuffer对象,in此时为SocketChannel, 即本质是调用NIO通道的API将数据读取至缓冲区: SocketChannel.read(ByteBuffer).
2.3 响应消息
Netty中Pipeline的任何一个Handler中都可以发送响应消息,响应消息也会沿着Pipeline的流水线传递,并经过网卡传递出去:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {ctx.writeAndFlush("hello");
}
注意:需要在此Handler前添加StringEncoder编码器,将String类型转为ByteBuf类型,否则会抛出异常。因为NioSocketChannel的Unsafe对象也维持在了Pipeline的HeadContext对象中,所有的消息最终会经过Unsafe的write方法,而Unsafe只会处理ByteBuf类型消息,其他类型会抛出异常。
追踪ctx.writeAndFlush("hello")
进入invokeWriteAndFlush
方法:
void invokeWriteAndFlush(Object msg, ChannelPromise promise) {// ...invokeWrite0(msg, promise);invokeFlush0();// ...
}
依次调用invokeWrite0和invokeFlush0实现写操作和刷盘操作, 分别进入Unsafe对象的write和flush方法:
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {unsafe.write(msg, promise);
}public void flush(ChannelHandlerContext ctx) {unsafe.flush();
}
unsafe最终调用doWrite方法实现IO功能:
protected void doWrite(ChannelOutboundBuffer in) throws Exception {SocketChannel ch = javaChannel();int writeSpinCount = config().getWriteSpinCount();do {// ... ByteBuffer buffer = nioBuffers[0];int attemptedBytes = buffer.remaining();final int localWrittenBytes = ch.write(buffer);--writeSpinCount;// ... } while (writeSpinCount > 0);incompleteWrite(writeSpinCount < 0);
}
核心逻辑在与ch.write(buffer),其中ch和buffer分别是NIO的SocketChannel和ByteBuffer,
即Netty向客户端发送消息底层仍是借助NIO的API.
相关文章:
Netty系列-2 NioServerSocketChannel和NioSocketChannel介绍
背景 本文介绍Netty的通道组件NioServerSocketChannel和NioSocketChannel,从源码的角度介绍其实现原理。 1.NioServerSocketChannel Netty本质是对NIO的封装和增强,因此Netty框架中必然包含了对于ServerSocketChannel的构建、配置以及向选择器注册&am…...

智能客服的四大优势,提升企业服务效率
在这个信息化快速发展的时代,客户服务的重要性越来越凸显。传统的客服方式已经无法满足企业日益增长的服务需求,于是智能客服服务应运而生。智能客服服务不仅改变了企业与客户的互动方式,还提高了服务效率和客户满意度。本文将深入探讨智能客…...
AutoGPT开源项目解读
AutoGPT开源项目解读 (qq.com) AutoGPT旨在创建一个自动化的自我改进系统,能够自主执行和学习各种任务 项目基本信息 首先阅读项目的README.md,下述代理和智能体两个名词可互换 项目简介:一个创建和运行智能体的工具,这些智能体…...

Linux离线安装fontconfig
Linux离线下载yum包,安装字体库 一、下载安装包 以CentOS Linux release 7.9.2009下载fontconfig的rpm包的为例 http://mirror.centos.org/centos/7/按提示跳转历史库 找到对应版本的centos https://vault.centos.org/7.9.2009/os/x86_64/Packages/在Packages目…...

海山数据库(He3DB)+AI:(一)神经网络基础
文章目录 1 引言2 基本结构2.1 神经元2.2 模型结构 3 训练过程3.1 损失函数3.2 反向传播3.3 基于梯度的优化算法 4 总结 1 引言 神经网络可以被视为一个万能的拟合器,通过深层的隐藏层实现输入数据到输出结果的映射。神经网络的思想源于对大脑的模拟,在…...
CSS中选择器有哪些?(史上最全选择器)
CSS选择器是用来选择和应用样式到HTML元素上的工具。以下是所有主要的CSS选择器的详细分类和描述: 1. 基本选择器 通配符选择器 (*):选择所有元素。例如,* { color: red; } 会将所有元素的文字颜色设置为红色。元素选择器:选择指…...

本地部署 AI 智能体,Dify 搭建保姆级教程(下):知识库 RAG + API 调用,我捏了一个红楼解读大师
话接上篇: 本地部署 AI 智能体,Dify 搭建保姆级教程(上):工作流 Agent,把 AI 接入个人微信 相信大家已经在本地搭建好 Dify 了。 今日分享,继续介绍 Dify 的另外两项重要功能: 知…...
HarmonyOS应用开发者高级认证,Next版本发布后最新题库 - 答案纯享版
这篇文章是高级题库答案纯享版,只有需要选择的选项。如果需要查看所有选项,可以点击下方链接跳转。以考代学,还是推荐点击下方链接,查看完整的题库,边看边学习鸿蒙应用开发。此题库已更新完毕,笔者将不继续…...
基于PHP的文件包含介绍
引言:在实际开发过程中,经常会遇到部分模块功能需要重复使用的情况,比如数据库的增删改查,文件包含通过将需要重复使用的功能模块代码引入其他文件的内容,实现重用代码、分离配置等。然而,如果文件包含操作…...

K7系列FPGA多重启动(Multiboot)
Xilinx 家的 FPGA 支持多重启动功能(Multiboot),即可以从多个 bin 文件中进行选择性加载,从而实现对系统的动态更新,或系统功能的动态调整。 这一过程可以通过嵌入在 bit 文件里的 IPROG 命令实现上电后的自动加载。而…...

关于武汉芯景科技有限公司的RS232通信接口芯片XJ3243EEUI开发指南(兼容MAX3243EEUI)
一、芯片引脚介绍 1.芯片引脚 2.引脚描述 二、典型应用电路 三、功能描述 1.Transmitter 通过T1,T2可以将TTL电平转换为RS232电平 2.Receiver 通过R1,R2可以将RS232电平转换为TTL电平 3.工作模式控制 4.INVALID引脚...

TreeSize Free:你的免费磁盘空间管理专家
TreeSize Free是一款专为Windows用户设计的磁盘空间分析工具。它能够帮助用户快速识别并管理那些占用大量空间的文件夹和文件。 功能亮点 快速扫描:TreeSize Free能够迅速扫描整个磁盘卷,展示所有文件夹及其子文件夹的大小,甚至可以细化到单…...
python办公自动化:初识`python-docx`
1.1 什么是python-docx python-docx是一个用于在Python中创建和操作Word文档的库。它提供了一组简洁的API,让开发者可以轻松地生成、修改、和读取Microsoft Word (.docx)文件,而不需要安装Microsoft Office。这使得python-docx成为办公自动化、报告生成…...
LeetCode 算法:划分字母区间 c++
原题链接🔗:划分字母区间难度:中等⭐️⭐️ 题目 给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。 注意,划分结果需要满足:将所有划分结果按顺序连接&#…...

PMP备考指南:策略、时间安排与心得分享
准备和时间安排,我是工作的时间把它顺便考了,大约花了一个月左右时间备考,前面的时间都在筹办婚礼,根本没时间,最后一个月都差点想放弃了,但想想还是冲一把就没有选择延考。 干货见下: ▌&…...
CentOS上通过frp实现HTTPS访问内网
要在CentOS上通过frp实现HTTPS访问内网,你需要按照以下步骤操作: 在外网服务器上安装frps(frp服务端)。 在外网服务器上配置frps,编辑配置文件frps.ini。 在frps服务器上启动frps服务。 在内网服务器上安装frpc&…...

短视频SDK解决方案,高效集成,助力商业变现
美摄科技,作为业界领先的多媒体技术服务商,其全面升级的短视频SDK解决方案,旨在为开发者与内容创作者提供一站式、高效能的创作工具,让每一个灵感都能瞬间转化为触动人心的视频作品。 【一站式解决方案,重塑短视频创作…...

C++系列-继承方式
继承方式 继承的语法继承方式:继承方式的特点继承方式的举例 继承可以减少重复的代码。继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。基类父类,派生类子类,派生类是在继承了基类的部分成员基础…...

web前端之选项卡的实现、动态添加类名、动态移除类名、动态添加样式、激活、间距、tabBar
MENU 原生(一)原生(二)vue(一) 原生(一) 效果图 html 代码 <div class"card"><div class"tab_bar"><div class"item" onclick"handleTabBar(this)">tabBar1</div><div class"item" onclick&qu…...
sql 优化,提高查询速度
文章目录 一、前言二、建议2.1 使用索引2.2 避免使用select *2.3. 使用表连接代替子查询2.4. 优化WHERE子句,减少返回结果集的大小2.5 用union all代替union2.6 使用合适的聚合策略2.7 避免在WHERE子句中使用函数2.8 使用EXPLAIN分析查询2.9 小表驱动大表2.10 使用窗…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
拉力测试cuda pytorch 把 4070显卡拉满
import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试,通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小,增大可提高计算复杂度duration: 测试持续时间(秒&…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...

3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...
Go语言多线程问题
打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...

消息队列系统设计与实践全解析
文章目录 🚀 消息队列系统设计与实践全解析🔍 一、消息队列选型1.1 业务场景匹配矩阵1.2 吞吐量/延迟/可靠性权衡💡 权衡决策框架 1.3 运维复杂度评估🔧 运维成本降低策略 🏗️ 二、典型架构设计2.1 分布式事务最终一致…...

论文阅读:Matting by Generation
今天介绍一篇关于 matting 抠图的文章,抠图也算是计算机视觉里面非常经典的一个任务了。从早期的经典算法到如今的深度学习算法,已经有很多的工作和这个任务相关。这两年 diffusion 模型很火,大家又开始用 diffusion 模型做各种 CV 任务了&am…...
《Offer来了:Java面试核心知识点精讲》大纲
文章目录 一、《Offer来了:Java面试核心知识点精讲》的典型大纲框架Java基础并发编程JVM原理数据库与缓存分布式架构系统设计二、《Offer来了:Java面试核心知识点精讲(原理篇)》技术文章大纲核心主题:Java基础原理与面试高频考点Java虚拟机(JVM)原理Java并发编程原理Jav…...
boost::filesystem::path文件路径使用详解和示例
boost::filesystem::path 是 Boost 库中用于跨平台操作文件路径的类,封装了路径的拼接、分割、提取、判断等常用功能。下面是对它的使用详解,包括常用接口与完整示例。 1. 引入头文件与命名空间 #include <boost/filesystem.hpp> namespace fs b…...
简单介绍C++中 string与wstring
在C中,string和wstring是两种用于处理不同字符编码的字符串类型,分别基于char和wchar_t字符类型。以下是它们的详细说明和对比: 1. 基础定义 string 类型:std::string 字符类型:char(通常为8位)…...