Java IO网络模型如何实现

发布时间:2023-03-16 13:58:52 作者:iii
来源:亿速云 阅读:98

本篇内容主要讲解“Java IO网络模型如何实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java IO网络模型如何实现”吧!

在开始本篇文章内容之前,有一个简单的关于Socket的知识需要说明:在进行网络通信的时候,需要一对Socket,一个运行于客户端,一个运行于服务端,同时服务端还会有一个服务端Socket,用于监听客户端的连接。下图进行一个简单示意。

Java IO网络模型如何实现

那么整个通信流程如下所示。

需要知道的就是,在客户端与服务端通信的过程中,出现了三种socket,分别是。

(注:上述中的socket,可以被称为套接字,也可以被称为文件描述符。)

正文

一. BIO

BIO,即同步阻塞IO模型。用户进程调用read时发起IO操作,此时用户进程由用户态转换到内核态,只有在内核态中将IO操作执行完后,才会从内核态切换回用户态,这期间用户进程会一直阻塞。

BIO示意图如下。

Java IO网络模型如何实现

简单的BIOJava编程实现如下。

服务端实现

public class BioServer {

    public static void main(String[] args) throws IOException {
        // 创建listen-socket
        ServerSocket listenSocket = new ServerSocket(8080);
        // 进入监听状态,是一个阻塞状态
        // 有客户端连接时从监听状态返回
        // 并创建代表这个客户端的client-socket
        Socket clientSocket = listenSocket.accept();
        // 获取client-socket输入流
        BufferedReader bufferedReader = new BufferedReader(
                new InputStreamReader(clientSocket.getInputStream()));
        // 读取客户端发送的数据
        // 如果数据没准备好,会进入阻塞状态
        String data = bufferedReader.readLine();
        System.out.println(data);

        // 获取client-socket输出流
        BufferedWriter bufferedWriter = new BufferedWriter(
                new OutputStreamWriter(clientSocket.getOutputStream()));
        // 服务端向客户端发送数据
        bufferedWriter.write("来自服务端的返回数据\n");
        // 刷新流
        bufferedWriter.flush();
    }

}

客户端实现

public class BioClient {

    public static final String SERVER_IP = "127.0.0.1";
    public static final int SERVER_PORT = 8080;

    public static void main(String[] args) throws IOException {
        // 客户端创建connect-socket
        Socket connectSocket = new Socket(SERVER_IP, SERVER_PORT);
        // 获取connect-socket输出流
        BufferedWriter bufferedWriter = new BufferedWriter(
                new OutputStreamWriter(connectSocket.getOutputStream()));
        // 客户端向服务端发送数据
        bufferedWriter.write("来自客户端的请求数据\n");
        // 刷新流
        bufferedWriter.flush();

        // 获取connect-socket输入流
        BufferedReader bufferedReader = new BufferedReader(
                new InputStreamReader(connectSocket.getInputStream()));
        // 读取服务端发送的数据
        String returnData = bufferedReader.readLine();
        System.out.println(returnData);
    }

}

BIO的问题就在于服务端在accept时是阻塞的,并且在主线程中,一次只能accept一个SocketacceptSocket后,读取客户端数据时又是阻塞的。

二. Non Blocking IO

Non Blocking IO,即同步非阻塞IO。是用户进程调用read时,用户进程由用户态转换到内核态后,此时如果没有系统资源数据能够被读取到内核缓冲区中,返回read失败,并从内核态切换回用户态。也就是用户进程发起IO操作后会立即得到一个操作结果。

Non Blocking IO示意图如下所示。

Java IO网络模型如何实现

简单的Non Blocking IOJava编程实现如下。

public class NonbioServer {

    public static final List<SocketChannel> clientSocketChannels = new ArrayList<>();

    public static void main(String[] args) throws Exception {
        // 客户端创建listen-socket管道
        // 管道支持非阻塞模式和同时读写
        ServerSocketChannel listenSocketChannel = ServerSocketChannel.open();
        // 设置为非阻塞模式
        listenSocketChannel.configureBlocking(false);
        // 绑定监听的端口号
        listenSocketChannel.socket().bind(new InetSocketAddress(8080));
        // 在子线程中遍历clientSocketChannels并读取客户端数据
        handleSocketChannels();

        while (true) {
            // 非阻塞方式监听客户端连接
            // 如果无客户端连接则返回空
            // 有客户端连接则创建代表这个客户端的client-socket管道
            SocketChannel clientSocketChannel = listenSocketChannel.accept();
            if (clientSocketChannel != null) {
                // 设置为非阻塞模式
                clientSocketChannel.configureBlocking(false);
                // 添加到clientSocketChannels中
                // 用于子线程遍历并读取客户端数据
                clientSocketChannels.add(clientSocketChannel);
            } else {
                LockSupport.parkNanos(1000 * 1000 * 1000);
            }
        }
    }

    public static void handleSocketChannels() {
        new Thread(() -> {
            while (true) {
                // 遍历每一个client-socket管道
                Iterator<SocketChannel> iterator = clientSocketChannels.iterator();
                while (iterator.hasNext()) {
                    SocketChannel clientSocketChannel = iterator.next();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    int read = 0;
                    try {
                        // 将客户端发送的数据读取到ByteBuffer中
                        // 这一步的操作也是非阻塞的
                        read = clientSocketChannel.read(byteBuffer);
                    } catch (IOException e) {
                        // 移除发生异常的client-socket管道
                        iterator.remove();
                        e.printStackTrace();
                    }
                    if (read == 0) {
                        System.out.println("客户端数据未就绪");
                    } else {
                        System.out.println("客户端数据为:" + new String(byteBuffer.array()));
                    }
                }
                LockSupport.parkNanos(1000 * 1000 * 1000);
            }
        }).start();
    }

}

上述是Non Blocking IO的一个简单服务端的实现,相较于BIO,服务端在accept时是非阻塞的,在读取客户端数据时也是非阻塞的,但是还是存在如下问题。

三. IO多路复用

在上述的BIONon Blocking IO中,一次系统调用,只会获取一个IO的状态,而如果采取IO多路复用机制,则可以一次系统调用获取多个IO的状态。

也就是获取多个IO的状态可以复用一次系统调用。

最简单的IO多路复用方式是基于select模型实现,步骤如下。

换言之,IO多路复用中,只需要一次系统调用,IO多路复用器就可以告诉用户进程,哪些IO已经准备就绪可以进行操作了,而如果不采用IO多路复用,则需要用户进程自己遍历每个IO并调用accept() 或者read() 方法去判断,且一次accept() 或者read() 方法调用只能判断一个IO

四. NIO

NIO,即New IO。关于NIO,有如下三大组件。

NIO的代码实现如下所示。

服务端实现

public class NioServer {

    private static Selector selector;

    public static void main(String[] args) {

        try {
            // 开启并得到多路复用器
            selector = Selector.open();
            // 服务端创建listen-socket管道
            ServerSocketChannel listenSocketChannel = ServerSocketChannel.open();
            // 设置为非阻塞模式
            listenSocketChannel.configureBlocking(false);
            // 为管道绑定端口
            listenSocketChannel.socket().bind(new InetSocketAddress(8080));
            // 将listen-socket管道注册到多路复用器上,并指定监听ACCEPT事件
            listenSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            while (true) {
                // 获取发生的事件,这个操作是阻塞的
                selector.select();
                // 拿到有事件发生的SelectionKey集合
                // SelectionKey表示管道与多路复用器的绑定关系
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                // 遍历每个发生的事件,然后判断事件类型
                // 根据事件类型,进行不同的处理
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();
                    if (selectionKey.isAcceptable()) {
                        // 处理客户端连接事件
                        handlerAccept(selectionKey);
                    } else if (selectionKey.isReadable()) {
                        // 处理客户端数据可读事件
                        handlerRead(selectionKey);
                    }
                }
                LockSupport.parkNanos(1000 * 1000 * 1000);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void handlerAccept(SelectionKey selectionKey) {
        // 从事件中获取到listen-socket管道
        ServerSocketChannel listenSocketChannel = (ServerSocketChannel) selectionKey.channel();
        try {
            // 为连接的客户端创建client-socket管道
            SocketChannel clientSocketChannel = listenSocketChannel.accept();
            // 设置为非阻塞模式
            clientSocketChannel.configureBlocking(false);
            // 将client-socket管道注册到多路复用器上,并指定监听READ事件
            clientSocketChannel.register(selector, SelectionKey.OP_READ);
            // 给客户端发送数据
            clientSocketChannel.write(ByteBuffer.wrap("连接已建立\n".getBytes()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void handlerRead(SelectionKey selectionKey) {
        // 从事件中获取到client-socket管道
        SocketChannel clientSocketChannel = (SocketChannel) selectionKey.channel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        try {
            // 读取客户端数据
            int read = clientSocketChannel.read(byteBuffer);
            if (read <= 0) {
                // 关闭管道
                clientSocketChannel.close();
                // 从多路复用器移除绑定关系
                selectionKey.cancel();
            } else {
                System.out.println(new String(byteBuffer.array()));
            }
        } catch (IOException e1) {
            try {
                // 关闭管道
                clientSocketChannel.close();
            } catch (IOException e2) {
                e2.printStackTrace();
            }
            // 从多路复用器移除绑定关系
            selectionKey.cancel();
            e1.printStackTrace();
        }
    }

}

客户端实现

public class NioClient {

    private static Selector selector;

    public static final String SERVER_IP = "127.0.0.1";
    public static final int SERVER_PORT = 8080;

    public static void main(String[] args) {
        try {
            // 开启并得到多路复用器
            selector = Selector.open();
            // 创建connect-socket管道
            SocketChannel connectSocketChannel = SocketChannel.open();
            // 设置为非阻塞模式
            connectSocketChannel.configureBlocking(false);
            // 设置服务端IP和端口
            connectSocketChannel.connect(new InetSocketAddress(SERVER_IP, SERVER_PORT));
            // 将connect-socket管道注册到多路复用器上,并指定监听CONNECT事件
            connectSocketChannel.register(selector, SelectionKey.OP_CONNECT);

            while (true) {
                // 获取发生的事件,这个操作是阻塞的
                selector.select();
                // 拿到有事件发生的SelectionKey集合
                // SelectionKey表示管道与多路复用器的绑定关系
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                // 遍历每个发生的事件,然后判断事件类型
                // 根据事件类型,进行不同的处理
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();
                    if (selectionKey.isConnectable()) {
                        // 处理连接建立事件
                        handlerConnect(selectionKey);
                    } else if (selectionKey.isReadable()) {
                        // 处理服务端数据可读事件
                        handlerRead(selectionKey);
                    }
                }
                LockSupport.parkNanos(1000 * 1000 * 1000);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void handlerConnect(SelectionKey selectionKey) throws IOException {
        // 拿到connect-socket管道
        SocketChannel connectSocketChannel = (SocketChannel) selectionKey.channel();
        if (connectSocketChannel.isConnectionPending()) {
            connectSocketChannel.finishConnect();
        }
        // 设置为非阻塞模式
        connectSocketChannel.configureBlocking(false);
        // 将connect-socket管道注册到多路复用器上,并指定监听READ事件
        connectSocketChannel.register(selector, SelectionKey.OP_READ);
        // 向服务端发送数据
        connectSocketChannel.write(ByteBuffer.wrap("客户端发送的数据\n".getBytes()));
    }

    private static void handlerRead(SelectionKey selectionKey) {
        // 拿到connect-socket管道
        SocketChannel connectSocketChannel = (SocketChannel) selectionKey.channel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        try {
            // 读取服务端数据
            int read = connectSocketChannel.read(byteBuffer);
            if (read <= 0) {
                // 关闭管道
                connectSocketChannel.close();
                // 从多路复用器移除绑定关系
                selectionKey.cancel();
            } else {
                System.out.println(new String(byteBuffer.array()));
            }
        } catch (IOException e1) {
            try {
                // 关闭管道
                connectSocketChannel.close();
            } catch (IOException e2) {
                e2.printStackTrace();
            }
            // 从多路复用器移除绑定关系
            selectionKey.cancel();
            e1.printStackTrace();
        }
    }

}

到此,相信大家对“Java IO网络模型如何实现”有了更深的了解,不妨来实际操作一番吧!这里是亿速云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

推荐阅读:
  1. Java怎么实现基于数字货币的动态平衡策略
  2. 怎么使用TronTool.Java

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

java io

上一篇:如何安装nvm并使用nvm安装nodejs及配置环境变量

下一篇:vue-devtools如何安装与使用

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》