Netty入门(聊天室案例)
netty介绍:
Netty是由JBoss开源的一款网络编程框架,可用于快递开发网络应用。netty.io官网中对netty的介绍是一个异步,基于事件驱动的网络应用架构,用于快速开发高性能的服务和客户端。
总的来说,netty是一个基于NIO和可扩展的事件模型的客户端及服务端框架,可以极大的简化基于TCP、UDO等网络协议的服务。并且Netty对于各种传输类型(堵塞或非阻塞的Socket)及通信方式(Http或WebSocket)都提供了统一的API接口,提供了灵活的可扩展性,高度可自定义的线程模型(单线程、线程池等),支持使用无连接的数据报进行通信,具有高吞吐量、低延迟、资源消耗低、最低限度的内存复制等特性。除了优越的性能外,Netty还完整的支持SSL/TLS和StartTLS等加密传输协议,保证了数据传输的安全性。
直接上代码:
首先引入Maven包:5的版本官方有解释到较为复杂的结构的,但是性能以及效率并没有明显的提升,所以就弃用了。
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.51.Final</version>
</dependency>
首先我们先编写服务端代码:
MyNettyServer.java
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* @Author 吴星辰
* @Date 2020/8/24 15:52
* @Version 1.0
* 聊天室服务端
*
*/
public class MyNettyServer {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//ServerBootstrap:服务端启动时的初始化操作
ServerBootstrap serverBootstrap = new ServerBootstrap();
//将bossGroup和workerGroup注册到服务端的Channel上,
//并注册一个服务端的初始化器NettyServerInitializer
//(该初始化器中的initChannel()方法,会在连接被注册后立刻执行);
//最后将端口号绑定到8888
ChannelFuture channelFuture =serverBootstrap
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new MyNettyServerInitializer())
.bind(8888).sync() ;
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
初始化类
MyNettyServerInitializer.java
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
/**
* @Author 吴星辰
* @Date 2020/8/24 16:00
* @Version 1.0
* 聊天室的服务端 初始化类
*
*/
public class MyNettyServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline channelPipeline=socketChannel.pipeline();
//DelimiterBasedFrameDecoder(maxFrameLength, delimiters):
// 分隔符处理器;将接收到的客户端消息,
// 通过回车符(Delimiters.lineDelimiter())进行分割。
channelPipeline.addLast("DelimiterBasedFrameDecoder", new DelimiterBasedFrameDecoder(2048, Delimiters.lineDelimiter()));
channelPipeline.addLast("StringDecoder",new StringDecoder(CharsetUtil.UTF_8)) ;
channelPipeline.addLast("StringEncoder",new StringEncoder(CharsetUtil.UTF_8)) ;
channelPipeline.addLast("MyNettyServerHandler",new MyNettyServerHandler());
}
}
处理器类:
MyNettyServerHandler.java
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
/**
* @Author 吴星辰
* @Date 2020/8/24 16:06
* @Version 1.0
* 服务器的处理方法
*
*/
public class MyNettyServerHandler extends SimpleChannelInboundHandler<String> {
//线程安全的集合(自我理解就是通道)
private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE) ;
//每当从服务端读取到客户端写入的信息时,就将该信息转发给所有的客户端Channel(实现聊天室的效果)。
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, final String receiveMsg) throws Exception {
Channel channel=channelHandlerContext.channel();
//遍历channelGroup,从而区分“我”和“别人”发出的消息,如果消息是自己发出的就显示“我”
channelGroup.forEach(chnl ->{
if(channel == chnl){
chnl.writeAndFlush("【我】发送的消息:" + receiveMsg + "\n") ;}
else{
chnl.writeAndFlush("【" + channel.remoteAddress()+"】发送的消息:" + receiveMsg +"\n");}
} );
}
//这个东西就是新通道的监听器(自我理解)
//连接建立。每当从服务端收到新的客户端连接时,就将新客户端的Channel
// 加入ChannelGroup列表中,并告知列表中的其他客户端Channel
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel() ;
channelGroup.writeAndFlush("客户端-" + channel.remoteAddress() + "加入\n") ;
channelGroup.add(channel) ;
}
//监听客户端上线
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel() ;
System.out.println(channel.remoteAddress() + "上线");
}
//监听客户端下线
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel() ;
System.out.println(channel.remoteAddress() + "下线");
}
//连接断开。每当从服务端感知有客户端断开时,
// 就将该客户端的Channel从ChannelGroup 列表中移除,
// 并告知列表中的其他客户端Channel
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel() ;
//会自动将channelGroup中断开的连接移除掉
channelGroup.writeAndFlush("客户端-" + channel.remoteAddress() + "离开\n") ;
}
}
服务端的代码已经写完了,接下来直接上客户端代码
MyNettyClientTest.java
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.io.BufferedReader;
import java.io.InputStreamReader;
/**
* @Author 吴星辰
* @Date 2020/8/24 16:30
* @Version 1.0
* 聊天室的客户端
*
*/
public class MyNettyClientTest {
public static void main(String[] args) {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup() ;
try{
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).handler(new MyNettyClientInitializer());
Channel channel = bootstrap.connect("127.0.0.1",8888).sync().channel();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)) ;
for(;;){//客户端不断的通过控制台向服务端发送消息
channel.writeAndFlush(bufferedReader.readLine() + "\r\n") ;
}
}catch (Exception e){
e.printStackTrace();
}
finally {
eventLoopGroup.shutdownGracefully();
}
}
}
客户端初始化类:
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
/**
* @Author 吴星辰
* @Date 2020/8/24 16:39
* @Version 1.0
*/
public class MyNettyClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//与服务端的Initializer作用相同:通过DelimiterBasedFrameDecoder将接收到的服务端消息,通过回车符(Delimiters.lineDelimiter())进行分割。
pipeline.addLast("DelimiterBasedFrameDecoder", new DelimiterBasedFrameDecoder(2048, Delimiters.lineDelimiter()));
pipeline.addLast("StringDecoder",new StringDecoder(CharsetUtil.UTF_8)) ;
pipeline.addLast("StringEncoder",new StringEncoder(CharsetUtil.UTF_8)) ;
//自定义处理器
pipeline.addLast("MyNettyClientHandler", new MyNettyClientHandler());
}
}
客户端处理类:
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* @Author 吴星辰
* @Date 2020/8/24 16:40
* @Version 1.0
*/
public class MyNettyClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
System.out.println(s);
}
}
测试截图:
由于代码比较简单,不做过多解释,改说的代码中的注解写的很清楚。