JAVA NIO编程的示例 与原方式编程方式 以及编程模型相关概念

原始的编程模型 Java Socket 示例

服务器端 也就是IO 阻塞式编程的普通写法
package com.xykj.server;  
  
import java.io.IOException;  
import java.io.InputStream;  
import java.io.OutputStream;  
import java.net.ServerSocket;  
import java.net.Socket;  
/**  
    服务器端继承Thread  
 * 可以实现简单的交互  
 * */  
public class Server extends Thread {  
  
    // 定义服务器接口ServerSocket  
    ServerSocket server = null;  
  
    // 定义一个服务器,定义端口  
    public Server(int port) {  
        try {  
            server = new ServerSocket(port);  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
  
    // 发送消息的线程  
    @Override  
    public void run() {  
        super.run();  
        try {  
            System.out.println("服务器在启动中...等待用户的连接");  
            //一直接收用户的连接,连接之后发送一条短信给用户  
            while(true){  
                // 建立socket接口,accept方法是一个阻塞进程,等到有用户连接才往下走  
                // 定义Socket类  
                Socket  socket = server.accept();  
                //通过socket对象可以获得输出流,用来写数据  
                OutputStream os = socket.getOutputStream();  
                // 向客户端发送消息  
                os.write("服务器正在向你发送消息!".getBytes());  
                //在服务器上显示连接的上的电脑、  
                System.out.println(socket.getInetAddress().getHostAddress()+"连接上了!");  
                //通过socket对象可以获得输入流,用来读取用户数据  
                InputStream is=socket.getInputStream();  
                //读取数据  
                int len=0;  
                byte[] buf=new byte[1024];  
                while ((len=is.read(buf))!=-1) {  
                    //直接把获得的数据打印出来  
                    System.out.println("服务器接收到客户端的数据:"+new String(buf,0,len));  
                }  
                  
            }  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
}  

**缺点: 一个请求对应着一个线程,那么对于大的系统以上面的选择肯定是不显示的 那么 我们来看看NIO 的写法 **
** Selector 概念 : Event 事件 ,Event 是异步编程不可缺少的概念 , 事件 比如说 连接事件,回调事件等, 异步的 **

NIO 的相关概念与 示例

Selector 与Channel的的关系是通过 SelectionKey来进行标识的

//SelectionKey Doc 文档 
 <p> A selectable channel's registration with a selector is represented by a
 * {@link SelectionKey} object.  A selector maintains three sets of selection
 * 三种SelectionKey的集合 
 * keys:
 *
 * <ul>
 * <li> 
 * SelectionKey  标识的一些事件 与操作 是否链接等等 ,是否有数据 ,已链接, 链接断开 等都是事件
 * SelectionKey  标识的一些事件的集合 
 * <p> The <i>key set</i> contains the keys representing the current
 *   channel registrations of this selector.  This set is returned by the
 *   {@link #keys() keys} method. </p></li>
 *		
 *	
 *	感兴趣的集合 
 *   <li><p> The <i>selected-key set</i> is the set of keys such that each
 *   key's channel was detected to be ready for at least one of the operations
 *   identified in the key's interest set during a prior selection operation.
 *   This set is returned by the {@link #selectedKeys() selectedKeys} method.
 *   The selected-key set is always a subset of the key set. </p></li>
 *
 	原来敢兴趣现在不感兴趣的集合   (不在关注某些动作)
 *   <li><p> The <i>cancelled-key</i> set is the set of keys that have been
 *   cancelled but whose channels have not yet been deregistered.  This set is
 *   not directly accessible.  The cancelled-key set is always a subset of the
 *   key set. </p></li>
	
	所有的出初始化都是为空的
 * <p> All three sets are empty in a newly-created selector.
 * 
 * 我们可以通过SelectableChannel#register方法,注册一个channel 到时 一个Key被添加到上面的集合中 
 * <p> A key is added to a selector's key set as a side effect of registering a
 * channel via the channel's {@link SelectableChannel#register(Selector,int)
 * register} method.  
	Cancelled keys are removed from the key set during
 * selection operations.  The key set itself is not directly modifiable.
 *  
 *  selection operation 概念  : 
 * 	
 * <p> A key is added to its selector's cancelled-key set when it is cancelled,
 * whether by closing its channel or by invoking its {@link SelectionKey#cancel
 * cancel} method.  Cancelling a key will cause its channel to be deregistered
 * during the next selection operation, at which time the key will removed from
 * all of the selector's key sets.
 * 
 *	移除的两中的方法 , 
 * <a name="sks"></a><p> Keys are added to the selected-key set by selection
 * operations.  A key may be removed directly from the selected-key set by
 * invoking the set's {@link java.util.Set#remove(java.lang.Object) remove}
 * method or by invoking the {@link java.util.Iterator#remove() remove} method
 * of an {@link java.util.Iterator iterator} obtained from the
 * set.  Keys are never removed from the selected-key set in any other way;
 * they are not, in particular, removed as a side effect of selection
 * operations.  Keys may not be added directly to the selected-key set. </p>
 *
 *
 <h2>Selection</h2>
 * //重要的动作都是通过Selection 的动作来 执行的
 * <p> During each selection operation, keys may be added to and removed from a
 * selector's selected-key set and may be removed from its key and
 * cancelled-key sets.  Selection is performed by the {@link #select()}, {@link
 * #select(long)}, and {@link #selectNow()} methods, and involves three steps:
 * </p>
 *	三个步骤 
 * <ol>
 *		通道会被取消注册 
 *   <li><p> Each key in the cancelled-key set is removed from each key set of
 *   which it is a member, and its channel is deregistered.  This step leaves
 *   the cancelled-key set empty. </p></li>
 *		查询底层操作系统 
 *   <li><p> The underlying operating system is queried for an update as to the
 *   readiness of each remaining channel to perform any of the operations
 *   identified by its key's interest set as of the moment that the selection
 *   operation began.  For a channel that is ready for at least one such
 *   operation, one of the following two actions is performed: </p>
 *

NIO的核心概念 以及组件当中的关系 串联

NIO编程的示例 也就是一个线程处理多个请求

画个简单的脑图
在这里插入图片描述

代码 关键要看注释

** 服务器端 **

private static Map<String, SocketChannel> clientMap = new HashMap<>();

    public static void main(String[] args) throws Exception {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        serverSocketChannel.configureBlocking(false);

        ServerSocket serverSocket = serverSocketChannel.socket();

        serverSocket.bind(new InetSocketAddress(8899));
        //打开Selector
        Selector selector = Selector.open();
        //注册并关注 事件  关注连接事件 serverSocketChannel 注册到 selctor上面
        //关注的事件是连接
        //进行服务器的监听
        //进行注册了
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (true) {
            try {
                //一旦监听到  上面的 OP_ACCEPT 就触发select() 方法 返回关注的数量
                selector.select();
                // 获取所有的SelectionKey 也就是所有事件的集合  可以获取到Channel对象

                Set<SelectionKey> selectionKeys = selector.selectedKeys();

                selectionKeys.forEach(selectionKey -> {
                    final SocketChannel client;
                    // 判断不同的事件
                    try {
                        if (selectionKey.isAcceptable()) {
                            //是不是 客户端向服务器端 发送 (因为前面注册的事件是ServerSocketChannel这里 转型)
                            ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
                            //接收连接
                            client = server.accept();
                            client.configureBlocking(false);
                            //接收到REAd的请求  --> 到此为止 我们注册两个channel
                            client.register(selector, SelectionKey.OP_READ);
                            //建立好连接了 将客户端的信息 保存到服务器端里面
                            //定义一个Key
                            String key = "{" + UUID.randomUUID().toString() + "}";
                            clientMap.put(key, client);
                            //客户端注册完成
                        } else if (selectionKey.isReadable()) {
                            //判断是否数据来了 可读的也就是
                            //获取到与之关联的channel对象  之前注册了什么对象就取什么对象
                            client = (SocketChannel) selectionKey.channel();
                            ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                            int count = client.read(readBuffer);
                            if (count > 0) {
                                readBuffer.flip();
                                //转字符集
                                Charset charset = Charset.forName("utf-8");
                                String receivedMessage = String.valueOf(charset.decode(readBuffer).array());
                                System.out.println(client + ": " + receivedMessage);
                                //获取到消息  分发给其他的客户端
                                //根据连接发送对象对应的key
                                String sendKey = null;
                                for (Map.Entry<String, SocketChannel> channelEntry : clientMap.entrySet()) {
                                    if (client == channelEntry.getValue()) {
                                        sendKey = channelEntry.getKey();
                                    }
                                }
                                for (Map.Entry<String, SocketChannel> entry : clientMap.entrySet()) {
                                    //发送到其他的对象
                                    SocketChannel value = entry.getValue();
                                    ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
                                    writeBuffer.put((sendKey + ": " + receivedMessage).getBytes());
                                    writeBuffer.flip();
                                    //写到其他的连接里面
                                    value.write(writeBuffer);
                                }

                            }

                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    //返回客户端的
                });
                //每次处理完之后一定要清空
                selectionKeys.clear();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

** 客户端 **

 public static void main(String[] args) {
        try {
            //打开一个socketChannel对象
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);

            Selector selector = Selector.open();
            //监听 OP_CONNECT发起连接的事件
            socketChannel.register(selector, SelectionKey.OP_CONNECT);
            //去连接 连接建立
            socketChannel.connect(new InetSocketAddress("127.0.0.1", 8899));

            while (true) {
                //一旦返回事件  阻塞的方法 也就是说等待事件发生
                selector.select();
                Set<SelectionKey> keySet = selector.selectedKeys();

                for (SelectionKey selectionKey : keySet) {
                    //已经建立好连接
                    if (selectionKey.isConnectable()) {
                        //获取到SocketChannel
                        SocketChannel client = (SocketChannel) selectionKey.channel();
                        //判断连接是否是 正在连接
                        if (client.isConnectionPending()) {
                            //完成连接  连接建立
                            client.finishConnect();

                            ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
                            //获取到连接的时间

                            writeBuffer.put((LocalDateTime.now() + " 连接成功").getBytes());
                            //反转
                            writeBuffer.flip();
                            //写到里面
                            client.write(writeBuffer);
                            //建立双向的链接 已经与服务器端

                            ExecutorService executorService = Executors.newSingleThreadExecutor(Executors.defaultThreadFactory());
                            //等待输入的线程
                            executorService.submit(() -> {
                                //客户端不断的等待输入 所以是个死循环
                                while (true) {
                                    try {
                                        writeBuffer.clear();
                                        InputStreamReader input = new InputStreamReader(System.in);
                                        BufferedReader br = new BufferedReader(input);

                                        String sendMessage = br.readLine();

                                        writeBuffer.put(sendMessage.getBytes());
                                        writeBuffer.flip();
                                        //读出 信息 写回给服务端
                                        client.write(writeBuffer);
                                    } catch (Exception ex) {
                                        ex.printStackTrace();
                                    }
                                }
                            });
                        }
                        //注册 服务器端向客户端发送数据的这个时间 把这个对象 注册到Selector上面
                        client.register(selector, SelectionKey.OP_READ);
                    } else if (selectionKey.isReadable()) {
                        //进行读取
                        SocketChannel client = (SocketChannel) selectionKey.channel();

                        //读取到服务器端发过来的数据
                        ByteBuffer readBuffer = ByteBuffer.allocate(1024);

                        int count = client.read(readBuffer);

                        if (count > 0) {
                            //获取到服务器端发过来的数据
                            String receivedMessage = new String(readBuffer.array(), 0, count);
                            System.out.println(receivedMessage);
                        }
                    }
                }
                //清除处理完的SelectorKey
                keySet.clear();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    } 
总结

上面的那一套 就是NIO 编程的模型的基本步骤 也就是说写基本的原生 基本步骤都是这样的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值