上两篇我们探讨了传统IO和NIO的区别,本篇就来正式介绍Netty的内容。
一、Netty介绍
学习了NIO的童鞋应该都知道,NIO是一个非阻塞的多线程的socket网络通信API,而正如我们上一篇写的demo一样,每次使用NIO进行网络通信的时候我们都需要自己编写网络交互的服务端、客户端,都要去编写数据的接收、解析、返回等逻辑方法,十分麻烦。基于此,JBOSS就推出了一个可以快速开发高性能、高可靠性的网络服务器和客户端程序的框架-----Netty。
Netty说白了就是一个基于NIO的客户、服务器端编程框架,本来需要写很久的客户端和服务端的代码,这里只需要Netty就可以快速、简单的开发出一个实现某种协议的网络应用。
Netty版本大致版本分为Netty3.x、Netty4.x和Netty5.x。
Netty在目前开发中的应用主要在以下两个方面:
1.分布式进程通信
像Hadoop、dubbo、akka等具有分布式功能的框架,底层RPC通信都是基于Netty实现的,这些框架版本通常都还在使用Netty3.x。
2.游戏服务器开发
众所周知,许多网络游戏需要在各个客户端传输网络信息,所以也需要快速搭建客户端与服务端之间的网络交互逻辑。
最新的游戏服务器有部分公司可能已经开始使用Netty4.x和Netty5.x。
二、Netty服务端hello world案例
我们这里的样例主要针对Netty3.x进行讲解,后面逐步衍生到Netty4.x以上,以便于大家对Netty的逐步理解。
首先打开Eclipse,我们新建一个java项目。名为“Netty_Hello”:
然后创建一个名为“libs”的文件夹,用于放置依赖jar包。我们在里面放置的是netty3.10.5的依赖jar和源码:
并且将netty-3.10.5.Final.jar的依赖add进编译环境。
我们在src下新建一个类(com.server包下)Server:
代码:
这些方法分别是处理网络连接、信息接收、连接断开、连接关闭、异常捕获的方法,我们为了看清楚它们的调用顺序,这里在重写的方法中打印了一些信息。
服务端搭建完毕之后,右键运行起来:
然后我们使用cmd控制台的telnet调用该服务:
回车之后,观察控制台,可以看到连接已经建立:
我们在cmd中使用“ctrl+]”进入消息输入界面,向服务端发送“hello”信息:
可以看到服务端收到了客户端传来的消息。
然后我们关闭cmd,可以看到控制台先打印了“channelDisconnected”,然后打印了“channelClosed”:
原因是,对于channelDisconnected方法,是必须有连接建立之后,关闭通道的时候才会触发此方法,而对于channelClosed就是无论连接建立与否,在channel关闭的时候就会触发。(例如客户端连接失败后,不会触发channelDisconnected,只会触发channelClosed)。
小伙伴们可以发现,我们的消息接收时总是要把字节流转换为String字符串,可不可以省去这一步,直接拿到字符串呢?是可以的,我们只需要在服务端的管道的工厂中设置decoder解码类为“StringDecoder”类即可:
可以看到使用String接收成功,是因为我们设置的decoder类帮我们把字符流转换为String类型的。
我们的Server服务端接收到信息后,可以会写给客户端信息,我们在messageReceived中编写回复逻辑:
然后我们重新启动服务端,然后使用telnet进去后(不用ctrl+])随便输一个字符(这里输入a),就拿到了服务端的回复信息:
如果我们在ctrl+]状态下输入信息时,发送很多信息后,直接关闭cmd窗口,服务端就会捕获一个强制关闭的异常:
一、Netty介绍
学习了NIO的童鞋应该都知道,NIO是一个非阻塞的多线程的socket网络通信API,而正如我们上一篇写的demo一样,每次使用NIO进行网络通信的时候我们都需要自己编写网络交互的服务端、客户端,都要去编写数据的接收、解析、返回等逻辑方法,十分麻烦。基于此,JBOSS就推出了一个可以快速开发高性能、高可靠性的网络服务器和客户端程序的框架-----Netty。
Netty说白了就是一个基于NIO的客户、服务器端编程框架,本来需要写很久的客户端和服务端的代码,这里只需要Netty就可以快速、简单的开发出一个实现某种协议的网络应用。
Netty版本大致版本分为Netty3.x、Netty4.x和Netty5.x。
Netty在目前开发中的应用主要在以下两个方面:
1.分布式进程通信
像Hadoop、dubbo、akka等具有分布式功能的框架,底层RPC通信都是基于Netty实现的,这些框架版本通常都还在使用Netty3.x。
2.游戏服务器开发
众所周知,许多网络游戏需要在各个客户端传输网络信息,所以也需要快速搭建客户端与服务端之间的网络交互逻辑。
最新的游戏服务器有部分公司可能已经开始使用Netty4.x和Netty5.x。
二、Netty服务端hello world案例
我们这里的样例主要针对Netty3.x进行讲解,后面逐步衍生到Netty4.x以上,以便于大家对Netty的逐步理解。
首先打开Eclipse,我们新建一个java项目。名为“Netty_Hello”:
然后创建一个名为“libs”的文件夹,用于放置依赖jar包。我们在里面放置的是netty3.10.5的依赖jar和源码:
并且将netty-3.10.5.Final.jar的依赖add进编译环境。
我们在src下新建一个类(com.server包下)Server:
代码:
package com.server; import org.jboss.netty.channel.Channels; import java.net.InetSocketAddress; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.jboss.netty.bootstrap.ServerBootstrap; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipelineFactory; import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; public class Server { public static void main(String[] args) { //1.创建一个Netty服务类 /** * ServerBootstrap 是一个启动NIO服务的辅助启动类 * 你可以在这个服务中直接使用Channel * */ ServerBootstrap bootstrap = new ServerBootstrap(); //2.创建两个线程池 /** * 第一个经常被叫做‘boss’,用来接收进来的连接。 * 第二个经常被叫做‘worker’,用来处理已经被接收的连接, * 一旦‘boss’接收到连接,就会把连接信息注册到‘worker’上。 * */ ExecutorService boss = Executors.newCachedThreadPool(); ExecutorService worker = Executors.newCachedThreadPool(); //3.为服务类设置一个NioSocket工厂 bootstrap.setFactory(new NioServerSocketChannelFactory(boss, worker)); //4.设置管道的工厂(匿名内部类实现) bootstrap.setPipelineFactory(new ChannelPipelineFactory() { @Override public ChannelPipeline getPipeline() throws Exception { ChannelPipeline pipeline = Channels.pipeline(); //设置一个处理服务端消息和各种消息事件的类(Handler) pipeline.addLast("hellohandler", new HelloHandler()); return pipeline; } }); //5.为服务端设置一个端口 bootstrap.bind(new InetSocketAddress(10101)); System.out.println("server start!"); } }其中的HelloHandler也是我们自己新建的,继承了SimpleChannelHandler:
package com.server; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.ExceptionEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.SimpleChannelHandler; public class HelloHandler extends SimpleChannelHandler{ /** * 连接关闭 * */ @Override public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { System.out.println("channelClosed"); super.channelClosed(ctx, e); } /** * 新建连接 * */ @Override public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { System.out.println("channelConnected"); super.channelConnected(ctx, e); } /** * 连接断开 * */ @Override public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { System.out.println("channelDisconnected"); super.channelDisconnected(ctx, e); } /** * 捕获异常 * */ @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { System.out.println("exceptionCaught,error:"+e.toString()); super.exceptionCaught(ctx, e); } /** * 消息接收 * */ @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { System.out.println("messageReceived"); //获取到的信息是使用ChannelBuffer包装的字节流 ChannelBuffer message = (ChannelBuffer)e.getMessage(); String msg = new String(message.array());//将字节流转换为String System.out.println(msg);//打印收到的字符串 super.messageReceived(ctx, e); } }实现了以下方法:
这些方法分别是处理网络连接、信息接收、连接断开、连接关闭、异常捕获的方法,我们为了看清楚它们的调用顺序,这里在重写的方法中打印了一些信息。
服务端搭建完毕之后,右键运行起来:
然后我们使用cmd控制台的telnet调用该服务:
回车之后,观察控制台,可以看到连接已经建立:
我们在cmd中使用“ctrl+]”进入消息输入界面,向服务端发送“hello”信息:
可以看到服务端收到了客户端传来的消息。
然后我们关闭cmd,可以看到控制台先打印了“channelDisconnected”,然后打印了“channelClosed”:
原因是,对于channelDisconnected方法,是必须有连接建立之后,关闭通道的时候才会触发此方法,而对于channelClosed就是无论连接建立与否,在channel关闭的时候就会触发。(例如客户端连接失败后,不会触发channelDisconnected,只会触发channelClosed)。
小伙伴们可以发现,我们的消息接收时总是要把字节流转换为String字符串,可不可以省去这一步,直接拿到字符串呢?是可以的,我们只需要在服务端的管道的工厂中设置decoder解码类为“StringDecoder”类即可:
//4.设置管道的工厂(匿名内部类实现) bootstrap.setPipelineFactory(new ChannelPipelineFactory() { @Override public ChannelPipeline getPipeline() throws Exception { ChannelPipeline pipeline = Channels.pipeline(); //设置解码方式为String pipeline.addLast("decoder", new StringDecoder()); //设置一个处理服务端消息和各种消息事件的类(Handler) pipeline.addLast("hellohandler", new HelloHandler()); return pipeline; } });然后接收方法我们直接使用String接收即可:
/** * 消息接收 * */ @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { System.out.println("messageReceived"); String msg = (String)e.getMessage();//将字节流转换为String System.out.println(msg);//打印收到的字符串 super.messageReceived(ctx, e); }实验:
可以看到使用String接收成功,是因为我们设置的decoder类帮我们把字符流转换为String类型的。
我们的Server服务端接收到信息后,可以会写给客户端信息,我们在messageReceived中编写回复逻辑:
/** * 消息接收 * */ @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { System.out.println("messageReceived"); String msg = (String)e.getMessage();//将字节流转换为String System.out.println(msg);//打印收到的字符串 //回写数据(需要用ChannelBuffer包装要回复的字节流信息) ChannelBuffer channelBuffer = ChannelBuffers.copiedBuffer("hi".getBytes()); ctx.getChannel().write(channelBuffer); super.messageReceived(ctx, e); }即是使用ChannelHandlerContext获取Channel,然后使用writer我们需要给客户端回复的信息。
然后我们重新启动服务端,然后使用telnet进去后(不用ctrl+])随便输一个字符(这里输入a),就拿到了服务端的回复信息:
如果我们在ctrl+]状态下输入信息时,发送很多信息后,直接关闭cmd窗口,服务端就会捕获一个强制关闭的异常:
以上就是一个基于Netty技术的的网络通信服务端的一个样例,下一篇讲解基于Netty的客户端的样例,并且与本篇的服务端进行通信。
转载请注明出处:https://blog.csdn.net/acmman/article/details/80298062