NIO核心为Selector、ByteBuffer、Channel,网上资料很多。大家可先查询。
本实例为基于NIO的管理端和客户端一次对话:服务器启服务,客户端发起连接,连接建立后,客户端向服务发送数据,服务器读取数据,服务器读取完成后,服务器向客户端发送回报数据。
服务器:
package com.NIO.niodeeplearning;
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.Iterator;
/**
* 服务器
* @author evan
*
*/
public class MyNIOServer extends Thread {
//服务套接字通道
private ServerSocketChannel serverSocketChannel;
//服务器所管理的选择器(selector)
private Selector selector;
private boolean isRun = true;
public MyNIOServer() {
try {
serverSocketChannel =ServerSocketChannel.open();
//通道为非阻塞的
serverSocketChannel.configureBlocking(false);
//将通道与本地的8888端口绑定,相当于服务端启的是本地的8888端口,客户端来连接。
//也可用serverSocketChaserverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1", 8888));
serverSocketChannel.bind(new InetSocketAddress("127.0.0.1", 8888));
selector = Selector.open();
//将通道注册在选择其上,并此通道对什么操作感兴趣(所谓感兴趣就是这个通道用来干嘛),并返回的是此通道的SelectionKey对象
//SelectionKey.OP_ACCEPT 此通道用来等待客户端的连接
//SelectionKey.OP_CONNECT; 此通道用来连接服务器
//SelectionKey.OP_READ 此通道用来读取数据
//SelectionKey.OP_WRITE 此通道用来写数据
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
System.out.println("服务端初始化失败");
}
}
@Override
public void run() {
//死循环来对selector进行轮询操作
while(isRun)
{
try {
//select方法是阻塞的,他会返回I/O已准备好的通道的数量
selector.select();
//遍历所有通道的selectionKey对象,
Iterator<SelectionKey> ite = selector.selectedKeys().iterator();
while(ite.hasNext())
{
SelectionKey sk = ite.next();
handle(sk);
ite.remove();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//SelectionKey能获取到相应的通道和通道对读写连接、被连接的兴趣
private void handle(SelectionKey sk) throws IOException {
//与客户端建立连接
if(sk.isValid()&&sk.isAcceptable())
{
try {
//这个channel就是我上面注册的channel,他们的hashcode是一样的
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) sk.channel();
//获取到客户端的通道
SocketChannel channel = serverSocketChannel.accept();
//配置通道为非阻塞的
channel.configureBlocking(false);
//将通道注册在selector上,并此通道用来读数据
channel.register(selector, SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
System.out.println("与客户端建链失败");
}
}
//从通道里读取数据
if(sk.isValid()&&sk.isReadable())
{
try {
SocketChannel channel = (SocketChannel) sk.channel();
//分配1024字节大小的字节缓冲区
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
//把数据从通道里读入到字节缓冲区,这里假定传输的数据小于1024个字节
int length = channel.read(readBuffer);
String messageFromClient = new String(readBuffer.array(),0,length);
System.out.println("来自客户端的数据:"+messageFromClient);
//服务器接收完客户端的数据后发送客户端数据进行响应
channel.register(selector, SelectionKey.OP_WRITE);
} catch (IOException e) {
e.printStackTrace();
}
}
//写数据
if(sk.isValid()&&sk.isWritable())
{
SocketChannel socketChannel = (SocketChannel)sk.channel();
//wrap把字节数组转为字节缓冲区
ByteBuffer byteBuffer = ByteBuffer.wrap("您好,客户端,我是服务器".getBytes());
/*
* write()方法的非阻塞调用哦只会写出其能够发送的数据,而不会阻塞等待所有数据,而后一起发送,
* 因此在调用write()方法将数据写入信道时,一般要用到while循环
*/
while(byteBuffer.hasRemaining())
{
try {
//写数据
socketChannel.write(byteBuffer);
System.out.println("服务器写数据完毕");
} catch (IOException e) {
e.printStackTrace();
System.out.println("写数据失败");
}
}
/*
* 这里进行一次循环即可,所以这里就不再注册到选择器上了,而是取消此通道在此选择器上的注册
* try {
* //客户端给服务器发送数据后接收来自服务器的回报,上面迭代里删除了SelectionKey, //所以这里要把通道再次注册再选择器里
* socketChannel.register(selector, SelectionKey.OP_READ); } catch
* (ClosedChannelException e) { e.printStackTrace(); }
*/
socketChannel.close();
}
}
public static void main(String[] args) {
MyNIOServer myNIOServer = new MyNIOServer();
myNIOServer.start();
}
}
客户端:
package com.NIO.niodeeplearning;
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.SocketChannel;
import java.util.Iterator;
/**
* 客户端
* @author evan
*
*/
public class MyNIOClient extends Thread{
//客户端的套接字通道
private SocketChannel channel;
//客户端管理的选择器
private Selector selector;
private boolean isRun = true;
public MyNIOClient() {
try {
//打开通道
channel = SocketChannel.open();
//设置通道为非阻塞
channel.configureBlocking(false);
//让通道和服务器连接,此方法连接不一定能建立
channel.connect(new InetSocketAddress("127.0.0.1", 8888));
selector = Selector.open();
channel.register(selector, SelectionKey.OP_CONNECT);
} catch (IOException e) {
e.printStackTrace();
System.out.println("客户端初始话失败");
}
}
public void run() {
while(isRun)
{
try {
//一直进行轮询,找出I/O已准备好的通道
selector.select();
Iterator<SelectionKey> ite = selector.selectedKeys().iterator();
while(ite.hasNext())
{
SelectionKey stk = ite.next();
handle(stk);
ite.remove();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//根据stk的属性执行相应的操作
private void handle(SelectionKey stk)
{
/*
* 这里为什么还要进行这个操作呢?
* 非阻塞SocketChannel来说,一旦已经调用connect()方法发起连接,底层套接字可能既不是已经连接,也不是没有连接,
* 而是正在连接。由于底层协议的工作机制,套接字可能会在这个状态一直保持下去,这时候就需要循环地调用finishConnect() 方法来检查是否完成连接,
*/
if(stk.isValid() &&stk.isConnectable())
{
//
try {
SocketChannel socketChannel = (SocketChannel) stk.channel();
//若通道为非阻塞的,1:连接成功,返回true;2:正在连接返回false 3:连接失败,抛出异常
//若通道为阻塞的,此方法会阻塞到连接成功或失败,然后返回true或者抛出异常
if(socketChannel.finishConnect())
{
System.out.println("与服务器建链完成");
socketChannel.register(selector, SelectionKey.OP_WRITE);
}else {
System.out.println("正在尝试与服务器连接。。。。。");
}
} catch (IOException e) {
e.printStackTrace();
System.out.println("连接失败");
}
}
//写数据
if(stk.isValid()&&stk.isWritable())
{
SocketChannel socketChannel = (SocketChannel)stk.channel();
//wrap把字节数组转为字节缓冲区
ByteBuffer byteBuffer = ByteBuffer.wrap("您好,服务器,我是客户端".getBytes());
/*
* write()方法的非阻塞调用哦只会写出其能够发送的数据,而不会阻塞等待所有数据,而后一起发送,
* 因此在调用write()方法将数据写入信道时,一般要用到while循环
*/
while(byteBuffer.hasRemaining())
{
try {
//写数据
socketChannel.write(byteBuffer);
System.out.println("客户端写数据完毕");
} catch (IOException e) {
e.printStackTrace();
System.out.println("写数据失败");
}
}
try {
//客户端给服务器发送数据后接收来自服务器的回报,上面迭代里删除了SelectionKey,
//所以这里要把通道再次注册再选择器里
socketChannel.register(selector, SelectionKey.OP_READ);
} catch (ClosedChannelException e) {
e.printStackTrace();
}
}
//从通道里读取数据
if(stk.isValid()&&stk.isReadable())
{
try {
SocketChannel channel = (SocketChannel) stk.channel();
//分配1024字节大小的字节缓冲区
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
//把数据从通道里读入到字节缓冲区,这里假定传输的数据小于1024个字节
int length = channel.read(readBuffer);
String messageFromServer= new String(readBuffer.array(),0,length);
System.out.println("来自服务器的数据:"+messageFromServer);
//这里客户端接收到来自服务器的信息后,把通道再次注册再选择器再给服务器发送数据,
//这样服务器和客户端就会进入你一句、我一句的死循环中
//channel.register(selector, SelectionKey.OP_WRITE);
//我们的例子之进行一次循环即可,关闭通道和选择器即可
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
MyNIOClient myNIOClient = new MyNIOClient();
myNIOClient.start();
}
}
代码可运行完成一次通讯,但是有缺陷,在通讯完成后,客户端和服务端都会在Selecotor.select处阻塞,因为无I/O准备好的通道,我尝试selector.close();会出现异常,这样肯定是不行的,后续在进行补充 。