当前位置: 移动技术网 > IT编程>软件设计>面向对象 > Netty入门系列(1) --使用Netty搭建服务端和客户端

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

2019年05月09日  | 移动技术网IT编程  | 我要评论

引言

前面我们介绍了网络一些基本的概念,虽然说这些很难吧,但是至少要做到理解吧。有了之前的基础,我们来正式揭开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基础系列。

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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网