Netty杂记2—NIO网络编程

前言

在上篇文章中对BIO网络编程的相关内容进行了讲解,经过咱们一步一步的优化,虽然咱们经过多线程解决了并发访问的问题,可是BIO自己的一些特性形成的问题却没有获得解决。java

BIO是阻塞IO,咱们使用线程来进行IO的调度,咱们没法肯定io是否就绪,可是每一个IO操做都会建立线程,这个时候若是IO未就绪,那么建立的线程也会处于阻塞状态。git

在以前讲解NIO基本知识的时候咱们提到过NIO经过通道选择器能够实现同时对多个通道的管理,实际上就是经过管理多个IO操做,换句话说是单线程处理多线程并发,有效的防止线程由于IO没有就绪而被挂起。github

在使用NIO进行网络编程的时候须要用到的就是通道选择器,因此咱们先看一下通道选择器的相关内容。编程

NIO

ServerSocketChannel

Java NIO中的 ServerSocketChannel 是一个能够监听新进来的TCP链接的通道, 就像标准IO中的ServerSocket同样。服务器

//代开ServerSocketChannel 
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
复制代码

经过 ServerSocketChannel.accept() 方法监听新进来的链接。当 accept()方法返回的时候,它返回一个包含新进来的链接的 SocketChannel。 能够设置成非阻塞模式。在非阻塞模式下,accept() 方法会马上返回,若是尚未新进来的链接,返回的将是null。网络

SocketChannel

Java NIO中的SocketChannel是一个链接到TCP网络套接字的通道。能够经过如下2种方式建立SocketChannel:多线程

//打开一个SocketChannel并链接到互联网上的某台服务器。
socketChannel.connect(new InetSocketAddress("localhost",8888));
//一个新链接到达ServerSocketChannel时,会建立一个SocketChannel。
SocketChannel socketChannel = SocketChannel.open();
复制代码

通道选择器 Selector

为何使用通道选择器?

在前言中提到过,NIO经过通道选择器能够实现同时对多个通道的管理,其实就是同时对多个IO操做的管理,也就是实现了单线程处理多线程并发问题。对于操做系统来讲,线程之间上下文切换的开销很大,并且每一个线程都要占用系统的一些资源(如内存)。所以太多的线程会耗费大量的资源,因此使用通道选择器来对多个通道进行管理。并发

Selector的使用

1. 建立Selectorsocket

//经过调用Selector.open()方法建立一个Selector,以下:
Selector selector = Selector.open();
复制代码

2.将通道注册到通道选择器中优化

//为了将Channel和Selector配合使用,必须将channel注册到selector上。经过SelectableChannel.register()方法来实现
// 建立ServerSocketChanner
        ServerSocketChannel ssc = ServerSocketChannel.open();
// 绑定端口号
        ssc.bind(new InetSocketAddress(8888));
// 设置通道非阻塞
        ssc.configureBlocking(false);
// 建立通道选择器
        Selector selector = Selector.open();
// 将通道注册到通道选择器中 要求:通道都必须是非阻塞的 意味着不能将FileChannel与Selector一块儿使用,由于FileChannel不能切换到非阻塞模式
// 第二个参数是咱们须要通道选择器帮咱们管理什么事件类型 注册为接受就绪
        ssc.register(selector, SelectionKey.OP_ACCEPT);
复制代码

register()方法的第二个参数表明注册的通道事件类型,一个通道触发一个事件就意味着该事件准备就绪了,总共有四种事件:Connect(链接就绪 ) Accept(接受就绪) Read(有数据可读的通道 读就绪) Write(写就绪)。对于选择器而言,能够针对性的找到(监听)的事件就绪的通道,进行相关的操做。

这四种事件用SelectionKey的四个常量来表示:

  1. SelectionKey.OP_CONNECT
  2. SelectionKey.OP_ACCEPT
  3. SelectionKey.OP_READ
  4. SelectionKey.OP_WRITE

能够用“位或”操做符将常量链接起来

SelectionKey对象

对象中包含不少的属性,譬如:

  • interest集合(事件集合)
  • ready集合
  • Channel
  • Selector
  • 附加的对象(可选)

3.选择器的select ()方法

select()方法返回的int值表示有多少通道已经就绪。自上次调用select()方法后有多少通道变成就绪状态。若是调用select()方法,由于有一个通道变成就绪状态,返回了1,若再次调用select()方法,若是另外一个通道就绪了,它会再次返回1。若是对第一个就绪的channel没有作任何操做,如今就有两个就绪的通道,但在每次select()方法调用之间,只会有一个通道就绪。

4.选择器的selectedKeys()方法

经过调用selector的selectedKeys()方法,能够获得就绪通道的集合。遍历集合能够找到本身须要的通道进行相关的操做。

Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();

            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                //处理nio事件
                if(key.isAcceptable()){
// 获取通道
                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                    SocketChannel socketChannel = channel.accept();

// 注册读事件类型
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ);


                }

                if(key.isReadable()){
                    SocketChannel socketChannel = (SocketChannel) key.channel();

// 读取数据
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    ByteArrayOutputStream bos = new ByteArrayOutputStream();
                    int len = -1;
                    while (true){
                        byteBuffer.clear();
                        len = socketChannel.read(byteBuffer);
                        if(len == -1){
                            break;
                        }

                        byteBuffer.flip();
                        while (byteBuffer.hasRemaining()){
                            bos.write(byteBuffer.get());
                        }
                    }
// 打印读取到的数据
                    System.out.println(bos.toString());
                    //写数据给客户端 注册事件类型 写事件
                    socketChannel.register(selector,SelectionKey.OP_WRITE);
                }
                if(key.isWritable()){
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    String msg = "你好,我是服务器";
                    ByteBuffer byteBuffer = ByteBuffer.allocate(msg.getBytes().length);
                    byteBuffer.put(msg.getBytes());
                    byteBuffer.flip();
                    socketChannel.write(byteBuffer);
                    socketChannel.close();
                }
                iterator.remove();
            }
复制代码

NIO网络编程实例

服务器编程

基本步骤
  1. 打开ServerSocketChanner
  2. 绑定端口号
  3. 设置通道非阻塞
  4. 打开选择器,把通道注册到选择器中
  5. 使用Selector轮询全部的key
    1. 获取socketChannel
    2. 设置非阻塞 注册读事件
    3. 读取操做
    4. 注册写事件
    5. 写操做

NIO网络编程

代码
import java.io.ByteArrayOutputStream;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

public class NIOServer {
    public static void main(String[] args) throws Exception{
// 建立ServerSocketChanner
        ServerSocketChannel ssc = ServerSocketChannel.open();

// 绑定端口号
        ssc.bind(new InetSocketAddress(8888));

// 设置通道非阻塞
        ssc.configureBlocking(false);

// 建立通道选择器
        Selector selector = Selector.open();

// 将通道注册到通道选择器中 要求:通道都必须是非阻塞的
// 第二个参数是咱们须要通道选择器帮咱们管理什么事件类型 注册为接受就绪
        ssc.register(selector, SelectionKey.OP_ACCEPT);

// 遍历通道选择器
        while (true){
            System.out.println("我在8888等你......");
// 返回准备就绪的通道数量
            int nums = selector.select();
// 若是数量小于1说明没有通道准备就绪 跳过本次循环
            if(nums<1) {continue;}

// 获取全部的keys(通道 事件类型)
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = keys.iterator();

            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                //处理nio事件
                if(key.isAcceptable()){
// 获取通道
                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                    SocketChannel socketChannel = channel.accept();

// 注册读事件类型
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ);
                }
                if(key.isReadable()){
                    SocketChannel socketChannel = (SocketChannel) key.channel();

// 读取数据
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    ByteArrayOutputStream bos = new ByteArrayOutputStream();
                    int len = -1;
                    while (true){
                        byteBuffer.clear();
                        len = socketChannel.read(byteBuffer);
                        if(len == -1){
                            break;
                        }

                        byteBuffer.flip();
                        while (byteBuffer.hasRemaining()){
                            bos.write(byteBuffer.get());
                        }
                    }
// 打印读取到的数据
                    System.out.println(bos.toString());
                    //写数据给客户端 注册事件类型 写事件
                    socketChannel.register(selector,SelectionKey.OP_WRITE);
                }
                if(key.isWritable()){
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    String msg = "你好,我是服务器";
                    ByteBuffer byteBuffer = ByteBuffer.allocate(msg.getBytes().length);
                    byteBuffer.put(msg.getBytes());
                    byteBuffer.flip();
                    socketChannel.write(byteBuffer);
                    socketChannel.close();
                }
                iterator.remove();
            }
        }
    }
}
复制代码

客户端编程

基本步骤
  1. 打开SocketChannel
  2. 链接服务器
  3. 写数据给服务器
  4. 读取数据
  5. 关闭SocketChannel
代码
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class NIOCleint {
    public static void main(String[] args) throws IOException {
// 建立sc
        SocketChannel socketChannel = SocketChannel.open();
// 链接服务器
        socketChannel.connect(new InetSocketAddress("localhost",8888));

// 写数据给服务器
        String msg = "你好,我是客户端";
        ByteBuffer byteBuffer = ByteBuffer.allocate(msg.getBytes().length);
        byteBuffer.put(msg.getBytes());
        byteBuffer.flip();
        socketChannel.write(byteBuffer);
// 关闭输出流
        socketChannel.shutdownOutput();

// 读取数据
        ByteBuffer readBuffer = ByteBuffer.allocate(1024);
        ByteArrayOutputStream bosread = new ByteArrayOutputStream();
        int len = -1;
        while (true){
            readBuffer.clear();
            len = socketChannel.read(readBuffer);
            if(len == -1){
                break;
            }
            readBuffer.flip();
            while (readBuffer.hasRemaining()){

                bosread.write(readBuffer.get());
            }
        }

        System.out.println("我收到:"+bosread.toString());
        socketChannel.close();
    }
}
复制代码

我不能保证每个地方都是对的,可是能够保证每一句话,每一行代码都是通过推敲和斟酌的。但愿每一篇文章背后都是本身追求纯粹技术人生的态度。

永远相信美好的事情即将发生。

相关文章
相关标签/搜索