Java面试——Netty

优质博文:IT-BLOG-CN

一、BIO、NIO 和 AIO

【1】阻塞 IO(Blocking I/O): 同步阻塞I/O模式,当一条线程执行 read() 或者 write() 方法时,这条线程会一直阻塞直到读取一些数据或者写出去的数据已经全部写出,在这期间这条线程不能做任何其他的事情。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。但是,当面对十万甚至百万级连接的时候,传统的 BIO模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。

【2】非阻塞 NIO(New I/O): NIO是一种同步非阻塞的 I/O模型,NIO 与原有的 IO 有同样的作用和目的,但是使用的方式完全不同,NIO 支持面向缓冲区、基于通道的操作。NIO 将以更加高效的方式进行文件读写操作。JAVA NIO的核心在于:通道(Channel)和缓冲区(Buffer)。通道表示打开 IO 设备(例如:文件、套接字)的连接。若需要使用 NIO系统,需要获取用于连接 IO设备的通道以及用于容纳数据的缓冲区。对数据进行处理。阻塞IO 会一直等待,所以非阻塞IO 是用来解决 IO线程与 Socket 之间的解耦问题【通过引入事件机制】,如果 Socket 发送缓冲区可写的话会通知 IO线程进行 write,如果 Socket 的接收缓冲区可读的话会通知 IO线程进行 read。NIO 提供了与传统 BIO 模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel。对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。

【3】I/O 复用模型: 非阻塞 IO中引入的机制就是 IO多路复用模型,Linux 提供了 epoll系统调用,epoll使用基于事件驱动方式代替顺序扫描(select/poll),因此性能更高。当有fd 就绪时,立即回调函数 rollback。IO多路复用技术底层就是通过 epoll 函数实现的。

【4】AIO (Asynchronous I/O): AIO 也就是 NIO2。在 Java 7 中引入了 NIO 的改进版 NIO2,它是异步非阻塞的IO模型。异步 IO 是基于事件回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步IO 的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。

二、Netty 的各大组件

【1】Channel: Channel 是 Netty 网络操作抽象类,它除了包括基本的 I/O 操作,如 bind、connect、read、write 之外,还包括了 Netty 框架相关的一些功能,如获取该 Channel 的 EventLoop。在传统的网络编程中,作为核心类的 Socket ,它对程序员来说并不是那么友好,直接使用其成本还是稍微高了点。而 Netty 的 Channel 则提供的一系列的 API ,它大大降低了直接与 Socket 进行操作的复杂性。而相对于原生 NIO 的 Channel,Netty 的 Channel 具有如下优势: ①、在 Channel 接口层,采用 Facade 模式进行统一封装,将网络 I/O 操作、网络 I/O 相关联的其他操作封装起来,统一对外提供。②、Channel 接口的定义尽量大而全,为 SocketChannel 和 ServerSocketChannel 提供统一的视图,由不同子类实现不同的功能,公共功能在抽象父类中实现,最大程度地实现功能和接口的重用。③、具体实现采用聚合而非包含的方式,将相关的功能类聚合在 Channel 中,有 Channel 统一负责和调度,功能实现更加灵活。

【2】EventLoop: Netty 基于事件驱动模型,使用不同的事件来通知我们状态的改变或者操作状态的改变。它定义了在整个连接的生命周期里当有事件发生的时候处理的核心抽象。Channel 为 Netty 网络操作抽象类,EventLoop 主要是为 Channel 处理 I/O 操作,两者配合参与 I/O 操作。Channel、EventLoop、Thread、EventLoopGroup之间的关系:
 ①、一个 EventLoopGroup 包含一个或多个 EventLoop。
 ②、一个 EventLoop 在它的生命周期内只能与一个 Thread绑定。
 ③、所有 EnventLoop 处理的 I/O事件都将在它专有的 Thread 上被处理。
 ④、一个 Channel 在它的生命周期内只能注册于一个 EventLoop。
 ⑤、一个 EventLoop 可被分配至一个或多个 Channel 。
当一个连接到达时,Netty 就会注册一个 Channel,然后从 EventLoopGroup 中分配一个 EventLoop 绑定到这个Channel上,在该 Channel的整个生命周期中都是有这个绑定的 EventLoop 来服务的。

【3】ChannelFuture:Netty 为异步非阻塞, 即所有的 I/O 操作都为异步的,因此,我们不能立刻得知消息是否已经被处理了。Netty 提供了 ChannelFuture 接口,通过该接口的 addListener() 方法注册一个 ChannelFutureListener,当操作执行成功或者失败时,监听就会自动触发返回结果。

【4】ChannelHandler:ChannelHandler 为 Netty 中最核心的组件,它充当了所有处理入站出站数据的应用程序逻辑的容器。ChannelHandler 主要用来处理各种事件,这里的事件很广泛,比如可以是连接、数据接收、异常、数据转换等。ChannelHandler 有两个核心子类 ChannelInboundHandler 和 ChannelOutboundHandler,其中 ChannelInboundHandler 用于接收、处理入站数据和事件,而 ChannelOutboundHandler 则相反。

【5】ChannelPipeline:ChannelPipeline 为 ChannelHandler 链提供了一个容器并定义了用于沿着链传播入站和出站事件流的 API。一个数据或者事件可能会被多个 Handler 处理,在这个过程中,数据或者事件经流 ChannelPipeline,由 ChannelHandler 处理。在这个处理过程中,一个 ChannelHandler 接收数据后处理完成后交给下一个 ChannelHandler,或者什么都不做直接交给下一个 ChannelHandler。当一个数据流进入 ChannlePipeline 时,它会从 ChannelPipeline 头部开始传给第一个 ChannelInboundHandler ,当第一个处理完后再传给下一个,一直传递到管道的尾部。与之相对应的是,当数据被写出时,它会从管道的尾部开始,先经过管道尾部的 “最后” 一个ChannelOutboundHandler,当它处理完成后会传递给前一个 ChannelOutboundHandler 。当 ChannelHandler 被添加到 ChannelPipeline 时,它将会被分配一个 ChannelHandlerContext,它代表了 ChannelHandler 和 ChannelPipeline 之间的绑定。其中 ChannelHandler 添加到 ChannelPipeline 过程如下:
①、一个 ChannelInitializer 的实现被注册到了 ServerBootStrap 中;
②、当 ChannelInitializer.initChannel() 方法被调用时,ChannelInitializer 将在 ChannelPipeline 中安装一组自定义的ChannelHandler;
③、ChannelInitializer 将它自己从 ChannelPipeline 中移除;

三、Netty 的线程模型

【参考答案】:链接

四、TCP 粘包/拆包的原因及解决方法

【参考答案】:链接

五、了解哪几种序列化协议?包括使用场景和如何去选择

【序列化的缺点】:链接
【序列化框架 Protobuf】:链接
【序列化框架 Thrift】:链接

六、Netty 的零拷贝实现

【参考答案】:链接

七、Netty 的高性能分析

【1】传输: IO 模型在很大程度上决定了框架的性能,相比于BIO,Netty建议采用异步通信模式,因为 NIO一个线程可以并发处理N个客户端连接和读写操作,这从根本上解决了传统同步阻塞IO “一连接一线程模型”,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。正如代码中所示,使用的是 NioEventLoopGroup 和 NioSocketChannel 来提升传输效率。
【2】协议: 采用什么样的通信协议,对系统的性能极其重要,Netty 默认提供了对 Google Protobuf 的支持,也可以通过扩展Netty 的编解码接口,用户可以实现其它的高性能序列化框架。
【3】线程: Netty 使用了Reactor线程模型,但 Reactor 模型不同,对性能的影响也非常大,上面线程模型中就介绍常用的 Reactor 模型的三种情况。

八、基于BIO实现的Server端,当建立了100个连接时,会有多少个线程?如果基于NIO,又会是多少个线程? 为什么?

BIO由于不是 NIO那样的事件机制,在连接的 IO读取上,无论是否真的有读/写发生,都需要阻塞住当前的线程,对于基于 BIO实现的 Server端,通常的实现方法都是用一个线程去 accept连接,当连接建立后,将这个连接的 IO读写放到一个专门的处理线程,所以当建立100个连接时,通常会产生1个 Accept线程 + 100个处理线程

NIO通过事件来触发,这样就可以实现在有需要读/写的时候才处理,不用阻塞当前线程,NIO在处理 IO的读写时,当从网卡缓冲区读或写入缓冲区时,这个过程是串行的,所以用太多线程处理 IO事件其实也没什么意义,连接事件由于通常处理比较快,用1个线程去处理就可以,IO事件呢,通常会采用 cpu core数+1cpu core数 * 2,这个的原因是 IO线程通常除了从缓冲区读写外,还会做些比较轻量的例如解析协议头等,这些是可以并发的,为什么不只用1个线程处理,是因为当并发的 IO事件非常多时,1个线程的效率不足以发挥出多核CPU 的能力,从而导致这个地方成为瓶颈,这种在分布式场景中比较明显,按照这个,也就更容易理解为什么在基于 Netty等写程序时,不要在 IO线程里直接做过多动作,而应该把这些动作转移到另外的线程池里去处理,就是为了能保持好 IO事件能被高效处理。从上面可以看出,对于大多数需要建立大量连接,但并发读写并不会同时产生的场景而言,NIO的优势是非常明显的。这种关于BIO、NIO的问法的变化空间是非常大的,还可以进一步拓展问问AIO和BIO、NIO的根本不同。

九、Netty 的心跳检测及重连机制怎么实现的

【参考答案】:链接

十、ChannelInboundHandlerAdapterSimpleChannelInboundHandler 的区别

如下就是两个类的声明,SimpleChannelInboundHandler是继承 ChannelInboundHandlerAdapter的。也就是说SimpleChannelInboundHandler 也拥有 ChannelInboundHandlerAdapter的方法。

//ChannelInboundHandlerAdapter 类
ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler
//SimpleChannelInboundHandler 类
SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter

继承关系图:
执行器
SimpleChannelInboundHandlerchannelRead相比 ChannelInboundHandlerAdapter而言,主要做了类型匹配以及用完之后释放指向保存该消息的 ByteBuf的内存引用。这里提供了一个模板,作用是把处理逻辑不变的内容写好在channelRead(ctx,msg) 中,并且在里面调用 channelRead0 ,这样变化的内容通过抽象方法实现传递到子类中去了(在Netty5channelRead0已被重命名为 messageReceived)。

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {boolean release = true;try {if (this.acceptInboundMessage(msg)) {this.channelRead0(ctx, msg);} else {release = false;ctx.fireChannelRead(msg);}} finally {if (this.autoRelease && release) {ReferenceCountUtil.release(msg);}}
}protected abstract void channelRead0(ChannelHandlerContext var1, I var2) throws Exception;

相比之下,ChannelInboundHandlerAdapter好像一无是处,毕竟他要自己处理资源的释放,例如如下的调用:buf.release();如果说 channelRead都是同步操作的话,SimpleChannelInboundHandler是不错的选择,如果操作是异步的话,那他的逻辑就有点麻烦了,例如你把数据交给另外的线程处理了,还没处理就会释放了 。这里必须说明一个问题,他的回收和 jvm的垃圾回收还不完全是一回事。netty是自己做了引用计数的操作。 buf.refCnt(); 通过上面的 api就可以获取到计数的个数。具体的引用计数的部分,不知道也不影响 netty的学习,这个点后面具体再说。ChannelInboundHandlerAdapter 处理自由的优点也就提现出来了,可以更好的处理更多的特定场景。

那该方法是什么时候释放资源的呢?writeAndFlush() 方法被调用时才被释放,我们点进去源码验证一下:

public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {if (msg == null) {// msg不能为空throw new NullPointerException("msg");}if (isNotValidPromise(promise, true)) {ReferenceCountUtil.release(msg);// 释放资源(保存消息的ByteBuf)// cancelledreturn promise;}write(msg, true, promise);// 异步写操作return promise;
}

上面源码中,最后资源是通过 ReferenceCountUtil来释放的。也就是说,当我们需要释放 ByteBuf相关内存的时候,也可以使用 ReferenceCountUtil.release()ReferenceCountUtil 底层实现是 ReferenceCounted ,当新的对象初始化的时候计数为1,retain() 方法被调用时引用计数加1,release()方法被调用时引用计数减1,当计数减少到0的时候会被显示清除,再次访问被清除的对象会出现访问冲突(这里想起了JVM判断对象是否存活的引用计数算法)。ReferenceCountUtil.release源码如下:

public static boolean release(Object msg) {if (msg instanceof ReferenceCounted) {return ((ReferenceCounted) msg).release();// Decreases the reference count by 1}return false;
}

SimpleChannelInboundHandlerChannelInboundHandlerAdapter区别:
在客户端的业务 Handler继承的是 SimpleChannelInboundHandler,而在服务器端继承的是 ChannelInboundHandlerAdapter。最主要的区别就是 SimpleChannelInboundHandler在接收到数据后会自动 release掉数据占用的 Bytebuffer资源(自动调用Bytebuffer.release())。而为何服务器端不能用呢,因为我们想让服务器把客户端请求的数据发送回去,而 write() 操作是异步的,而服务器端有可能在channelRead方法返回后还没有写完数据,因此不能让它自动releaseSimpleChannelInboundHandler 是有泛型参数的。可以指定一个具体的类型参数,通过 decoder配合使用,非常方便。ChannelInboundHandlerAdapter 则是直接操作 byte数组的。

SimpleChannelInboundHandler 的好处是可以处理不同的类型对象,并且可以做释放。ChannelInboundHandlerAdapter的好处则是更自由,在异步的场景下更适合。

ChannelInboundHandlerAdapter 注意事项 : 用户自定义了一个 Handler类,代码如下:

@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {this.ctx = ctx;
}@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {super.channelRead(ctx, msg);log.debug("thread.name={}", Thread.currentThread().getName());ByteBuf in = (ByteBuf) msg;String readStr = in.toString(CharsetUtil.UTF_8);log.debug("Server received: {}", readStr);log.debug("release msg");ReferenceCountUtil.release(msg);}@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {ctx.flush();
}@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();ctx.close();
}

行时出现了如下堆栈异常:

io.netty.util.IllegalReferenceCountException: refCnt: 0
at io.netty.buffer.AbstractByteBuf.ensureAccessible(AbstractByteBuf.java:1173)
at io.netty.buffer.AbstractByteBuf.checkIndex(AbstractByteBuf.java:1119)
at io.netty.buffer.UnpooledUnsafeDirectByteBuf.internalNioBuffer(UnpooledUnsafeDirectByteBuf.java:385)
at io.netty.buffer.ByteBufUtil.decodeString(ByteBufUtil.java:568)
at io.netty.buffer.AbstractByteBuf.toString(AbstractByteBuf.java:979)
at io.netty.buffer.AbstractByteBuf.toString(AbstractByteBuf.java:974)
at com.spy.apollo.netty.demo.demo02_biz_logic.ServerBizHandler.channelRead(ServerBizHandler.java:50)

异常说是引用计数 refCnf = 0,也就是没有被引用,因此报错;常规的 channelRead中消息读取完毕是要立即释放当前消息的引用计数即(减一操作)如下代码操作。但是其他 Handler也去释放时,发现没有引用,就会报错。

ReferenceCountUtil.release(msg);

分析: 通过调试代码发现根源就在 super.channelRead(ctx, msg);这个函数。其实 ChannelInboundHandlerAdapter 是提供了一种默认实现,子类如果要继承,需要覆盖父类中的方法,并且不需要调用 super.xxxxMethod()。源码如下:意思是此实现只是将操作转发到下一个 channelhandler。子类可以重写方法实现来改变这一点。

/*** <p>* This implementation just forward the operation to the next {@link ChannelHandler} in the* {@link ChannelPipeline}. Sub-classes may override a method implementation to change this.* </p>*/
public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler {

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

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

相关文章

泛微OA服务器获取 token

泛微OA服务器获取 token 文章目录 泛微OA服务器获取 token一、泛微官方方法1 ecology 系统配置2 发放/生成许可证(appid)3 限制许可证使用ip地址&#xff08;该步骤也可以跳过&#xff09;4 使用 postman 注册5 获取 token6 访问业务系统接口 二、java 代码获取 token三、封装到…

20240308-2-校招前端面试常见问题-网络及浏览器

校招前端面试常见问题【4】——网络及浏览器 1、网络相关 Q&#xff1a;请简述一下 HTTP 协议&#xff0c;以及 HTTP1.0/1.1/2.0/3.0 的区别&#xff1f; HTTP 协议&#xff1a;超文本传输协议&#xff0c;使用 TCP/IP 协议传输数据。是一个应用层的协议。 HTTP1.0&#xff…

特性螺旋面的刀具设计记录

最近和成型类刀具杠上了&#xff0c;这不最近有小伙伴提供了两个比较特殊的螺旋面工件&#xff0c;通常称作阴、阳转子。具体形状如下&#xff1a; 阴转子 阴转子端面齿形没看出有什么特殊的&#xff0c;但是在轴剖面齿形是内凹的&#xff0c;这个是比较特殊的形式。 阳转子…

信息系统项目管理师003:信息化(1信息化发展—1.1信息与信息化—1.1.3 信息化)

文章目录 1.1.3 信息化1.信息化内涵2.信息化体系3.信息化趋势 要点总结 1.1.3 信息化 信息化是一个过程&#xff0c;与工业化、现代化一样&#xff0c;是一个动态变化的过程。信息化是指培养、发展以计算机为主的智能化工具为代表的新生产力&#xff0c;并使之造福于社会的历史…

Qt自定义控件

自定义控件 目的&#xff1a;将多个控件或者窗口作为一个整体被多次复用。 操作方式 1.首先进行自定义的ui设计&#xff0c;以及对应的.h和.cpp文件 2.到要使用的UI界面上&#xff0c;从控件库中拖拽一个Widget控件 3.右键点击"提升为" 4.填写自定义实现的类名&…

Mysql的Cardinality值

什么是Cardinality值&#xff1f; Cardinality值是Mysql做索引优化时一个非常关键的值&#xff0c;优化器会根据这个值来判断是否使用这个索引&#xff0c;它表示索引中唯一值的数目估计值&#xff0c;该值应该尽可能接近1&#xff0c;如果非常小&#xff0c;则用户需要考虑是否…

7. 交叉开发环境设置

嵌入式交叉编译工具 ​ 交叉编译工具是为了使在上位机中编译的文件能够在不同平台的目标机中执行&#xff0c;搭建交叉编译环境是嵌入式开发的第一步&#xff0c;也是关键的一步。不同的体系结构、不同的操作系统&#xff0c;甚至是不同版本的内核&#xff0c;都会用到不同的交…

win11中微软商店如何使用微信支付?microsoft store支付教程

Microsoft Store是由微软公司提供的一个数字分发平台&#xff0c;用于购买和下载Windows操作系统及其相关应用、游戏、音乐、电影、电视节目和其他数字内容。该平台最初是作为Windows 8的一部分引入的&#xff0c;后来也适用于Windows 10和其他Microsoft平台。 以下是Microsof…

JavaWeb笔记 --- 一JDBC

一、JDBC JDBC就是Java操作关系型数据库的一种API DriverManager 注册驱动可以不写 Class.forName("com.mysql.jdbc.Driver"); Connection Statement ResultSet PrepareStatement 密码输入一个SQL脚本&#xff0c;直接登录 预编译开启在url中 数据库连接池

HTML5+CSS3+JS小实例:暗紫色Tabbar

实例:暗紫色Tabbar 技术栈:HTML+CSS+JS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scal…

【梳理】k8s使用Operator搭建Flink集群

文章目录 架构图安装cert-manager依赖helm 安装operator运行集群实例k8s上的两种模式&#xff1a;Native和Standalone两种CRDemo1&#xff1a;Application 单任务Demo2&#xff1a;Session 多任务创建ingress 总结 架构图 参考&#xff1a;部署验证demo 安装cert-manager依赖 …

CSS中 ,有哪些方式可以隐藏页面元素

文章目录 CSS中 &#xff0c;有哪些方式可以隐藏页面元素实现方式display&#xff1a;nonevisibility:hiddenopacity:0设置height 、width属性为0position:absoluteclip-path小结 CSS中 &#xff0c;有哪些方式可以隐藏页面元素 实现方式 通过 css 实现隐藏元素方法有如下 : …

uniapp列表进入动画

app列表入场动画 - DCloud 插件市场 列表入场动画https://ext.dcloud.net.cn/plugin?id16957

中医把脉笔记

目录 寸关尺对应的五脏六腑自己给自己把脉把脉五布法定寸关尺分浮中沉分快慢辨阴阳看虚实 参考文章 寸关尺对应的五脏六腑 自己给自己把脉 up主道道总是睡不着的把脉教学视频 用中指按住小骨头下面一点&#xff0c;这是关脉&#xff0c;左手的关脉对应肝脏。 把脉五布法 定…

网络基础aaa

三次握手 四次挥手 网络模型 TCP or UDP 的特点 如何理解 TCP 的5层协议 TCP的5层协议是指计算机网络体系结构中&#xff0c;与TCP&#xff08;传输控制协议&#xff09;相关的五个层次。这五个层次从高到低依次是&#xff1a;应用层、传输层、网络层、数据链路层和物理层。每…

java注释的详尽解析

一、什么是注解 (1).注解的作用 ①&#xff1a;注解一般用于对程序的说明&#xff0c;就像注释一样&#xff0c;但是区别是注释是给人看的&#xff0c;但是注解是给程序看的。 ②&#xff1a;让编译器进行编译检查的作用&#xff0c;比如下边这个Override注解是重写的意思&am…

内存安全的编程语言

美国政府新颁布《回归基础构件&#xff1a;通往安全软件之路》 《回归基础构件&#xff1a;通往安全软件之路》中&#xff0c;白宫国家网络主任办公室&#xff08;ONCD&#xff09;呼吁开发者使用「内存安全的编程语言」 内存安全的编程语言 根据NSA的建议&#xff0c;内存…

sqlyog社区版下载,数据库客户端,mysql

Downloads webyog/sqlyog-community Wiki GitHubhttps://github.com/webyog/sqlyog-community/wiki/Downloadssqlyog社区版下载

liteIDE 解决go root报错 go: cannot find GOROOT directory: c:\go

liteIDE环境配置 我使用的liteIDE为 x36 5.9.5版本 。在查看–>选项 中可以看到 LiteEnv&#xff0c;双击LiteEnv &#xff0c;在右侧选择对应系统的env文件&#xff0c;我的是win64系统&#xff0c;所以文件名为win64.env 再双击 win64.env &#xff0c;关闭当前窗口&…

C++ Qt开发:QHostInfo主机地址查询组件

Qt 是一个跨平台C图形界面开发库&#xff0c;利用Qt可以快速开发跨平台窗体应用程序&#xff0c;在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置&#xff0c;实现图形化开发极大的方便了开发效率&#xff0c;本章将重点介绍如何运用QHostInfo组件实现对主机地址查询功能…