使用NIO实现非阻塞Socket通信

服务器端:
  1. import java.io.IOException;
  2. import java.net.InetSocketAddress;
  3. import java.nio.ByteBuffer;
  4. import java.nio.channels.SelectableChannel;
  5. import java.nio.channels.SelectionKey;
  6. import java.nio.channels.Selector;
  7. import java.nio.channels.ServerSocketChannel;
  8. import java.nio.channels.SocketChannel;
  9. import java.nio.charset.Charset;

  10. public class ServerNio {
  11.         public static void main(String[] args) throws IOException{
  12.                 //打开一个多路复用器对象,作用是监控多个SelectableChannel的IO状况。
  13.                 Selector selector = Selector.open();
  14.                 //使用open()方法打开一个未绑定的服务器插口通道,支持非阻塞操作
  15.                 ServerSocketChannel serverChannel = ServerSocketChannel.open();
  16.                 //将服务器插口绑定到指定ip和端口号
  17.                 serverChannel.socket().bind( new InetSocketAddress("127.0.0.1",55555));
  18.                 //调整服务器插口通道的阻塞模式为非阻塞模式
  19.                 serverChannel.configureBlocking(false);
  20.                 //将服务器插口通道注册到多路复用器(selector选择容器中)
  21.                 //SelectionKey表示注册通道时产生的选择键
  22.                 serverChannel.register(selector , SelectionKey.OP_ACCEPT);
  23.                 
  24.                 //创建一个1字节的缓冲区
  25.                 ByteBuffer buffer = ByteBuffer.allocate(1024);
  26.                 //指定编码集
  27.                 Charset charset = Charset.forName("UTF-8");
  28.                 
  29.                 //服务器不断地调用select()方法,若大于0表示有多少个Channel通道具有可用的I/O操作
  30.                 while(selector.select() > 0){
  31.                         //遍历已经注册到selector容器的通道
  32.                         for(SelectionKey sk: selector.selectedKeys()){
  33.                                 //把正在处理的sk从集合中移除
  34.                                 selector.selectedKeys().remove(sk);        
  35.                                 
  36.                                 //如果sk对应的通道包含客户端的连接请求
  37.                                 if(sk.isAcceptable()){
  38.                                         //返回创建此键的通道
  39.                                         SelectableChannel sc1 = sk.channel();
  40.                                         //如果这个通道是服务器插口通道类型的
  41.                                         if(sc1 instanceof ServerSocketChannel){
  42.                                                 //将其强制转换成服务器插口通道类型的对象
  43.                                                 ServerSocketChannel ssc = (ServerSocketChannel)sc1;
  44.                                                 //调用accept()方法接受连接并返回普通插口通道类型的I/O接口
  45.                                                 SocketChannel socketChannel1 = ssc.accept();
  46.                                                 //设置采用非阻塞模式
  47.                                                 socketChannel1.configureBlocking(false);
  48.                                                 //将插口通道也注册到selector
  49.                                                 socketChannel1.register(selector,SelectionKey.OP_READ);
  50.                                                 //将sk对应的Channel设置成准备接受其他请求
  51.                                                 sk.interestOps(SelectionKey.OP_ACCEPT);
  52.                                         }
  53.                                 }
  54.                                 
  55.                                 //如果sk对应的通道有数据需要读取
  56.                                 if(sk.isReadable()){
  57.                                         //获取该SelectionKey对应的Channel,该Channel中有可读得数据
  58.                                         SelectableChannel sc2 = sk.channel();
  59.                                         if(sc2 instanceof SocketChannel){
  60.                                                 SocketChannel socketChannel2 = (SocketChannel)sc2;
  61.                                                 String msg = "";
  62.                                                 
  63.                                                 //开始读取数据
  64.                                                 while(socketChannel2.read(buffer)>0){
  65.                                                         buffer.flip();        //重绕(锁定)缓冲区(pos=0,limit=pos)
  66.                                                         msg+=charset.decode(buffer);
  67.                                                         System.out.println(msg);
  68.                                                         buffer.clear();        //重置缓冲区(pos=0,limit=capacity)
  69.                                                 }
  70.                                                 //将sk对应的Channel设置成准备下一次读取
  71.                                                 sk.interestOps(SelectionKey.OP_READ);
  72.                                                 
  73.                                                 //发送给客户端
  74.                                                 //如果msg的长度大于0,即聊天信息不为空
  75.                                                 if(msg.length()>0){
  76.                                                         //遍历该selector里注册的所有SelectKey
  77.                                                         for(SelectionKey key:selector.keys()){
  78.                                                                 //获取该key对应的Channel
  79.                                                                 SelectableChannel targetChannel = key.channel();
  80.                                                                 if(targetChannel instanceof SocketChannel){
  81.                                                                         //将读到的内容写入该Channel中
  82.                                                                         SocketChannel dest =(SocketChannel)targetChannel;
  83.                                                                         dest.write(charset.encode(msg));
  84.                                                                 }
  85.                                                         }
  86.                                                 }
  87.                                         }
  88.                                 }
  89.                         }
  90.                 }
  91.         }
  92. }
复制代码
客户端:
  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.SocketChannel;
  6. import java.nio.charset.Charset;
  7. import java.util.Scanner;


  8. public class ClientNio {
  9.         public static void main(String[] args) throws IOException{
  10.                 //定义检测SocketChannel的Selector对象
  11.                 Selector selector = Selector.open();
  12.                 InetSocketAddress server = new InetSocketAddress("127.0.0.1",55555);
  13.                 //调用open静态方法创建连接到指定主机的SocketChannel
  14.                 SocketChannel clientChannel = SocketChannel.open(server);
  15.                 //设置该客户端插口通道为非阻塞模式
  16.                 clientChannel.configureBlocking(false);
  17.                 //注册插口到selector容器
  18.                 clientChannel.register(selector, SelectionKey.OP_READ);
  19.                 
  20.                 //定义编码和解码的字符集
  21.                 Charset charset = Charset.forName("UTF-8");
  22.                 
  23.                 //启动一个线程用于读取服务器发送来的数据
  24.                 new ClientNioThread(selector,charset);
  25.                 
  26.                 //创建键盘输入流
  27.                 Scanner scanner = new Scanner(System.in);
  28.                 
  29.                 //向服务器发送数据
  30.                 while( scanner.hasNextLine()){
  31.                         //读取键盘输入
  32.                         String message = scanner.nextLine();
  33.                         //将键盘输入的内容输出到SocketChannel
  34.                         clientChannel.write(charset.encode(message));
  35.                 }
  36.         }
  37. }
复制代码
读取服务器端数据的线程:
  1. import java.io.IOException;
  2. import java.nio.ByteBuffer;
  3. import java.nio.channels.SelectableChannel;
  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. public class ClientNioThread extends Thread{
  9.         private Selector selector;
  10.         private Charset charset;
  11.         
  12.         public ClientNioThread(Selector selector,Charset charset){
  13.                 this.selector = selector;
  14.                 this.charset = charset;
  15.                 this.start();        //启动线程
  16.         }

  17.         @Override
  18.         public void run() {
  19.                 try{
  20.                         ByteBuffer buffer = ByteBuffer.allocate(1024);
  21.                         while( selector.select() > 0 ){
  22.                                 //遍历每个有可用I/O操作Channel对应的SelectionKey
  23.                                 for( SelectionKey sk : selector.selectedKeys() ){
  24.                                         //删除正在处理的SelectionKey
  25.                                         selector.selectedKeys().remove(sk);
  26.                                         
  27.                                         SelectableChannel sc = sk.channel();
  28.                                         if( sc instanceof SocketChannel ){
  29.                                                 SocketChannel socketChannel = (SocketChannel) sc;
  30.                                                 StringBuilder sb = new StringBuilder();
  31.                                                 while( socketChannel.read( buffer ) > 0){
  32.                                                         buffer.flip();
  33.                                                         sb.append( charset.decode( buffer ));
  34.                                                         buffer.clear();
  35.                                                 }
  36.                                                 //打印输出读取的内容
  37.                                                 System.out.println( sb.toString());
  38.                                         }
  39.                                         //为下一次读取做准备
  40.                                         sk.interestOps( SelectionKey.OP_READ);
  41.                                 }
  42.                         }
  43.                 }catch(IOException e){
  44.                         e.printStackTrace();
  45.                 }
  46.                 
  47.         }
  48.         
  49. }
复制代码
总结:

服务器端:

1-获得Selector对象

2 获得ServerSocketChannel 对象serverChannel

3 把serverChannel跟指定的ip和端口(serverChannel对应的ServerSocket)

4 设置非阻塞模式

5 把serverChannel 注册到selector,指定模式(SelectionKey.OP_ACCEPT)

6 反复执行

        1> select()选择可以选择的通道

        2> 遍历所有的selectedKeys

           isAcceptable:打开监听 SocketChannel channel = serverChannel.accept();

                        把通过监听获得到的channel设置为无阻塞模式

                        注册channel到selector上,同时指定模式(OP_READ)

                        为sk的interest集合插入一个值(为下次操作做准备)

                        sk.interestOps(SelectionKey)

          sk.isReadable:根据sk获得它对应的通道:SelectableChannel sc = sk.channel();

                        类型检查if(sc.instanceof SocketChannel)

                        如果满足条件做类型转换:SocketChannel socketChannel = (SocketChannel)sc

                        读取数据(读取ByteBuffer)socketChannel.read(buffer);

                        向客户端发送:

                        if(msg.length()>0){

                                //遍历keys

                                for(SelectionKey key:selector keys()){

                                        SelectableChannel selectChannel = key.channel();

                                        if(selectChannel instance of SocketChannel){

                                                SocketChannel sssccc = (SocketChannel)selectChannel

                                                sssccc.write(charset.encode(msg));

                                        }

                                }

                        }

客户端:

1 获得一个Selector对象selector;

2 获得SocketChannel同时指定要连接的服务器的InetSocketAddress对象

3 设置为非阻塞模式

4 注册SocketChannel到selector同时指定模式为SelectionKey.OP_READ

5 使用主线程向服务器发送数据

6 使用单独的一个线程读取服务器发送来的数据


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值