NIO服务器以异步非阻塞方式发送和接收数据

  1. IO模型:BIO传统阻塞IO、NIO同步非阻塞IO、AIO异步非阻塞IO。
  2. 同步与阻塞:同步指读写过程,同步读写必须得到对方响应才继续往下进行,异步读写无须得到对方响应即可往下进行;阻塞与否是指线程。
  3. NIO:同步非阻塞IO模型,它的读写是同步的,线程的处理是非阻塞的,但是,不代表它不能实现“异步非阻塞”的效果。由于它的线程是非阻塞的,就好比BIO中开辟多个线程实现异步读写一样,NIO同样可以实现异步读写。
  4. NIO服务端测试代码

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.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

/*
 * 同步非阻塞IO服务端
 */
public class NIOServer {
	private static List<byte[]> command = new ArrayList<byte[]>();

	private static void handlerAccept(SelectionKey key, Selector selector) throws IOException {
		// 选择器关注了ServerSocketChannel通道的OP_ACCEPT事件,所以这里的通道肯定是ServerSocketChannel通道
		ServerSocketChannel ssc = (ServerSocketChannel) key.channel();

		// ServerSocketChannel通道的accept()与ServerSocket的accept()差不多,较ServerSocket多了一些nio操作api
		SocketChannel socketChannel = ssc.accept();

		socketChannel.configureBlocking(false);
		// 通道注册选择器,并让选择器关注它“读事件”和“写事件”
		socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);

		System.out.println("服务器 接受连接:" + socketChannel.getRemoteAddress());
	}

	private static void handlerRead(SelectionKey key) throws IOException {
		// 选择器关注了SocketChannel通道的OP_READ事件,所以这里的通道肯定是SocketChannel通道
		SocketChannel socketChannel = (SocketChannel) key.channel();

		ByteBuffer readBuff = ByteBuffer.allocate(1024);
		readBuff.clear();

		int readSize = socketChannel.read(readBuff);

		int pos = readBuff.position();
		readBuff.flip();

		byte[] bs = Arrays.copyOf(readBuff.array(), pos);

		if (readSize > 0) {
			System.out.print("服务器 读取数据 " + System.currentTimeMillis() / 1000 + " <== ");
			for (byte b : bs) {
				String hex = Integer.toHexString(b & 0xFF);
				System.out.print(hex.length() < 2 ? "0" + hex : hex);
			}

			System.out.println("");
		} else if (readSize == 0) {
			// 没有数据可读,可不操作
		} else {
			System.out.println("服务器 断开连接" + socketChannel.getRemoteAddress());

			// key.cancel()后会在下次select()期间注销该key对应的通道,而非即时注销
			key.cancel();
		}
	}

	private static void handlerWrite(SelectionKey key) throws IOException {
		if (command.size() > 0) {

			ByteBuffer writeBuff = ByteBuffer.wrap(command.get(0));
			command.remove(0);

			SocketChannel socketChannel = (SocketChannel) key.channel();
			socketChannel.write(writeBuff);
			System.out.println(
					"服务器 发送数据 " + System.currentTimeMillis() / 1000 + " ==> " + HexUtil.bytes2Hex(writeBuff.array()));
		}
	}

	private static void autoRunCommand() {
		new Thread(new Runnable() {
			public void run() {
				while (true) {
					command.add(new byte[] { 0x1a, 0x2b, 0x3c });
					try {
						Thread.sleep(3000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}

		}).start();
	}

	public static void main(String[] args) throws Exception {
		// 自动创建命令,用于向客户发送数据
		autoRunCommand();

		// SelectorProvider提供了各种channel的open,各种channel的底层还是调用SelectorProvider
		SelectorProvider selectorProvider = SelectorProvider.provider();

		ServerSocketChannel serverSocketChannel = selectorProvider.openServerSocketChannel();
		serverSocketChannel.configureBlocking(false);// 先设置非阻塞模式,再设置其他,最后bind端口
		serverSocketChannel.socket().bind(new InetSocketAddress(1024), 10);

		Selector selector = selectorProvider.openSelector();
		serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);// channel注册选择器,并设置选择器对此channel感兴趣的事件

		System.out.println("NIO服务器就绪,等待连接....");

		while (true) {
			/*
			 * 选择器的select()是阻塞的,当有感兴趣的事件时才往下进行;返回int值为较上次select()变化个数【非现存个数】
			 */
			if (selector.select(1000) == 0)
				continue;

			Iterator<SelectionKey> it = selector.selectedKeys().iterator();

			while (it.hasNext()) {
				SelectionKey key = it.next();
				/*
				 * 此处key为nio自身维护的,与以往认识有误不同,必须remove,否则存在重复消费问题。
				 */
				it.remove();

				if (key.isValid() && key.isAcceptable())
					handlerAccept(key, selector);

				if (key.isValid() && key.isReadable())
					handlerRead(key);

				if (key.isValid() && key.isWritable())
					handlerWrite(key);
			}
		}
	}
}
  1. BIO客户端测试代码

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*
 * 传统同步阻塞IO客户端
 */
public class BIOClient {
	private Socket socket;
	private InputStream input;
	private OutputStream output;
	private ExecutorService pool = Executors.newFixedThreadPool(2);

	public static void main(String[] args) throws IOException {
		new BIOClient("localhost", 1024).start();
	}

	private BIOClient(String address, int port) {
		try {
			socket = new Socket(address, port);
			this.input = socket.getInputStream();
			this.output = socket.getOutputStream();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void start() {
		pool.submit(new Runnable() {
			public void run() {
				new Writer().run();
			}
		});

		pool.submit(new Runnable() {
			public void run() {
				new Reader().run();
			}
		});
	}

	private class Reader extends Thread {
		@Override
		public void run() {
			try {
				byte[] bs = new byte[1024];

				int readSize = 0;
				while ((readSize = input.read(bs)) > 0) {
					System.out.print("客户端 读取数据 <== ");

					for (byte b : Arrays.copyOf(bs, readSize)) {
						String hex = Integer.toHexString(b & 0xFF);
						System.out.print(hex.length() < 2 ? "0" + hex : hex);
					}

					System.out.println("");
				}

				System.out.println("客户端 读线程关闭");

				if (!socket.isClosed())
					socket.close();

				if (!pool.isShutdown())
					pool.shutdown();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	private class Writer extends Thread {
		@Override
		public void run() {
			while (true) {
				try {
					Thread.sleep(5000);

					byte[] bs = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };

					if (null != output) {
						output.write(bs);

						System.out.print("客户端 发送数据 ==> ");

						for (byte b : bs) {
							String hex = Integer.toHexString(b & 0xFF);
							System.out.print(hex.length() < 2 ? "0" + hex : hex);
						}

						System.out.println("");

					} else {
						System.out.println("客户端 写线程关闭");

						if (!socket.isClosed())
							socket.close();

						if (!pool.isShutdown())
							pool.shutdown();

						break;
					}

				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}
}

你可能感兴趣的:(工作笔记,java,socket,nio,多线程)