使用NIO搭建一个聊天室

搭建一个一台服务端多台客户端连接的聊天室

首先梳理一下核心点:

      服务端有一个本地Map,记录了所有连接的信息,以达到给所有人发送的效果

编码时的公共流程:

1.先打开各自的channel和selector,然后channel注册到selector上,注册事件C:CONNECT;S:ACCEPT

2.然后进行一个死循环,第一行是selector.select()该事件会阻塞自己知道感兴趣的事件发生。

3.事件发生后进行selector.selectedKeys(),返回一个Set<SelectionKey>,因为可能同时发生多个感兴趣的事件。

4.进行各自的操作

5.在操作完之后需要清空调用clear()对set集合清空。

客户端:

isConnectable()与isReadable()是处于同一级的,分别监听连接和读取

连接:isisConnectable->isConnectionPending->finishConnect才算正式建立好连接;再把建立连接成功的信息写到通道里.write(buffer);在开启一个线程一直监听本地键盘输入;最后把读事件注册到selector上

读取时:从管道中.read(buffer)即可读取到buffer中

服务端:

例子监听了Accept和Read事件:

当有连接的时候:吧该管道感兴趣的时间设为读并放进本地map中

当管道有数据可读的时候:先从管道读数据,然后遍历map向所有连接端写数据

源码:

server.java

package com.rikka.nio;

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.*;

/**
 * @Author Yan1less
 * @Date 2019/4/21 12:55
 * @Description 实现分发给所有客户端
 **/
public class NioServer {

    private static Map<String,SocketChannel> clientMap = new HashMap<>();

    //服务器端只有一个线程
    public static void main(String[] args) throws Exception{
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        ServerSocket serverSocket = serverSocketChannel.socket();
        serverSocket.bind(new InetSocketAddress(8899));


        Selector selector = Selector.open();
        //把channel注册到selector上
        //ServerSocketChannel关注连接事件
        serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);

        while (true){
            try {
                //这个方法会一直阻塞,直到发生了其感兴趣的事件
                selector.select();

                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                selectionKeys.forEach(selectionKey -> {
                    //SocketChannel关注读取事件
                    final SocketChannel client;
                    try{
                        if(selectionKey.isAcceptable()){
                            //这里为什么能强行向下转型呢?是因为上面只把serverSocketChannel注册到了OP_ACCEPT事件上
                            ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
                            //到这一步ServerSocketChannel的任务完成了,我们接下来只需要从client进行操作
                            client = server.accept();
                            client.configureBlocking(false);
                            client.register(selector,SelectionKey.OP_READ);

                            String key = "【"+UUID.randomUUID().toString() +"】";
                            clientMap.put(key,client);
                        }else if(selectionKey.isReadable()){
                            client = (SocketChannel)selectionKey.channel();
                            //一个不够用咋整
                            ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                            int count = client.read(readBuffer);
                            if(count>0){
                                readBuffer.flip();

                                Charset charset = Charset.forName("utf-8");
                                String receivedMessage = String.valueOf(charset.decode(readBuffer).array());

                                System.out.println(client + ": " + receivedMessage);

                                String senderKey = null;
                                for(Map.Entry<String,SocketChannel> entry : clientMap.entrySet()){
                                    if(client == entry.getValue()){
                                        senderKey = entry.getKey();
                                        break;
                                    }
                                }

                                for(Map.Entry<String,SocketChannel> entry : clientMap.entrySet()){
                                    SocketChannel value = entry.getValue();
                                    ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
                                    writeBuffer.put((senderKey+" : "+receivedMessage).getBytes());
                                    //把数据放到buffer里称为读
                                    writeBuffer.flip();
                                    value.write(writeBuffer);
                                }
                            }

                        }
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                });
                selectionKeys.clear();
            }catch (Exception ex){
                ex.printStackTrace();
            }
        }

    }
}

client.java

package com.rikka.nio;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.Socket;
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;

/**
 * @Author Yan1less
 * @Date 2019/4/21 13:49
 * @Description TODO
 **/
public class NioClient {
    public static void main(String[] args) throws Exception {
        try{
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);

            Selector selector = Selector.open();
            socketChannel.register(selector,SelectionKey.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();
                                ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
                                writeBuffer.put((LocalDateTime.now()+"连接成功").getBytes());
                                writeBuffer.flip();
                                client.write(writeBuffer);
                                ExecutorService executorService = Executors.newSingleThreadExecutor(Executors.defaultThreadFactory());
                                executorService.submit(()->{
                                   while(true){
                                       try{
                                           writeBuffer.clear();
                                           InputStreamReader input = new InputStreamReader(System.in);
                                           BufferedReader br = new BufferedReader(input);
                                           String sendMessage = br.readLine();
                                           writeBuffer.put(sendMessage.getBytes());
                                           writeBuffer.flip();
                                           client.write(writeBuffer);
                                       }catch (Exception ex){
                                           ex.printStackTrace();
                                       }
                                   }
                                });
                            } 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 receivedMesage = new String(readBuffer.array(),0,read);
                                System.out.println(receivedMesage);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }


                    }

                });
                selectionKeys.clear();
            }


        }catch (Exception ex){

        }

    }
}

 

Tips:

1.每次新建一个channel的时候都要设置其configureBlocking(false)为非阻塞

2.无论是C或是S通过SocketChannel.open()方法获得的channel通道都只是为了链接,真正的数据传输通道都是通过selectionKey.channel()获得的(对于server来说需要在进行accept()方法来获得实际操作channel,注:ServerSocketChannel的作用也是为了获得SocketChannel),该方法需要强制类型转换,转换成SocketChannel

3.channel.read(buffer)是把buffer读取出来

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值