JAVA NIO创建服务端(含代码详解)

目录

整体代码

代码详解

注意事项

非阻塞模式:

Selector的正确使用:

适当的缓冲区管理:

事件处理的错误处理:

性能优化:

进程退出和资源释放:


 

当使用Java NIO(New I/O)创建一个服务端时,你需要使用ServerSocketChannelSelector两个关键类。

整体代码

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;
import java.util.Set;

public class NIOServer {
    private static final int BUFFER_SIZE = 1024;
    private static final int TIMEOUT = 3000;

    public static void main(String[] args) {
        // 创建一个Selector实例
        try (Selector selector = Selector.open();
             // 创建一个ServerSocketChannel实例
             ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {

            // 设置为非阻塞模式
            serverSocketChannel.configureBlocking(false);
            // 绑定端口
            serverSocketChannel.socket().bind(new InetSocketAddress(8080));
            // 注册到Selector,监听ACCEPT事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            while (true) {
                // 阻塞等待就绪的事件
                if (selector.select(TIMEOUT) == 0) {
                    System.out.print(".");
                    continue;
                }

                // 获取所有已就绪的事件的SelectionKey集合
                Set selectionKeys = selector.selectedKeys();
                Iterator iterator = selectionKeys.iterator();

                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    // 处理事件
                    handleKey(key);
                    iterator.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void handleKey(SelectionKey key) throws IOException {
        if (key.isAcceptable()) {
            // 处理ACCEPT事件
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
            SocketChannel socketChannel = serverSocketChannel.accept();
            socketChannel.configureBlocking(false);
            socketChannel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(BUFFER_SIZE));
        } else if (key.isReadable()) {
            // 处理READ事件
            SocketChannel socketChannel = (SocketChannel) key.channel();
            ByteBuffer buffer = (ByteBuffer) key.attachment();
            int bytesRead = socketChannel.read(buffer);
            if (bytesRead == -1) {
                // 客户端关闭连接
                socketChannel.close();
            } else if (bytesRead > 0) {
                // 处理接收到的数据
                buffer.flip();
                byte[] data = new byte[bytesRead];
                buffer.get(data);
                String message = new String(data);
                System.out.println("Received message: " + message);
                buffer.clear();
            }
        }
    }
}

代码详解

导入必要的类:

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;
import java.util.Set;

设置常量:

这里定义了一个缓冲区大小和一个超时时间,可以根据需要进行调整。

private static final int BUFFER_SIZE = 1024;
private static final int TIMEOUT = 3000;

创建Selector和ServerSocketChannel实例:

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

使用try-with-resources语句创建了SelectorServerSocketChannel的实例。这样可以确保在使用完后自动关闭它们。

配置ServerSocketChannel:

serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(8080));

ServerSocketChannel配置为非阻塞模式,并绑定到指定的端口(这里使用的是8080)

注册到Selector:

 

ServerSocketChannel注册到Selector上,以便监听ACCEPT事件。

serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

进入事件循环:

这是服务端的主要事件循环。在每次循环中,使用selector.select(TIMEOUT)阻塞等待就绪的事件。如果没有任何事件就绪,将继续下一次循环。

while (true) {
    if (selector.select(TIMEOUT) == 0) {
        System.out.print(".");
        continue;
    }
处理就绪的事件:

 

获取就绪的事件的SelectionKey集合,并遍历处理每个就绪的事件。

Set selectionKeys = selector.selectedKeys();
Iterator iterator = selectionKeys.iterator();

while (iterator.hasNext()) {
    SelectionKey key = iterator.next();
    handleKey(key);
    iterator.remove();
}

处理ACCEPT事件:

如果就绪的事件是ACCEPT事件,表示有新的客户端连接请求。通过ServerSocketChannel.accept()方法接受连接,并将SocketChannel注册到Selector上,以便监听READ事件。

if (key.isAcceptable()) {
    ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
    SocketChannel socketChannel = serverSocketChannel.accept();
    socketChannel.configureBlocking(false);
    socketChannel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(BUFFER_SIZE));
}

处理READ事件:

如果就绪的事件是READ事件,表示有数据可读取。使用SocketChannel.read()方法读取数据,并进行相应的处理。在示例中,将读取的数据存储到ByteBuffer中,并打印接收到的消息。

SocketChannelread方法在处理READ事件时返回的整数值有以下含义:

  • 大于0:表示读取到了数据。返回的值表示实际读取到的字节数。
  • 等于0:表示没有读取到任何数据。这通常发生在非阻塞模式下,当没有可读取的数据时,read方法立即返回0。
  • 小于0:表示通道已经到达了流的末尾(End of Stream)。这通常意味着客户端关闭了连接或发生了异常。返回的值依赖于底层操作系统和网络协议的实现,通常为-1。

在处理read事件时,需要根据返回值进行适当的处理。以下是对这些返回值的典型处理方式:

  • 大于0的情况:根据实际读取到的字节数,从ByteBuffer中获取数据并进行相应的处理。可以将读取到的数据转换为字符串、处理数据包等操作。
  • 等于0的情况:可以选择继续等待下一次READ事件,或进行其他操作。
  • 小于0的情况:通常表示连接已关闭或发生了错误。可以关闭相关的通道,释放资源,并进行适当的异常处理。
else if (key.isReadable()) {
    SocketChannel socketChannel = (SocketChannel) key.channel();
    ByteBuffer buffer = (ByteBuffer) key.attachment();
    int bytesRead = socketChannel.read(buffer);
    if (bytesRead == -1) {
        socketChannel.close();
    } else if (bytesRead > 0) {
        buffer.flip();
        byte[] data = new byte[bytesRead];
        buffer.get(data);
        String message = new String(data);
        System.out.println("Received message: " + message);
        buffer.clear();
    }
}

注意事项

在实现Java NIO服务端时,以下是一些需要注意的关键事项,并附带一些示例说明:

非阻塞模式:

  • 通过调用configureBlocking(false)ServerSocketChannelSocketChannel配置为非阻塞模式。
  • 示例代码:
serverSocketChannel.configureBlocking(false);
socketChannel.configureBlocking(false);

Selector的正确使用:

  • 注册通道时选择正确的事件类型,如OP_ACCEPTOP_READ等。
  • 示例代码:
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
socketChannel.register(selector, SelectionKey.OP_READ);

 

  • 在处理就绪事件时,检查事件类型并采取相应的操作。
  • 示例代码:
if (key.isAcceptable()) {
    // 处理ACCEPT事件
} else if (key.isReadable()) {
    // 处理READ事件
}

适当的缓冲区管理:

  • 合理分配和管理ByteBuffer缓冲区的大小,避免过小或过大的缓冲区。
  • 示例代码:
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);

事件处理的错误处理:

  • 在处理读写操作时,捕获可能的异常,并进行适当的错误处理。
  • 示例代码:
try {
    // 读取数据
} catch (IOException e) {
    // 处理读取异常
}

并发访问的线程安全性:

  • 在多线程环境下共享Selector和通道时,确保正确的线程同步和保护共享资源的访问。
  • 示例代码:
synchronized (selector) {
    // 同步访问共享资源
}

性能优化:

  • 使用适当的缓存策略,如使用DirectByteBuffer代替HeapByteBuffer,以减少数据复制和内存拷贝。
  • 示例代码:
ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);

进程退出和资源释放:

  • 在程序退出时,确保正确关闭通道、释放资源,避免资源泄漏。
  • 示例代码:
serverSocketChannel.close();
socketChannel.close();
selector.close();

 

 

你可能感兴趣的:(java,NIO,java,nio,开发语言)