NIO的网络IO操作

一、网络IO

1.1 概述

文件IO用到的FileChannel并不支持非阻塞操作,学习NIO主要就是进行网络IO,JavaNIO中的网络通道是非阻塞IO的实现,基于事件驱动,非常适用于服务器需要维持大量连接,但是数据交换量不大的情况,例如一些即时通信的服务等…

在Java中编写Socket服务器,通常有以下几种模式:

  1. 一个客户 端连接用一个线程,优点:程序编写简单;缺点:如果连接非常多,分配的线程也会非常多,服务器可能会因为资源耗尽而崩溃。
  2. 把每一个客户端连接交给一个拥有固定数量线程的连接池,优点:程序编写相对简单,可以处理大量的连接。线程的开销非常大,连接如果非常多,排队现象会比较严重。
  3. 使用 Java的NIO,用非阻塞的IO方式处理。这种模式可以用一个线程,处理大量的客户端连接。

1.2 核心API

1.2.1 Selector选择器

Selector选择器能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的响应处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时,才会调用函数来进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程,并且避免了多线程之间的上下文切换导致的开销。

该类的常用方法如下所示:

1. public static Selector open(),得到一个选择器对象
2. public int select(long timeout),监控所有注册的channel,当其中有注册的IO操作可以进行时,将对应的SelectionKey 加入到内部集合中并返回,参数用来设置超时时间
3. public Set<SelectionKey> selectedKeys(),从内部集合中得到所有的SelectionKey
4. SelectionKey, 代表了Selector 和serverSocketChannel 的注册关系,一共四种:
5. int OP_ACCEPT:有新的网络连接可以accept,值为16
6. int OP_CONNECT:代表连接已经建立,值为8

1.2.2 SelectionKey

SelectionKey代表了Selector 和serverSocketChannel 的注册关系,一共四种:

  1. int OP_ACCEPT:有新的网络连接可以accept,值为16
  2. int OP_CONNECT:代表连接已经建立,值为8
  3. int OP_READ和int OP_WRITE:代表了读、写操作,值为1和4

该类的常用方法如下所示:

* public abstract Selector selector(),得到与之,关联的Selector对象
 * public abstract SelectableChannel channel(),得到与之关联的通道
* public final Object attachment(),得到与之关联的共享数据
* public abstract SelectionKey interestOps(int ops),设置或改变监听事件
*  public final boolean isAcceptable(), 是否可以accept
* public final boolean isReadable(),是否可以读
* public final boolean isWritable(),是否可以写

1.2.3 ServerSocketChannel

用来在服务器端监听新的客户端Socket连接,常用方法如下所示:

* public static ServerSocketChannel open(),得到- - 个ServerSocketChannel通道
* public final ServerSocketChannel bind(SocketAddress local),设置服务器端端口号
* public final SelectableChannel configureBlocking(boolean block),设置阻塞或非阻塞模式,取值false 表示采用非阻塞模式
* public SocketChannel accept(),接受-一个连接,返回代表这个连接的通道对象
* public final SelectionKey register(Selector sel, int ops),注册一个选择器并设置监听事件

1.2.4 SocketChannel 网络IO通道

具体负责进行读写操作。NIO总是把缓冲区的数据写入通道,或者把通道里的数据读出到缓冲区( buffer)。常用方法如下所示:

* public static SocketChannel open(),得到一个SocketChannel通道
* public final SelectableChannel configureBlocking(boolean block),设置阻塞或非阻塞模式,取值false表示采用非阻塞模式
* public boolean connect(SocketAddress remote),连接服务器
* public boolean finishConnect(),如果上面的方法连接失败,接下来就要通过该方法完成连接操作
* public int write(ByteBuffer src),往通道里写数据
* public int read(ByteBuffer dst),从通道里读数据
* public final SelectionKey register(Selector sel, int ops, Object att),注册一一个选择 器并设置监听事件,最后一个参数可以设置共享数据
* public final void close(),关闭通道

二、简单的网络IO的例子(客户端向服务端发送消息)

2.1 客户端代码

package com.example.demo;

import java.net.InetSocketAddress;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/**
 * @author : pengweiwei
 * @date : 2020/1/28 4:54 下午
 */
public class NIOClient {

    public static void main(String[] args) throws Exception{
        //1.先得到一个网络通道
        SocketChannel socketChannel = SocketChannel.open();

        //2.设置阻塞方式为非阻塞
        socketChannel.configureBlocking(false);

        //3.设置连接的服务器的IP和端口号
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8888);

        //4.连接服务器
        if(!socketChannel.connect(inetSocketAddress)){
            //如果没连接上,在等待连接的时候还可以做其他事
            while (!socketChannel.finishConnect()){
                System.out.println(" do something...");
            }
        }

        //5.得到一个缓冲区并存入数据
        String msg = "hello server";
        ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes());

        //6.发送数据
        socketChannel.write(byteBuffer);

        //7.不能关闭连接,让客户端保持连接
        System.in.read();
    }
}

2.2 服务端代码

package com.example.demo;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/**
 * @author : pengweiwei
 * @date : 2020/1/28 5:17 下午
 */
public class NIOServer {

    public static void main(String[] args) throws Exception{

        //1.得到一个ServerSocketChannel对象
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        //2.得到一个选择器Selector对象
        Selector selector = Selector.open();

        //3.绑定客户端的端口号
        serverSocketChannel.bind(new InetSocketAddress(8888));

        //4.设置阻塞方式
        serverSocketChannel.configureBlocking(false);

        //5.把ServerSocketChannel对象注册给Selector
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        //6.监控客户端
        while (true){
            //表示没有客户端尝试连接
            if(selector.select(2000) == 0){
                System.out.println("没有客户端尝试连接");
                continue;
            }
            //如果有的话,得到所有的SelectionKey,判断通道里的事件类型
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()){
                SelectionKey selectionKey = iterator.next();

                //判断selectionKey的事件类型
                 if(selectionKey.isAcceptable()){
                     //客户端连接事件
                     System.out.println("OP_ACCEPT");
                     SocketChannel socketChannel = serverSocketChannel.accept();
                     socketChannel.configureBlocking(false);
                     socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                 }

                 if(selectionKey.isReadable()){
                     //读取客户端数据事件
                     SocketChannel channel = (SocketChannel)selectionKey.channel();
                     ByteBuffer buffer = (ByteBuffer)selectionKey.attachment();
                     channel.read(buffer);
                     System.out.println("客户端发来的数据"+new String(buffer.array()));
                 }

                iterator.remove();

            }

        }
    }
}

运行结果:
在这里插入图片描述
后面的空格是由于缓冲区设置了1024的大小,而发送的消息只有hello,server 后面都是空格,所以,发送消息之前调用字符串的trim去掉空格就行。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值