解释:NIO → non-blocking input output(非阻塞式的输入和输出)
使用场景:一般用于高并发场景下;请求(客户端)
、响应(服务端)
两方,约定好使用TCP/IP通信;双方约定好报文格式(报文头+报文体)
及编码格式(这里用UTF-8)
,报文头内容(约定好长度比如8位,不够前面补零)
里面内容为报文体长度,再根据报文头内容,获取后面的报问体的内容。
例如:报文示例:00000007aaaaaaa
;报文体内容为7个a,所以报文头长度为7不够八位前面补零。
一、 服务端
NioServerThread: nio的服务类(selector、buffer、channel)
ServerThreadOperate: 新线程用来处理客户端请求
/**
* selector轮询线程的开启并绑定channel通道
* @author zhb
*/
public class NioServerThread {
private static int count = 0;
// 事件轮询器
private static Selector selector;
// 服务通道
private static ServerSocketChannel serverSocketChannel;
// 开启一个服务线程
public void action() throws IOException, InterruptedException{
// 开启服务端的事件轮询器,只有这一个线程
selector = Selector.open();
// 开通服务端的socket通道
serverSocketChannel = ServerSocketChannel.open();
// socket为非阻塞
serverSocketChannel.configureBlocking(false);
// 绑定端口
serverSocketChannel.socket().bind(new InetSocketAddress(Constant.serverSocketPort));
// channel和socket绑定,注册可以接入事件
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
System.err.println("---------------------线程开启-----------------------");
handerSelector();
}
/**
* 处理轮询事件
* @throws IOException
*/
private void handerSelector() throws IOException {
while(true){
//多长时间轮询一次
// selector.select(1000);
selector.select();
//事件轮询器,查出的kernel中的所有的事件标识
Set selectedKeys = selector.selectedKeys();
Iterator iterator = selectedKeys.iterator();
// SelectionKey 事件标识,每个请求的客户端都有自己的一个key作为唯一标识,该key不同的事件处理
SelectionKey key;
while(iterator.hasNext()){
key = iterator.next();
// 将该事件去除掉,防止重复
iterator.remove();
//检查key事件是否被处理过,如果没有被处理过
if(key.isValid()){
// 处理客户端的请求接入
if(key.isAcceptable()){
handlerAccept(key);
// 处理可读事件
}else if(key.isReadable()){
handlerRead(key);
// 处理可写事件
}if(key.isWritable()){
handlerWrite(key);
}
}
}
}
}
private void handlerWrite(SelectionKey key) throws UnsupportedEncodingException, IOException, ClosedChannelException {
SocketChannel accept = (SocketChannel)key.channel();
String resStr = "ab";
System.err.println("可以写了");
byte[] resByte = resStr.getBytes(Constant.charset);
ByteBuffer resBuffer = ByteBuffer.allocate(resByte.length);
resBuffer.put(resByte);
resBuffer.flip();
accept.write(resBuffer);
accept.register(selector, SelectionKey.OP_CONNECT);
}
/**
* 处理可读的事件
* (1)只处理读取数据,不管响应信息
* (2)读取到请求信息,根据请求信息返回处理后的响应信息(常见的),
*
* @param selector 事件轮询器
* @param key 某个客户端的标识
* @throws IOException
* @throws UnsupportedEncodingException
* @throws ClosedChannelException
*/
private static void handlerRead(SelectionKey key) throws IOException, UnsupportedEncodingException, ClosedChannelException {
// 该通道是接收该客户连接请求时,开启的通道
SocketChannel socketChannel = (SocketChannel)key.channel();
socketChannel.configureBlocking(false);
// 按照客户端和服务端约定好的(报文头+报文体)的格式信息
String reqHeadStr = getStrFromChannel(socketChannel, Constant.reqHeadLength);
String reqBodyStr = getStrFromChannel(socketChannel, Integer.valueOf(reqHeadStr));
System.err.println("-------------------nio服务端接收请求数据→" + reqBodyStr);
// 这种情况可以开启一个新的线程处理
new Thread(new ServerThreadOperate(reqBodyStr, socketChannel)).start();
// 下面是简单的处理信息,可以用简单的测试信息
// ByteBuffer buffer = ByteBuffer.allocate(1024);
// socketChannel.read(buffer);
// // 整理buffer中的数据,position复位
// buffer.flip();
// // 建立和buffer中相同长度的字节的数组
// byte[] bytes = new byte[buffer.remaining()];
// buffer.get(bytes);
// buffer.clear();
// String reqStr = new String(bytes,"utf-8");
// // 这里简单处理请返回的信息
// simplehandler(socketChannel, reqBodyStr);
// 根据上面的处理情况,再注册这里的信息
socketChannel.register(selector, SelectionKey.OP_CONNECT);
}
// 根据参数长度,从该客户的channel中获取返回字符串
public static String getStrFromChannel(SocketChannel socketChannel, int bufferLength) throws IOException{
// 先读取报文头内容
ByteBuffer buffer = ByteBuffer.allocate(bufferLength);
byte[] reqbyte = new byte[buffer.remaining()];
// 先从通道中读取请求报文头长度的buffer信息
socketChannel.read(buffer);
// 整理buffer中的数据,position复位
buffer.flip();
// 把buffer中的信息放入到byte字节数组中
buffer.get(reqbyte);
buffer.clear();
// 将字节数组转成字符串,返回
return new String(reqbyte, Constant.charset);
}
// 不用开启新线程 ,简单模拟烦回信息
private static void simplehandler(SocketChannel socketChannel, String reqStr) throws UnsupportedEncodingException, IOException, ClosedChannelException {
ByteBuffer respBuffer = ByteBuffer.allocate(1024);
byte[] respByte = ("响应信息:"+reqStr).getBytes(Constant.charset);
// 将响应信息写入buffer中
respBuffer.put(respByte);
System.err.println("服务端返回了请求信息"+respBuffer);
// 整理buffer,position复位
respBuffer.flip();
// 发送响应信息
socketChannel.write(respBuffer);
}
/**
* 处理客户端的请求接入的事件
*
* @param selector 事件轮询器
* @param serverSocketChannel 注册OP_ACCEPT事件的服务端channel
* @throws IOException
* @throws ClosedChannelException
*/
private static void handlerAccept(SelectionKey key) throws IOException,
ClosedChannelException {
System.err.println(count+"--------------------------OP_READ------------------");
//返回创建此键的通道
serverSocketChannel = (ServerSocketChannel)key.channel();
// 建立和客户端的链接, 因为 OP_ACCEPT是注册在 serverSocketChannel上;每个客户有自己的SocketChannel通道
SocketChannel accept = serverSocketChannel.accept();
// 非阻塞
accept.configureBlocking(false);
// 开启注册该客户端的可读时间,即该客户上传完所有的请求数据到系统的kernel的buffer缓存中后,开启的的可读事件通知
accept.register(selector, SelectionKey.OP_READ);
}
public static void main(String[] args) throws IOException, InterruptedException {
// nio线程的开启
NioServerThread thread = new NioServerThread();
thread.action();
}
}
/**
* 开启一个新线程,用来处理客户端的请求 ;也可以用简单模式不用这个类
* @author zhb
*/
public class ServerThreadOperate implements Runnable {
// 客户端请求的信息
private String reqStr;
private int count;
// 和某个客户端的通道
private SocketChannel socketChannel;
public ServerThreadOperate(String reqStr) {
this.reqStr = reqStr;
}
public ServerThreadOperate(String reqStr, SocketChannel socketChannel) {
this.reqStr = reqStr;
this.socketChannel = socketChannel;
}
// 线程主体
public void run() {
ByteBuffer respBuffer = ByteBuffer.allocate(1024);
byte[] respBodyByte = null;
try {
respBodyByte = ("响应信息为:"+reqStr).getBytes(Constant.charset);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//转成约定好的报文头长度,不足的前面补零
StringBuilder format = new StringBuilder("%0").append(Constant.respHeadLength).append("d");
byte[] respHeadByte = String.format(format.toString(), respBodyByte.length).getBytes();
respBuffer.put(respHeadByte);
respBuffer.put(respBodyByte);
System.err.println("服务端返回了请求信息:"+respBuffer);
// buffer内的信息要复位整理
respBuffer.flip();
try {
// 烦回要烦回的信息
socketChannel.write(respBuffer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 系统常量
*
* @author zhb
*
*/
public class Constant {
// 请求报文头的位数
public static final int reqHeadLength = 8;
// 返回响应报文头的长度
public static final int respHeadLength = 8;
// 字节数组和字符创之间的转换时的编码
public static final String charset = "UTF-8";
//接收请求的最长时间 单位毫秒
public static final int reqTimeOut = 5000;
//接收请求的服务端口
public static final int serverSocketPort = 8080;
}