BIO 是JAVA网络通信中同步阻塞的实现方式,NIO是JAVA的同步非阻塞方式,大致示意如下
每个客户端以socketchannel(可以视同bio下的socket)向服务器发送连接或者请求,服务器端在启动时创建一个ServerSocketChannel,用于绑定服务的端口和IP以及处理连接到socket的请求.同时ServerSocketChannel也注册在selector下。
当selector以指定的时间间隔进行轮询时,如果发现有新的请求进来,会根据情况采取两个方向的处理。一个是新建连接的请求,由ServerSocketChannel通过accept去生成一个新的socketChannel,并注册到selector上。或者在轮询时发现个某个socketchannel满足了某类处理要求(如读数据),则进行相应的处理处理
下附相关代码
客户端
package netty.nio;
public class ClientThreadMain {
public static void main(String[] args) {
String ip = "127.0.0.1";
int port = 9999;
new Thread(new NIOSocketClient(ip,port)).start();
}
}
package netty.nio;
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;
import java.util.Set;
/**
- NIO模式客户端实现
- @author [email protected]
-
*/
public class NIOSocketClient implements Runnable{private String ip;
private int port;
private boolean running = true;
private Selector selector;
private SocketChannel socketChannel;//初始化时完成ip,port selector与socketChannel的设置
public NIOSocketClient(String ip,int port) {
this.ip = ip;
this.port = port;try { selector = Selector.open(); socketChannel = SocketChannel.open(); //设置为非阻塞的 socketChannel.configureBlocking(false); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }
}
@Override
public void run() {
//发起连接请求
connect();
while(running) {/* * 超时毫秒数 * timeout --If positive, block for up to timeoutmilliseconds, * more or less, while waiting for achannel to become ready; * if zero, block indefinitely;must not be negative */ try { selector.select(1000); //迭代处理selector中所有的SelectionKey Set
selectionKeys = selector.selectedKeys(); Iterator it = selectionKeys.iterator(); SelectionKey key = null; while(it.hasNext()) { key = it.next(); it.remove(); handler(key); } } catch (IOException e) { e.printStackTrace(); } } }
//处理器,处理当连接成功与收到返回成功后的各自处理业务
private void handler(SelectionKey selectionKey) {if(selectionKey.isValid()) { SocketChannel sc = (SocketChannel)selectionKey.channel(); //如果已经联通,那么开始发送数据 if(selectionKey.isConnectable()) { if(sc.isConnected()) { try { sc.register(selector, SelectionKey.OP_READ); sendMessage(sc); selectionKey.cancel(); try { sc.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } catch (ClosedChannelException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //如果可以读取,那么开始进行读取 if(selectionKey.isReadable()) { ByteBuffer buffer = ByteBuffer.allocate(1024); try { int bufferSize = sc.read(buffer); if(bufferSize>0) { //按照这个长度创建一个比特数组接收传入的数据 //The number of elements remaining in this buffer byte[] bytes = new byte[buffer.remaining()]; //转成实际的字符串打印出来 String info = new String(bytes,"UTF-8"); System.out.println("传回的信息为:"+info); }else { selectionKey.cancel(); sc.close(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
}
/*
-
发送消息
*/
private void sendMessage(SocketChannel sc) {byte[] bytes = "HELLO JAVA NIO".getBytes();
ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length);
buffer.put(bytes);
buffer.flip();
try {
sc.write(buffer);
if(!buffer.hasRemaining()) {
System.out.println("已经发送信息");
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/*
-
一启动线程就发起服务端请求
*/
private void connect() {try {
socketChannel.connect(new InetSocketAddress(ip,port));
if(socketChannel.finishConnect()) {
//如果连通即将socketChannel注册到selector上,关注的事件是读
socketChannel.register(selector, SelectionKey.OP_READ);
//向服务端发送数据
sendMessage(socketChannel);
}else { socketChannel.register(selector, SelectionKey.OP_CONNECT); }
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
-
}
上边代码可以看出客户端的Selector也在轮询确认服务端的返回情况,并进行处理
以下是服务端实现
package netty.nio;
/**
- NIO模式服务端实现
- @author [email protected]
-
*/
public class NIOSocketServer {public static void main(String[] args) {
int port = 9999; NIOSocketSelector selector = new NIOSocketSelector(port); new Thread(selector).start();
}
}
package netty.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.util.Iterator;
import java.util.Set;
/**
- NIO的多路复用器的实现
- @author [email protected]
-
*/
public class NIOSocketSelector implements Runnable{//多路复用器
private Selector selector;
//设通道绑定队列的最大长度
private int backlog = 800;
//任务持续执行标志
private boolean running = true;private int requestNum =0;
//通道
private ServerSocketChannel serverSocketChannel;//传入端口参数
public NIOSocketSelector(int port) {try { //设置多路复用器 selector = Selector.open(); //设置通道 serverSocketChannel = ServerSocketChannel.open(); /* 设置通道为非阻 塞模式 * block If true then this channel will be placed inblocking mode; * if false then it will be placednon-blocking mode */ serverSocketChannel.configureBlocking(false); /* 最大队列长度 * backlog - requested maximum length of the queue of incoming connections */ serverSocketChannel.socket().bind( new InetSocketAddress(port), backlog); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }
}
@Override
public void run() {while(running) { //System.out.println("启动服务端 监听请求"); /* * 超时毫秒数 * timeout --If positive, block for up to timeoutmilliseconds, * more or less, while waiting for achannel to become ready; * if zero, block indefinitely;must not be negative */ try { //每秒进行一次轮询处理 selector.select(1000); //迭代处理selector中所有的SelectionKey Set
selectionKeys = selector.selectedKeys(); Iterator it = selectionKeys.iterator(); SelectionKey key = null; while(it.hasNext()) { key = it.next(); it.remove(); handler(key); } } catch (IOException e) { e.printStackTrace(); } } if(selector!=null) { try { selector.close(); }catch(IOException ie) { } } }
private void handler(SelectionKey selectionKey) {
//如果当前的Key可用,取到当前key对应的channel if(selectionKey.isValid()) { ServerSocketChannel ssc; SocketChannel sc; //如果是新来的请求就新建一条socketchannel绑定到selector if(selectionKey.isAcceptable()) { ssc = (ServerSocketChannel)selectionKey.channel(); try { sc = ssc.accept(); //设置为非阻塞模式 sc.configureBlocking(false); //注册到Selector下,设定为读打开 sc.register(selector, SelectionKey.OP_READ); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //如果是可以读取数据了,那就进行数据读取 if(selectionKey.isReadable()) { requestNum = requestNum +1; System.out.println("当前请求数:"+requestNum); sc = (SocketChannel)selectionKey.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); try { //判断读入数据的长度 int bufferSize = sc.read(buffer); if(bufferSize>0) { //将缓存字节数组的指针设置为数组的开始序列即数组下标0 //这样从buffer的头部开始对buffer进行遍历 buffer.flip(); //按照这个长度创建一个比特数组接收传入的数据 //The number of elements remaining in this buffer byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); //转成实际的字符串打印出来 String info = new String(bytes,"UTF-8"); System.out.println("传入的信息为:"+info); //对传入的信息进行响应返回 response(sc,new Long(System.currentTimeMillis()).toString()); }else if(bufferSize<0){ selectionKey.cancel(); sc.close(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
}
/*
-
处理回写的数据
*/
private void response(SocketChannel socketChannel,String msg) {if(msg!=null && msg.trim().length()>0) {
byte[] bytes = msg.getBytes();
//按字符串长度新建ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(bytes.length) ;
buffer.put(bytes);
buffer.flip();
try {
//将返回的数据信息写入到SocketChannel中
socketChannel.write(buffer);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void isRunning(boolean running) {
this.running = running;
} -
}
这里注意几个地方,一个是backlog,这个值在windows下设置为大于等于1000后会抛一个异常。需要将之调小,目前怀疑的是当对端 口的监听大于一定数量,会报这个异常
第二是running虽然写了,但是并没有用。只用于提供一个可能性,可以在某些必要的时候打断这种运行
第三是在IDE下进行调试时最好在两个IDE下进行调试。不要把服务端与客户端都启动在一个IDE下,会出现某名的异常