netty初使用及核心组件介绍

2021SC@SDUSC

目录

一、Netty核心组件简单介绍

二、netty初使用


一、Netty核心组件简单介绍

Netty中的三大核心组件包括Channel、EventLoop、ChannelFuture。

1. Channel接口

  基础的IO操作,如绑定、连接、读写等都依赖于底层网络传输所提供的原语,在Java的网络编程中,基础核心类是Socket,而Netty的Channel提供了一组API,极大地简化了直接与Socket进行操作的复杂性,并且Channel是很多类的父类,如EmbeddedChannel、LocalServerChannel、NioDatagramChannel、NioSctpChannel、NioSocketChannel等。

2. EventLoop接口

  EventLoop定义了处理在连接过程中发生的事件的核心抽象,之后会进一步讨论,其中Channel、EventLoop、Thread和EventLoopGroup之间的关系如下图所示

可以看到:

    · 一个EventLoopGroup包含一个或多个EventLoop。

    · 一个EventLoop在生命中周期绑定到一个Thread上。

    · EventLoop使用其对应的Thread处理IO事件。

    · 一个Channel使用EventLoop进行注册。

    · 一个EventLoop可被分配至一个或多个Channel。

3. ChannelFuture接口

  Netty中的所有IO操作都是异步的,不会立即返回,需要在稍后确定操作结果。因此Netty提供了ChannelFuture,其addListener方法可以注册一个ChannelFutureListener,当操作完成时,不管成功还是失败,均会被通知。ChannelFuture存储了之后执行的操作的结果并且无法预测操作何时被执行,提交至Channel的操作按照被唤醒的顺序被执行。

在后面的博客中,会结合实例逐一分析。

二、netty初使用

使用netty实现简单的客户端和服务器,初步了解netty使用,更好的进行代码分析

服务端主要包含两部分内容,分为引导和实现服务器处理器。引导服务器完成服务端的创建,如指定端口和处理器等,并未涉及到服务端的具体逻辑,具体业务逻辑可以在处理器中完成。客户端部分的逻辑同服务器类似,也包含引导客户端和实现客户端处理器两部分,客户端连接服务端,并且接收服务端的消息,关闭连接等。

1.  引导服务端:

package test.MavenProject;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
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 java.net.InetSocketAddress;

public class EchoServer {
    private int port;

    public EchoServer(int port) {
        this.port = port;
    }

    public void start() throws Exception {
    	
    	//创建NioEventLoopGroup实例来处理事件,如接受连接,读写数据等。
        EventLoopGroup group = new NioEventLoopGroup();
        try {
        	// 创建ServerBootstrap实例。
            ServerBootstrap b = new ServerBootstrap();
            b.group(group)
                    .channel(NioServerSocketChannel.class)
                    .localAddress(new InetSocketAddress(port))
                    .childHandler(new ChannelInitializer<SocketChannel>() { //设置childHandler来处理每一次连接。
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new EchoServerHandler());
                        }
                    });
            // 使用ServerBootstrap的bind方法进行绑定并同步直至其完成绑定。
            ChannelFuture f = b.bind().sync();
            System.out.println("Server start listen at " + port);
            // 等待服务器socket关闭
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port;
        if (args.length > 0) {
            port = Integer.parseInt(args[0]);
        } else {
        	
        	// 指定服务端绑定的端口。
            port = 8085;
        }
        new EchoServer(port).start();
    }
}

2.  服务端逻辑实现

package test.MavenProject;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf bb = (ByteBuf) msg;
        bb.markReaderIndex();
        System.out.println("Server received: " + ByteBufUtil
                .hexDump(bb.readBytes(bb.readableBytes())));
        bb.resetReaderIndex();
        ctx.write(msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
                .addListener(ChannelFutureListener.CLOSE);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

当服务器接受到消息后,channelRead方法会被调用,具体消息为msg,用户可以对该消息进行处理,本例中首先将接收的消息进行转化后打印,然后将消息写入ctx中,其中值得注意的是需要标记读索引,然后恢复,否则写入的数据为空。channelReadComplete将之前写入客户端的消息刷新,待操作完成后关闭。exceptionCaught方法则会捕捉处理中的异常。

3. 引导客户端

package test.MavenProject;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.net.InetSocketAddress;

public final class EchoClient {

    private String host;
    private int port;

    public EchoClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void start() throws Exception {    
        EventLoopGroup group = new NioEventLoopGroup();
        try {
        	
        	
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioSocketChannel.class)
                    .remoteAddress(new InetSocketAddress(host, port))
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new EchoClientHandler());
                        }
                    });

            // 启动客户端
            ChannelFuture f = b.connect().sync();
            // 直到连接关闭
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        String host = "127.0.0.1";
     
        int port = 8085;
        if (args.length == 2) {
            host = args[0];
            port = Integer.parseInt(args[1]);
        }

        new EchoClient(host, port).start();
    }
}

这里给出了服务端的地址和端口号,Bootstrap的connect函数将会根据指定的地址和端口号连接服务器。

4.  实现客户端逻辑

package test.MavenProject;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
/*
    EchoClientHandler继承SimpleChannelInboundHandler,需要重写如下三个函数

         · channelActive函数,在建立了与服务端的连接后该函数被调用。

       · channelRead0函数,当接收到服务端发送来的消息后被调用。
 
       · exceptionCaught函数,当处理发生异常时被调用。
*/
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8));
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {
        System.out.println("Client received: " + ByteBufUtil
                .hexDump(in.readBytes(in.readableBytes())));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

当同服务器的连接建立后,客户端会发送消息至服务端,然后当接收到服务端发送来的消息时,打印该消息。

由于本应用依赖的jar文件使用maven构建,其pom.xml文件如下。

<?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-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>NettyInAction</groupId>
    <artifactId>com.hust.grid.leesf</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <version.jackson.core>2.6.3</version.jackson.core>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.2</version>
                <configuration>
                    <optimize>true</optimize>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>

        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.0.32.Final</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>${version.jackson.core}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${version.jackson.core}</version>
        </dependency>
    </dependencies>


</project>

运行:

启动EchoServer,等待客户端连接。

启动EchoClient,连接服务端并发送消息。

结果:

服务端启动:

客户端建立连接后发送消息:

 服务端:

参考:

【Netty】Netty核心组件介绍 - leesf - 博客园

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Netty是一个基于NIO的客户端、服务器端编程框架,它提供了一组易于使用的API,可以帮助开发人员快速地开发出高性能、可伸缩的网络应用程序。Netty框架提供了很多编码器,其中最常用的就是ByteToMessageCodec和MessageToByteCodec,它们分别用于将字节流转换为消息对象和将消息对象转换为字节流。 ByteToMessageCodec和MessageToByteCodec的使用方式非常简单,只需要继承它们,并实现其中的抽象方法即可。以下是使用ByteToMessageCodec和MessageToByteCodec的示例代码: ```java public class MyMessageDecoder extends ByteToMessageCodec<MyMessage> { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { if (in.readableBytes() < 4) { return; } int length = in.readInt(); if (in.readableBytes() < length) { in.resetReaderIndex(); return; } byte[] data = new byte[length]; in.readBytes(data); MyMessage message = new MyMessage(length, data); out.add(message); } @Override protected void encode(ChannelHandlerContext ctx, MyMessage msg, ByteBuf out) throws Exception { out.writeInt(msg.getLength()); out.writeBytes(msg.getData()); } } public class MyMessageEncoder extends MessageToByteCodec<MyMessage> { @Override protected void encode(ChannelHandlerContext ctx, MyMessage msg, ByteBuf out) throws Exception { out.writeInt(msg.getLength()); out.writeBytes(msg.getData()); } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { if (in.readableBytes() < 4) { return; } int length = in.readInt(); if (in.readableBytes() < length) { in.resetReaderIndex(); return; } byte[] data = new byte[length]; in.readBytes(data); MyMessage message = new MyMessage(length, data); out.add(message); } } ``` MyMessage是自定义的消息对象,包含了消息的长度和数据。MyMessageDecoder和MyMessageEncoder分别用于将字节流转换为MyMessage对象和将MyMessage对象转换为字节流。在decode方法中,首先从ByteBuf中读取消息的长度,然后再读取消息的数据,最后将它们封装成MyMessage对象,加入到解码结果列表中。在encode方法中,首先将消息的长度写入到ByteBuf中,然后再将消息的数据写入到ByteBuf中。 使用ByteToMessageCodec和MessageToByteCodec时,只需要将它们注册到ChannelPipeline中即可。以下是注册的示例代码: ```java ChannelPipeline pipeline = channel.pipeline(); pipeline.addLast(new MyMessageDecoder()); pipeline.addLast(new MyMessageEncoder()); ``` 这样,在ChannelPipeline中的所有ChannelHandler都可以使用MyMessage对象,而无需关心它们与字节流的转换过程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值