Java NIO:在网络编程中的基本范式

在我的专栏Java NIO中,已经简单的完成了对Java NIO的基本学习,本篇将完成一个Server - Client的完整示例,演示一下学习成果,对Java NIO在网络编程中的应用做一个总结

服务端:

package com.leolee.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

/**
 * @ClassName NIOServerTest
 * @Description: 客户端服务端完整测试——模拟简单群聊,服务端单端口监听
 * @Author LeoLee
 * @Date 2020/9/23
 * @Version V1.0
 **/
public class NIOServerTest {

    //该map维护通道的唯一标识和通道对象本身
    private Map<String, SocketChannel> clientChannelMap = new HashMap<>();

    public void buildServer () throws IOException {

        //服务端初始化
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);//非阻塞模式(异步模式)
        ServerSocket serverSocket = serverSocketChannel.socket();
        serverSocket.bind(new InetSocketAddress("127.0.0.1", 8899));

        //构建selector
        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        //开始阻塞代码,进行请求监听
        while (true) {
            try {
                //开始监听selector中“感兴趣事件”的通道
                int number = selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                selectionKeys.forEach(selectionKey -> {
                    //对应客户端的Channel,服务端通过此channel和客户端联系
                    final SocketChannel clientSocketChannel;

                    //开始判断SelectionKey的事件类型
                    if (selectionKey.isAcceptable()) {//客户端请求服务端,建立连接请求
                        //获取当前事件发生的通道,ServerSocketChannel的作用就是帮助服务端和客户端建立连接
                        ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) selectionKey.channel();
                        try {
                            //建立服务端和客户端连接的操作
                            clientSocketChannel = serverSocketChannel.accept();
                            clientSocketChannel.configureBlocking(false);//非阻塞模式(异步模式)
                            //将与客户端建立好连接的channel注册到selector,并对“读”操作感兴趣
                            clientSocketChannel.register(selector, SelectionKey.OP_READ);

                            //连接建立后,给每一个对应客户端的channel分配唯一标识
                            clientChannelMap.put(UUID.randomUUID().toString(), clientSocketChannel);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    } else if (selectionKey.isReadable()) {//判断客户端是否发送消息给服务端(通道是否有数据可读)
                        clientSocketChannel = (SocketChannel) selectionKey.channel();

                        //开始读数据
                        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                        try {
                            int i = clientSocketChannel.read(byteBuffer);

                            if (i > 0) {//开始回写数据到客户端
                                byteBuffer.flip();
                                //字节转字符
                                Charset charset = Charset.forName("UTF-8");//设置字符集
                                String receiveMessage = String.valueOf(charset.decode(byteBuffer).array());
                                System.out.println("接收到客户端[" + clientSocketChannel + "]的消息:" + receiveMessage);

                                //开始向发送者之外的客户端推送消息
                                //获取发送客户端的唯一标识
                                String sendKey = null;
                                for (Map.Entry<String, SocketChannel> entry : clientChannelMap.entrySet()) {
                                    if (entry.getValue().equals(clientSocketChannel)) {
                                        sendKey = entry.getKey();
                                        break;
                                    }
                                }
                                //推送消息
                                for (Map.Entry<String, SocketChannel> entry : clientChannelMap.entrySet()) {
                                    SocketChannel socketChannel = entry.getValue();
                                    //为每一个客户端写入数据
                                    ByteBuffer writeByteBuffer = ByteBuffer.allocate(1024);
                                    writeByteBuffer.put((sendKey + ":" + receiveMessage).getBytes());//将发送的数据写入buffer
                                    writeByteBuffer.flip();
                                    socketChannel.write(writeByteBuffer);
                                }

                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
                //重点重点重点重点重点重点重点重点重点重点重点
                //处理完每一个selectionKey,就从selectionKeys集合将它清理掉,不然会报空指针异常
                selectionKeys.clear();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /*
     * 功能描述: <br> 运行该方法,即可使用[nc localhost 8899]命令,开启多个客户端进行消息发送测试
     * 〈〉
     * @Param: [args]
     * @Return: void
     * @Author: LeoLee
     * @Date: 2020/9/24 0:13
     */
    public static void main(String[] args) throws IOException {

        NIOServerTest scTest = new NIOServerTest();
        scTest.buildServer();
    }
}

客户端:

package com.leolee.nio;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.time.LocalDateTime;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @ClassName NIOClientTest
 * @Description: 客户端服务端完整测试——模拟简单群聊,本类为客户端,通过Scanner作为数据输入
 * @Author LeoLee
 * @Date 2020/9/24
 * @Version V1.0
 **/
public class NIOClientTest {

    public void buildClient () {

        try {
            //发起对服务端连接的建立
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);//非阻塞模式,异步模式
            Selector selector = Selector.open();
            socketChannel.register(selector, SelectionKey.OP_CONNECT);//注意:客户端这里注册的是 OP_CONNECT
            socketChannel.connect(new InetSocketAddress("127.0.0.1", 8899));

            while (true) {//同样是阻塞代码,始终接收服务端的消息数据
                selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                selectionKeys.forEach(selectionKey -> {
                    if (selectionKey.isConnectable()) {//判断是否是已经连接状态,是否已经和服务端建立好了连接
                        SocketChannel client = (SocketChannel) selectionKey.channel();
                        if (client.isConnectionPending()) {//判断连接是否在这个通道中“进行”
                            try {
                                client.finishConnect();//需要手动的去完成连接的建立[Finishes the process of connecting a socket channel.]
                                //发送消息通知服务端:连接已建立
                                ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
                                writeBuffer.put((LocalDateTime.now() + ":连接成功").getBytes());
                                writeBuffer.flip();
                                client.write(writeBuffer);

                                //使用异步来接收键盘的标准输入流
                                ExecutorService executorService = Executors.newSingleThreadScheduledExecutor(Executors.defaultThreadFactory());
                                executorService.submit(() -> {
                                    while (true) {
                                        writeBuffer.clear();
                                        InputStreamReader input = new InputStreamReader(System.in);
                                        BufferedReader bufferedReader = new BufferedReader(input);

                                        String sendMessage = bufferedReader.readLine();

                                        writeBuffer.put(sendMessage.getBytes());
                                        writeBuffer.flip();
                                        client.write(writeBuffer);
                                    }
                                });
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                            //重点重点重点重点重点重点重点重点重点重点
                            try {
                                //在建立好连接之后,注册通道为:对读操作“感兴趣”,即通道关注于读操作,以便之后接收服务端的消息
                                client.register(selector, SelectionKey.OP_READ);
                            } catch (ClosedChannelException e) {
                                e.printStackTrace();
                            }
                        }
                    } else if (selectionKey.isReadable()) {//判断通道是否可读
                        SocketChannel client = (SocketChannel) selectionKey.channel();
                        //开始读取
                        ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                        try {
                            int read = client.read(readBuffer);
                            if (read > 0) {
                                String receiveMessage = new String(readBuffer.array(), 0 , read);
                                System.out.println(receiveMessage);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
                //重点重点重点重点重点重点重点重点重点重点
                //处理完每一个selectionKey,就从selectionKeys集合将它清理掉,不然会报空指针异常
                selectionKeys.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {

        NIOClientTest test = new NIOClientTest();
        test.buildClient();
    }
}

客户端同样可以使用 nc 或者 telnet 命令来代替

需要代码的来这里拿嗷:demo项目地址

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值