netty实时通讯demo 详细讲解

目录

前言

一、创建服务端

1、创建 ServerBootstrap 对象

2、创建 boss 和 work 线程池 

3、组合 netty 组件

4、配置 handle 组件

5、绑定端口并启动服务器

6、关闭线程池

完整代码

二、创建ServerChanelHandle类

1、定义类成员变量 ChannelGroup cg,用于保存所有客户端的 Channel。

2、在 handlerAdded() 方法中,当有新客户端连接时,向所有已连接的客户端发送提示信息,并将新客户端的 Channel 添加到 cg 中。

3、在 channelActive() 方法中,当客户端上线时,向所有已连接的客户端发送提示信息。

4、在 channelRead0() 方法中,当接收到客户端发送的消息时,向所有已连接的客户端发送消息。

5、在 handlerRemoved() 方法中,当客户端连接断开时,从 cg 中移除该客户端的 Channel。

6、在 channelInactive() 方法中,当客户端下线时,向所有已连接的客户端发送提示信息。

7、完整代码

三、创建客户端

1、创建 Bootstrap 对象并设置相关参数

2、连接到指定的服务器,并返回 ChannelFuture 对象

3、获取连接成功后的 Channel 对象,并通过 Scanner 从控制台获取用户输入的消息,将其发送给服务器

4、关闭事件循环组,释放资源

5、完整代码

四、创建ClientChanelHandle类

实现效果


前言

Netty 是一个高性能、异步事件驱动的网络应用程序框架,主要用于开发高性能、高可靠性的网络服务器和客户端。Netty 支持多种传输协议和编解码技术,如 TCP、UDP、HTTP、WebSocket、SSL 等,并且提供了简单易用的 API 接口,使得开发者可以轻松地构建各种网络应用程序。

一、创建服务端

1、创建 ServerBootstrap 对象

ServerBootstrap bootstrap = new ServerBootstrap();

创建一个 ServerBootstrap 对象,该对象是 Netty 中用于启动服务器的入口类。

2、创建 boss 和 work 线程池 

EventLoopGroup boss = new NioEventLoopGroup(1);
EventLoopGroup work = new NioEventLoopGroup(5);

在 Netty 中,通常会创建两个 EventLoopGroup,一个用于接收客户端连接(boss),另一个用于处理客户端请求(work)。其中,boss 线程池的大小通常为 1,work 线程池的大小根据服务器的性能和负载情况来确定。

3、组合 netty 组件

bootstrap.group(boss, work);

将 boss 和 work 线程池组合到 ServerBootstrap 对象中。

4、配置 handle 组件

bootstrap.childHandler(new ChannelInitializer<SocketChannel>() { 
@Override 
protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast("encoder",new StringEncoder());
 ch.pipeline().addLast("decoder",new StringDecoder());
 ch.pipeline().addLast(new ServerChanelHandle()); 
} 
});

在这里,我们使用了匿名内部类的方式创建了一个 ChannelInitializer 对象,并通过 addLast 方法添加了三个 handler 组件:StringEncoder、StringDecoder 和自定义的 ServerChanelHandle。其中,StringEncoder 和 StringDecoder 是 Netty 内置的编码和解码器,用于将字节流转换成字符串。而 ServerChanelHandle 则是我们自己实现的 handler 组件,用于处理客户端请求。

指定使用 NioServerSocketChannel 类作为服务器的 Channel 实现

bootstrap.channel(NioServerSocketChannel.class);

指定使用 NioServerSocketChannel 类作为服务器的 Channel 实现,该类是 Netty 中用于创建 TCP 服务器的通道类型。

5、绑定端口并启动服务器

ChannelFuture channel = bootstrap.bind(port).sync(); 
System.out.println(("服务端已启动,绑定端口:" + port)); channel.channel().closeFuture().sync();

调用 bind 方法绑定服务器的端口,并启动服务器。在这里,我们使用了 sync 方法来等待服务器启动完成。一旦服务器启动完成,我们就可以输出一条消息,告诉用户服务器已经启动,并通过 closeFuture 方法来等待服务器关闭。

6、关闭线程池

boss.shutdownGracefully(); work.shutdownGracefully();

在服务器关闭之后,我们需要手动关闭 boss 和 work 线程池,以释放资源

完整代码

public class ChatServer {

    public void openServer(int port){
        ServerBootstrap bootstrap = new ServerBootstrap();
        EventLoopGroup boss = new NioEventLoopGroup(1);  //create boss group, threadpool size is 1
        EventLoopGroup work = new NioEventLoopGroup(5); //create work group, threadpool size is 5
        bootstrap.group(boss,work);   //组合netty组件
        //配置handle组件
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {

            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ch.pipeline().addLast("encoder",new StringEncoder());
                ch.pipeline().addLast("decoder",new StringDecoder());
                ch.pipeline().addLast(new ServerChanelHandle());
            }
        });
        bootstrap.channel(NioServerSocketChannel.class);

        try{
            ChannelFuture channel = bootstrap.bind(port).sync();
            System.out.println(("服务端已启动,绑定端口:" + port));
            channel.channel().closeFuture().sync();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            boss.shutdownGracefully();
            work.shutdownGracefully();
        }


    }

    public static void main(String[] args){
        ChatServer server = new ChatServer();
        server.openServer(8090);
    }

}

二、创建ServerChanelHandle类

1、定义类成员变量 ChannelGroup cg,用于保存所有客户端的 Channel。

 
public static ChannelGroup cg = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

2、在 handlerAdded() 方法中,当有新客户端连接时,向所有已连接的客户端发送提示信息,并将新客户端的 Channel 添加到 cg 中。

@Override 
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
 Channel ch = ctx.channel(); 
for (Channel chanel : cg) { 
chanel.writeAndFlush(ch.remoteAddress() + "进来啦!"); 
} 
cg.add(ch); 
}

3、在 channelActive() 方法中,当客户端上线时,向所有已连接的客户端发送提示信息。

@Override 
public void channelActive(ChannelHandlerContext ctx) throws Exception {
 Channel channel = ctx.channel(); 
for (Channel ch : cg) { 
ch.writeAndFlush(channel.remoteAddress() + "上线啦");
 }
 }

4、在 channelRead0() 方法中,当接收到客户端发送的消息时,向所有已连接的客户端发送消息。

@Override 
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { Channel channel = ctx.channel();
 for (Channel ch : cg) { 
if (channel == ch) { 
ch.writeAndFlush("我说" + msg); 
} else {
 ch.writeAndFlush(channel.remoteAddress() + "说:" + msg); 
} 
} 
}

5、在 handlerRemoved() 方法中,当客户端连接断开时,从 cg 中移除该客户端的 Channel。

@Override 
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
 Channel channel = ctx.channel(); 
cg.remove(channel); 
}

6、在 channelInactive() 方法中,当客户端下线时,向所有已连接的客户端发送提示信息。

@Override 
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
 Channel channel = ctx.channel(); 
String customerAddress = channel.remoteAddress().toString();
 for (Channel ch : cg) { 
ch.writeAndFlush("客户端" + customerAddress + "下线了!");

}
 }

7、完整代码

public class ServerChanelHandle extends SimpleChannelInboundHandler {

    //必须定义为类成员变量。每个客户端连接时,都会new ChatServerHandler。static保证数据共享
    public static ChannelGroup cg = new  DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel ch = ctx.channel();
        for(Channel chanel: cg){
            chanel.writeAndFlush(ch.remoteAddress()+"进来啦!");
        }
        cg.add(ch);
    }

    /**
     * 上线处理
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        for(Channel ch: cg){
            ch.writeAndFlush(channel.remoteAddress()+"上线啦");
        }
    }


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        Channel channel = ctx.channel();
        for(Channel ch: cg){
            if(channel ==ch){
                ch.writeAndFlush("我说"+msg);
            }else {
                ch.writeAndFlush(channel.remoteAddress()+"说:"+msg);
            }
        }
    }



    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        cg.remove(channel);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        String customerAddress = channel.remoteAddress().toString();
        for(Channel ch:cg){
            ch.writeAndFlush("客户端" + customerAddress + "下线了!");
        }
    }
}

三、创建客户端

1、创建 Bootstrap 对象并设置相关参数

Bootstrap bootstrap = new Bootstrap(); 
EventLoopGroup work = new NioEventLoopGroup(1); 
bootstrap.group(work);
 bootstrap.handler(new ChannelInitializer<SocketChannel>() {
 @Override
 protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast("encode",new StringEncoder());
 ch.pipeline().addLast("decode",new StringDecoder()); 
ch.pipeline().addLast(new ClientChanelHandle());
 } 
}); 
bootstrap.channel(NioSocketChannel.class);

在这里,我们使用 Bootstrap 对象创建了一个 Netty 客户端,并设置了一个 NioEventLoopGroup 对象作为事件循环组。然后,我们通过 handler() 方法设置了一个 ChannelInitializer 对象,用于初始化 SocketChannel 中的通道处理器。其中,我们添加了 StringEncoder 和 StringDecoder 两个编解码器,以便在发送和接收数据时进行自动编解码。最后,我们添加了一个自定义的 ClientChanelHandle 处理器,用于处理接收到的消息。

2、连接到指定的服务器,并返回 ChannelFuture 对象

ChannelFuture channelFuture = bootstrap.connect(serverIP,port);

这里我们使用 connect() 方法连接到指定的服务器,传入服务器 IP 和端口号。这个方法是异步执行的,它会立即返回一个 ChannelFuture 对象,表示操作的异步结果。我们可以对该对象添加监听器来处理连接成功或失败的情况。

3、获取连接成功后的 Channel 对象,并通过 Scanner 从控制台获取用户输入的消息,将其发送给服务器

Scanner scanner = new Scanner(System.in);
 while(scanner.hasNext()){ 
String sendMsg = scanner.nextLine(); 
channel.writeAndFlush(sendMsg); 
}

在这里,我们通过调用 channelFuture.channel() 方法来获取连接成功后的 Channel 对象。然后,我们使用 Scanner 从控制台获取用户输入的消息,并通过 writeAndFlush() 方法将其发送给服务器。这个方法也是异步执行的,它会将消息写入到底层的缓冲区中,并尝试将其发送给远程节点。如果发送成功,则会触发 writeComplete() 事件;否则,会触发 exceptionCaught() 事件。

4、关闭事件循环组,释放资源

work.shutdownGracefully();

最后,在程序退出时,我们需要手动关闭事件循环组,以便释放资源。这里我们调用 shutdownGracefully() 方法来安全地关闭事件循环组。该方法会等待所有线程优雅地停止,并释放相关资源。

5、完整代码

public class ChatClient implements Runnable{


    private String serverIP;
    private int port;
    public ChatClient(String serverIp,int port ){
        this.serverIP = serverIp;
        this.port = port;
    }

    @Override
    public void run() {

        Bootstrap bootstrap = new Bootstrap();
        EventLoopGroup work = new NioEventLoopGroup(1);
        bootstrap.group(work);
        bootstrap.handler(new ChannelInitializer<SocketChannel>() {

            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ch.pipeline().addLast("encode",new StringEncoder());
                ch.pipeline().addLast("decode",new StringDecoder());
                ch.pipeline().addLast(new ClientChanelHandle());
            }
        });
        bootstrap.channel(NioSocketChannel.class);

        ChannelFuture channelFuture = bootstrap.connect(serverIP,port);
        Channel channel = channelFuture.channel();

        Scanner scanner = new Scanner(System.in);
        while(scanner.hasNext()){
            String sendMsg = scanner.nextLine();
            channel.writeAndFlush(sendMsg);
        }
        work.shutdownGracefully();
    }

    public static void main(String[] args){
        new Thread(new ChatClient("127.0.0.1",8090)).start();
    }

四、创建ClientChanelHandle类

这是一个 Netty 客户端的处理器类,继承了 SimpleChannelInboundHandler 类。在 Netty 中,处理器类用于处理来自服务端的消息和客户端的请求,并将其转换为可读的格式。

channelRead0() 方法是该处理器类中的一个重要方法,它会在接收到来自服务端的消息时被调用,并将消息打印输出到控制台上。

public class ClientChanelHandle extends SimpleChannelInboundHandler {


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println(msg);
    }
}

实现效果

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
以下是一个简单的Netty自定义协议的示例代码,该协议由消息头和消息体组成,消息头包含消息的长度信息,消息体包含具体的消息内容。 1. 定义消息类 ```java public class MyMessage { private int length; private String content; // getter and setter methods } ``` 2. 编写编解码器 ```java public class MyMessageCodec extends MessageToByteEncoder<MyMessage> { @Override protected void encode(ChannelHandlerContext ctx, MyMessage msg, ByteBuf out) throws Exception { byte[] contentBytes = msg.getContent().getBytes(CharsetUtil.UTF_8); out.writeInt(contentBytes.length); out.writeBytes(contentBytes); } } public class MyMessageDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { if (in.readableBytes() < 4) { return; } in.markReaderIndex(); int length = in.readInt(); if (in.readableBytes() < length) { in.resetReaderIndex(); return; } byte[] contentBytes = new byte[length]; in.readBytes(contentBytes); String content = new String(contentBytes, CharsetUtil.UTF_8); MyMessage message = new MyMessage(); message.setLength(length); message.setContent(content); out.add(message); } } ``` 3. 编写服务端和客户端代码 服务端代码: ```java public class MyServer { public static void main(String[] args) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.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 MyMessageDecoder()); pipeline.addLast(new MyServerHandler()); pipeline.addLast(new MyMessageCodec()); } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); ChannelFuture f = b.bind(8888).sync(); f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } } public class MyServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { MyMessage message = (MyMessage) msg; System.out.println("Server receive message: " + message.getContent()); message.setContent("Hello, " + message.getContent() + "!"); ctx.writeAndFlush(message); } } ``` 客户端代码: ```java public class MyClient { public static void main(String[] args) throws Exception { EventLoopGroup workerGroup = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(workerGroup) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new MyMessageCodec()); pipeline.addLast(new MyMessageDecoder()); pipeline.addLast(new MyClientHandler()); } }); ChannelFuture f = b.connect("localhost", 8888).sync(); for (int i = 0; i < 10; i++) { MyMessage message = new MyMessage(); message.setContent("world-" + i); f.channel().writeAndFlush(message); Thread.sleep(1000); } f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); } } } public class MyClientHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { MyMessage message = (MyMessage) msg; System.out.println("Client receive message: " + message.getContent()); } } ``` 以上代码演示了如何使用Netty实现自定义协议,其中MyMessageCodec和MyMessageDecoder负责编解码,MyServer和MyServerHandler负责服务端逻辑,MyClient和MyClientHandler负责客户端逻辑。在本示例中,自定义协议包含消息头和消息体,消息头包含消息长度信息,消息体包含具体的消息内容。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不喜欢编程的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值