非阻塞的NIO可以开发出性能很不错的网络应用程序,而Netty和Mina这类NIO框架对Java NIO做了封装,可以让高性能IO程序的开发变得容易。

Netty是一个基于Java NIO的网络通信框架,它有两个特点

基于事件
全异步
话不多说先上代码,从代码感受Netty的思想是最直接的。要想实现一个服务器,需要三个类

Server类

public class SimpleChatServer {
private int port;
public SimpleChatServer(int port){
this.port = port;
}
public void run() throws Exception{
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new SimpleChatServerInitializer())
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
System.out.println(">>>>>>>>>>Server start<<<<<<<<<<");
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
}
finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
System.out.println(">>>>>>>>>>Server stop<<<<<<<<<<");
System.out.println();
}
}
public static void main(String[] args) throws Exception{
int port = Host.SERVER_PORT;
new SimpleChatServer(port).run();
}
}

服务器的任务是接受来自客户端的连接,以及处理客户端发来的数据,这就依靠EventLoopGroup来实现,Group实际上就是线程组,它包含了一些EventLoop(线程)。我们需要2个Group,是因为第一个用来接受连接(boss),另一个用来处理收到的信息(worker)。

因为启动配置服务器操作繁杂,Netty提供的Bootstrap类(快速引导类)给我们带来方便,新建一个ServerBootstrap实例,配置好线程组,以及服务器Channel(一般都会选择NIO实现的NioServerSocketChannel.class)以及childHandler、option、childOption

Netty里的child就可以理解为worker,parent是boss。所以childHandler意思就是处理收到的信息的处理器,因为我们暂时不需要对服务器新接受的连接做额外的处理,所以不配置.handler,只需要配置.childHandler

.option和.childOption和上面同理,一个是服务器的TCP选项,另一个是来自客户端的连接的TCP选项。

配置好这项项目,Bootstrap.bind方法绑定端口,并开始监听连接,服务器就启动了。

Initializer类

public class SimpleChatServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast("idle", new IdleStateHandler(10,10,30, TimeUnit.SECONDS));
pipeline.addLast("decoder", new MyDecoder());
pipeline.addLast("encoder", new MyEncoder());
pipeline.addLast("heartbeat", new HeartbeatServerHandler());
pipeline.addLast("handler", new SimpleChatServerHandler());
}
}

bootstrap的childHandler配置,就是这个类。这个类可以理解为许多Handler 的容器,作为基于事件的框架,最重要的就是处理事件(比如新的连接,收到的消息等等),pipeline就像一个管道,里面流通着许多的事件,通过addLast的方式给pipeline添加各种各样的handler,然后netty通过pipeline来把各个事件分发给handler

我添加了5个handler,他们的作用分别是

idlestatehandler 它是netty提供的一个工具型handler,它能够检查channel的读、写是否空闲,我们可以通过实现userEventTriggered方法来处理这些情况。我们主要通过他来实现心跳功能,如果channel长时间读写空闲,它可能已经掉线,我们就需要发心跳包来确定它的状态。

myDecoder 是我的解码器,它继承Netty的BytesToMessageDecoder。因为TCP的数据传输是基于流的传输,也就是说我们发的信息“Hi, Shawn!”传输时会被分成很多字节并且可能分到多个数据包中,解码器的作用就是把字节流翻译成需要的数据。

myEncoder是我的编码器,它继承Netty的MessageToBytesEncoder。作用和解码器相反。

heartbeatServerHandler是我的心跳处理器,针对客户端发来的心跳包做特定的处理。

SimpleChatServerHandler就是聊天的核心处理器了,客户端发送的信息,可以在channelRead0方法里得到,并且做一些业务逻辑,比如持久化到数据库,或者广播给其他channel等等。

注意initializer类其实也是个Handler,加载了我们的Handler以后,它会从pipeline里移除,所以他的作用就是加载其他handler。

Handler类

public class SimpleChatServerHandler extends SimpleChannelInboundHandler<MyProtocol> {
//begin
public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
channels.add(incoming);
channels.writeAndFlush(DateUtil.getTime()+" "+incoming.remoteAddress()+" online\n");
System.out.println(DateUtil.getTime()+" "+ incoming.remoteAddress()+" online "+Conter.oncnt++);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
channels.remove(incoming);
channels.writeAndFlush(DateUtil.getTime()+" "+incoming.remoteAddress()+" offline\n");
System.out.println(DateUtil.getTime()+" "+ incoming.remoteAddress()+" offline "+Conter.offcnt++);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//Channel incoming = ctx.channel();
//System.out.println(DateUtil.getTime()+" "+ incoming.remoteAddress()+" exception");
System.out.println("ERROR: "+cause.getMessage());
ctx.close();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, MyProtocol myProtocol) throws Exception {
if(myProtocol.getContentType() == Constant.TYPE_HEARTBEAT){
Heartbeat heartbeat = (Heartbeat) ConvertUtil.getObject(myProtocol.getContent());
System.out.println("received heartbeat "+heartbeat.getInfo()+" from "+heartbeat.getOrigin());
} else {
Book book = (Book) ConvertUtil.getObject(myProtocol.getContent());
Channel incoming = ctx.channel();
System.out.println(DateUtil.getTime()+" "+incoming.remoteAddress()+" "+book.toString());
channels.writeAndFlush(DateUtil.getTime()+" "+incoming.remoteAddress()+" "+book.toString()+"\n");
}
}
}

(这是其中一个handler的代码)

new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);得到所有的channel,也就是所有的连接

channelActive 当有一个新的连接,触发这个方法,我在里面写了channels.writeAndFlush就是给所有channel发一个广播,当有人连接成功,所有人都会收到一条消息 xxx.xxx.xxx online

channelInactive 某个连接断开,触发这个方法

exceptionCaught 当遇到异常,触发这个方法。比如某个客户端强制断开连接。

channelRead0 读取信息

关注我的公众号SpaceObj 领取idea系列激活码

posted on   张伯灵  阅读(49)  评论(0编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)



点击右上角即可分享
微信分享提示