Netty入门系列(1) --使用Netty搭建服务端和客户端
引言
前面我们介绍了网络一些基本的概念,虽然说这些很难吧,但是至少要做到理解吧。有了之前的基础,我们来正式揭开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基础系列。
如果博主哪里说得有问题,希望大家提出来,一起进步~