简单的NIO通信模型:实现服务器与客户端通信

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_40178464/article/details/90136852

NIO:新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的,弥补了原来的 I/O 的不足,提供了高速的、面向块的 I/O,同时支持阻塞与非阻塞模式

I/O 与 NIO 最重要的区别是数据打包和传输的方式,I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。

·通道channel:对原 I/O 包中的流的模拟,可以通过它读取和写入数据,通道与流的不同之处在于,流只能在一个方向上移动(一个流必须是 InputStream 或者
 OutputStream 的子类),而通道是双向的,可以用于读、写或者同时用于读写。通道包括以下类型:
    |- FileChannel:从文件中读写数据
    |- DatagramChannel:通过 UDP 读写网络中数据
    |- SocketChannel:通过 TCP 读写网络中数据
    |- ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel
·选择器Selector:NIO 现了IO多路复用中的Reactor模型,一个线程Thread使用一个选择器Selector通过轮询的方式去监听多个通道 Channel 上的事件,从
而让一个线程就可以处理多个事件,通过配置监听的通道Channel为非阻塞,那么当Channel上的IO事件还未到达时,就不会进入阻塞状态一直等待,而
是继续轮询其它Channel,找到IO事件已经到达的Channel执行。因为创建和切换线程的开销很大,因此使用一个线程来处理多个事件而不是一个线程处理一
个事件,对于IO密集型的应用具有很好地性能。

在这里插入图片描述

·缓冲区:提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程,实际是一个数组,类型有ByteBuffer、CharBuffer、ShortBuffer、
 InterBuffer、LongBuffer、FloatBuffer、DoubleBuffer,状态量有:
    |-mark:标记
    |-capacity:最大容量
    |-position:位置
    |-limit:限制
·关系:0 <= 标记 <= 位置 <= 限制 <= 容量
    |-flip()方法:反转,切换读写模式

NIO编程过程

·客户端:
    |-通过SocketChannel连接到远程服务器
    |-创建读数据/写数据缓冲区对象来读取服务端数据或向服务端发送数据
    |-关闭SocketChannel
·服务端:
    |-通过ServerSocketChannel 绑定ip地址和端口号
    |-通过ServerSocketChannelImpl的accept()方法创建一个SocketChannel对象用户从客户端读/写数据
    |-创建读数据/写数据缓冲区对象来读取客户端数据或向客户端发送数据
    |-关闭SocketChannel和ServerSocketChannel

看一下简单多线程的例子:
1.服务器:

import org.apache.commons.lang3.concurrent.BasicThreadFactory;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;

public class ChatServer {
    public static void main(String[] args) throws IOException {
   		 // 这里使用Apache的线程池工厂来实现线程池
        ScheduledExecutorService pool = new ScheduledThreadPoolExecutor(3,
                new BasicThreadFactory.Builder().namingPattern("mypool-%d").daemon(false).build());

        Selector selector = Selector.open();
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

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

        //serverSocketChannel的socket绑定服务端口
        serverSocketChannel.socket().bind(new InetSocketAddress(8888));

        //将serverSocketChannel 注册到selector上
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务器开始");

        while (true) {
            //当注册的事件到达时,方法返回
            selector.select();
            // 获得selector中选中的项的迭代器,选中的项为注册的事件
            Iterator ite = selector.selectedKeys().iterator();
            while (ite.hasNext()) {
                SelectionKey key = (SelectionKey) ite.next();
                ite.remove();

                if (key.isAcceptable()) {
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    // 获得和客户端连接的通道
                    SocketChannel channel = server.accept();
                    // 设置成非阻塞
                    channel.configureBlocking(false);
                    //在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。
                    channel.register(selector, SelectionKey.OP_READ);
                    // 获得了可读的事件
                } else if (key.isReadable()) {
                    //取消可读触发标记
                    key.interestOps(key.interestOps() & (~SelectionKey.OP_READ));
                    //加入线程池
                    pool.execute(new ThreadHandlerChannel(key));
                }
            }
        }
    }
}

2.客户端:

import org.apache.commons.lang3.concurrent.BasicThreadFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;

class ClientThread implements Runnable {
    private String ip;

    private int port;

    private Selector selector;

    private SocketChannel socketChannel;

    public ClientThread(String ip, int port) {
        this.ip = ip;
        this.port = port;

        try {
            selector = Selector.open();
            socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
            System.out.println("客户端" + ip + "开始");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {

        try {
            socketChannel.connect(new InetSocketAddress(8888));
            socketChannel.register(selector, SelectionKey.OP_WRITE);
        } catch (IOException e) {
            e.printStackTrace();
        }

        while (true) {
            try {
                //获取注册在selector上的所有的就绪状态的serverSocketChannel中发生的事件
                Set<SelectionKey> set = selector.selectedKeys();
                Iterator<SelectionKey> iterator = set.iterator();
                SelectionKey key;
                while (iterator.hasNext()) {
                    key = iterator.next();
                    if (!key.isValid()) {
                        key.cancel();
                        continue;
                    }
                    handleInput(key);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void handleInput(SelectionKey key) throws IOException {
        if (key.isValid()) {
            SocketChannel sc = (SocketChannel) key.channel();
            if (key.isConnectable()) {
                if (sc.finishConnect()) {
                    sc.register(selector, SelectionKey.OP_READ);
                    doWrite(sc);
                } else {
                    System.out.println("error");
                }
            }

            if (key.isReadable()) {
                ByteBuffer bf = ByteBuffer.allocate(1024);
                int bytes = sc.read(bf);
                if (bytes > 0) {
                    bf.flip();
                    byte[] byteArray = new byte[bf.remaining()];
                    bf.get(byteArray);
                    String msg = new String(byteArray);
                    System.out.println("服务器 :" + msg);
                } else if (bytes < 0) {
                    key.cancel();
                    sc.close();
                }
            }
        }
    }


    private void doWrite(SocketChannel sc) throws IOException {
        BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));
        ByteBuffer bf = ByteBuffer.allocate(sin.readLine().length());
        bf.put(sin.readLine().getBytes());
        bf.flip();
        sc.write(bf);
    }
}

3.启动客户端

public class ChatClient {
    public static void main(String[] args) {
    	// 这里使用Apache的线程池工厂来实现线程池
        ScheduledExecutorService pool = new ScheduledThreadPoolExecutor(3,
                new BasicThreadFactory.Builder().namingPattern("mypool-%d").daemon(false).build());
        pool.execute(new ClientThread("127.0.0.1", 8888));
    }
}

猜你喜欢

转载自blog.csdn.net/qq_40178464/article/details/90136852