NIO 实现非阻塞 Socket 通讯

打印 上一主题 下一主题

主题 990|帖子 990|积分 2970

NIO 实现多人聊天室的案例
服务端
  1. import java.io.IOException;
  2. import java.net.InetSocketAddress;
  3. import java.nio.ByteBuffer;
  4. import java.nio.channels.*;
  5. import java.nio.charset.Charset;
  6. /**
  7. * 聊天室服务端
  8. */
  9. public class NServer {
  10.     private Selector selector = null;
  11.     static final int PORT = 30000;
  12.     private Charset charset = Charset.forName("UTF-8");
  13.     ServerSocketChannel server = null;
  14.     public void init() throws IOException {
  15.         selector = Selector.open();
  16.         server = ServerSocketChannel.open();
  17.         InetSocketAddress isa = new InetSocketAddress("127.0.0.1", PORT);
  18.         // 将该 ServerSocketChannel 绑定到指定 IP 地址
  19.         server.bind(isa);
  20.         //设置为以非阻塞方式工作
  21.         server.configureBlocking(false);
  22.         // 将 server 注册到指定的 Selector
  23.         server.register(selector, SelectionKey.OP_ACCEPT);
  24.         while (selector.select() > 0) {
  25.             for (SelectionKey sk : selector.selectedKeys()) {
  26.                 // 从 selector 上的已选择 Key 集中删除正在处理的 SelectionKey
  27.                 selector.selectedKeys().remove(sk);
  28.                 // 如果 sk 对应 channel 包含客户端的连接请求
  29.                 if (sk.isAcceptable()) {
  30.                     // 接受请求
  31.                     SocketChannel sc = server.accept();
  32.                     // 采用非阻塞模式
  33.                     sc.configureBlocking(false);
  34.                     // 将该 SocketChannel 也注册到 selector
  35.                     sc.register(selector, SelectionKey.OP_READ);
  36.                     // 将 sk 对应的 channel 设置成准备接受其他请求
  37.                     sk.interestOps(SelectionKey.OP_ACCEPT);
  38.                 }
  39.                 // 如果 sk 对应的channel 有数据需要读取
  40.                 if (sk.isReadable()) {
  41.                     SocketChannel sc = (SocketChannel) sk.channel();
  42.                     ByteBuffer buffer = ByteBuffer.allocate(1024);
  43.                     String content = "";
  44.                     try {
  45.                         // 读取数据操作
  46.                         while (sc.read(buffer) > 0) {
  47.                             buffer.flip();
  48.                             content += charset.decode(buffer);
  49.                         }
  50.                         System.out.println("读取的数据: " + content);
  51.                         sk.interestOps(SelectionKey.OP_READ);
  52.                     } catch (IOException e) {
  53.                         sk.cancel();
  54.                         if (sk.channel() != null) {
  55.                             sk.channel().close();
  56.                         }
  57.                     }
  58.                     // 如果 content 的长度大于 0,即聊天信息不为空
  59.                     if (content.length() > 0) {
  60.                         // 遍历该 selector 里注册的所有 SelectionKey
  61.                         for (SelectionKey key : selector.keys()) {
  62.                             // 获取 channel
  63.                             Channel targetChannel = key.channel();
  64.                             // 如果该 channel 是 SocketChannel
  65.                             if (targetChannel instanceof SocketChannel) {
  66.                                 // 将读到的内容写到该 channel 中
  67.                                 SocketChannel dest = (SocketChannel) targetChannel;
  68.                                 dest.write(charset.encode(content));
  69.                             }
  70.                         }
  71.                     }
  72.                 }
  73.             }
  74.         }
  75.     }
  76.     public static void main(String[] args) throws IOException {
  77.         new NServer().init();
  78.     }
  79. }
复制代码
启动时建立一个可监听连接请求的 ServerSocketChannel,并注册到 Selector,接着直接采用循环不断监听 Selector 对象的 select() 方法返回值,大于0时,处理该 Selector 上所有被选择的 SelectionKey。
服务端仅需监听两种操作:连接和读取数据。
处理连接操作时,只需将连接完成后产生的 SocketChannel 注册到指定的 Selector 对象;
处理读取数据时,先从该 Socket 中读取数据,再将数据写入 Selector 上注册的所有 Channel 中。
客户端
  1. import java.io.IOException;
  2. import java.net.InetSocketAddress;
  3. import java.nio.ByteBuffer;
  4. import java.nio.channels.SelectionKey;
  5. import java.nio.channels.Selector;
  6. import java.nio.channels.SocketChannel;
  7. import java.nio.charset.Charset;
  8. import java.util.Scanner;
  9. /**
  10. * 聊天室客户端
  11. */
  12. public class NClient {
  13.     private Selector selector = null;
  14.     static final int PORT = 30000;
  15.     private Charset charset = Charset.forName("UTF-8");
  16.     private SocketChannel sc = null;
  17.     public void init() throws IOException {
  18.         selector = Selector.open();
  19.         InetSocketAddress isa = new InetSocketAddress("127.0.0.1", PORT);
  20.         // 打开套接字通道并将其连接到远程地址
  21.         sc = SocketChannel.open(isa);
  22.         // 设置为非阻塞模式
  23.         sc.configureBlocking(false);
  24.         // 注册到 selector
  25.         sc.register(selector, SelectionKey.OP_READ);
  26.         new ClientThread().start();
  27.         // 创建键盘输入流
  28.         Scanner scan = new Scanner(System.in);
  29.         while (scan.hasNextLine()) {
  30.             String line = scan.nextLine();
  31.             // 将键盘大忽如的内容输出到 SocketChannel 中
  32.             sc.write(charset.encode(line));
  33.         }
  34.     }
  35.     private class ClientThread extends  Thread {
  36.         @Override
  37.         public void run() {
  38.             try {
  39.                 while (selector.select() > 0) {
  40.                     for (SelectionKey sk : selector.selectedKeys()) {
  41.                         // 从 set集合删除正在处理的 SelectionKey
  42.                         selector.selectedKeys().remove(sk);
  43.                         // 如果 sk 对应的 channel 中有可读数据
  44.                         if (sk.isReadable()) {
  45.                             // 使用 NIO 读取 channel 中的数据
  46.                             SocketChannel sc = (SocketChannel) sk.channel();
  47.                             ByteBuffer buff = ByteBuffer.allocate(1024);
  48.                             String content = "";
  49.                             while (sc.read(buff) > 0) {
  50.                                 sc.read(buff);
  51.                                 buff.flip();
  52.                                 content += charset.decode(buff);
  53.                             }
  54.                             System.out.println("聊天信息: " + content);
  55.                             // 为下一次读取做准备
  56.                             sk.interestOps(SelectionKey.OP_READ);
  57.                         }
  58.                     }
  59.                 }
  60.             } catch (IOException e) {
  61.                 e.printStackTrace();
  62.             }
  63.         }
  64.     }
  65.     public static void main(String[] args) throws IOException {
  66.         new NClient().init();
  67.     }
  68. }
复制代码
 
相比于服务端程序,客户端要简单一些,只有一个 SocketChannel ,将其注册到指定的 Selector 后,程序启动另一个线程来监听该 Selector 即可。
分别启动两个程序后,可以在客户点输入内容,在服务端就可以读取到输入的内

 
服务端内容:

 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

种地

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表