写在开头:Netty是个什么玩意?这里摘抄官网的一段话:Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。java
也就是说,Netty 是一个基于NIO的客户、服务器端编程框架,使用Netty 能够确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户,服务端应用。Netty至关简化和流线化了网络应用的编程开发过程,例如,TCP和UDP的socket服务开发。linux
总结一句话:Netty是用于开发客户端、服务端通讯系统的一套框架。数据库
咱们学习Netty能用来干什么呢?或者说有哪些咱们经常使用的东西使用Netty开发的呢?举两个例子:Hadoop和dubbo,Hadoop底层使用netty作通讯架构的,dubbo底层也是用netty写的。netty在中间件系统开发中用的特别多,可能你会认为我用不到啊,我平时都是SofaMVC或者SpringMVC,Mybatis,Spring一类的,而后基本上都是和数据库打交道,每天crud。对,说的没错,可是你别忘了,互联网的技术本质其实就是通讯,你操做数据库其实也是在不断和数据库在通讯,并且随着系统规模增大,大到现有的框架已经不能知足要求了,那么你就须要开发本身的通讯系统。编程
在没有Netty以前咱们是怎么开发通讯系统的,记得之前作过一个项目,是替有关部门作一个车辆监控系统,每一个车上面有个GPS设备(与卫星通讯的专业设备),他们但愿可以监控车辆的运行状态。当时在作服务端的时候用的是BIO框架,也就是阻塞IO,采用一端一线程来解决,在开发的时候遇到了不少坑,好比说客户端(GPS模块)与服务端连接超时,网络闪断,板报读写,网络拥塞等,而且一台机器的线程数有限,还好当时只监控500辆车,当时遇到的最恶心的是GPS这个模块他们用了一个厂家作的嵌入式设备,由于我之前是开发单片机的,对着还比较了解,当时他们的协议栈有问题,虽然客户端与服务器链接断了,可是服务器这边仍是现实链接着,后来发现是他们经过基站链接着服务器,总之,特别多的问题。基本上好多代码都不是去解决业务自己并且在构建一个通讯框架,这固然不是我想要的。缓存
再后来,使用了NIO框架,好处不用多说,单机支撑上万线程,采用linux的enpoll模型,多路io复用,可是在采用原生的NIO框架开发通讯系统的时候,依旧大量代码用于构建通讯框架而不是业务自己。而且一直崩溃,不稳定,这其实也是NIO框架的问题所在,主要有如下几点:服务器
1. NIO的类库和API繁杂。网络
2. 须要具有其余的额外技能作铺垫。架构
3. 可靠性能力不齐,工做了和难度大。例如:客户端断线重连,网络闪断,半包读写,失败缓存,网络拥塞,异常码流等。框架
4. JDK的BUG,例如:epoll的bug会致使selector空轮询,致使CPU 100%,该问题到JDK1.7版本仍是一直存在。异步
废话很少说,先看一个简单的采用原生NIO框架开发的服务器例子(简单)。
package com.dlb.note.server.nio; import java.io.Closeable; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.*; import java.nio.charset.Charset; import java.util.Iterator; /** * <li> * <b>nio作服务端开发的问题:</b> * 1. NIO的类库和API繁杂。 * 2. 须要具有其余的额外技能作铺垫。 * 3. 可靠性能力不齐,工做了和难度大。例如:客户端断线重连,网络闪断,半包读写,失败缓存,网络拥塞,异常码流等。 * 4. JDK的BUG,例如:epoll的bug会致使selector空轮询,致使CPU 100%,该问题到JDK1.7版本仍是一直存在。 * </li> * * 功能:nio服务端 * 版本:1.0 * 日期:2016/12/21 11:38 * 做者:馟苏 */ public class NioServer { // 本地字符集 private static final String LocalCharsetName = "gbk"; // 缓冲区大小 private static final int Buffer_Size=1024; /** * 主函数 * @param args */ public static void main(String []args) throws Exception { Selector selector = null; ServerSocketChannel serverSocketChannel = null; try { selector = Selector.open(); // 开启IO多路复用轮询 serverSocketChannel = ServerSocketChannel.open(); // 打开服务器channel serverSocketChannel.configureBlocking(false); // 配置为非阻塞 serverSocketChannel.socket().bind(new InetSocketAddress(8888)); // 绑定服务器本地地址和端口 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); // 在多路复用轮询器上注册操做 System.out.println("服务器在8888端口监听"); while (selector.select() > 0) { // 若是没有事件发生,那么在这里阻塞 Iterator<SelectionKey> it = selector.selectedKeys().iterator(); // 获取键的迭代器 while (it.hasNext()) { SelectionKey key = it.next(); it.remove(); // 移除key,防止屡次遍历 try { /** * 处理 */ handler(key); } catch (Exception e) { e.printStackTrace(); // 打印远程客户端的ip SocketChannel channel = (SocketChannel) key.channel(); System.out.println(channel.socket().getRemoteSocketAddress()); // 取消注册,关闭客户端的channel key.cancel(); closeConnects(key.channel()); } System.out.println("处理结束"); } } } catch (Exception e) { e.printStackTrace(); } finally { try { closeConnects(serverSocketChannel, selector); } catch (Exception e) { e.printStackTrace(); } } } /** * 处理 * @param key */ private static void handler(SelectionKey key) throws Exception { if(key.isAcceptable()) { // 客户端链接到来 SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept(); clientChannel.configureBlocking(false); // 客户端注册为读事件而且设置与该键关联的附加对象 clientChannel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(Buffer_Size)); } else if(key.isReadable()) { // 收到客户端消息 // 得到与客户端通讯的信道 SocketChannel clientChannel = (SocketChannel) key.channel(); System.out.println(key.hashCode()); // 获取此键的附加对象 ByteBuffer buffer = (ByteBuffer) key.attachment(); buffer.clear(); // 读取信息得到读取的字节数 long bytesRead = clientChannel.read(buffer); if(bytesRead == -1) { // 打印远程客户端的ip SocketChannel channel = (SocketChannel) key.channel(); System.out.println(channel.socket().getRemoteSocketAddress()); // 取消注册,关闭客户端的channel key.cancel(); closeConnects(clientChannel); } else { // 将缓冲区准备为数据传出状态 buffer.flip(); // 将得到字节字符串(使用Charset进行解码) String receivedString = Charset.forName(LocalCharsetName).newDecoder().decode(buffer).toString(); // 控制台打印出来 System.out.println("接收到信息:" + receivedString); // 准备发送的文本 String sendString = "你好,客户端. 已经收到你的信息" + receivedString; // 将要发送的字符串编码(使用Charset进行编码)后再进行包装 buffer = ByteBuffer.wrap(sendString.getBytes(LocalCharsetName)); // 发送给客户端 clientChannel.write(buffer); // 设置为下一次读取或是写入作准备 // key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); } } } /** * 关闭链接 * @param closeables */ public static void closeConnects(Closeable ...closeables) throws Exception { if (closeables == null) { return; } for (Closeable c : closeables) { c.close(); } } }
看完以后什么感受,是否是特别恶心,对吧,写个简单的接受字符串程序写了这么多代码,并且也没有解决半包等问题,这岂不是要疯的节奏,那么如何解决这个问题?Netty这个犹如救世主的框架给咱们带来了曙光!预知后事如何,待我下回分解。
参考书籍《netty权威指南》