Java NIO网络通信编程

本文详细介绍了Java NIO库的原理与应用,包括其核心组件如Buffer、Channel和Selector的使用,以及在服务器端和客户端的示例代码。适合高并发网络应用开发者了解和提升效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

NIO简介

NIO库是在JDK 1.4中引入的,NIO弥补了原来同步阻塞I/O的不足,它在标准Java代码中提供了高速的、面向块的I/O。

NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。

NIO核心组件

在Socket网络通信中主要用到的NIO核心组件是:缓冲区Buffer、通道Channel和多路复用器Selector。

缓冲区Buffer

Buffer是一个缓冲区对象,实质上是一个数组。它包含一些要写入或者要读出的数据。最常用的缓冲区是ByteBuffer,每一种Java基本类型都对应有一种缓冲区。

Buffer对象是NIO库与IO库的一个重要区别。在原面向流的I/O中,可以将数据直接写入或者将数据直接读到Stream对象中;在NIO库中,所有的数据都是用缓冲区处理的,在读取数据的时,它是直接读到缓冲区中的,在写数据时也是写入到缓冲区中。

通道Channel

Channel是一个通道,可以通过它读取和写入数据,它就像自来水管道一样,网络数据通过Channel读取和写入。

通道与流的不同之处在于通道是双向的,而流是单向的(一个流必须是InputStream或者OutputStream的子类)。

实际上Channel可以分为两大类:分别是用于网络读写的SelectableChannel和用于文件操作的FileChannel。Socket网络通信中要用到的ServerSocketChannel和SocketChannel都是SelectableChannel的子类。

多路复用器 Selector

多路复用器Selector提供选择已经就绪的任务的能力。Selector会不断地轮询注册在其上的Channel,如果某个Channel上面有新的TCP连接接入、读和写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的IO操作。

一个Selector可以轮询多个Channel。

示例代码

服务端:
在这里插入图片描述

package com.niotest;

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.util.Date;
import java.util.Iterator;
import java.util.Set;

public class MutiplexerServerTest implements Runnable{

	private Selector selector;
	private ServerSocketChannel servChannel;
	private volatile boolean stop;
	
	/**
	 * 初始化多路复用器、绑定监听端口
	 * 
	 * @param port
	 */
	public MutiplexerServerTest(int port) {
		// TODO Auto-generated constructor stub
		try {
			//1、打开ServerSocketChannel和Selector
			selector = Selector.open();
			servChannel = ServerSocketChannel.open();
			//2、设置为非阻塞方式
			servChannel.configureBlocking(false);
			//3、绑定监听端口
			servChannel.bind(new InetSocketAddress(port), 1024);
			//4、将ServerSocketChannel注册到多路复用器Selector上
			servChannel.register(selector, SelectionKey.OP_ACCEPT);
			System.out.println("The time server is start in port : " + port);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void stop() {
		this.stop = stop;
	}
	
	@Override
	public void run() {
		//5、轮询就绪准备的key
		while (!stop) {
			try {
				//每隔一秒轮询一次
				selector.select(1000);
				Set<SelectionKey> selectedKeys = selector.selectedKeys();
				Iterator<SelectionKey> it = selectedKeys.iterator();
				SelectionKey key = null;
				while (it.hasNext()) {
					key = it.next();
					it.remove();
					try {
						//7、处理新接入的请求
						handleInput(key);
					}catch (Exception e) {
						// TODO: handle exception
						if (key != null) {
							key.cancel();
							if (key.channel() != null) {
								key.channel().close();
							}
						}
					}
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		//多路复用器关闭后,所有注册在上面的Channel和 Pipe等资源都会被自动去注册并关闭,
		//所以不需要重复释放资源
		if (selector != null) {
			try {
				selector.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	private void handleInput(SelectionKey key) throws IOException {
		if (key.isValid()) {
			//处理新接入的请求消息
			if (key.isAcceptable()) {
				ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
				//监听客户端连接
				SocketChannel sc = ssc.accept();
				//设置客户端链路为非阻塞模式
				sc.configureBlocking(false);
				//将新接入的客户端连接注册到多路复用器Selector,监听读操作
				sc.register(selector, SelectionKey.OP_READ);
			}
			if (key.isReadable()) {
				//读取数据
				SocketChannel sc = (SocketChannel) key.channel();
				ByteBuffer readBuffer = ByteBuffer.allocate(1024);
				//读取数据到缓冲区
				int readBytes = sc.read(readBuffer);
				if (readBytes > 0) {
					readBuffer.flip();
					byte[] bytes = new byte[readBuffer.remaining()];
					readBuffer.get(bytes);
					String body = new String(bytes, "UTF-8");
					System.out.println("The time server receive order : " + body);
					String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) 
							? new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
					//8、将消息异步发送到客户端
					doWrite(sc, currentTime);
				}else if (readBytes < 0) {
					//对端链路关闭
					key.cancel();
					sc.close();
				}
			}
		}
	}
	
	private void doWrite(SocketChannel channel, String response) throws IOException {
		if (response != null && response.trim().length() > 0) {
			byte[] bytes = response.getBytes();
			ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
			writeBuffer.put(bytes);
			writeBuffer.flip();
			channel.write(writeBuffer);
		}
	}
	
}
  •  

服务端测试主代码:

package com.niotest;

public class NIOServerTest {
	public static void main(String[] args) {
		int port = 3333;
		MutiplexerServerTest mutiplexerServer = new MutiplexerServerTest(port);
		new Thread(mutiplexerServer,"NIOServerTest-001").start();
	}
}
  •  

客户端:
在这里插入图片描述

package com.niotest;

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.Set;

public class ClientHandle implements Runnable{
	private String host;
	private int port;
	private Selector selector;
	private SocketChannel socketChannel;
	private volatile boolean stop;
	
	/**
	 * 构造方法
	 * @param host
	 * @param port
	 */
	public ClientHandle(String host, int port) {
		// TODO Auto-generated constructor stub
		this.host = host;
		this.port = port;
		try {
			//1、打开ServerSocketChannel和Selector
			selector = Selector.open();
			socketChannel = SocketChannel.open();
			//2、设置为非阻塞方式
			socketChannel.configureBlocking(false);
		} catch (IOException e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		try {
			//3、异步连接服务端
			doConnect();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		while(!stop) {
			//轮询准备就绪的Key
			try {
				selector.select(1000);
				Set<SelectionKey> selectedKeys = selector.selectedKeys();
				Iterator<SelectionKey> it = selectedKeys.iterator();
				SelectionKey key = null;
				while (it.hasNext()) {
					key = it.next();
					it.remove();
					try {
						handleInput(key);
					} catch (Exception e) {
						// TODO: handle exception
						if (key != null) {
							key.cancel();
							if (key.channel() != null) {
								key.channel().close();
							}
						}
					}
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
		//多路复用器关闭后,所有注册在上面的Channel和 Pipe等资源都会被自动去注册并关闭,
		//所以不需要重复释放资源
		if (selector != null) {
			try {
				selector.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	private void handleInput(SelectionKey key) throws IOException {
		if (key.isValid()) {
			//判断是否连接成功
			SocketChannel sc = (SocketChannel) key.channel();
			if (key.isConnectable()) {
				if (sc.finishConnect()) {
					//注册读事件到多路复用器
					sc.register(selector, SelectionKey.OP_READ);
					doWrite(sc);
				}
			}
			if (key.isReadable()) {
				//异步读客户端请求消息到缓冲区
				ByteBuffer readBuffer = ByteBuffer.allocate(1024);
				int readBytes = sc.read(readBuffer);
				if (readBytes > 0) {
					readBuffer.flip();
					byte[] bytes = new byte[readBuffer.remaining()];
					readBuffer.get(bytes);
					String body = new String(bytes, "UTF-8");
					System.out.println("Now is : " + body);
					this.stop = true;
				}else if (readBytes < 0) {
					//对端链路关闭
					key.cancel();
					sc.close();
				}
			}
		}
	}
	
	private void doConnect() throws IOException {
		//判断是否连接成功,如果连接成功,则直接注册状态位到多路复用器中
		if (socketChannel.connect(new InetSocketAddress(host,port))) {
			socketChannel.register(selector, SelectionKey.OP_READ);
			doWrite(socketChannel);
		}else {
			//向Reactor线程的多路复用器注册OP_CONNECT状态位,监听服务端的TCP ACK应答
			socketChannel.register(selector, SelectionKey.OP_CONNECT);
		}
	}
	
	private void doWrite(SocketChannel sc) throws IOException {
		byte[] req = "QUERY TIME ORDER".getBytes();
		ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
		writeBuffer.put(req);
		writeBuffer.flip();
		sc.write(writeBuffer);
		if (!writeBuffer.hasRemaining()) {
			System.out.println("Send order 2 server succeed.");
		}
	}
	
}
  •  

客户端测试主代码:

package com.niotest;

public class NIOClientTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int port = 3333;
		new Thread(new ClientHandle("127.0.0.1", port), "Client-001").start();
	}

}
  •  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值