>>分享Java编程技术,对《Java面向对象编程》等书籍提供技术支持 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 21745 个阅读者 刷新本主题
 * 贴子主题:  使用javaNIO实现C/S模式的通信 回复文章 点赞(0)  收藏  
作者:flybird    发表时间:2020-02-06 23:41:23     消息  查看  搜索  好友  邮件  复制  引用


      NIO使用非阻塞IO的方式实现,服务器与客户端的交流,适用于大量连接,而数据量少的情况。通过一个线程轮询所有的通道,处理注册的事件,而主线程可以继续干其他的事情。这样所有的I/O都交给一个线程处理,减少了线程IO的切换。如果具体学习NIO的架构和原理请点击下面的连接

       点击打开链接 http://ifeve.com/selectors/

          以下为一个使用NIO实现的C/S通信模式,对应简单例子学习,可以更容易入手。

      服务端代码如下:

      主线程类:MainChannel              
  1.        import java.io.IOException;
  2.       import java.net.InetSocketAddress;
  3.       import java.nio.channels.SelectionKey;
  4.       import java.nio.channels.Selector;
  5.       import java.nio.channels.ServerSocketChannel;
  6.       public   class  MainChannel {
  7.        public  static  void  main (String[] args) {
  8.      // TODO Auto-generated method stub
  9.      // 声明服务器监听通道 和select选择器
  10.      ServerSocketChannel serverChannel =  null;
  11.      Selector selector =  null;
  12.      try {
  13.      // 实例化selector 此种实例化模式说明 selector 是单例的
  14.      selector = Selector.open();
  15.      // 实例化服务器监听端口
  16.      serverChannel = ServerSocketChannel.open();
  17.      // 绑定监听地址。
  18.      serverChannel.socket().bind( new InetSocketAddress( 8881));
  19.      // 设置channel为非阻塞模式,一定要非阻塞模式才能注册到selector中
  20.      serverChannel.configureBlocking( false);
  21.      // 把监听通道注册到选择器中, 监听此通道的连接事件。SelectionKey.OP_ACCEPT 指定为连接事件。
  22.      serverChannel.register(selector, SelectionKey.OP_ACCEPT);
  23.      //所有通道,交给通道管理器,统一管理
  24.      ChannelManager.addChannel(serverChannel);
  25.      // 开启一个线程负责管理selector,并轮询是否有注册监听的事件就绪。
  26.      Thread thread =  new Thread( new ServerNIO(selector));
  27.      thread.start();
  28.      // 然后主线程 就可以干其他的事情了。不管客户端连接 还是I/O
  29.      // 都不会阻塞此线程,只会阻塞selector管理线程,且只在等待事件发生时阻塞。
  30.      }  catch (IOException e) {
  31.      // TODO Auto-generated catch block
  32.      System.out.println( "服务器监听发生异常");
  33.      }
  34.      }
  35.      }

   管理selector的线程类:          

  1.        import java.io.IOException;
  2.       import java.nio.ByteBuffer;
  3.       import java.nio.channels.SelectionKey;
  4.       import java.nio.channels.Selector;
  5.       import java.nio.channels.ServerSocketChannel;
  6.       import java.nio.channels.SocketChannel;
  7.       import java.util.Iterator;
  8.       import java.util.Set;
  9.       public   class  ServerNIO  implements  Runnable {
  10.      // 客户端通道,用于给某个客户端收发数据
  11.      private SocketChannel socketChannel;
  12.      // 缓冲区,用户收发数据,面向通道。
  13.      private ByteBuffer buf = ByteBuffer.allocate( 1024);
  14.      // 选择器,从主线程注入
  15.      private Selector selector;
  16.      // 指定线程是否轮询
  17.      private  boolean flag =  true;
  18.      // 构造器中注入selector
  19.        public  ServerNIO (Selector selector) {
  20.      // TODO Auto-generated constructor stub
  21.      this.selector = selector;
  22.      }
  23.      // 开启线程等待事件的发生,轮询通道。
  24.      @Override
  25.        public  void  run () {
  26.      // TODO Auto-generated method stub
  27.      try {
  28.      while (flag) {
  29.        /**
  30.        * 获取等待的事件就绪的通道,如果没有通道有事件就绪有三种情况
  31.        * 1. 直接返回:selectNow();
  32.        * 2. 超时返回:select(int timeout);
  33.        * 3. 阻 塞: select();
  34.        */
  35.      int nums = selector.select();  // 阻塞模式
  36.      if (nums >  0) {  // 阻塞模式下 此次判定多余
  37.      // 当事件发生了,获取发生事件通道的key集合;
  38.      Set<SelectionKey> selectKeys = selector.selectedKeys();
  39.      // 迭代遍历这个keys集合
  40.      Iterator<SelectionKey> iter = selectKeys.iterator();
  41.      while (iter.hasNext()) {
  42.      // 获取单个通道的key
  43.      SelectionKey key = iter.next();
  44.      // 如果是读取事件就绪。说明有客户端向服务器发送数据了。
  45.      if (key.isReadable()) {
  46.      // 先获取到客户端channel
  47.      socketChannel = (SocketChannel) key.channel();
  48.      buf.clear();
  49.      // 利用buffer读取数据。
  50.      int len = socketChannel.read(buf);
  51.      if (len >  0) {
  52.      byte[] str =  new  byte[len];
  53.      buf.rewind();
  54.      buf.get(str,  0, len);
  55.      buf.clear();
  56.      System.out.println( "获取客户端数据:" +  new String(str));
  57.      // 给客户端回复数据
  58.      String temp =  "服务器回复: 已经收到您发送的数据,祝您一路平安!";
  59.      buf.clear();
  60.      buf.put(temp.getBytes());
  61.      buf.flip();
  62.      socketChannel.write(buf);
  63.      System.out.println( "已经向客户端回复");
  64.      //此处可以利用ChannelManager向其他所有通道广播数据。只要在ChannelManager中写一个广播方法即可
  65.      }
  66.      }  else  if (key.isAcceptable()) {  // 如果是接受客户端连接就绪
  67.      // 从key中获取对应的通道
  68.      ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
  69.      // 接受这个连接。
  70.      SocketChannel socketChannel = serverChannel.accept();
  71.      // 如果连接不为空,则控制台打印 连接的客户端地址。
  72.      if (socketChannel !=  null) {
  73.      // 由于关闭selector的时候,并不会关闭通道,最好使用一个容器,将通道都保存起来
  74.      //然后开启心跳连接,如果通道出现异常则关闭此通道,当应用程序关闭的时候,关闭所有的通道。
  75.      ChannelManager.addChannel(socketChannel);
  76.      System.out.println(String.format( "接收到客户端的连接:IP[%s]-端口[%s]",
  77.      socketChannel.socket().getPort(), socketChannel.socket().getInetAddress()));
  78.      // 把这个通道设置为非阻塞模式,然后又注册到selector中,等待事件发生
  79.      socketChannel.configureBlocking( false);
  80.      socketChannel.register(selector, SelectionKey.OP_READ);
  81.      }
  82.      }  //写事件在缓冲区直接达到阈值时候出发,一般不注册写事件。
  83.      // 通道处理完毕后 将此通道删除。如果下次又有此时间,会有一个新的key,所以记得删除处理过的key。
  84.      iter.remove();
  85.      }
  86.      }
  87.      }
  88.      }  catch (IOException e) {
  89.      System.out.println( "服务器断开连接");
  90.      }
  91.      try {
  92.      // 注意此处只会关闭,selector使注册到琪上面的selectionKeys无效,通道本身不会关闭。
  93.      selector.close();
  94.      ChannelManager.closeChannles();
  95.      }  catch (IOException e1) {
  96.      // TODO Auto-generated catch block
  97.      e1.printStackTrace();
  98.      }
  99.      }
  100.      }

   通道管理类:          

  1.        import java.io.IOException;
  2.       import java.nio.channels.Channel;
  3.       import java.util.LinkedList;
  4.       public   class  ChannelManager {
  5.      private  static LinkedList<Channel> list =  new LinkedList<>();
  6.      private  static Thread thread;  //用于开启心跳测试通道连接,实现省略
  7.        public  static  void  addChannel (Channel channel) {
  8.      list.add(channel);
  9.      }
  10.      //关闭所有的通道连接
  11.        public  static  void  closeChannles () {
  12.      for (Channel channel : list) {
  13.      try {
  14.      channel.close();
  15.      }  catch (IOException e) {
  16.      // TODO Auto-generated catch block
  17.      e.printStackTrace();
  18.      System.out.println( "关闭通道失败");
  19.      }
  20.      list.remove();
  21.      }
  22.      }
  23.      }

   一个简单的客户端类: 这个脚本可以直接通过cmd 编译运行。          

  1.        import java.io.IOException;
  2.       import java.net.InetSocketAddress;
  3.       import java.nio.ByteBuffer;
  4.       import java.nio.channels.SocketChannel;
  5.       import java.util.Scanner;
  6.       public   class  MainChannel {
  7.        public  static  void  main (String[] args) {
  8.      // TODO Auto-generated method stub
  9.      SocketChannel socketChannel =  null;
  10.      boolean flag =  true;
  11.      Scanner in =  new Scanner(System.in);
  12.      ByteBuffer buffer = ByteBuffer.allocate( 1024);
  13.      try {
  14.      socketChannel = SocketChannel.open();
  15.      // 设置连接为非阻塞模式,可能未连接完成就返回
  16.      // socketChannel.configureBlocking(false);
  17.      socketChannel.socket().connect( new InetSocketAddress( "192.168.1.100",  8881));
  18.      // 判断是否连接成功,等待连接成功
  19.      while (!socketChannel.finishConnect()) {
  20.      }
  21.      while (flag) {
  22.      String temp = in.nextLine();
  23.      buffer.clear();
  24.      buffer.put(temp.getBytes());
  25.      // limit指针 移动到 position位置
  26.      buffer.flip();
  27.      // 当buffer中有足够空间,则写到buffer中
  28.      while (buffer.hasRemaining())
  29.      socketChannel.write(buffer);
  30.      if ( "exit".equals(temp))
  31.      flag =  false;
  32.      }
  33.      }  catch (IOException e) {
  34.      // TODO Auto-generated catch block
  35.      System.out.println( "与服务断开连接");
  36.      }
  37.      if (socketChannel !=  null) {
  38.      try {
  39.      socketChannel.close();
  40.      }  catch (IOException e) {
  41.      // TODO Auto-generated catch block
  42.      e.printStackTrace();
  43.      }
  44.      }
  45.      }
  46.      }

   上述代码,可以实现客户端向服务器发送消息,然后服务器接受到消息,在自己的控制台打印输出。

                                                                                            
----------------------------
原文链接:https://blog.csdn.net/qq_32250495/article/details/76796084

程序猿的技术大观园:www.javathinker.net



[这个贴子最后由 flybird 在 2020-02-06 23:41:23 重新编辑]
  Java面向对象编程-->数组
  JavaWeb开发-->JSP技术详解(Ⅱ)
  JSP与Hibernate开发-->Java应用分层架构及软件模型
  Java网络编程-->创建非阻塞的HTTP服务器
  精通Spring-->组合(Composition)API
  Vue3开发-->绑定CSS样式
  利用堆栈将中缀表达式转换成后缀表达式
  Java虚拟机进行类连接的原理
  Java集合框架学习---深入探究ArrayList源码
  Java中保留数字的若干位小数位数的方法
  整理收集的一些常用java工具类
  Java关键字final、static使用总结
  java 中文繁简体转换工具 opencc4j
  Synchronized与ReentrantLock区别总结
  Java读取大文件的高效率实现_java大文件
  Java Scoket之java.io.EOFException解决方案
  5个非常有挑战性的Java面试题
  Java入门实用代码:修改文件最后的修改日期
  Java入门实用代码:字符串小写转大写
  Java中的main()方法详解
  Java线程实现龟兔赛跑
  更多...
 IPIP: 已设置保密
楼主      
1页 0条记录 当前第1
发表一个新主题 开启一个新投票 回复文章


中文版权所有: JavaThinker技术网站 Copyright 2016-2026 沪ICP备16029593号-2
荟萃Java程序员智慧的结晶,分享交流Java前沿技术。  联系我们
如有技术文章涉及侵权,请与本站管理员联系。