Java教程

Netty网络通讯学习入门教程

本文主要是介绍Netty网络通讯学习入门教程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
概述

本文详细介绍了Netty网络通讯学习的基础知识,包括Netty的简介、环境搭建以及基本组件和事件处理模型。文章还深入讲解了如何使用Netty构建简单的TCP服务器,并提供了HTTP和WebSocket协议的实现示例。此外,文中还讨论了性能优化策略和注意事项,帮助读者更好地理解和应用Netty。

Netty网络通讯学习入门教程
Netty简介与环境搭建

Netty是什么

Netty 是一个基于 Java NIO 的异步事件驱动的网络应用框架,可以快速开发高性能、高可靠性的网络服务器和客户端。它提供了包括缓冲区管理、网络连接管理、事件处理、异步IO操作等功能的实现。Netty 的设计目标是提供一个可重用的、异步的事件驱动的网络应用程序框架和工具,从而快速地开发出高效、稳定、健壮的协议服务器和客户端程序。

为什么选择Netty

Netty 在以下几个方面优于传统的 Java 网络编程方式:

  1. 高性能:Netty 使用了高效的内存管理机制,如使用了池化的直接内存来减少垃圾回收的压力,实现了高性能的事件驱动模型。
  2. 灵活的协议支持:Netty 支持多种协议,如 HTTP、WebSocket、自定义协议等。它提供了一个丰富的协议支持库,开发者可以快速地开发出支持不同协议的应用程序。
  3. 强大的编码解码工具:Netty 提供了多种编码解码工具,如字符串编码解码器、基于字节的编码解码器、基于对象的编码解码器等。开发者可以根据需要选择合适的编码解码工具。
  4. 易于扩展的事件处理模型:Netty 采用了一种可扩展的事件处理模型,通过 ChannelHandler 可以方便地处理各种网络事件。同时提供了多种事件处理策略,如入站、出站、异常处理等。
  5. 强大的错误处理机制:Netty 提供了强大的错误处理机制,包括异常处理、超时处理等,可以有效地处理各种网络错误。
  6. 灵活的线程模型:Netty 的线程模型可以根据具体的应用场景灵活配置,有助于提高应用程序的性能和响应速度。

开发环境搭建

为了使用 Netty 开发网络应用程序,首先需要搭建开发环境。

  1. 安装 JDK

    • 确保你已经安装了 JDK 1.8 或更高版本。推荐使用最新版的 JDK。
  2. 创建 Maven 项目
    • 使用 IDE(如 IntelliJ IDEA 或 Eclipse)创建一个新的 Maven 项目。
    • pom.xml 文件中添加对应的依赖。

以下是一个简单的 Maven 配置文件示例,包含了 Netty 的依赖:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>netty-tutorial</artifactId>
    <version>1.0.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.69.Final</version>
        </dependency>
    </dependencies>
</project>
  1. 创建 Netty 应用
    • 开发环境搭建完成后,就可以开始编写 Netty 应用了。
Netty的基本组件

Netty 的设计基于几个核心组件,主要包括 Channel, ChannelHandler, EventLoop, EventLoopGroup, Bootstrap, 和 ServerBootstrap

Channel与ChannelHandler

Channel 是一个抽象的接口,它代表了网络通信的一种打开的连接,可以进行读写操作。Channel 的作用类似于传统网络编程中的 Socket,但更强大。

ChannelHandler 是一个用于处理 Channel 事件的接口。Channel 事件包括连接、读写、关闭等。每个 ChannelHandler 可以实现不同的处理逻辑,如读取或发送消息。

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class MyChannelHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 处理读取的数据
        System.out.println("Received: " + msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 异常处理
        cause.printStackTrace();
        ctx.close();
    }
}

EventLoop与EventLoopGroup

EventLoop 是一个执行异步任务的线程。每个 Channel 都会关联一个 EventLoop,并且每个 EventLoop 会拥有一个 Selector,用于监听 Channel 上的 I/O 事件。EventLoop 还可以分配其他线程的任务,如异步执行任务等。

EventLoopGroup 是一个 EventLoop 的集合。它管理一个或多个 EventLoop,并根据需要分配任务到不同的 EventLoop 中。在 Netty 中,我们通常会使用 EventLoopGroup 来管理 Channel 的生命周期。

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class SimpleTcpServer {
    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new MyChannelHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

Bootstrap与ServerBootstrap

BootstrapServerBootstrap 是用于配置和启动 Channel 的启动助手类。Bootstrap 用于客户端 Channel 的启动,而 ServerBootstrap 用于服务端 Channel 的启动。

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class SimpleTcpClient {
    public static void main(String[] args) throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new MyChannelHandler());
                        }
                    })
                    .option(ChannelOption.TCP_NODELAY, true)
                    .option(ChannelOption.SO_RCVBUF, 1024)
                    .option(ChannelOption.SO_SNDBUF, 1024);

            ChannelFuture channelFuture = bootstrap.connect("localhost", 8080).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }
}
Netty的事件处理模型

Netty 的事件处理模型是基于异步 I/O 和事件驱动的,由 EventLoopGroup 和 ChannelHandler 组成。

事件驱动与异步编程

在 Netty 中,事件驱动模型允许开发者编写非阻塞的异步代码,使得应用程序可以高效地处理多个连接。每个连接的读写操作都是异步完成的,整个系统不需要等待 I/O 操作完成,而是由 EventLoop 负责监听 I/O 事件,并将事件异步地分发到相应的 ChannelHandler 处理。

事件循环与线程模型

Netty 采用了一个事件循环模型,每个 EventLoop 负责一个或多个 Channel 的 I/O 事件处理。每个 EventLoop 都会分配一个线程,该线程会执行所有与该 EventLoop 关联的 I/O 事件处理任务。EventLoop 使用 Selector 来监听 I/O 事件,当 I/O 事件发生时,EventLoop 会调用相应的 ChannelHandler 处理事件。

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class InboundHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        System.out.println("Received: " + msg);
    }
}

自定义ChannelHandler处理网络事件

通过实现 ChannelHandler 接口,可以自定义网络事件的处理逻辑。例如,可以实现读取、写入、连接关闭等事件的处理。

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class MyChannelHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 处理读取的数据
        System.out.println("Received: " + msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // 读取完成后触发
        ctx.writeAndFlush("Data received");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // 异常处理
        cause.printStackTrace();
        ctx.close();
    }
}
实战:构建一个简单的TCP服务器

创建ServerBootstrap

ServerBootstrap 是用于启动服务器的启动助手类。通过 ServerBootstrap,可以配置服务器的监听端口、事件处理程序、连接池大小等。

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class SimpleTcpServer {
    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast(new MyChannelHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

处理连接事件

在连接事件被触发时,Netty 提供了多个回调方法用于处理连接事件,如 channelActivechannelInactive

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class MyChannelHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        System.out.println("Client connected: " + ctx.channel().remoteAddress());
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        System.out.println("Client disconnected: " + ctx.channel().remoteAddress());
    }
}

数据读写与关闭连接

在数据读取事件触发时,可以读取并处理接收到的数据。当数据写入事件触发时,可以在 Channel 内部或外部发送数据。关闭连接时,可以通过调用 Channelclose 方法来关闭连接。

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class MyChannelHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 处理读取的数据
        System.out.println("Received: " + msg);
        ctx.writeAndFlush("Echo: " + msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // 读取完成后触发
        ctx.writeAndFlush("Data received");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // 异常处理
        cause.printStackTrace();
        ctx.close();
    }
}
网络通讯协议解析

Netty 支持多种网络协议的解析,包括 HTTP、WebSocket、自定义协议等。通过在处理流水线中添加相应的编码解码器,可以方便地处理这些协议。

HTTP协议解析

Netty 提供了内置的 HTTP 编码解码器,使得 HTTP 协议的处理变得非常简单。以下是一个简单的 HTTP 服务器示例。

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;

public class SimpleHttpServer {
    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast(new HttpServerCodec());
                            ch.pipeline().addLast(new HttpObjectAggregator(1024));
                            ch.pipeline().addLast(new MyHttpHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;

public class MyHttpHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof FullHttpRequest) {
            FullHttpRequest request = (FullHttpRequest) msg;
            ByteBuf content = request.content();
            String message = content.toString(CharsetUtil.UTF_8);
            System.out.println("Received HTTP request: " + message);

            // 构造 HTTP 响应
            FullHttpResponse response = new DefaultFullHttpResponse(
                    HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
                    Unpooled.copiedBuffer("Hello, Client", CharsetUtil.UTF_8));
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");

            // 发送响应
            ctx.writeAndFlush(response);
        }
    }
}

WebSocket协议实现

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,Netty 提供了 WebSocket 的支持,使得 WebSocket 的开发变得简单。

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.stream.ChunkedWriteHandler;

public class WebSocketServer {
    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast(new HttpServerCodec());
                            ch.pipeline().addLast(new HttpObjectAggregator(1024));
                            ch.pipeline().addLast(new ChunkedWriteHandler());
                            ch.pipeline().addLast(new WebSocketServerProtocolHandler("/ws"));
                            ch.pipeline().addLast(new MyWebSocketHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;

public class MyWebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        // 收到 WebSocket 消息
        String message = msg.text();
        System.out.println("Received WebSocket message: " + message);
        // 回复消息
        ctx.writeAndFlush(new TextWebSocketFrame("Echo: " + message));
    }
}

自定义协议的处理

处理自定义协议时,首先需要定义协议的格式,然后编写相应的编码解码器实现协议的解析和生成。

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

public class MyEncoder extends MessageToByteEncoder<String> {
    @Override
    protected void encode(ChannelHandlerContext ctx, String in, ByteBuf out) throws Exception {
        byte[] data = in.getBytes(CharsetUtil.UTF_8);
        out.writeBytes(data);
    }
}

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

import java.util.List;

public class MyDecoder extends ByteToMessageDecoder {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        if (in.readableBytes() >= 5) {
            String message = in.toString(in.readerIndex(), in.readableBytes(), CharsetUtil.UTF_8);
            out.add(message);
        }
    }
}

public class SimpleTcpClientWithCustomProtocol {
    public static void main(String[] args) throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new MyDecoder());
                            ch.pipeline().addLast(new MyEncoder());
                            ch.pipeline().addLast(new MyCustomProtocolHandler());
                        }
                    })
                    .option(ChannelOption.TCP_NODELAY, true)
                    .option(ChannelOption.SO_RCVBUF, 1024)
                    .option(ChannelOption.SO_SNDBUF, 1024);

            ChannelFuture channelFuture = bootstrap.connect("localhost", 8080).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }
}

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class MyCustomProtocolHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 处理读取的数据
        System.out.println("Received: " + msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // 读取完成后触发
        ctx.writeAndFlush("Data received");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // 异常处理
        cause.printStackTrace();
        ctx.close();
    }
}
性能优化与注意事项

Netty 提供了许多性能优化的方法和技巧,如零拷贝技术、连接池与心跳机制等。同时需要注意异常处理和资源管理,以保证应用程序的健壮性和稳定性。

零拷贝技术

零拷贝技术可以减少数据从磁盘到用户空间再回到磁盘的拷贝次数,从而提高应用程序的性能。Netty 通过直接内存的使用来实现零拷贝,减少了垃圾回收的压力。

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

public class MyEncoder extends MessageToByteEncoder<String> {
    @Override
    protected void encode(ChannelHandlerContext ctx, String in, ByteBuf out) throws Exception {
        byte[] data = in.getBytes(CharsetUtil.UTF_8);
        out.writeBytes(data);
    }
}

连接池与心跳机制

连接池可以重用已经建立的连接,减少新连接的建立时间,提高应用程序的性能。心跳机制可以定期检查连接的状态,确保连接的可靠性。

import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class MyHeartbeatHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 处理心跳包
        if (msg instanceof ByteBuf) {
            ByteBuf heartbeat = (ByteBuf) msg;
            heartbeat.release();
            ctx.writeAndFlush(new ByteBuf(ChannelHandlerContext.UNPREFETCHED, 0, 0));
        }
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
        if (evt instanceof IdleStateHandler) {
            ctx.writeAndFlush(new ByteBuf(ChannelHandlerContext.UNPREFETCHED, 0, 0));
        }
    }
}

异常处理与资源管理

在异常处理方面,Netty 提供了多种回调方法用于处理异常,如 exceptionCaught 方法。在资源管理方面,需要确保在资源使用完毕后释放资源,避免资源泄露。

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class MyExceptionHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // 异常处理
        cause.printStackTrace();
        ctx.close();
    }
}
``

通过以上内容,你应该已经掌握了 Netty 的基本概念和使用方法,并能够构建一个简单的 TCP 服务器。同时,了解了如何处理不同的网络协议,并进行性能优化。希望这篇文章对你有所帮助。
这篇关于Netty网络通讯学习入门教程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!