欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Netty入门系列(1) --使用Netty搭建服务端和客户端

程序员文章站 2022-04-11 08:39:15
引言 前面我们介绍了网络一些基本的概念,虽然说这些很难吧,但是至少要做到理解吧。有了之前的基础,我们来正式揭开Netty这神秘的面纱就会简单很多。 服务端 我们来分析一下上面的这段代码(下面的每一点对应上面的注释) 1~2:首先我们创建了两个NioEventLoopGroup实例,它是一个由Nett ......

引言

前面我们介绍了网络一些基本的概念,虽然说这些很难吧,但是至少要做到理解吧。有了之前的基础,我们来正式揭开netty这神秘的面纱就会简单很多。

服务端

public class printserver {

    public void bind(int port) throws exception {
        eventloopgroup bossgroup = new nioeventloopgroup();                     //1
        eventloopgroup workergroup = new nioeventloopgroup();                   //2
        try {
            serverbootstrap b = new serverbootstrap();                          //3
            b.group(bossgroup, workergroup)                                     //4                                         
                    .channel(nioserversocketchannel.class)                      //5
                    .option(channeloption.so_backlog, 1024)                     //6
                    .childhandler(new channelinitializer<socketchannel>() {     //7
                        @override
                        protected void initchannel(socketchannel ch) throws exception {
                            ch.pipeline().addlast(new printserverhandler());
                        }
                    });

            channelfuture f = b.bind(port).sync();              //8
            
            f.channel().closefuture().sync();                   //9
        } finally {
            // 优雅退出,释放线程池资源
            bossgroup.shutdowngracefully();
            workergroup.shutdowngracefully();
        }
    }


    /**
     * @param args
     * @throws exception
     */
    public static void main(string[] args) throws exception {
        int port = 8080;
        new timeserver().bind(port);
    }
}

我们来分析一下上面的这段代码(下面的每一点对应上面的注释)

1~2:首先我们创建了两个nioeventloopgroup实例,它是一个由netty封装好的包含nio的线程组。为什么创建两个?我想经过前面的学习大家应该都清楚了。对,因为netty的底层是io多路复用,bossgroup 是用于接收客户端的连接,原理就是一个实现的selector的reactor线程。而workergroup用于进行socketchannel的网络读写。

3:创建一个serverbootstrap对象,可以把它想象成netty的入口,通过这类来启动netty,将所需要的参数传递到该类当中,大大降低了的开发难度。

4:将两个nioeventloopgroup实例绑定到serverbootstrap对象中。

5:创建channel(典型的channel有niosocketchannel,nioserversocketchannel,oiosocketchannel,oioserversocketchannel,epollsocketchannel,epollserversocketchannel),这里创建的是nioserversocketchannel,它的功能可以理解为当接受到客户端的连接请求的时候,完成tcp三次握手,tcp物理链路建立成功。并将该“通道”与workergroup线程组的某个线程相关联。

6:设置参数,这里设置的so_backlog,意思是客户端连接等待队列的长度为1024.

7:建立连接后的具体handler。就是我们接受数据后的具体操作,例如:记录日志,对信息解码编码等。

8:绑定端口,同步等待成功

9:等待服务端监听端口关闭

绑定该服务端的handler

public class printserverhandler extends channelhandleradapter {

    @override
    public void channelread(channelhandlercontext ctx, object msg)
        throws exception {
    bytebuf buf = (bytebuf) msg;                                        //1
    byte[] req = new byte[buf.readablebytes()]; 
    buf.readbytes(req); //将缓存区的字节数组复制到新建的req数组中
    string body = new string(req, "utf-8");
    system.out.println(body);
    string response= "打印成功";
    bytebuf resp = unpooled.copiedbuffer(response.getbytes());                      
    ctx.write(resp);                                                    //2
    }   

    @override
    public void channelreadcomplete(channelhandlercontext ctx) throws exception {
    ctx.flush();                                                        //3
    }


    @override
    public void exceptioncaught(channelhandlercontext ctx, throwable cause) {
    ctx.close();
    }
}

printserverhandler 继承 channelhandleradapter ,在这里它的功能为 打印客户端发来的数据并且返回客户端打印成功。

我们只需要实现channelread,exceptioncaught,前一个为接受消息具体逻辑的实现,后一个为发生异常后的具体逻辑实现。

1:我们可以看到,接受的消息被封装为了object ,我们将其转换为bytebuf ,前一章的讲解中也说明了该类的作用。我们需要读取的数据就在该缓存类中。

2~3:我们将写好的数据封装到bytebuf中,然后通过write方法写回到客户端,这里的3调用flush方法的作用为,防止频繁的发送数据,write方法并不直接将数据写入socketchannel中,而是把待发送的数据放到发送缓存数组中,再调用flush方法发送数据。

客户端

public class printclient {

    public void connect(int port, string host) throws exception {
    eventloopgroup group = new nioeventloopgroup();                 //1
    try {
        bootstrap b = new bootstrap();                              //2
         b.group(group)                                             //3
            .channel(niosocketchannel.class)                        //4
            .option(channeloption.tcp_nodelay, true)                //5
            .handler(new channelinitializer<socketchannel>() {      //6
            @override
            public void initchannel(socketchannel ch)               
                throws exception {
                ch.pipeline().addlast(new timeclienthandler());
            }
            });

        channelfuture f = b.connect(host, port).sync();             //7
        f.channel().closefuture().sync();                           //8
    } finally {
        // 优雅退出,释放nio线程组
        group.shutdowngracefully();
    }
    }

    /**
     * @param args
     * @throws exception
     */
    public static void main(string[] args) throws exception {
    int port = 8080;
    new timeclient().connect(port, "127.0.0.1");
    }
}

我们继续来分析一下上面的这段代码(下面的每一点对应上面的注释)

1:区别于服务端,我们在客户端只创建了一个nioeventloopgroup实例,因为客户端你并不需要使用i/o多路复用模型,需要有一个reactor来接受请求。只需要单纯的读写数据即可

2:区别于服务端,我们在客户端只需要创建一个bootstrap对象,它是客户端辅助启动类,功能类似于serverbootstrap。

3:将nioeventloopgroup实例绑定到bootstrap对象中。

4:创建channel(典型的channel有niosocketchannel,nioserversocketchannel,oiosocketchannel,oioserversocketchannel,epollsocketchannel,epollserversocketchannel),区别与服务端,这里创建的是niosocketchannel.

5:设置参数,这里设置的tcp_nodelay为true,意思是关闭延迟发送,一有消息就立即发送,默认为false。

6:建立连接后的具体handler。注意这里区别与服务端,使用的是handler()而不是childhandler()。handler和childhandler的区别在于,handler是接受或发送之前的执行器;childhandler为建立连接之后的执行器。

7:发起异步连接操作

8:当代客户端链路关闭

绑定该客户端的handler

public class printclienthandler extends channelhandleradapter {

    private static final logger logger = logger
        .getlogger(timeclienthandler.class.getname());

    private final bytebuf firstmessage;

    /**
     * creates a client-side handler.
     */
    public timeclienthandler() {
    byte[] req = "你好服务端".getbytes();
    firstmessage = unpooled.buffer(req.length);                                 //1
    firstmessage.writebytes(req);

    }

    @override
    public void channelactive(channelhandlercontext ctx) {
    ctx.writeandflush(firstmessage);                                            //2             
    }

    @override
    public void channelread(channelhandlercontext ctx, object msg)              //3
        throws exception {
    bytebuf buf = (bytebuf) msg;    
    byte[] req = new byte[buf.readablebytes()];
    buf.readbytes(req);
    string body = new string(req, "utf-8");
    system.out.println("服务端回应消息 : " + body);
    }

    @override
    public void exceptioncaught(channelhandlercontext ctx, throwable cause) {   //4
    // 释放资源
    system.out.println("unexpected exception from downstream : "
        + cause.getmessage());
    ctx.close();
    }
}

printclienthandler 继承 channelhandleradapter ,在这里它的功能为 发送数据并打印服务端发来的数据。

我们只需要实现channelactive,channelread,exceptioncaught,第一个为建立连接后立即执行,后两个与一个为接受消息具体逻辑的实现,另一个为发生异常后的具体逻辑实现。

1:将发送的信息封装到bytebuf中。

2:发送消息。

3:接受客户端的消息并打印

4:发生异常时,打印异常信息,释放客户端资源

总结

这是一个入门程序,对应前面所讲的i/o多路复用模型以及nio的特性,能很有效的理解该模式的编程方式。如果这几段代码看着很费劲,那么可以看看之前博主的netty基础系列。

如果博主哪里说得有问题,希望大家提出来,一起进步~