上两篇我们探讨了传统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