非阻塞的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 2023-05-10 18:31  张伯灵  阅读(46)  评论(0编辑  收藏  举报