• NIO 多人聊天室


    一前言

    在家休息没事,敲敲代码,用NIO写个简易的仿真天室。下面直接讲聊天室设计和编码。对NIO不了解的朋友,推荐一个博客,里面写的很棒:

    https://javadoop.com/     里面有NIO的部分

    二设计

    1.进入的时候,提示输入聊天昵称,重复的话,重新输入,成功后进到聊天室。

    2.成功进到聊天室,广播通知,XXX进到了聊天室;离开聊天室,XXX离开了聊天室。

    3.@XXX 给XXX发消息,只有双方可以看到。

    4.服务端收到的内容会转发给其他客户端。

    三代码

    目前版本(设计工程3,4有待改进)

    服务端:

      1 package com.lee.demo.nio;
      2 
      3 import java.io.IOException;
      4 import java.net.InetSocketAddress;
      5 import java.nio.ByteBuffer;
      6 import java.nio.channels.Channel;
      7 import java.nio.channels.SelectionKey;
      8 import java.nio.channels.Selector;
      9 import java.nio.channels.ServerSocketChannel;
     10 import java.nio.channels.SocketChannel;
     11 import java.nio.charset.Charset;
     12 import java.util.HashSet;
     13 import java.util.Iterator;
     14 import java.util.Set;
     15 
     16 public class ChatServer {
     17 
     18     private Selector  selector = null;
     19     private Charset charset = Charset.forName("UTF-8");
     20     public static final int PORT = 8765; 
     21     private static String USER_CONTENT_SPILIT = "#";
     22     private static HashSet<String> users = new HashSet<String>();
     23     
     24     public void init() throws IOException {
     25         selector = Selector.open();
     26         ServerSocketChannel server = ServerSocketChannel.open();
     27         server.socket().bind(new InetSocketAddress(PORT));
     28         // 将其注册到 Selector 中,监听 OP_ACCEPT 事件
     29         server.configureBlocking(false);
     30         server.register(selector, SelectionKey.OP_ACCEPT);
     31 
     32         while (true) {
     33             int readyChannels = selector.select();
     34             if (readyChannels == 0) {
     35                 continue;
     36             }
     37             Set<SelectionKey> readyKeys = selector.selectedKeys();
     38             // 遍历
     39             Iterator<SelectionKey> iterator = readyKeys.iterator();
     40             while (iterator.hasNext()) {
     41                 SelectionKey key = iterator.next();
     42                 iterator.remove();
     43                 dealWithKey(server, key);
     44                 
     45             }
     46         }
     47         
     48     }
     49     
     50     private void dealWithKey(ServerSocketChannel server, SelectionKey key) throws IOException {
     51         String content = null;
     52         if (key.isAcceptable()) {
     53             // 有已经接受的新的到服务端的连接
     54             SocketChannel socketChannel = server.accept();
     55 
     56             // 有新的连接并不代表这个通道就有数据,
     57             // 这里将这个新的 SocketChannel 注册到 Selector,监听 OP_READ 事件,等待数据
     58             socketChannel.configureBlocking(false);
     59             socketChannel.register(selector, SelectionKey.OP_READ);
     60             //将此对应的channel设置为准备接受其他客户端请求
     61             key.interestOps(SelectionKey.OP_ACCEPT);
     62             System.out.println("Server is listening from client " + socketChannel.getRemoteAddress());
     63             socketChannel.write(charset.encode("Please input your name: "));
     64             
     65         } else if (key.isReadable()) {
     66             // 有数据可读
     67             // 上面一个 if 分支中注册了监听 OP_READ 事件的 SocketChannel
     68             SocketChannel socketChannel = (SocketChannel) key.channel();
     69             ByteBuffer readBuffer = ByteBuffer.allocate(1024);
     70             int num = socketChannel.read(readBuffer);
     71             if (num > 0) {
     72                 content = new String(readBuffer.array()).trim();
     73                 // 处理进来的数据...
     74                 System.out.println("Server is listening from client " + 
     75                                     socketChannel.getRemoteAddress() + 
     76                                     " data received is: " + 
     77                                     content);
     78 /*                ByteBuffer buffer = ByteBuffer.wrap("返回给客户端的数据...".getBytes());
     79                 socketChannel.write(buffer);*/
     80                 //将此对应的channel设置为准备下一次接受数据
     81                 key.interestOps(SelectionKey.OP_READ);
     82                 
     83                 String[] arrayContent = content.split(USER_CONTENT_SPILIT);
     84                 //注册用户
     85                 if(arrayContent != null && arrayContent.length ==1) {
     86                     String name = arrayContent[0];
     87                     if(users.contains(name)) {
     88                         socketChannel.write(charset.encode("system message: user exist, please change a name"));
     89                         
     90                     } else {
     91                         users.add(name);
     92                         int number = OnlineNum(selector);
     93                         String message = "welcome " + name + " to chat room! Online numbers:" + number;
     94                         broadCast(selector, null, message);
     95                     }
     96                 } 
     97                 //注册完了,发送消息
     98                 else if(arrayContent != null && arrayContent.length >1){
     99                     String name = arrayContent[0];
    100                     String message = content.substring(name.length() + USER_CONTENT_SPILIT.length());
    101                     message = name + " say " + message;
    102                     if(users.contains(name)) {
    103                         //不回发给发送此内容的客户端
    104                         broadCast(selector, socketChannel, message);
    105                     }
    106                 }
    107             } else if (num == -1) {
    108                 // -1 代表连接已经关闭
    109                 socketChannel.close();
    110             }
    111         }
    112     }
    113     
    114     private void broadCast(Selector selector, SocketChannel except, String content) throws IOException {
    115         //广播数据到所有的SocketChannel中
    116         for(SelectionKey key : selector.keys())
    117         {
    118             Channel targetchannel = key.channel();
    119             //如果except不为空,不回发给发送此内容的客户端
    120             if(targetchannel instanceof SocketChannel && targetchannel!=except)
    121             {
    122                 SocketChannel dest = (SocketChannel)targetchannel;
    123                 dest.write(charset.encode(content));
    124             }
    125         }
    126     }
    127     
    128     public static int OnlineNum(Selector selector) {
    129         int res = 0;
    130         for(SelectionKey key : selector.keys())
    131         {
    132             Channel targetchannel = key.channel();
    133             if(targetchannel instanceof SocketChannel)
    134                 res++;
    135         }
    136         return res;
    137     }
    138     
    139     public static void main(String[] args) throws IOException {
    140         new ChatServer().init();
    141     }
    142 
    143 }

    客户端:

      1 package com.lee.demo.nio;
      2 
      3 import java.io.IOException;
      4 import java.net.InetSocketAddress;
      5 import java.nio.ByteBuffer;
      6 import java.nio.channels.SelectionKey;
      7 import java.nio.channels.Selector;
      8 import java.nio.channels.SocketChannel;
      9 import java.nio.charset.Charset;
     10 import java.util.Iterator;
     11 import java.util.Scanner;
     12 import java.util.Set;
     13 
     14 public abstract class ChatClient {
     15 
     16     private Selector selector = null;
     17     public static final int port = 8765;
     18     private Charset charset = Charset.forName("UTF-8");
     19     private SocketChannel sc = null;
     20     private String name = "";
     21     private static String USER_EXIST = "system message: user exist, please change a name";
     22     private static String USER_CONTENT_SPILIT = "#";
     23     
     24     public void init() throws IOException
     25     {
     26         selector = Selector.open();
     27         //连接远程主机的IP和端口
     28         sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", port));
     29         sc.configureBlocking(false);
     30         sc.register(selector, SelectionKey.OP_READ);
     31         //开辟一个新线程来读取服务器端的数据
     32         new Thread(new ClientThread()).start();
     33         //在主线程中 从键盘读取数据输入到服务器端
     34         Scanner scan = new Scanner(System.in);
     35         try {
     36             while (scan.hasNextLine()) {
     37                 String line = scan.nextLine();
     38                 if ("".equals(line))
     39                     continue; // 不允许发空消息
     40                 if ("".equals(name)) {
     41                     name = line;
     42                     line = name + USER_CONTENT_SPILIT;
     43                 } else {
     44                     line = name + USER_CONTENT_SPILIT + line;
     45                 }
     46                 sc.write(charset.encode(line));// sc既能写也能读,这边是写
     47             }
     48         } finally {
     49             scan.close();
     50         }
     51         
     52         
     53     }
     54     private class ClientThread implements Runnable
     55     {
     56         public void run()
     57         {
     58             try
     59             {
     60                 while(true) {
     61                     int readyChannels = selector.select();
     62                     if(readyChannels == 0) continue; 
     63                     //可以通过这个方法,知道可用通道的集合
     64                     Set<SelectionKey> selectedKeys = selector.selectedKeys();  
     65                     Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
     66                     while(keyIterator.hasNext()) {
     67                          SelectionKey sk = (SelectionKey) keyIterator.next();
     68                          keyIterator.remove();
     69                          dealWithSelectionKey(sk);
     70                     }
     71                 }
     72             }
     73             catch (IOException io)
     74             {}
     75         }
     76 
     77         private void dealWithSelectionKey(SelectionKey sk) throws IOException {
     78             if(sk.isReadable())
     79             {
     80                 //使用 NIO 读取 Channel中的数据,这个和全局变量sc是一样的,因为只注册了一个SocketChannel
     81                 //sc既能写也能读,这边是读
     82                 SocketChannel sc = (SocketChannel)sk.channel();
     83                 
     84                 ByteBuffer buff = ByteBuffer.allocate(1024);
     85                 String content = "";
     86                 while(sc.read(buff) > 0)
     87                 {
     88                     buff.flip();
     89                     content += charset.decode(buff);
     90                 }
     91                 //若系统发送通知名字已经存在,则需要换个昵称
     92                 if(USER_EXIST.equals(content)) {
     93                     name = "";
     94                 }
     95                 System.out.println(content);
     96                 sk.interestOps(SelectionKey.OP_READ);
     97             }
     98         }
     99     }
    100     
    101 }

    自己生成一个类,将Client这个抽象类继承,执行就可以了。观看结果的时候,会有个小地方需要注意,就是Eclipse中,console的结果,需要开启多个控制台,要不会发生混乱。

    方法:

    第一步:

    第二步:

     左侧红框,pin console ,作用是锁定console,固定显示选择的线程的输出;
     右侧红框,作用是线程选择显示哪个线程的输出。

  • 相关阅读:
    关于响应式布局
    HTML5 学习笔记--------》HTML5概要与新增标签!
    jQuery学习笔记:attr()与prop()的区别
    关于php语言的使用!
    MYSQL的常用命令和增删改查语句和数据类型!
    html5 和css3的小知识!
    应收单
    Spring Boot 启动过程及 自定义 Listener等组件
    23. Spring Boot JPA BaseDao 配置 文章
    22. SpringBoot 集成 Mybatis
  • 原文地址:https://www.cnblogs.com/lihao007/p/9095548.html
Copyright © 2020-2023  润新知