NIO实现网络通讯

首先创建NIOSocket的服务端;其次创建NIOSocket的客户端。
通信过程如下:
客户端(C)向服务端(S)发送任意数据(包括用户直接从控制台输入数据,使用Scanner),服务端接受到来自客户端的数据并展示,同时客户端发过来的数据原封不动的再发给客户端;客户端接受来自服务端的数据并展示。
说明:通信过程如上。下面看代码如何实现:
服务端:


package socket;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.Channel;
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.Iterator;

public class NIOService {

 public String IP = "127.0.0.1";// 10.50.200.120
 public static final int PORT = 4444;
 private static final int SIZE = 256;

 // 对于以字符方式读取和处理的数据必须要进行字符集编码和解码
 String encoding = System.getProperty("file.encoding");
 // 加载字节编码集
 Charset charse = Charset.forName(encoding);

 public NIOService() throws IOException {
  // NIO的通道channel中内容读取到字节缓冲区ByteBuffer时是字节方式存储的,
  // 分配两个字节大小的字节缓冲区
  ByteBuffer buffer = ByteBuffer.allocate(SIZE);
  SocketChannel ch = null;
  Selector selector = null;
  ServerSocketChannel serverChannel = null;

  try {
   // 打开通道选择器
   selector = Selector.open();
   // 打开服务端的套接字通道
   serverChannel = ServerSocketChannel.open();
   // 将服务端套接字通道连接方式调整为非阻塞模式
   serverChannel.configureBlocking(false);
   // serverChannel.socket().setReuseAddress(true);
   // 将服务端套接字通道绑定到本机服务端端口
   serverChannel.socket().bind(new InetSocketAddress(IP, PORT));
   // 将服务端套接字通道OP_ACCEP事件注册到通道选择器上
   serverChannel.register(selector, SelectionKey.OP_ACCEPT);
   System.out.println("Server on port:" + PORT);
   while (true) {
    // 通道选择器开始轮询通道事件
    selector.select();
    Iterator it = selector.selectedKeys().iterator();
    while (it.hasNext()) {
     // 获取通道选择器事件键
     SelectionKey skey = (SelectionKey) it.next();
     it.remove();
     // 服务端套接字通道发送客户端连接事件,客户端套接字通道尚未连接
     if (skey.isAcceptable()) {
      // 获取服务端套接字通道上连接的客户端套接字通道
      ch = serverChannel.accept();
      System.out.println("Accepted connection from:" + ch.socket());
      // 将客户端套接字通过连接模式调整为非阻塞模式
      ch.configureBlocking(false);
      // 将客户端套接字通道OP_READ事件注册到通道选择器上
      ch.register(selector, SelectionKey.OP_READ);
     }
     // 如果sk对应的Channel有数据需要读取
     if (skey.isReadable()) {
      // 获取该SelectionKey对银行的Channel,该Channel中有刻度的数据
      SocketChannel sc = (SocketChannel) skey.channel();
      String content = "";
      // 开始读取数据
      try {
       content = receiverFromClient(sc,buffer);
       // 将sk对应的Channel设置成准备下一次读取
       skey.interestOps(SelectionKey.OP_READ);
      } catch (IOException e) {// 如果捕获到该sk对银行的Channel出现了异常,表明
       // Channel对应的Client出现了问题,所以从Selector中取消
       // 从Selector中删除指定的SelectionKey
       skey.cancel();
       if (skey.channel() != null) {
        skey.channel().close();
       }
      }
      // 如果content的长度大于0,则处理信息返回给客户端
      if (content.length() > 0) {
       System.out.println("接受客户端数据:" + content);
       // 处理信息返回给客户端
       sendToClient(selector,content);
      }
      //ch.write((ByteBuffer)buffer.rewind());
      //buffer.clear();
     }
     if(skey.isWritable()){
      
     }
    }
   }
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   if (ch != null)
    ch.close();
   serverChannel.close();
   selector.close();
  }
 }

 /**
  * 向客户端发送数据
  * @param selector
  * @param content
  * @throws IOException
  */
 public void sendToClient(Selector selector,String content) throws IOException{
  // 遍历selector里注册的所有SelectionKey
  for (SelectionKey key1 : selector.keys()) {
   // 获取该key对应的Channel
   Channel targerChannel = key1.channel();
   // 如果该Channel是SocketChannel对象
   if (targerChannel instanceof SocketChannel) {
    // 将读取到的内容写入该Channel中
    SocketChannel dest = (SocketChannel) targerChannel;
    sendToClient(dest,content);
   }
  }
 }
 
 /**
  * 向指定频道发送数据
  * @param channel
  * @param data
  * @throws IOException
  */
 public void sendToClient(SocketChannel channel, String data) throws IOException {
  channel.write(charse.encode(data));
  //channel.socket().shutdownOutput();
 }

 /**
  * 接受来自客户端数据
  * @param channel
  * @param buffer
  * @return
  * @throws Exception
  */
 private String receiverFromClient(SocketChannel channel,ByteBuffer buffer) throws Exception {
  String content = "";
  //* 取客户端发送的数据两个方法任选其一即可
  // 开始读取数据
  // 法一
  channel.read(buffer);
  CharBuffer cb = charse.decode((ByteBuffer) buffer.flip());
  content = cb.toString();
  // 法二
  /*
  while (sc.read(buffer) > 0) {
   buffer.flip();
   content += charse.decode(buffer);
  }//*/
  buffer.clear();
  return content;
 }

 public static void main(String[] args) {
  try {
   new NIOService();
  } catch (IOException e) {
   e.printStackTrace();
  }
 }

}

客户端:

package socket;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Scanner;

/**
 *
 * NIOClient
 *
 * @author 王俊伟 [email protected]
 * @date 2016年7月21日 下午5:26:25
 */
public class NIOClient {
 private static final int SIZE = 1024;
 private static NIOClient instance = new NIOClient();
 public String IP = "127.0.0.1";// 10.50.200.120
 public int CLIENT_PORT = 4444;// 4444 9666
 private SocketChannel channel;
 private Selector selector = null;

 String encoding = System.getProperty("file.encoding");
 Charset charset = Charset.forName(encoding);

 private NIOClient() {
 }

 public static NIOClient getInstance() {
  return instance;
 }

 public void send(String content) throws IOException {
  selector = Selector.open();
  channel = SocketChannel.open();
  // channel = SocketChannel.open(new InetSocketAddress(IP,CLIENT_PORT));
  InetSocketAddress remote = new InetSocketAddress(IP, CLIENT_PORT);
  channel.connect(remote);
  // 设置该sc以非阻塞的方式工作
  channel.configureBlocking(false);
  // 将SocketChannel对象注册到指定的Selector
  // SelectionKey.OP_READ | SelectionKey.OP_WRITE | SelectionKey.OP_CONNECT
  channel.register(selector, SelectionKey.OP_READ);//这里注册的是read读,即从服务端读数据过来
  // 启动读取服务器数据端的线程
  new ClientThread().start();
  channel.write(charset.encode(content));
  // 创建键盘输入流
  Scanner scan = new Scanner(System.in);//这里向服务端发送数据,同时启动了一个键盘监听器
  while (scan.hasNextLine()) {
   System.out.println("输入数据:\n");
   // 读取键盘的输入
   String line = scan.nextLine();
   // 将键盘的内容输出到SocketChanenel中
   channel.write(charset.encode(line));
  }
  scan.close();
 }

 /**
  * 从服务端读入数据的线程
  *
  * @author 王俊伟 [email protected]
  * @date 2016年10月20日 下午9:59:11
  */
 private class ClientThread extends Thread {
  @Override
  public void run() {
   try {
    while (selector.select() > 0) {
     // 遍历每个有可能的IO操作的Channel对银行的SelectionKey
     for (SelectionKey sk : selector.selectedKeys()) {
      // 删除正在处理的SelectionKey
      selector.selectedKeys().remove(sk);
      // 如果该SelectionKey对应的Channel中有可读的数据
      if (sk.isReadable()) {
       // 使用NIO读取Channel中的数据
       SocketChannel sc = (SocketChannel) sk.channel();
       String content = "";
       ByteBuffer bff = ByteBuffer.allocate(SIZE);
       while (sc.read(bff) > 0) {
        sc.read(bff);
        bff.flip();
        content += charset.decode(bff);
       }
       // 打印读取的内容
       System.out.println("服务端返回数据:" + content);
       // 处理下一次读
       sk.interestOps(SelectionKey.OP_READ);
      }
     }
    }

   } catch (IOException io) {
    io.printStackTrace();
   }
  }
 }

 /**
  * TCP 处理 线程
  */
 class TCPClientReadThread implements Runnable {
  private Selector selector;

  public TCPClientReadThread(Selector selector) {
   this.selector = selector;
   new Thread(this).start();
  }

  @Override
  public void run() {
   try {
    channel.configureBlocking(false);
    // selector.select(3000);
    channel.register(selector, SelectionKey.OP_READ);

    while (true) {
     if (selector.select(1000) > 0) {
      // 遍历每个有可用IO操作Channel对应的SelectionKey
      for (SelectionKey sk : selector.selectedKeys()) {
       // 如果该SelectionKey对应的Channel中有可读的数据
       if (sk.isReadable()) {
        // 使用NIO读取Channel中的数据
        SocketChannel sc = (SocketChannel) sk.channel();
        // 将字节转化为为UTF-8的字符串
        receiveData(sc);
        // 为下一次读取作准备
        sk.interestOps(SelectionKey.OP_READ);
       } else if (sk.isWritable()) {
        // 取消对OP_WRITE事件的注册
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        sk.interestOps(sk.interestOps() & (~SelectionKey.OP_WRITE));
        SocketChannel sc = (SocketChannel) sk.channel();

        // 此步为阻塞操作,直到写入操作系统发送缓冲区或者网络IO出现异常
        // 返回的为成功写入的字节数,若缓冲区已满,返回0
        int writeenedSize = sc.write(buffer);

        // 若未写入,继续注册感兴趣的OP_WRITE事件
        if (writeenedSize == 0) {
         sk.interestOps(sk.interestOps() | SelectionKey.OP_WRITE);
        }
       } else if (sk.isConnectable()) {
        SocketChannel sc = (SocketChannel) sk.channel();
        sc.configureBlocking(false);

        // 注册感兴趣的IO事件,通常不直接注册写事件,在发送缓冲区未满的情况下
        // 一直是可写的,所以如果注册了写事件,而又不写数据,则很容易造成CPU消耗100%
        // SelectionKey sKey = sc.register(selector,
        // SelectionKey.OP_READ);

        // 完成连接的建立
        sc.finishConnect();
       }
       // 删除正在处理的SelectionKey
       selector.selectedKeys().remove(sk);
      }
     }
     if (selector.select(1000) <= 0) {
      Thread.sleep(1000);
      continue;
     }
    }
   } catch (Exception ex) {
    ex.printStackTrace();
   }
  }
 }

 /**
  * 客户端发送数据
  *
  * @param channel
  * @param bytes
  * @throws Exception
  */
 protected void sendData(SocketChannel channel, byte[] bytes) throws Exception {
  ByteBuffer buffer = ByteBuffer.wrap(bytes);
  channel.write(buffer);
  //channel.socket().shutdownOutput();
 }

 protected void sendData(SocketChannel channel, String data) throws Exception {
  this.sendData(channel, data.getBytes());
 }

 /**
  * 接受服务端的数据
  *
  * @param channel
  * @return
  * @throws Exception
  */
 protected void receiveData(SocketChannel channel) throws Exception {
  ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
  int count = 0;
  while ((count = channel.read(buffer)) != -1) {
   if (count == 0) {
    Thread.sleep(100); // 等等一下
    continue;
   }
   // 转到最开始
   buffer.flip();
   while (buffer.remaining() > 0) {
    System.out.print((char) buffer.get());
   }
   buffer.clear();
  }
 }

 public static void main(String[] args) {
  try {
   NIOClient nio = new NIOClient();
   nio.send("test\n");//向服务端发送数据
   //nio.send("metrics:memory: swap: cpu: network i/o: disks i/o: tcp:\n");
  } catch (IOException e) {
   e.printStackTrace();
  }
 }

}

如上两个类代码注释很全了。

运行:
首先运行服务端,然后执行客户端,返回数据如下:

服务端:


Server on port:4444
Accepted connection from:Socket[addr=/127.0.0.1,port=58153,localport=4444]
接受客户端数据:test
接受客户端数据:你好啊!


客户端:


服务端返回数据:test
输入数据:
你好啊!
输入数据:
服务端返回数据:你好啊!
---------------------
作者:junehappylove
来源:CSDN
原文:https://blog.csdn.net/junehappylove/article/details/54344826
版权声明:本文为博主原创文章,转载请附上博文链接!

猜你喜欢

转载自blog.csdn.net/qq_41974391/article/details/83419716