netty入门二NIO

5 篇文章 0 订阅
4 篇文章 0 订阅

s1.NIO概述

首先大家形成一个共识,那就是在我们讨论IO模型的时候,目前来说都是针对网络编程而言,因为网络编程才有大规模的连接,IO输入输出,NIO出现的目的也正是为了解决BIO网络编程模型中大量连接产生大量线程的痛点。

IO模型一般包含三种

BIO,同步阻塞IO,参考下图BIO的线程模型

NIO,同步非阻塞IO

AIO,异步非阻塞IO

2.NIO核心API

2.1Channel

channel与IO流的区别:

channel即可以读,也可以写,IO流输入输出是不同的流,且输入后必须同步的输出

channel读写可以在不同的线程执行,即异步执行,这是最大的区别

公共API

//从通道读数据到buffer  channel->buffer

int read(ByteBuffer var1);

//从buffer写数据到通道  buffer->channel

int write(ByteBuffer var1);

//关闭通道

final void close() throws IOException;

1.ServerSocketChannel

//打开一个server通道

static ServerSocketChannel open() throws IOException;

//给当前server通道绑定一个网络地址监听

final ServerSocketChannel bind(SocketAddress var1) throws IOException;

//监听客户端的socket连接, 此方法默认为阻塞,configureBlocking设置为false,则不阻塞

SocketChannel accept();

//设置false则为NIO非阻塞模式,默认true即是与BIO一样为阻塞模式

final SelectableChannel configureBlocking(boolean var1) throws IOException;

//把当前通道注册到selecor上,var2表示注册事件类型,var3可以附加一个任意对象

final SelectionKey register(Selector var1, int var2, Object var3);

//获取当前server通道监听的网络地址

SocketAddress getLocalAddress() throws IOException;

//是否阻塞模式

boolean isBlocking();

//是否已注册到某个selector上

boolean isRegistered();

代码示例:

public static void main(String[] args) throws Exception {
		String host = "127.0.0.1";
		int port = 9991;
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open().bind(new InetSocketAddress(host, port));
		serverSocketChannel.configureBlocking(false);
		SocketAddress localAddress = serverSocketChannel.getLocalAddress();
		System.out.println(serverSocketChannel.isBlocking());
		System.out.println(serverSocketChannel.isRegistered());
		System.out.println(localAddress);
		SocketChannel socketChannel = serverSocketChannel.accept();
		System.out.println(socketChannel);
	}

2.SocketChannel

//打开一个通道

static SocketChannel open() throws IOException;

//为当前通道绑定一个网络地址监听

SocketChannel bind(SocketAddress local) throws IOException;

//与服务端通道建立连接,此方法默认为阻塞,configureBlocking设置为false,则不阻塞

boolean connect(SocketAddress remote) throws IOException;

//非阻塞模式下调用此方法来判断连接是否建立成功,此方法内部为阻塞模式,此方法的意义在于发起建立连接请求以后,有一定的延时,这段时间可以做一些事情,当事情做完后,再调用此方法来判断连接是否建立完成

boolean finishConnect() throws IOException;

//此方法内部只是返回一个是否连接中的状态标识

boolean isConnected();

//设置false则为NIO非阻塞模式,默认true即是与BIO一样为阻塞模式

final SelectableChannel configureBlocking(boolean var1) throws IOException;

//打开一个通道并与服务端通道建立连接,建立连接的过程为阻塞模式

static SocketChannel open(SocketAddress var0) throws IOException;

//获取当前通道监听的网络地址

SocketAddress getLocalAddress() throws IOException;

//获取服务端的网络地址

SocketAddress getRemoteAddress() throws IOException;

代码示例:

public static void main(String[] args) throws IOException {
    String host = "127.0.0.1";
    int port = 9992;
    String serverHost = "127.0.0.1";
    int serverPort = 18001;
    SocketChannel socketChannel = SocketChannel.open();
    socketChannel.bind(new InetSocketAddress(host, port));
    socketChannel.configureBlocking(false);
    boolean connected = socketChannel.connect(new InetSocketAddress(serverHost, serverPort));
    System.out.println(connected);
    System.out.println(socketChannel.isConnected());
    System.out.println(socketChannel.finishConnect());
    
}

3.FileChannel

//打开一个磁盘文件的IO通道,OpenOption为对该通道的操作类型,包括读、写、删除

static FileChannel open(Path var0, OpenOption... var1);

//通道中数据的字节大小

long size();

//把通道中的数据映射到buffer中

MappedByteBuffer map(MapMode var1, long var2, long var4);

//把当前通道中的数据copy到目标通道var5

long transferTo(long var1, long var3, WritableByteChannel var5);

//把目标通道var1中的数据copy到当前通道

long transferFrom(ReadableByteChannel var1, long var2, long var4);

代码示例:

public static void copyFile() {
    FileChannel source = null;
    FileChannel target = null;
    try {
        source = FileChannel.open(Paths.get("C:\\Users\\Lenovo\\Desktop\\shell.txt"));
        target = FileChannel.open(Paths.get("C:\\Users\\Lenovo\\Desktop\\shell2.txt"), StandardOpenOption.WRITE,
                StandardOpenOption.CREATE);
        source.transferTo(0, source.size(), target);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            source.close();
            target.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2.2ByteBuffer

Buffer中核心的几个属性

private int position = 0;  //当前操作位,无论读写都会+1

//limit表示当前操作无论读写操作的次数,初次构建buffer的时候,该值<=capacity
private int limit; 

private int capacity; //容量,创建一个buffer时,必须指定容量,即每次读写最大上限

//创建一个容量大小为capacity的字节缓冲

static ByteBuffer allocate(int capacity);

//首先创建一个等容的缓存,再把字节数组写到缓冲

static ByteBuffer wrap(byte[] var0);

//获取一个字节

byte get();

//添加一个字节

ByteBuffer put(byte var1); 

//把缓冲中的字节数组直接返回,注意不是深clone

final byte[] array();

//buffer中是否还有未读取的数据

final boolean hasRemaining();

//每次从buffer中读数据之前,先flip一下,因为读之前,肯定发生过写,因此需要把position置为0,limit置为写的时候写到的位置

public final Buffer flip() {
        this.limit = this.position;
        this.position = 0;
        this.mark = -1;
        return this;
    }

//每次从buffer中读完数据之后,必须clear一下,把position再次置为0,limit置为capacity,接下来可能要继续向buffer中写数据了

public final Buffer clear() {
        this.position = 0;
        this.limit = this.capacity;
        this.mark = -1;
        return this;
    }

代码示例:

注意:以下代码中当read==-1,即表示提供数据的channel通道已断开,read==0表示通道数据已读完,read > 0表示可以继续从通道读取数据

public static String readBuffer(SocketChannel channel, ByteBuffer buffer) throws IOException {
		List<Byte> list = new ArrayList<>();
		int read = channel.read(buffer);
		while (read > 0) {
			buffer.flip();
			while (buffer.hasRemaining()) {
				list.add(buffer.get());
			}
			buffer.clear();
			read = channel.read(buffer);
		}
		if (read == -1) {
			throw new ClosedChannelException();
		}
		byte[] bytes = new byte[list.size()];
		for (int i = 0; i < bytes.length; i++) {
			bytes[i] = list.get(i);
		}
		return new String(bytes);
	}

2.3Selector

// 获取所有的事件key

public abstract Set<SelectionKey> keys();

//获取已经准备好的所有事件key,注意与keys方法区别

public abstract Set<SelectionKey> selectedKeys();

//非阻塞返回就绪的事件个数

public abstract int selectNow() throws IOException;

//阻塞var1毫秒返回就绪的事件个数

public abstract int select(long var1) throws IOException;

//阻塞到有事件就绪后返回

public abstract int select() throws IOException;

2.4SelectionKey

//在事件上注册的通道

SelectableChannel channel();

//当前事件所在的选择器

Selector selector();

//取消事件,注意该方法与从selector的keys中remove都要执行才会真正的取消事件

void cancel();

//附加一个对象

final Object attach(Object var1); 

//获取附加的对象

final Object attachment();

final boolean isReadable();  //读事件

final boolean isWritable();  //写事件

final boolean isAcceptable();  //接收连接事件

final boolean isConnectable(); //连接事件

3. NIO模拟向服务端发起请求并接收一个响应代码示例

1.buffer工具类

public class BufferUtils {

	public static String readBuffer(SocketChannel channel, ByteBuffer buffer) throws IOException {
		return readBuffer(channel, buffer, false);
	}

	/**
	 * @Description: 从通道读取数据
	 * @param channel            一个SocketChannel通道
	 * @param buffer             缓冲对象
	 * @param checkChannelClosed 是否检测通道是否断开
	 * @return 返回读取的数据字符串
	 * @throws IOException
	 */
	public static String readBuffer(SocketChannel channel, ByteBuffer buffer, boolean checkChannelClosed)
			throws IOException {
		List<Byte> list = new ArrayList<>();
		int read = channel.read(buffer);
		while (read > 0) {
			buffer.flip();
			while (buffer.hasRemaining()) {
				list.add(buffer.get());
			}
			buffer.clear();
			read = channel.read(buffer);
		}

		if (read == -1 && checkChannelClosed) {
			throw new ClosedChannelException();
		}

		byte[] bytes = new byte[list.size()];
		for (int i = 0; i < bytes.length; i++) {
			bytes[i] = list.get(i);
		}
		return new String(bytes);
	}

}

2.Server端

public static void main(String[] args) throws Exception {
    String host = "127.0.0.1";
    int port = 9991;
    Server server = Server.createServer(host, port);
    server.start(1000, 64);
}

// NIOServer对象
static class Server {

    // serverSocketChannel
    private ServerSocketChannel serverSocketChannel;

    /**
        * @Description: 创建一个NIOServer实例
        * @param host
        * @param port
        * @return
        * @throws Exception
        */
    public static Server createServer(String host, int port) throws IOException {
        Server server = new Server();
        server.init(host, port);
        return server;
    }

    private Server() {
    }

    // 初始化serverSocketChannel
    private void init(String host, int port) throws IOException {
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(host, port));
        serverSocketChannel.configureBlocking(false);
    }

    /**
        * @Description: 启动Server
        * @param selectTimeout  选择器获取就绪事件的阻塞时长
        * @param bufferCapacity buffer的容量
        * @throws IOException
        */
    public void start(long selectTimeout, int bufferCapacity) throws IOException {
        final long timeout = selectTimeout <= 0 ? 1000 : selectTimeout;
        final int capacity = bufferCapacity <= 12 ? 12 : bufferCapacity;
        System.out.println("........NIOServer.start..........");

        // 打开选择器
        Selector selector = Selector.open();
        // 注册
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        try {
            while (true) {
                // 循环监听就绪事件
                if (selector.select(timeout) > 0) {
                    // 获取就绪事件
                    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                    // 迭代处理就绪事件
                    while (iterator.hasNext()) {
                        SelectionKey key = iterator.next();
                        try {
                            if (key.isAcceptable()) { // 接收到连接事件
                                SocketChannel channel = serverSocketChannel.accept();
                                InetSocketAddress address = (InetSocketAddress) channel.getRemoteAddress();
                                String hostName = address.getHostName();
                                int port = address.getPort();
                                System.out.println(hostName + ":" + port + "->建立了连接事件...");
                                channel.configureBlocking(false);
                                channel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(capacity));
                            } else if (key.isReadable()) { // 读事件
                                this.handler(key);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }

                        iterator.remove(); // 删除处理完的事件
                    }
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                serverSocketChannel.close();
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("连接已关闭");
        }

    }

    // 处理读事件
    private void handler(SelectionKey key) throws IOException {
        try {
            SocketChannel channel = (SocketChannel) key.channel();
            InetSocketAddress remoteAddress = (InetSocketAddress) channel.getRemoteAddress();
            ByteBuffer buffer = (ByteBuffer) key.attachment();
            String message = BufferUtils.readBuffer(channel, buffer, true);
            message = remoteAddress.getHostName() + ":" + remoteAddress.getPort() + " message -> " + message;
            System.out.println(message);
            Thread.sleep(50); // 模拟业务处理耗时
            // 构建响应消息
            String response = "status=200;server已收到来自 " + message;
            channel.write(ByteBuffer.wrap(response.getBytes()));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            key.channel().close();
        }
    }

}

3. client端

public static void main(String[] args) throws IOException {
    String host = "127.0.0.1";
    int port = 9991;
    Client client = Client.createClient();
    String message = "client call";
    for (int i = 0; i < 10; i ++) {
        new Thread(() -> {
            String response = client.call(host, port, message);
            System.out.println(response);
        }).start();
    }
}

static class Client {

    // 创建Client客户端
    public static Client createClient() {
        Client client = new Client();
        return client;
    }

    private Client() {
    }

    /**
        * 
        * @Description: 向服务器端发送请求
        * @param host    服务器host
        * @param port    服务器port
        * @param message 发送的消息
        * @return
        */
    public String call(String host, int port, String message) {
        SocketChannel channel = null;
        try {
            //打开通道并以阻塞模式连接服务器端
            channel = SocketChannel.open(new InetSocketAddress(host, port));
            //把消息存入缓冲
            ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
            //从缓冲写数据到通道
            channel.write(buffer);
            ByteBuffer byteBuffer = ByteBuffer.allocate(64);
            
            //从通道读服务端响应的数据
            String response = BufferUtils.readBuffer(channel, byteBuffer);
            return response;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                channel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

}

4. NIO模拟长链接群聊系统示例:

需求:

服务端监听各个客户端上线、下线,并且转发任意客户端的消息给所有的客户端

设计思路:

首先抽象出以下几个对象

Commond 指令对象

指令对象又分为服务端指令和客户端指令 

Server 对象

client 对象

最终根据当下的时间还是没用GUI,简单的用了一下控制台输入扫描Scaner

代码如下:(累了,注释后面补充)

1.BufferUtils 

参考3.1BufferUtils代码片段

2. commond

package cn.qu.scanner;

import java.util.Map;
import java.util.function.Function;

public interface Commond {
	
	/**
	 * @Description: 发起停止指令
	 */
	void stop();
	
	/**
	 * @Description: 轮询监听
	 */
	void listen();
	
	/**
	 * @Description: 构建指令集
	 * @param commondContainer 存放指令的容器
	 */
	void buildCommondContainer(Map<String, Function<String, Object>> commondContainer);	

}

3.ScannerCommond

package cn.qu.scanner;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Collectors;

//控制台命令组件
public abstract class ScannerCommond implements Commond {

	private AtomicBoolean flag = new AtomicBoolean(true);
	private Scanner scaner;
	private final Map<String, Function<String, Object>> commondContainer = new HashMap<>();
	private List<String> commondKeyList;

	protected ScannerCommond() {
		this.init();
	}

	@Override
	public void stop() {
		flag.compareAndSet(true, false);
	}

    //监听控制台输入命令的task
	@Override
	public void listen() {
		while (flag.get()) {
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			if (scaner.hasNext()) {
				String commond = scaner.nextLine();
				if (checkCommond(commond)) {
					String commondKey, commondContent;
					if (commond.contains(":")) {
						commondKey = commond.substring(0, commond.indexOf(":"));
						commondContent = commond.substring(commond.indexOf(":") + 1);
					} else {
						commondKey = commond;
						commondContent = commond;
					}
					Function<String, Object> function = commondContainer.get(commondKey);
					if (function != null) {
						function.apply(commondContent);
					}
				}
			}
		}
	}

    //检查输入的命令是否合法
	private boolean checkCommond(String commond) {
		for (String commondKey : commondKeyList) {
			if (commond.startsWith(commondKey)) {
				return true;
			}
		}
		return false;
	}

	private void init() {
		this.buildCommondContainer(commondContainer);
		this.commondKeyList = commondContainer.keySet().stream().collect(Collectors.toList());
		scaner = new Scanner(System.in);
	}

}

4.Server

package cn.qu.nio.groupchat;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;

import org.springframework.util.StringUtils;

import cn.qu.scanner.ScannerCommond;
import cn.qu.util.BufferUtils;

public class GroupChatServer {

	public static void main(String[] args) {
		String host = "127.0.0.1";
		int port = 18001;
		Server server = Server.createServer(host, port);
		new Thread(server).start();
		GroupChatServerScaner.start(server);
	}

    //服务端的控制台命令组件
	static class GroupChatServerScaner extends ScannerCommond {

		private GroupChatServerScaner() {
		}

		private final static GroupChatServerScaner scaner = new GroupChatServerScaner();

		private Server server;

		public static void start(Server server) {
			scaner.server = server;
			scaner.listen();
		}

		@Override
		public void buildCommondContainer(Map<String, Function<String, Object>> commondContainer) {

			commondContainer.put("stop", (param) -> {
				try {
					this.server.stop();
					this.stop();
				} catch (Exception e) {
					e.printStackTrace();
				}
				return null;
			});
		}

	}

    //NioServer
	static class Server implements Runnable {
		private AtomicBoolean flag;
		private ServerSocketChannel serverSocketChannel;
		Selector selector = null;
		private InetSocketAddress address;

        //创建一个Server对象
		public static Server createServer(String host, int port) {
			Server server = new Server();
			try {
				server.init(host, port);
				return server;
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}

        //server工作task
		@Override
		public void run() {
			try {
				while (flag.get()) {
					if (selector.select(1000) > 0) {
						Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
						while (iterator.hasNext()) {
							SelectionKey key = iterator.next();
							try {
								if (key.isAcceptable()) {
									this.onLine();
								} else if (key.isReadable()) {
									this.readMessage(key);
								}
							} catch (IOException e) {
								key.cancel();
								try {
									key.channel().close();
								} catch (IOException e1) {
									e1.printStackTrace();
								}
								// e.printStackTrace();
							}
							iterator.remove(); // 删除处理完的事件
						}
					}
					
				}

			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				this.close();
			}
		}

        //停止服务端server
		public void stop() {
			this.flag.set(false);
		}

		private Server() {
		}

		private void init(String host, int port) throws IOException {
			address = new InetSocketAddress(host, port);
			serverSocketChannel = ServerSocketChannel.open();
			serverSocketChannel.bind(address);
			serverSocketChannel.configureBlocking(false);
			selector = Selector.open();
			serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
			this.flag = new AtomicBoolean(true);
			String current = address.getHostName() + ":" + address.getPort();
			System.out.println("-----------------------" + current + " server is started-----------------------");
		}

        //上线,接收accept事件,并注册客户端channel到selector
		private void onLine() throws IOException {
			String user = "";
			try {
				SocketChannel channel = serverSocketChannel.accept();
				InetSocketAddress clientAddress = (InetSocketAddress) channel.getRemoteAddress();
				String hostName = clientAddress.getHostName();
				int port = clientAddress.getPort();
				user = hostName + ":" + port;
				System.out.println(user + " -> 已上线...");
				channel.configureBlocking(false);
				channel.register(selector, SelectionKey.OP_READ, user);
			} catch (IOException e) {
				System.out.println("远程服务建立连接异常... clientAddress=" + user);
				throw e;
			}
		}

        //发送消息给所有客户端channel,writer事件
		private void sendMessage(Selector selector, String message) throws IOException {
			try {
				Iterator<SelectionKey> iterator = selector.keys().iterator();
				while (iterator.hasNext()) {
					SelectionKey key = iterator.next();
					SelectableChannel selectableChannel = key.channel();
					if (!(selectableChannel instanceof ServerSocketChannel)) {
						SocketChannel channel = (SocketChannel) selectableChannel;
						channel.write(ByteBuffer.wrap(message.getBytes()));
					}
				}
			} catch (IOException e) {
				System.out.println("转发消息异常... message=" + message);
				throw e;
			}
		}

        //读事件
		private void readMessage(SelectionKey key) throws IOException {
			SocketChannel channel = (SocketChannel) key.channel();
			String user = (String) key.attachment();
			String message = "";
			try {
				message = BufferUtils.readBuffer(channel, ByteBuffer.allocate(1024), true);
				if (!StringUtils.isEmpty(message)) {
					message = user + " -> " + message;
					this.sendMessage(selector, message);
				}
			} catch (IOException e) {
				System.out.println(user + " -> " + "已下线...");
				throw e;
			}
		}

        //关闭服务端channel
		private void close() {
			try {
				serverSocketChannel.close();
				selector.close();
				String current = address.getHostName() + ":" + address.getPort();
				System.out.println("-----------------------" + current + " server is stoped-----------------------");
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}

}

 5.client

package cn.qu.nio.groupchat;

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.util.Iterator;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;

import cn.qu.scanner.ScannerCommond;
import cn.qu.util.BufferUtils;

public class GroupChatClient {

	public static void main(String[] args) {
		String serverHost = "127.0.0.1";
		int serverPort = 18001;
		Client client = Client.create(serverHost, serverPort);
		new Thread(client).start();
		GroupChatClientScaner.start(client);
	}

    //客户端命令组件
	static class GroupChatClientScaner extends ScannerCommond {

		private GroupChatClientScaner() {
		}

		private final static GroupChatClientScaner scaner = new GroupChatClientScaner();

		public static void start(Client client) {
			scaner.client = client;
			scaner.listen();
		}

		private Client client;

		@Override
		public void buildCommondContainer(Map<String, Function<String, Object>> commondContainer) {

			commondContainer.put("stop", (commond) -> {
				try {
					this.client.stop();
					this.stop();
				} catch (Exception e) {
					e.printStackTrace();
				}
				return null;
			});

			commondContainer.put("say", (commond) -> {
				this.client.sendMessage(commond);
				return null;
			});
		}

	}

    //Nio客户端
	static class Client implements Runnable {

		private SocketChannel socketChannel;
		private AtomicBoolean flag;
		private Selector selector;
		private InetSocketAddress serverAddress;
		private InetSocketAddress address;

        //创建client对象
		public static Client create(String host, int port) {
			Client client = new Client();
			try {
				client.init(host, port);
				return client;
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}

		private Client() {
		}

        //初始化
		public void init(String host, int port) throws IOException {
			selector = Selector.open();
			serverAddress = new InetSocketAddress(host, port);
			socketChannel = SocketChannel.open(serverAddress);
			socketChannel.configureBlocking(false);
			socketChannel.register(selector, SelectionKey.OP_READ);
			flag = new AtomicBoolean(true);
			address = (InetSocketAddress) socketChannel.getLocalAddress();
			String current = address.getHostName() + ":" + address.getPort();
			System.out.println("-----------------------" + current + " client is started-----------------------");
		}

        //停止客户端
		public void stop() {
			flag.compareAndSet(true, false);
		}

        //客户端工作task
		@Override
		public void run() {

			try {
				while (flag.get()) {
					if (selector.select(1000) > 0) {
						Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
						while (iterator.hasNext()) {
							SelectionKey key = iterator.next();
							if (key.isReadable()) {
								this.readMessage(key);
							}
							iterator.remove();
						}
					}
				}

			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				this.close();
			}
		}

        //关闭客户端
		private void close() {
			try {
				selector.close();
				socketChannel.close();
				String current = address.getHostName() + ":" + address.getPort();
				System.out.println("-----------------------" + current + " client is stoped-----------------------");
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

        //发送消息,写事件
		public void sendMessage(String message) {
			try {
				socketChannel.write(ByteBuffer.wrap(message.getBytes()));
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

        //读取消息,读事件,注意key.cancel
		private void readMessage(SelectionKey key) {
			SocketChannel channel = (SocketChannel) key.channel();
			String message = "";
			try {
				message = BufferUtils.read(channel, ByteBuffer.allocate(1024), true);
			} catch (IOException e) {
				message = serverAddress.getHostName() + ":" + serverAddress.getPort() + " -> 服务器已掉线...";
				key.cancel();
				try {
					channel.close();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				// e.printStackTrace();
			}
			message = "[" + address.getHostName() + ":" + address.getPort() + "] ==>> " + message;
			System.out.println(message);
		}

	}
}

5.Nio 多线程模型实例

此处只演示了NioServer端代码

主要涉及如下几个对象:

MainReactor:主reactor用于处理所有accept事件

Reactor:其实是对selector事件的封装,这里的reactor其实指的就是subReactor

PollingPool:轮询池,持有多个reactor的集合,内部get方法会轮询获取一个reactor

InHandler:封装reactor的读

OutHandler:封装reactor的写

代码如下:

package cn.qu.nio.reactor;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

public class NioServerMultiThreadDemo {

	final static int nThreads = 10;

	public static void main(String[] args) throws IOException {
		start();
	}

	// 主线程启动Server
	public static void start() throws IOException {
		String host = "127.0.0.1";
		int port = 18001;
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open().bind(new InetSocketAddress(host, port));
		serverSocketChannel.configureBlocking(false);
		ThreadPoolExecutor threadPool = ThreadPoolUtils.create(nThreads, 10000);

		PollingPool<Reactor> pollingPool = PollingPool.create(new Reactor(threadPool), new Reactor(threadPool),
				new Reactor(threadPool), new Reactor(threadPool), new Reactor(threadPool));
		new MainReactor(pollingPool).register(serverSocketChannel);
		new MainReactor(pollingPool).register(serverSocketChannel);
	}

	// 出站执行器
	static class OutHandler implements Runnable {
		private SocketChannel channel;
		private InetSocketAddress remoteAddress;

		public OutHandler(SocketChannel channel) {
			this.channel = channel;
		}

		@Override
		public void run() {
			try {
				remoteAddress = (InetSocketAddress) channel.getRemoteAddress();
				String message = "收到消息来自 -> " + remoteAddress.getHostName() + ":" + remoteAddress.getPort();
				channel.write(ByteBuffer.wrap(message.getBytes()));
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				this.close();
			}

		}

		private void close() {
			try {
				channel.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	// 入站执行器
	static class InHandler implements Runnable {

		private SocketChannel channel;
		private InetSocketAddress remoteAddress;
		private final static int capacity = 64;

		public InHandler(SocketChannel channel) {
			this.channel = channel;
		}

		@Override
		public void run() {
			try {
				ByteBuffer buffer = ByteBuffer.allocate(capacity);
				remoteAddress = (InetSocketAddress) channel.getRemoteAddress();
				String message = BufferUtils.read(channel, buffer);
				this.beforeFilter(message);
				this.handler(message);
				this.afterFilter(message);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		private void handler(String message) {
			System.out.println(remoteAddress.getHostName() + ":" + remoteAddress.getPort() + " ==>> " + message);
		}

		private void beforeFilter(String message) {
			try {
				Thread.sleep(20);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			message = "before<<" + message;
		}

		private void afterFilter(String message) {
			message += ">>after";
		}

	}
	
	//主reactor,注意主accept事件不要cancel
	static class MainReactor {

		public MainReactor(PollingPool<Reactor> pollingPool) {
			this.pollingPool = pollingPool;
		}

		private PollingPool<Reactor> pollingPool;

		private AtomicBoolean flag;

		private Selector selector;

		public void register(ServerSocketChannel channel) throws IOException {

			if (channel == null) {
				return;
			}

			if (selector == null) {
				this.listen();
			}

			channel.configureBlocking(false);
			channel.register(selector, SelectionKey.OP_ACCEPT);

		}

		private void listen() throws IOException {
			this.flag = new AtomicBoolean(true);
			selector = Selector.open();
			new Thread(() -> {
				try {
					while (flag.get()) {
						if (selector.selectNow() > 0) {
							Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
							while (iterator.hasNext()) {
								SelectionKey key = iterator.next();
								if (key.isAcceptable()) {
									ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
									System.out.println("main-react -> " + this.hashCode());
									SocketChannel channel = serverSocketChannel.accept();
									pollingPool.get().register(channel);
								}
								iterator.remove();
							}
						} else {
							Thread.sleep(1);
						}
					}
				} catch (IOException | InterruptedException e) {
					e.printStackTrace();
				} finally {
					try {
						selector.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}).start();
		}

		public void destory() {
			if (flag != null) {
				flag.compareAndSet(true, false);
			}
		}

	}

	// 一个reactor持有一个selector,各个reactor共有一个线程池
	static class Reactor {

		public Reactor(ThreadPoolExecutor threadPool) {
			this.threadPool = threadPool;
		}
		
		private ThreadPoolExecutor threadPool;

		private AtomicBoolean flag;

		private Selector selector;

		// 注册channel给selecor
		public void register(SocketChannel channel) throws IOException {

			if (channel == null) {
				return;
			}

			if (selector == null) {
				this.listen();
			}

			channel.configureBlocking(false);
			channel.register(selector, SelectionKey.OP_READ);

		}

		// 当前reactor的监听task,监听selector的读、写事件封装进对应Handler,并交由线程池处理
		private void listen() throws IOException {
			this.flag = new AtomicBoolean(true);
			selector = Selector.open();
			new Thread(() -> {
				try {
					while (flag.get()) {
						if (selector.selectNow() > 0) {
							Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
							while (iterator.hasNext()) {
								SelectionKey key = iterator.next();
								if (key.isReadable()) {
									// 开始交由handler读
									SocketChannel channel = (SocketChannel) key.channel();
									channel.register(selector, SelectionKey.OP_WRITE);
									threadPool.execute(new InHandler(channel));
								} else if (key.isWritable()) {
									// 开始交由handler写
									SocketChannel channel = (SocketChannel) key.channel();
									key.cancel();
									threadPool.execute(new OutHandler(channel));
								}
								iterator.remove();

							}
						} else {
							Thread.sleep(1);
						}
					}
				} catch (IOException | InterruptedException e) {
					e.printStackTrace();
				} finally {
					try {
						selector.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}).start();
		}

		// 停止当前reactor的监听task
		public void destory() {
			if (flag != null) {
				flag.compareAndSet(true, false);
			}
		}

	}

	// 自定义的轮询池
	static class PollingPool<T> extends ArrayList<T> {

		/**
		 * @Fields serialVersionUID :
		 */
		private static final long serialVersionUID = -7865320985484338340L;

		private PollingPool() {

		}

		@SafeVarargs
		public static <T> PollingPool<T> create(T... t) {
			PollingPool<T> pool = new PollingPool<>();
			for (T e : t) {
				pool.add(e);
			}
			return pool;
		}

		private int position = 0;

		public int position() {
			return position;
		}

		public T get() {
			if (position >= this.size()) {
				position = 0;
			}
			T t = this.get(position);
			position++;
			return t;
		}

	}

	static class ThreadPoolUtils {

		/**
		 * @Description: 创建一个fixed线程池,并指定queue的容量
		 * @param nThreads 线程个数
		 * @param capacity queue最大容量
		 * @return
		 */
		public static ThreadPoolExecutor create(int nThreads, int capacity) {
			return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
					new LinkedBlockingQueue<Runnable>(capacity));
		}

	}

	static class BufferUtils {

		public static String read(SocketChannel channel, ByteBuffer buffer) throws IOException {
			return read(channel, buffer, false);
		}

		/**
		 * @Description: 从通道读取数据
		 * @param channel            一个SocketChannel通道
		 * @param buffer             缓冲对象
		 * @param checkChannelClosed 是否检测通道是否断开
		 * @return 返回读取的数据字符串
		 * @throws IOException
		 */
		public static String read(SocketChannel channel, ByteBuffer buffer, boolean checkChannelClosed)
				throws IOException {
			List<Byte> list = new ArrayList<>();
			int read = channel.read(buffer);
			while (read > 0) {
				buffer.flip();
				while (buffer.hasRemaining()) {
					list.add(buffer.get());
				}
				buffer.clear();
				read = channel.read(buffer);
			}

			if (read == -1 && checkChannelClosed) {
				throw new ClosedChannelException();
			}

			byte[] bytes = new byte[list.size()];
			for (int i = 0; i < bytes.length; i++) {
				bytes[i] = list.get(i);
			}
			return new String(bytes);
		}

	}

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值