Java入门系列-25-NIO(实现非阻塞网络通信)

简介:

还记得之前介绍NIO时对比传统IO的一大特点吗?就是NIO是非阻塞式的,这篇文章带大家来看一下非阻塞的网络操作。

补充:以数组的形式使用缓冲区

package testnio;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class TestBufferArray {

    public static void main(String[] args) throws IOException {
        RandomAccessFile raf1=new RandomAccessFile("D:/1.txt","rw");
        
        //1.获取通道
        FileChannel channel1=raf1.getChannel();
        
        //2.创建缓冲区数组
        ByteBuffer buf1=ByteBuffer.allocate(512);
        ByteBuffer buf2=ByteBuffer.allocate(512);
        ByteBuffer[] bufs= {buf1,buf2};
        //3.将数据读入缓冲区数组
        channel1.read(bufs);
        
        for (ByteBuffer byteBuffer : bufs) {
            byteBuffer.flip();
        }
        System.out.println(new String(bufs[0].array(),0,bufs[0].limit()));
        System.out.println("-----------");
        System.out.println(new String(bufs[1].array(),0,bufs[1].limit()));
        
        //写入缓冲区数组到通道中
        RandomAccessFile raf2=new RandomAccessFile("D:/2.txt","rw");
        FileChannel channel2=raf2.getChannel();
        channel2.write(bufs);
        
    }
}

使用NIO实现阻塞式网络通信

TCP协议的网络通信传统实现方式是通过套接字编程(Socket和ServerSocket),NIO实现TCP网络通信需要用到 Channel 接口的两个实现类:SocketChannel和ServerSocketChannel

使用NIO实现阻塞式网络通信

客户端

package com.jikedaquan.blockingnio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class Client {

    public static void main(String[] args) {

        SocketChannel sChannel=null;

        FileChannel inChannel=null;
        try {
            //1、获取通道
            sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 1666));
            //用于读取文件            
            inChannel = FileChannel.open(Paths.get("F:/a.jpg"), StandardOpenOption.READ);

            //2、分配指定大小的缓冲区
            ByteBuffer buf=ByteBuffer.allocate(1024);

            //3、读取本地文件,发送到服务器端

            while(inChannel.read(buf)!=-1) {
                buf.flip();
                sChannel.write(buf);
                buf.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //关闭通道
            if (inChannel!=null) {
                try {
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if(sChannel!=null) {
                try {
                    sChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

new InetSocketAddress("127.0.0.1", 1666) 用于向客户端套接字通道(SocketChannel)绑定要连接地址和端口

服务端

package com.jikedaquan.blockingnio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class Server {

    public static void main(String[] args) {

        ServerSocketChannel ssChannel=null;

        FileChannel outChannel=null;

        SocketChannel sChannel=null;
        try {
            //1、获取通道
            ssChannel = ServerSocketChannel.open();
            //用于保存文件的通道
            outChannel = FileChannel.open(Paths.get("F:/b.jpg"), StandardOpenOption.WRITE,StandardOpenOption.CREATE);

            //2、绑定要监听的端口号
            ssChannel.bind(new InetSocketAddress(1666));
            //3、获取客户端连接的通道
            sChannel = ssChannel.accept();

            //4、分配指定大小的缓冲区
            ByteBuffer buf=ByteBuffer.allocate(1024);

            //5、接收客户端的数据,并保存到本地
            while(sChannel.read(buf)!=-1) {
                buf.flip();
                outChannel.write(buf);
                buf.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //6、关闭通道
            if(sChannel!=null) {
                try {
                    sChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(outChannel!=null) {
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(ssChannel!=null) {
                try {
                    ssChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }    
            }        
        }
    }    
}

服务端套接字仅绑定要监听的端口即可 ssChannel.bind(new InetSocketAddress(1666));

上面的代码使用NIO实现的网络通信,可能有同学会问,没有看到阻塞效果啊,确实是阻塞式的看不到效果,因为客户端发送一次数据就结束了,服务端也是接收一次数据就结束了。那如果服务端接收完成数据后,再向客户端反馈呢?

能够看到阻塞效果的网络通信

客户端

package com.jikedaquan.blockingnio2;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class Client {

    public static void main(String[] args) {
        SocketChannel sChannel=null;
        FileChannel inChannel=null;
        try {
            sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 1666));
            inChannel = FileChannel.open(Paths.get("F:/a.jpg"), StandardOpenOption.READ);

            ByteBuffer buf=ByteBuffer.allocate(1024);

            while(inChannel.read(buf)!=-1) {
                buf.flip();
                sChannel.write(buf);
                buf.clear();
            }
            
            //sChannel.shutdownOutput();//去掉注释掉将不会阻塞

            //接收服务器端的反馈
            int len=0;
            while((len=sChannel.read(buf))!=-1) {
                buf.flip();
                System.out.println(new String(buf.array(),0,len));
                buf.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(inChannel!=null) {
                try {
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(sChannel!=null) {
                try {
                    sChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

服务端

package com.jikedaquan.blockingnio2;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class Server {

    public static void main(String[] args) {

        ServerSocketChannel ssChannel=null;
        FileChannel outChannel=null;
        SocketChannel sChannel=null;
        try {
            ssChannel = ServerSocketChannel.open();
            outChannel = FileChannel.open(Paths.get("F:/a.jpg"),StandardOpenOption.WRITE,StandardOpenOption.CREATE);

            ssChannel.bind(new InetSocketAddress(1666));
            sChannel = ssChannel.accept();
            ByteBuffer buf=ByteBuffer.allocate(1024);

            while(sChannel.read(buf)!=-1) {
                buf.flip();
                outChannel.write(buf);
                buf.clear();
            }

            //发送反馈给客户端
            buf.put("服务端接收数据成功".getBytes());
            buf.flip();
            sChannel.write(buf);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(sChannel!=null) {
                try {
                    sChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(outChannel!=null) {
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(ssChannel!=null) {
                try {
                    ssChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

服务端将向客户端发送两次数据

选择器(Selector)

想要实现非阻塞的IO,必须要先弄懂选择器。Selector 抽象类,可通过调用此类的 open 方法创建选择器,该方法将使用系统的默认选择器提供者创建新的选择器。

将通道设置为非阻塞之后,需要将通道注册到选择器中,注册的同时需要指定一个选择键的类型 (SelectionKey)。

选择键(SelectionKey)可以认为是一种标记,标记通道的类型和状态。

SelectionKey的静态字段:
OP_ACCEPT:用于套接字接受操作的操作集位
OP_CONNECT:用于套接字连接操作的操作集位
OP_READ:用于读取操作的操作集位
OP_WRITE:用于写入操作的操作集位

用于检测通道状态的方法:

方法名称 说明
isAcceptable() 测试此键的通道是否已准备好接受新的套接字连接
isConnectable() 测试此键的通道是否已完成其套接字连接操作
isReadable() 测试此键的通道是否已准备好进行读取
isWritable() 测试此键的通道是否已准备好进行写入

将通道注册到选择器:

ssChannel.register(selector, SelectionKey.OP_ACCEPT);

IO操作准备就绪的通道大于0,轮询选择器

while(selector.select()>0) {
    //获取选择键,根据不同的状态做不同的操作
}

实现非阻塞式TCP协议网络通信

非阻塞模式:channel.configureBlocking(false);

客户端

package com.jikedaquan.nonblockingnio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Scanner;

public class Client {

    public static void main(String[] args) {
        SocketChannel sChannel=null;
        try {
            //1、获取通道
            sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",1666));
            
            //2、切换非阻塞模式
            sChannel.configureBlocking(false);
            
            //3、分配指定大小的缓冲区
            ByteBuffer buf=ByteBuffer.allocate(1024);
            //4、发送数据给服务端
            Scanner scanner=new Scanner(System.in);
            //循环从控制台录入数据发送给服务端
            while(scanner.hasNext()) {
                
                String str=scanner.next();
                buf.put((new Date().toString()+"\n"+str).getBytes());
                buf.flip();
                sChannel.write(buf);
                buf.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //5、关闭通道
            if(sChannel!=null) {
                try {
                    sChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

服务端

package com.jikedaquan.nonblockingnio;

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

public class Server {

    public static void main(String[] args) throws IOException {
        
        //1、获取通道
        ServerSocketChannel ssChannel=ServerSocketChannel.open();
        //2、切换非阻塞模式
        ssChannel.configureBlocking(false);
        //3、绑定监听的端口号
        ssChannel.bind(new InetSocketAddress(1666));
        //4、获取选择器
        Selector selector=Selector.open();
        //5、将通道注册到选择器上,并指定“监听接收事件”
        ssChannel.register(selector, SelectionKey.OP_ACCEPT);
        
        //6、轮询式的获取选择器上已经 “准备就绪”的事件
        while(selector.select()>0) {
            //7、获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
            Iterator<SelectionKey> it=selector.selectedKeys().iterator();
            while(it.hasNext()) {
                //8、获取准备就绪的事件
                SelectionKey sk=it.next();
                //9、判断具体是什么事件准备就绪
                if(sk.isAcceptable()) {
                    //10、若“接收就绪”,获取客户端连接
                    SocketChannel sChannel=ssChannel.accept();
                    //11、切换非阻塞模式
                    sChannel.configureBlocking(false);
                    //12、将该通道注册到选择器上
                    sChannel.register(selector, SelectionKey.OP_READ);
                }else if(sk.isReadable()) {
                    //13、获取当前选择器上“读就绪”状态的通道
                    SocketChannel sChannel=(SocketChannel)sk.channel();
                    //14、读取数据
                    ByteBuffer buf=ByteBuffer.allocate(1024);
                    int len=0;
                    while((len=sChannel.read(buf))>0) {
                        buf.flip();
                        System.out.println(new String(buf.array(),0,len));
                        buf.clear();
                    }
                }
                //15、取消选择键 SelectionKey
                it.remove();
            }
            
        }
    }
}

服务端接收客户端的操作需要在判断 isAcceptable() 方法内将就绪的套接字通道以读操作注册到 选择器中

在判断 isReadable() 内从通道中获取数据

实现非阻塞式UDP协议网络通信

发送端

package com.jikedaquan.nonblockingnio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.Scanner;

public class TestDatagramSend {

    public static void main(String[] args) throws IOException {
        //获取通道
        DatagramChannel dChannel=DatagramChannel.open();
        //非阻塞
        dChannel.configureBlocking(false);
        ByteBuffer buf=ByteBuffer.allocate(1024);
        Scanner scanner=new Scanner(System.in);
        while(scanner.hasNext()) {
            String str=scanner.next();
            buf.put(str.getBytes());
            buf.flip();
            //发送数据到目标地址和端口
            dChannel.send(buf,new InetSocketAddress("127.0.0.1", 1666));
            buf.clear();
        }
        dChannel.close();
    }
}

接收端

package com.jikedaquan.nonblockingnio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;

public class TestDatagramReceive {
    public static void main(String[] args) throws IOException {
        //获取通道
        DatagramChannel dChannel=DatagramChannel.open();
        dChannel.configureBlocking(false);
        //绑定监听端口
        dChannel.bind(new InetSocketAddress(1666));
        //获取选择器
        Selector selector=Selector.open();
        //读操作注册通道
        dChannel.register(selector, SelectionKey.OP_READ);
        while(selector.select()>0) {
            Iterator<SelectionKey> it=selector.selectedKeys().iterator();
            //迭代选择键
            while(it.hasNext()) {
                SelectionKey sk=it.next();
                //通道可读
                if(sk.isReadable()) {
                    ByteBuffer buf=ByteBuffer.allocate(1024);
                    //接收数据存入缓冲区
                    dChannel.receive(buf);
                    buf.flip();
                    System.out.println(new String(buf.array(),0,buf.limit()));
                    buf.clear();
                }
            }
            
            it.remove();
        }
    }
}
相关文章
|
28天前
|
存储 监控 安全
单位网络监控软件:Java 技术驱动的高效网络监管体系构建
在数字化办公时代,构建基于Java技术的单位网络监控软件至关重要。该软件能精准监管单位网络活动,保障信息安全,提升工作效率。通过网络流量监测、访问控制及连接状态监控等模块,实现高效网络监管,确保网络稳定、安全、高效运行。
52 11
|
12天前
|
自然语言处理 Java
Java中的字符集编码入门-增补字符(转载)
本文探讨Java对Unicode的支持及其发展历程。文章详细解析了Unicode字符集的结构,包括基本多语言面(BMP)和增补字符的表示方法,以及UTF-16编码中surrogate pair的使用。同时介绍了代码点和代码单元的概念,并解释了UTF-8的编码规则及其兼容性。
82 60
|
21天前
|
JSON Dart 前端开发
鸿蒙应用开发从入门到入行 - 篇7:http网络请求
在本篇文章里,您将掌握鸿蒙开发工具DevEco的基本使用、ArkUI里的基础组件,并通过制作一个简单界面掌握使用
61 8
|
1月前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
67 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
1月前
|
机器学习/深度学习 资源调度 算法
图卷积网络入门:数学基础与架构设计
本文系统地阐述了图卷积网络的架构原理。通过简化数学表述并聚焦于矩阵运算的核心概念,详细解析了GCN的工作机制。
113 3
图卷积网络入门:数学基础与架构设计
|
1月前
|
Web App开发 网络协议 安全
网络编程懒人入门(十六):手把手教你使用网络编程抓包神器Wireshark
Wireshark是一款开源和跨平台的抓包工具。它通过调用操作系统底层的API,直接捕获网卡上的数据包,因此捕获的数据包详细、功能强大。但Wireshark本身稍显复杂,本文将以用抓包实例,手把手带你一步步用好Wireshark,并真正理解抓到的数据包的各项含义。
90 2
|
1月前
|
监控 架构师 Java
Java虚拟机调优的艺术:从入门到精通####
本文作为一篇深入浅出的技术指南,旨在为Java开发者揭示JVM调优的神秘面纱,通过剖析其背后的原理、分享实战经验与最佳实践,引领读者踏上从调优新手到高手的进阶之路。不同于传统的摘要概述,本文将以一场虚拟的对话形式,模拟一位经验丰富的架构师向初学者传授JVM调优的心法,激发学习兴趣,同时概括性地介绍文章将探讨的核心议题——性能监控、垃圾回收优化、内存管理及常见问题解决策略。 ####
|
1月前
|
机器学习/深度学习 人工智能 算法
深度学习入门:用Python构建你的第一个神经网络
在人工智能的海洋中,深度学习是那艘能够带你远航的船。本文将作为你的航标,引导你搭建第一个神经网络模型,让你领略深度学习的魅力。通过简单直观的语言和实例,我们将一起探索隐藏在数据背后的模式,体验从零开始创造智能系统的快感。准备好了吗?让我们启航吧!
84 3
|
2月前
|
数据采集 XML 存储
构建高效的Python网络爬虫:从入门到实践
本文旨在通过深入浅出的方式,引导读者从零开始构建一个高效的Python网络爬虫。我们将探索爬虫的基本原理、核心组件以及如何利用Python的强大库进行数据抓取和处理。文章不仅提供理论指导,还结合实战案例,让读者能够快速掌握爬虫技术,并应用于实际项目中。无论你是编程新手还是有一定基础的开发者,都能在这篇文章中找到有价值的内容。
|
2月前
|
Java 程序员 数据库连接
Java中的异常处理:从入门到精通
在Java编程的海洋中,异常处理是一艘不可或缺的救生艇。它不仅保护你的代码免受错误数据的侵袭,还能确保用户体验的平稳航行。本文将带你领略异常处理的风浪,让你学会如何在Java中捕捉、处理和预防异常,从而成为一名真正的Java航海家。