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 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 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的方式  把轮询 变成响应(监听)

相关