Java 实现NIO网络通信
IO模型
IO模型就是说用什么样的通道进行数据的发送和接收,Java共支持3种网络编程IO模式:BIO,NIO,AIO
BIO
BIO(Blocking IO)
同步阻塞模型,一个客户端连接对应一个处理线程
不难看出 BIO的劣势在于如果客户端过多 会产生很多的线程
BIO代码实例
server端
1 package com.io.bio; 2 3 import java.io.IOException; 4 import java.net.ServerSocket; 5 import java.net.Socket; 6 7 public class SocketServer { 8 public static void main(String[] args) throws IOException { 9 ServerSocket serverSocket = new ServerSocket(9000); 10 while (true) { 11 System.out.println("等待连接。。"); 12 //阻塞方法 accept会阻塞线程 直到有连接事件发生 13 Socket clientSocket = serverSocket.accept(); 14 System.out.println("有客户端连接了。。"); 15 handler(clientSocket); 16 17 /*new Thread(new Runnable() { 18 @Override 19 public void run() { 20 try { 21 handler(clientSocket); 22 } catch (IOException e) { 23 e.printStackTrace(); 24 } 25 } 26 }).start();*/ 27 } 28 } 29 30 private static void handler(Socket clientSocket) throws IOException { 31 byte[] bytes = new byte[1024]; 32 System.out.println("准备read。。"); 33 //接收客户端的数据,阻塞方法,没有数据可读时就阻塞 一直到客户端发送消息后 跳出阻塞 执行后续的逻辑 34 int read = clientSocket.getInputStream().read(bytes); 35 System.out.println("read完毕。。"); 36 if (read != -1) { 37 System.out.println("接收到客户端的数据:" + new String(bytes, 0, read)); 38 } 39 clientSocket.getOutputStream().write("HelloClient".getBytes()); 40 clientSocket.getOutputStream().flush(); 41 } 42 }
client端
1 //客户端代码 2 public class SocketClient { 3 public static void main(String[] args) throws IOException { 4 Socket socket = new Socket("localhost", 9000); 5 //向服务端发送数据 6 socket.getOutputStream().write("HelloServer".getBytes()); 7 socket.getOutputStream().flush(); 8 System.out.println("向服务端发送数据结束"); 9 byte[] bytes = new byte[1024]; 10 //接收服务端回传的数据 11 socket.getInputStream().read(bytes); 12 System.out.println("接收到服务端的数据:" + new String(bytes)); 13 socket.close(); 14 } 15 }
劣势:
1.accept read 是阻塞方法 如果没有相关动作 会浪费资源
2.连接后,如果需要异步处理消息,过造成线程过多 服务器压力增大
应用场景:
BIO 方式适用于连接数目比较小且固定的架构, 这种方式对服务器资源要求比较高, 但程序简单易理解。
NIO(Non Blocking IO) 同步非阻塞IO
服务端一个线程可以处理多个连接请求,并且把这些请求都注册到多路复用器(selector)中,selector通过轮询的方式,获取到客户端相应的事件,比如accept read
示例代码
1 package com.socket.nio.server; 2 3 import java.io.IOException; 4 import java.net.InetSocketAddress; 5 import java.nio.ByteBuffer; 6 import java.nio.channels.*; 7 import java.util.Iterator; 8 import java.util.Set; 9 10 public class NIOSocketServer { 11 12 public static void main(String[] args) throws IOException { 13 //创建一个serversocket通道,并且设置为非阻塞 14 ServerSocketChannel socketChannel = ServerSocketChannel.open(); 15 //设置为非阻塞 默认是阻塞 16 socketChannel.configureBlocking(false); 17 //服务端绑定一个端口号 实时监听所有的客服端 18 socketChannel.socket().bind(new InetSocketAddress(8888)); 19 20 //创建一个selector多路复用器 并且把serversocektchannel注册到selector中 21 Selector selector = Selector.open(); 22 //注册serversocketchannel到selector 并且告知selector当前这个serversocketchannel 对连接感兴趣 23 socketChannel.register(selector, SelectionKey.OP_ACCEPT); 24 25 //循环监听处理客户端事件 26 while (true) { 27 System.out.println("等待连接"); 28 //轮询监听channel 如果有事件发生 将跳出监听 处理客户端事件 29 //select()是阻塞方法 accpet()也是一个阻塞方法 30 selector.select(); 31 //客户端的请求 会被监听到 并且把key放入到一个集合中 可以异步处理对应消息 32 Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); 33 while (iterator.hasNext()) { 34 SelectionKey selectionKey = iterator.next(); 35 iterator.remove(); 36 handleSelectionKey(selectionKey); 37 } 38 } 39 40 } 41 42 private static void handleSelectionKey(SelectionKey selectionKey) throws IOException { 43 if (selectionKey.isAcceptable()) { 44 //处理连接事件 45 ServerSocketChannel socketChannel = (ServerSocketChannel) selectionKey.channel(); 46 //accept()是一个阻塞方法 但是因为此时处理的是连接事件 必定又客户端连接过来 所以此方法 47 //马上就执行,继续处理其他事件 48 SocketChannel channel = socketChannel.accept(); 49 channel.configureBlocking(false); 50 //服务端接下来对read操作感兴趣 51 channel.register(selectionKey.selector(), SelectionKey.OP_READ); 52 } else if (selectionKey.isReadable()) { 53 //处理read事件 54 System.out.println("服务端收到数据"); 55 SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); 56 //创建读取缓冲区 57 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); 58 //read方法是一个阻塞方法 但read是响应事件,此时必定有客户端发送了消息 处理的是read事件 必定马上执行 不会阻塞线程 59 int len = socketChannel.read(byteBuffer); 60 if (len != -1) { 61 System.out.println("服务端收到数据;" + new String(byteBuffer.array(), 0, len)); 62 } 63 byteBuffer = ByteBuffer.wrap("hello".getBytes()); 64 socketChannel.write(byteBuffer); 65 selectionKey.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); 66 socketChannel.close(); 67 } 68 } 69 }
客户端代码实例
1 package com.socket.nio.client; 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.util.Iterator; 10 11 public class NIOClient { 12 13 private Selector selector; 14 15 public static void main(String[] args) throws Exception { 16 NIOClient nioClient=new NIOClient(); 17 nioClient.initSocket("127.0.0.1",8888); 18 nioClient.connect(); 19 } 20 21 private void initSocket(String ip, int port) throws IOException { 22 //创建一个socket通道 并且设置为非阻塞 23 SocketChannel socketChannel = SocketChannel.open(); 24 socketChannel.configureBlocking(false); 25 26 //创建一个selector 27 this.selector = Selector.open(); 28 //连接服务端 29 socketChannel.connect(new InetSocketAddress(ip,port)); 30 //注册当前的socketchannel到selector 31 socketChannel.register(selector, SelectionKey.OP_CONNECT); 32 } 33 int count =1; 34 public void connect() throws Exception{ 35 while(true){ 36 //对于客户端来说 一个客户端一个channel 此时select()是一个非阻塞方法 37 //可以通过输出count,确定此时的select()是非阻塞的 38 selector.select(); 39 Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); 40 System.out.println(count++); 41 while (iterator.hasNext()){ 42 SelectionKey selectionKey = iterator.next(); 43 iterator.remove(); 44 SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); 45 if (selectionKey.isConnectable()) { 46 if(socketChannel.isConnectionPending()){ 47 socketChannel.finishConnect(); 48 } 49 socketChannel.configureBlocking(false); 50 //发送消息给服务端 51 ByteBuffer byteBuffer = ByteBuffer.wrap("hello server".getBytes()); 52 socketChannel.write(byteBuffer); 53 socketChannel.register(this.selector, SelectionKey.OP_READ); 54 }else if(selectionKey.isReadable()){ 55 ByteBuffer byteBuffer=ByteBuffer.allocate(1024); 56 int len = socketChannel.read(byteBuffer); 57 if(len!=-1){ 58 System.out.println("收到服务端消息:"+new String(byteBuffer.array(),0,len)); 59 } 60 } 61 // socketChannel.close(); 62 } 63 } 64 } 65 }
劣势
因为selector多路复用器 会轮询所有的channel 如果有10000个channel 只有10个有动作 那么就会造成大量的轮询浪费 是不合理的
所以,在之后的处理中 NIO使用了epoll的方式 把轮询 变成响应(监听)