首页 > 编程笔记 > Java笔记 阅读:6

Java NIO同步非阻塞网络编程(附带实例)

NIO 的全称是 non-blocking IO。从 JDK 1.4 开始,Java 提供了一系列改进的输入/输出的新特性,被统称为 NIO。

NIO 是同步非阻塞的。NIO 的相关类都被存储在 java.nio 包及其子包下,具有 3 个核心组件,分别是 Buffer(缓冲区)、Channel(通道)和 Selector(选择器)。

Buffer(缓冲区)

缓冲区本质上是一个可以写入数据的内存块(类似于数组),可以再次被读取。此内存块包含在 NIO Buffer 对象中,该对象提供了一组方法,可以更轻松地使用内存块。

使用 Buffer 进行数据写入和读取,需要进行如下 4 个步骤:
Buffer(缓冲区)具有 3 个重要属性:

Channel(通道)

Channel(通道)的 API 涵盖了 UDP/TCP 网络和文件 IO。和标准 IOStream 操作的区别如下:
SocketChannel 用于建立 TCP 网络连接,类似于 Socket。创建 SocketChannel 有两种方式:
ServerSocketChannel 可以监听新建的 TCP 连接通道,类似于 ServerSocket。

Selector(选择器)

Selector(选择器)是一个 JavaNIO 组件,可以检查一个或多个 NIO 通道,并确定哪些通道已准备好进行读取或者写入。实现单个线程可以管理多个通道,从而管理多个网络连接。

一个线程使用 Selector(选择器)监听多个 channel 的不同事件:4 个事件分别对应 SelectionKey 的 4 个常量。

SelectionKey 的 4 个常量如下:
下面使用 NIO 分别编码实现客户端和服务器端。客户端的代码如下:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

public class NIOClient {
    public static void main(String[] args) throws IOException {
        SocketChannel scl = SocketChannel.open();
        scl.configureBlocking(false);
        scl.connect(new InetSocketAddress("127.0.0.1", 8080));
        while (!scl.finishConnect()) {
            // 如果没有连接到服务器,就一直等待
            Thread.yield();
        }
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入:");
        // 发送内容
        String msg = scanner.nextLine();
        ByteBuffer bbw = ByteBuffer.wrap(msg.getBytes());
        while (bbw.hasRemaining()) {
            scl.write(bbw);
        }
        // 读取响应
        System.out.println("收到服务器端响应:");
        ByteBuffer bba = ByteBuffer.allocate(1024);
        while (scl.isOpen() && scl.read(bba) != -1) {
            // 长连接情况下,需要手动判断数据有没有读取结束
            // 此处做一个简单的判断,超过 0 字节就认为请求结束了
            if (bba.position() > 0)
                break;
        }
        bba.flip();
        byte[] b = new byte[bba.limit()];
        bba.get(b);
        System.out.println(new String(b));
        scanner.close();
        scl.close();
    }
}

服务器端的代码如下:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class NIOServer {
    public static void main(String[] args) throws IOException {
        // 创建网络服务器
        ServerSocketChannel ssc = ServerSocketChannel.open(); // 设置为非阻塞模式
        ssc.configureBlocking(false);
        ssc.socket().bind(new InetSocketAddress(8080)); // 绑定端口
        System.out.println("服务器已启动");
        while (true) {
            SocketChannel sca = ssc.accept(); // 获取新 tcp 连接通道
            //tcp 请求读取/响应
            if (sca != null) {
                System.out.println("获取新连接:" + sca.getRemoteAddress()); // 默认阻塞,设置为非阻塞
                sca.configureBlocking(false);
                ByteBuffer bba = ByteBuffer.allocate(1024);
                while (sca.isOpen() && sca.read(bba) != -1) { // 长连接情况下,需要手动判断有没有读取结束
                    //此处做一个简单判断,超过 0 字节就认为请求结束了
                    if (bba.position() > 0)
                        break;
                }
                if (bba.position() == 0)
                    continue; // 如果没数据了,则不继续之后的处理
                bba.flip();
                byte[] b = new byte[bba.limit()];
                bba.get(b);
                bba.clear();
                System.out.println("收到数据:" + new String(b) + " , 来自:" + sca.getRemoteAddress());
                //响应结果
                String str = "Hello";
                ByteBuffer bbw = ByteBuffer.wrap(str.getBytes());
                while (bbw.hasRemaining()) {
                    sca.write(bbw); // 非阻塞
                }
            }
        }
    }
}

先运行服务器端,再运行客户端。客户端输出到控制台上的内容如下:

请输入:
hello
收到服务器端响应:
Hello!


服务器端输出到控制台上的内容如下:

服务器已启动
获取新连接:/127.0.0.1:60364
收到数据:hello,来自:/127.0.0.1:60364

相关文章