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 编程的模型的基本步骤 也就是说写基本的原生 基本步骤都是这样的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java网络编程(第三版)中文版(不看后悔) JAVA Network Programming,Third Edition 出版社: O'Reilly 作者: (美)Elliotte Rusty Harold [作译者介绍] 译者: 朱涛江[同译者作品] 林剑 丛书名: O'Reilly Java系列 出版社:中国电力出版社 编辑推荐 “直到找到这本书之后,我才开始理解Java网络编程。” ——Bruce Eckel,《Thinking in Java》的作者 内容简介回到顶部↑《Java网络编程》第三版会为你介绍Java网络API的最新特性。本书讨论了JDK 1.4和1.5(现在已命名为J2SE 5)中所做的所有修改和增补。本书内容全面,涵盖了从网络基础知识到远程方法调用(RMI)等各方面的内容,书中章节涉及到TCP和UDP socket、服务器socket、URL和URI、组播以及特殊用途的API(如JavaMail)等等。本书展示了如何使用JSSE编写安全的网络应用程序,解释了如何使用NIO API编写超高性能的服务器。它还涵盖了Java对网络代理、Web cookie和URL缓存的支持。 《Java网络编程》不仅仅是对API的解释:它还展示了如何使用API。本书有很多示例,包含了几千行可以实际工作的代码(所有代码都可以在线获得),实现了功能完整的网络客户端和服务器。无论是希望编写特殊用途的web服务器、安全的在线订单接收程序、简单的组播代理还是电子邮件客户端,都会找到可供学习和借用的代码。 本书适合熟悉Java语言的读者的编程人员和计算机专业的学生阅读。 前言 1 第一章 Java网络编程因 13 网络程序的功能 14 安全性 27 等等!还有更多! 29 第二章 基本网络概念 30 网络 30 网络的分层 32 IP、TCP和UDP 37 Internet 40 客户/服务器模型 46 Internet标准 47 第三章 基本Web概念 56 URI 56 HTML、SGML和XML 63 HTTP 65 MIME媒体类型 69 服务器端程序 74 第四章 流 78 输出流 79 .输入流 83 过滤器流 87 阅读器和书写器 101 第五章 线程 116 运行线程 118 返回线程中的信息 122 同步 133 死锁 139 线程调度 140 线程池 153 第六章 查找Internet地址 159 InetAddress类 161 Inet4Address和Inet6Address 177 NetworkInterface类 178 一些有用的程序 181 第七章 URL和URI 192 URL类 192 URLEncoder和URLDecoder类 216 URI类 222 代理 230 通过GET方法与服务器端程序通信 233 访问受口令保护的网站 237 第八章 Swing中的HTML 245 组件上的HTML 245 JEditorPane 247 解析HTML 256 cookie 274 第九章 客户端Socket 283 socket基础 283 用Telnet研究协议 284 Socket类 286 Socket异常 312 Socket地址 313 示例 314 第十章 服务器socket 332 ServerSocket类 332 一些有用的服务器 349 第十一章 安全Socket 370 保护通信 371 创建安全客户端Socket 374 SSLSocket类的方法 378 创建安全的服务器Socket 383 SSLServerSocket类的方法 388 第十二章 非阻塞I/O 391 一个示例客户端 392 一个示例服务器 396 缓冲区 402 通道 421 就绪选择 427 第十三章 UDP数据报和Socket 431 UDP协议 431 DatagramPacket类 433 DatagramSocket类 442 一些有用的应用程序 456 DatagramChannel 469 第十四章 组播socket 478 何为组播socket? 479 使用组播socket 487 两个简单示例 495 第十五章 URLConnection 501 打开URLConnection 502 读取服务器的数据 503 读取首部 505 配置连接 514 配置客户端的请求HTTP首部 523 向服务器写入数据 525 内容处理器 530 Object方法 532 URLConnection的安全考虑 533 猜测MIME内容类型 533 HttpURLConnection 537 缓存 552 JarURLConnection 557 第十六章 协议处理器 560 何为协议处理器? 560 URLStreamHandler类 564 编写协议处理器 571 更多协议处理器示例和技术 576 URLStreamHandlerFactory接口 583 第十七章 内容处理器 588 何为内容处理器? 590 ContentHandler类 592 ContentHandlerFactory接口 603 FITS图片格式的内容处理器 606 第十八章 远程方法调用 617 何为远程方法调用? 617 实现 623 在运行时加载类 631 java.rmi包 634 java.rmi.registry包 640 java.rmi.server包 642 第十九章 JavaMail API 648 何为JavaMail API? 649 发送电子邮件 651 接收邮件 661 口令认证 666 地址 670 URLName类 674 Message类 677 Part接口 689 多部分消息和附件 699 MIME消息 703 文件夹 705

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值