Netty就是这么回事(一)

写在开头:Netty是个什么玩意?这里摘抄官网的一段话:Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。

也就是说,Netty 是一个基于NIO的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户,服务端应用。Netty相当简化和流线化了网络应用的编程开发过程,例如,TCP和UDP的socket服务开发。

总结一句话: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权威指南》

转载于:https://my.oschina.net/u/3203060/blog/818903

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值