Netty | 示例代码

本文详细介绍了如何使用Netty实现服务端与客户端的通信过程。包括服务端的启动与配置,客户端的连接与数据交互,以及双方的异常处理机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

服务端:

  • 启动类
package com.example.demo.netty;


import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
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.nio.NioServerSocketChannel;

import java.net.InetSocketAddress;

/**
 * netty服务端 它包含业务逻辑,决定当有一个请求连接或接收数据时该做什么
 */
public class EchoServer {
    /**
     * 线程组
     */
    private EventLoopGroup eventLoopGroup;

    /**
     * 绑定端口
     */
    private int port;

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

    /**
     * 执行方法
     */
    public void start() throws InterruptedException {

        try {


        //创建引导类:端口绑定,启动NETTY服务
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        //创建NioEventLoopGroup 线程组来处理事件,如接受新连接、接收数据、写数据等等 包括boss,worker
        eventLoopGroup  = new NioEventLoopGroup();
        //指定通道类型为NioServerSocketChannel,设置InetSocketAddress让服务器监听某个端口已等待客户端连接,并配置线程组
        serverBootstrap.group(eventLoopGroup).channel(NioServerSocketChannel.class).localAddress(new InetSocketAddress("localhost",port)).childHandler(new ChannelInitializer<Channel>() {
            //设置childHandler执行所有的连接请求
            @Override
            protected void initChannel(Channel ch) throws Exception {
                ch.pipeline().addLast(new EchoInServerHandler());
            }
        });
        // 线程同步阻塞等待服务器绑定到指定端口
         ChannelFuture channelFuture  = serverBootstrap.bind().sync();
        //成功绑定到端口之后,给channel增加一个 管道关闭的监听器并同步阻塞,直到channel关闭,线程才会往下执行,结束进程。
         channelFuture.channel().closeFuture().sync();

        }finally {
            //优雅的关机
            eventLoopGroup.shutdownGracefully().sync();
        }

    }

    /**
     * 启动方法
     */
    public static void main(String[] args) throws InterruptedException {
        EchoServer echoServer = new EchoServer(8899);
        echoServer.start();
    }

    /**
     *     public Promise<V> sync() throws InterruptedException {
     *         await();
     *         rethrowIfFailed();  // 异步操作失败抛出异常
     *         return this;
     *     }
     *
     *    Future类
     *         // 异步操作完成且正常终止
     *     boolean isSuccess();
     *     // 异步操作是否可以取消
     *     boolean isCancellable();
     *     // 异步操作失败的原因
     *     Throwable cause();
     *     // 添加一个监听者,异步操作完成时回调,类比javascript的回调函数
     *     Future<V> addListener(GenericFutureListener<? extends Future<? super V>> listener);
     *     Future<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener);
     *     // 阻塞直到异步操作完成
     *     Future<V> await() throws InterruptedException;
     *     // 同上,但异步操作失败时抛出异常
     *     Future<V> sync() throws InterruptedException;
     *     // 非阻塞地返回异步结果,如果尚未完成返回null
     *     V getNow();
     */

}

  • 服务端handle执行类
package com.example.demo.netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;

import java.util.Date;

public class EchoInServerHandler extends ChannelInboundHandlerAdapter {
    /**
     *ChannelHandlerContext pipeline 中handle共享的
     * 客户端请求读取方法
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        System.out.println("server 读取数据……");
        //读取数据
        ByteBuf buf = (ByteBuf) msg;
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);
        //序列化
        String body = new String(req, "UTF-8");
        System.out.println("接收客户端数据:" + body);
        //向客户端写数据
        System.out.println("server向client发送数据" + body);
        String currentTime = new Date(System.currentTimeMillis()).toString();
        //bytes转byteBuff
        ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
        //然后从byteBuff中写入通道,但是这个write动作也是异步,不是及时执行得。
        ChannelFuture channelFuture = ctx.writeAndFlush(resp); //刷新后才将数据发出到SocketChannel
        channelFuture.addListener(ChannelFutureListener.CLOSE); //关闭跟客户端的连接通道
        //所以可能下面得代码会先执行
       // System.out.println("通道写入");
    }

    /**
     * 读取数据完毕走的方法
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("server 读取数据完毕..");
    }

    /**
     * 异常捕获处理
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();
        //停止往下进行handle
        ctx.close();
    }


}

客户端

  • 客户端启动类
package com.example.demo.netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
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.NioSocketChannel;

import java.net.InetSocketAddress;

/**
 * NETTY客户端
 * 连接服务器 • 写数据到服务器 • 等待接受服务器返回相同的数据 • 关闭连接
 */
public class EchoClient {

    private final String host;
    private final int port;

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

    public void start() throws Exception {
        EventLoopGroup nioEventLoopGroup = null;
        try {
            //创建Bootstrap对象用来引导启动客户端
            Bootstrap bootstrap = new Bootstrap();
            //创建EventLoopGroup对象并设置到Bootstrap中,EventLoopGroup可以理解为是一个线程池,这个线程池用来处理连接、接受数据、发送数据
            nioEventLoopGroup = new NioEventLoopGroup();
            //创建InetSocketAddress并设置到Bootstrap中,InetSocketAddress是指定连接的服务器地址
            bootstrap.group(nioEventLoopGroup).channel(NioSocketChannel.class).remoteAddress(new InetSocketAddress(host, port))
                    .handler(new ChannelInitializer<SocketChannel>() {
                        //添加一个ChannelHandler,客户端成功连接服务器后就会被执行
                        @Override
                        protected void initChannel(SocketChannel ch)
                                throws Exception {
                            ch.pipeline().addLast(new EchoInClientHandler());
                        }
                    });
            // • 调用Bootstrap.connect()来连接服务器,阻塞式连接
            ChannelFuture f = bootstrap.connect().sync();
           //阻塞关闭,直到通道关闭
           f.channel().closeFuture().sync();

        } finally {
            // • 最后关闭EventLoopGroup来释放资源
            nioEventLoopGroup.shutdownGracefully().sync();
        }
    }

    public static void main(String[] args) throws Exception {
        new EchoClient("localhost", 8899).start();
    }
}

  • 客户端handle执行类
package com.example.demo.netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class EchoInClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    private ChannelFuture  channelFuture;
    //客户端连接服务器成功后被调用
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端连接服务器,开始发送数据……");
        byte[] req = "你好啊,请给我发送现在的时间(客户端)".getBytes();
        ByteBuf  firstMessage = Unpooled.buffer(req.length);
        firstMessage.writeBytes(req);
        channelFuture = ctx.writeAndFlush(firstMessage);
    }
    //•	从服务器接收到数据后调用
    @Override
    protected void messageReceived(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception {
        System.out.println("client 读取server数据..");
        //服务端返回消息后
        ByteBuf buf = byteBuf;
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);
        String body = new String(req, "UTF-8");
        System.out.println("服务端数据为 :" + body);
        channelFuture.addListener(ChannelFutureListener.CLOSE);//关闭和服务端的连接通道,接着会出发通道的关闭,然后回继续执行sync后面的代码,因为对应的通道为连接通道
    }

    //•	发生异常时被调用
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("client exceptionCaught..");
        // 释放资源
        ctx.close();
    }

    /**
     * 这三个Listener对象定义了对Channel处理时常用的操作,如果符合需求,可以直接使用。
     *     ChannelFutureListener CLOSE = (future) --> {
     *         future.channel().close();   //操作完成时关闭Channel
     *     };
     *
     *     ChannelFutureListener CLOSE_ON_FAILURE = (future) --> {
     *         if (!future.isSuccess()) {
     *             future.channel().close();   // 操作失败时关闭Channel
     *         }
     *     };
     *
     *     ChannelFutureListener FIRE_EXCEPTION_ON_FAILURE = (future) --> {
     *         if (!future.isSuccess()) {
     *             // 操作失败时触发一个ExceptionCaught事件
     *             future.channel().pipeline().fireExceptionCaught(future.cause());
     *         }
     *     };
     */
}

以下是一个简单的 Netty WebSocket 示例代码: ```java import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; 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.WebSocketFrameAggregator; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.stream.ChunkedWriteHandler; import io.netty.util.concurrent.DefaultThreadFactory; public class WebSocketServer { private static final int PORT = 8080; public static void main(String[] args) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("boss")); EventLoopGroup workerGroup = new NioEventLoopGroup(0, new DefaultThreadFactory("worker")); try { ServerBootstrap bootstrap = new ServerBootstrap() .group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new HttpServerCodec()); pipeline.addLast(new HttpObjectAggregator(65536)); pipeline.addLast(new ChunkedWriteHandler()); pipeline.addLast(new WebSocketFrameAggregator(65536)); pipeline.addLast(new WebSocketServerProtocolHandler("/ws")); pipeline.addLast(new WebSocketServerHandler()); } }); Channel ch = bootstrap.bind(PORT).sync().channel(); System.out.println("WebSocket server started at port " + PORT + "."); System.out.println("Open your browser and navigate to http://localhost:" + PORT + "/"); ch.closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } ``` 上述代码创建了一个 Netty WebSocket 服务器,监听端口为 8080。新连接会通过 `ChannelInitializer` 进行处理,其中添加了以下几个处理器: - `HttpServerCodec`:用于将 HTTP 请求和响应编解码为 Netty 的 `HttpRequest` 和 `HttpResponse` 对象。 - `HttpObjectAggregator`:用于将 HTTP 消息的多个部分合并成一个完整的消息。 - `ChunkedWriteHandler`:用于处理大文件或者大数据流,将数据以块(Chunk)的形式写入到客户端。 - `WebSocketFrameAggregator`:用于将 WebSocket 帧的多个部分合并成一个完整的帧。 - `WebSocketServerProtocolHandler`:用于处理 WebSocket 握手和帧的编解码。 - `WebSocketServerHandler`:自定义的处理器,用于处理客户端的 WebSocket 帧。 `WebSocketServerHandler` 的示例代码如下: ```java import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketFrameAggregator; public class WebSocketServerHandler extends SimpleChannelInboundHandler<WebSocketFrame> { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("Client connected: " + ctx.channel().remoteAddress()); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { System.out.println("Client disconnected: " + ctx.channel().remoteAddress()); } @Override protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception { if (frame instanceof TextWebSocketFrame) { String text = ((TextWebSocketFrame) frame).text(); System.out.println("Received message: " + text); ctx.channel().writeAndFlush(new TextWebSocketFrame("Server received: " + text)); } else { throw new UnsupportedOperationException("Unsupported frame type: " + frame.getClass().getName()); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.err.println("Exception caught: " + cause.getMessage()); ctx.close(); } } ``` 上述代码实现了 `SimpleChannelInboundHandler` 接口,用于处理客户端发送的 WebSocket 帧。当收到 `TextWebSocketFrame` 类型的帧时,会将其内容打印到控制台,并将其转发给客户端。其他类型的帧会抛出异常。 客户端可以通过 JavaScript 代码连接到服务器: ```javascript let socket = new WebSocket("ws://localhost:8080/ws"); socket.onopen = function() { console.log("Connected to WebSocket server."); socket.send("Hello, WebSocket server!"); }; socket.onmessage = function(event) { console.log("Received message: " + event.data); }; socket.onclose = function(event) { console.log("Disconnected from WebSocket server."); }; ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值