Netty-基础

基础概念

同步和异步

描述的方法跟调用者间通信的方式,如果不需要调用者主动等待,调用者调用后立即返回,然后方法本身通过回调,消息通知等方式通知调用者结果,就是异步的。如果调用方法后一直需要调用者一直等待方法返回结果,那么就是同步的。

阻塞和非阻塞

描述的是调用者调用方法后的状态,比如:线程A调用了B方法,A线程处于阻塞状态。

下面用一个案例说明:小明烧开水

  • 第一版烧水: 当小明将水倒入烧水壶之后,需要坐在沙发看着水是否烧开。因此该场就是同步阻塞的,小明无法去做其他事情,而且得一直看着水是否开了。

  • 第二版烧水:当小明将水倒入烧水壶之后,小明就去干其他事情了,每个2分钟回来看一下水是否烧开,因此该场就是同步非阻塞的,小明处于非阻塞状态,但是他需要主动去观察水是否烧开

  • 第三版烧水:当小明将水倒入烧水壶之后,小明就去干其他事情了,当水开时,烧水壶会发出尖锐的叫声,这是小明就回来处理了。因此该场景就是异步非阻塞的,小明可以忙自己的事情,因此他是非阻塞的。而且小明也不需要自己主动的观察水是否烧开,而是烧水壶提醒小明水开了,因此是异步的。

java 中的IO 模型

BIO 模型

  • 同步并阻塞(传统阻塞模型),服务器实现模式为一个连接一个线程,即客户端有连接请求服务端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销。

  • 在客户端和服务器建立连接后,服务器启动一个线程去监听客户端的输入流,当客户端未输入任何数据,服务端这个线程处于阻塞状态,而且服务器是主动的等待客户端信息,所以是同步进行的。

image-20201207125239657.png

NIO 模型

  • 同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理。

  • 服务器线程不会阻塞等待某个客户端传输内容,而是轮询的进行处理,因此是非阻塞的。由于客户端的请求需要服务端主动去查询,因此是同步的。

image-20201207130130396.png

NIO有三个核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)

image-20201207154441247.png
  • Buffer

    一个Buffer对象是固定数量的数据的容器。其作用是一个存储器,或者分段运输区,在这里数据可被存储并在之后用于检索。

  • Channel

    通道(Channel)可以理解为数据传输的管道。通道与流不同的是,流只是在一个方向上移动(一个流必须是inputStream或者outputStream的子类),而通道可以用于读、写或者同时用于读写。

  • Selector

    选择器提供选择执行已经就绪的任务的能力,这使得多元 I/O 成为可能,就绪选择和多元执行使得单线程能够有效率地同时管理多个 I/O 通道(channels)。

  1. 每个channel都会对应一个Buffer

  2. Selector对应一个线程,一个线程对应多个channel(连接)

  3. 该图反应了有三个channel注册到了 selector

  4. 程序切换到哪个channel是由事件决定的

  5. Selector会根据不同的事件,在各个通道上切换

  6. Buffer就是一个内存块,底层是有一个数组

  7. 数据的读取写入是通过Buffer,这个和BIO不同,BIO中要么是输入流或者是输出流,不能双向,但是NIO的Buffer是可以读也可以写,需要flip方法切换

  8. channel是双向的,可以反映底层操作系统的情况。比如Linux,底层操作系统通道就是双向的。

AIO 模型(NIO.2)

异步非阻塞,AIO引入异步通道的概念,采用了Proactor模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后,才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用

下面用一个实例展示下NIO客户端和服务器,具体功能:客户端发送hello world, 服务端打印内容

服务器代码:

package nio.demo2;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Set;

/**
 * @author huangyichun
 * @date 2020/9/11
 */
@Slf4j
public class NIOServer {

    public static void main(String[] args) throws IOException {
        //创建ServerSocketChannel ->ServerSocket
        final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        //得到一个selector对象
        final Selector selector = Selector.open();

        //绑定一个端口 6666,在服务器监听
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));

        //设置为非阻塞模式
        serverSocketChannel.configureBlocking(false);

        //把serverSocketChannel注册到selector,关心事件为OP_ACCEPT
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        log.info("注册后的selectionKey数量={}", selector.keys().size());
        //循环等待
        while(true) {

            //这里我们等待1秒,如果没有事件发生,返回
            if (selector.select(1000) == 0) {
                log.info("服务器等待了1秒,无连接");
                continue;
            }
            //如果返回的大于0,获取到相关的selectKey集合
            //1.如果返回的大于0,标识已经获取到关注的时间
            //2. selector.selectedKeys()返回关注事件的集合
            final Set<SelectionKey> selectionKeys = selector.selectedKeys();

            //遍历selectionKeys
            selectionKeys.forEach(key -> {
                //获取到selectionKey,根据key对应的通道发生的事件做相应的处理
                if(key.isAcceptable()) {//如果是OP_ACCEPT,有新的客户端连接
                    //给该客户端生成一个socketChannel
                    try {
                        SocketChannel socketChannel = serverSocketChannel.accept();
                        log.info("客户端连接成功,socketChannel={}", socketChannel.hashCode());

                        socketChannel.configureBlocking(false);
                        //将客户端channel注册到selector上,关注时间为OP_READ,同时给该channel关联一个Buffer
                        socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                        log.info("客户端连接后,注册的selectionKey数量={}", selector.keys().size());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }else if(key.isReadable()) {//发送OP_READ
                    //通过key反向获取到对应的channel
                    final SocketChannel channel = (SocketChannel)key.channel();
                    //获取到该channel关联的buffer
                    log.info("客户端连接成功,socketChannel={}", channel.hashCode());
                    ByteBuffer byteBuffer = (ByteBuffer)key.attachment();
                    try {
                        channel.read(byteBuffer);
                        log.info("客户端发送的数据是:{}", new String(byteBuffer.array()));
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                //从集合中移除当前的selectionKey,防止重复操作
                selectionKeys.remove(key);
            });
        }
    }
}

客户端代码:

package nio.demo2;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/**
 * @author huangyichun
 * @date 2020/9/14
 */
@Slf4j
public class NIOClient {

    public static void main(String[] args) throws IOException {
        //得到一个通道
        SocketChannel socketChannel = SocketChannel.open();
        //设置非阻塞
        socketChannel.configureBlocking(false);

        //提供服务端的ip和端口
        final InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);

        //连接服务器
        if (!socketChannel.connect(inetSocketAddress)) {

            while (!socketChannel.finishConnect()) {
                log.info("因为连接需要时间,客户端不会阻塞,可以做其他工作...");
            }
        }

        //连接成功,就发送数据
        String str = "hello world";
        ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());

        //发送数据,将buffer数据写入channel
        socketChannel.write(buffer);
        System.in.read();
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容