为了更好的理解Netty异步事件驱动网络通信框架,有必要先了解一点Java NIO原生的通信理论,下面将结合基于TCP的例子程序,含客户端和服务端的源码,实现了Echo流程。

Java NIO的核心概念有三个:ChannelSelectorByteBuffer

而这当中,Channel的比重最大,NIO的功能主要基于Channel来实现,进行业务逻辑操作。Selector主要是IO事件选择器,当一个Channel创建并配置好后,注册到Selector上,与Selector相关的重要概念是SelectionKey,这个上面绑定了IO事件相关的Channel。在获取到Channel后,进行数据的读写操作,Channel的数据读写是不能直接操作数据的,必须基于ByteBuffer进行,然而,Java NIO原生的ByteBuffer操作比较繁琐,要flip和clear操作。

1. 而我们在业务逻辑操作中,用到的channel,主要有ServerSocketChannel,SocketChannel,DataGramChannel。下面,用一个图,来简要的描述下Channel到这三个具体之类之间的继承/实现关系(该图来自网络,若有不妥,请告知,谢谢)。

2. Selector,是事件选择器,创建Selector后,在调用select之前,在注册Channel到这个Selector上时,必须指定关注的事件类型(interestOps)。通过这个类的select函数,可以获取选择上监听到的IO事件。一旦select函数检测到事件,就可以从Selector上获取到具体有哪些IO事件,这些事件通过SelectionKey承载,SelectionKey上标记出该事件的类型,比如是OP_CONNECT,OP_ACCEPT还是OP_READ等。另外,SelectionKey还记录了对应该IO事件发生的Channel,可以通过SelectionKey得到该Channel。

3. ByteBuffer。 因为字节操作,是操作系统与IO设备之间进行通信的基本数据单元,在Java NIO中,各通道Channel之间进行数据通信时,指定必须是基于ByteBuffer的。 ByteBuffer有两个重要的函数,flip和clear。当Channel调用read函数,将数据读到ByteBuffer中后,ByteBuffer的数据长度指针将会移动到数据长度所在的位置,这个位置是小于等于ByteBuffer容量capacity值的。当业务逻辑操作读取到的数据前,需要对ByteBuffer做一下flip操作,就是将limit指针指向当前数据指针position的位置,然后,将position指针指向0的位置。数据逻辑结束后,一般要恢复ByteBuffer,即调用clear函数。

这三个重要的概念,做了一番解释和描述后,就以一个demo程序,基于Java NIO的TCP C/S源码,代码中带有了重要逻辑的注释,后续不再单独解释。

A. TCP Server:

/**
* @author "shihuc"
* @date 2017年3月16日
*/
package javaSocket.tcp.server; 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.charset.Charset;
import java.util.Iterator;
import java.util.Set; import javaSocket.tcp.Constants; /**
* @author chengsh05
*
*/
public class TcpServer { /**
* @param args
*/
public static void main(String[] args) {
try {
startServer(Constants.SERVER_PORT);
} catch (IOException e) {
e.printStackTrace();
}
} public static void startServer(int port) throws IOException{
/*
*开启一个服务channel,
*A selectable channel for stream-oriented listening sockets.
*/
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.bind(new InetSocketAddress(port)); /*
* 创建一个selector
*/
Selector selector = Selector.open();
/*
* 将创建的serverChannel注册到selector选择器上,指定这个channel只关心OP_ACCEPT事件
*/
serverChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) {
/*
* select()操作,默认是阻塞模式的,即,当没有accept或者read时间到来时,将一直阻塞不往下面继续执行。
*/
int readyChannels = selector.select();
if (readyChannels <= ) {
continue;
} /*
* 从selector上获取到了IO事件,可能是accept,也有可能是read
*/
Set<SelectionKey> SelectonKeySet = selector.selectedKeys();
Iterator<SelectionKey> iterator = SelectonKeySet.iterator(); /*
* 循环遍历SelectionKeySet中的所有的SelectionKey
*/
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isAcceptable()) { //处理OP_ACCEPT事件
SocketChannel socketChannel = serverChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) { //处理OP_READ事件
SocketChannel socketChannel = (SocketChannel) key.channel();
StringBuilder sb = new StringBuilder();
ByteBuffer byteBuffer = ByteBuffer.allocate(); int readBytes = ;
int ret = ;
/*
* 注意读数据的时候,ByteBuffer的操作,需要flip,clear进行指针位置的调整
*/
while ((ret = socketChannel.read(byteBuffer)) > ) {
readBytes += ret;
byteBuffer.flip();
sb.append(Charset.forName("UTF-8").decode(byteBuffer).toString());
byteBuffer.clear();
} if (readBytes == ) {
System.err.println("handle opposite close Exception");
socketChannel.close();
} String message = sb.toString();
System.out.println("Message from client: " + message);
if (Constants.CLIENT_CLOSE.equalsIgnoreCase(message.toString().trim())) {
System.out.println("Client is going to shutdown!");
socketChannel.close();
} else if (Constants.SERVER_CLOSE.equalsIgnoreCase(message.trim())) {
System.out.println("Server is going to shutdown!");
socketChannel.close();
serverChannel.close();
selector.close();
System.exit();
} else {
String outMessage = "Server response:" + message;
socketChannel.write(Charset.forName("UTF-8").encode(outMessage));
}
}
/*
* 将selector上当前已经监听到的且已经处理了的事件标记清除掉。
*/
iterator.remove();
}
}
}
}

B. TCP Client

/**
* @author "shihuc"
* @date 2017年3月16日
*/
package javaSocket.tcp.client; 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.nio.charset.Charset;
import java.util.Scanner; import javaSocket.tcp.Constants; /**
* @author chengsh05
*
*/
public class TcpClient { /**
* @param args
*/
public static void main(String[] args) {
try {
startClient(Constants.SERVER_IP, Constants.SERVER_PORT);
} catch (IOException e) {
e.printStackTrace();
}
} public static void startClient(String serverIp, int serverPort) throws IOException{
/*
* 创建一个SocketChannel,指定为非阻塞模式
* A selectable channel for stream-oriented connecting sockets.
*/
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false); /*
* 连接到指定的服务地址
*/
socketChannel.connect(new InetSocketAddress(serverIp, serverPort)); /*
* 创建一个事件选择器Selector
*/
Selector selector = Selector.open(); /*
* 将创建的SocketChannel注册到指定的Selector上,并指定关注的事件类型为OP_CONNECT
*/
socketChannel.register(selector, SelectionKey.OP_CONNECT); /*
* 从系统输入终端读取数据,作为客户端信息输入源
*/
Scanner sc = new Scanner(System.in);
String cont = null;
while(true){
if(socketChannel.isConnected()){
cont = sc.nextLine();
socketChannel.write(Charset.forName("UTF-8").encode(cont));
if(cont == null || cont.equalsIgnoreCase(Constants.CLIENT_CLOSE)){
socketChannel.close();
selector.close();
sc.close();
System.out.println("See you, 客户端退出系统了");
System.exit();
}
}
/*
* 设置1sec的超时时间,进行IO事件选择操作
*/
int nSelectedKeys = selector.select();
if(nSelectedKeys > ){
for(SelectionKey skey: selector.selectedKeys()){
/*
* 判断检测到的channel是不是可连接的,将对应的channel注册到选择器上,指定关心的事件类型为OP_READ
*/
if(skey.isConnectable()){
SocketChannel connChannel = (SocketChannel) skey.channel();
connChannel.configureBlocking(false);
connChannel.register(selector, SelectionKey.OP_READ);
connChannel.finishConnect();
}
/*
* 若检测到的IO事件是读事件,则处理相关数据的读相关的业务逻辑
*/
else if(skey.isReadable()){
SocketChannel readChannel = (SocketChannel) skey.channel();
StringBuilder sb = new StringBuilder();
/*
* 定义一个ByteBuffer的容器,容量为1k
*/
ByteBuffer byteBuffer = ByteBuffer.allocate(); int readBytes = ;
int ret = ;
/*
* 注意,对ByteBuffer的操作,需要关心的是flip,clear等。
*/
while ((ret = readChannel.read(byteBuffer)) > ) {
readBytes += ret;
byteBuffer.flip();
sb.append(Charset.forName("UTF-8").decode(byteBuffer).toString());
byteBuffer.clear();
} if (readBytes == ) {
System.err.println("handle opposite close Exception");
readChannel.close();
}
}
}
/*
* 一次监听的事件处理完毕后,需要将已经记录的事件清除掉,准备下一轮的事件标记
*/
selector.selectedKeys().clear();
}else{
System.err.println("handle select timeout Exception");
socketChannel.close();
}
}
}
}

阅读上述代码时,请注意,server和client的实现风格不太一样,主要是针对SelectionKeySet的遍历,一次select操作获取到的所有的SelectionKey处理完后的扫尾工作,体现出Selector的工作逻辑,若写过C程序实现过TCP server/client程序,对事件选择的过程应该就更清楚了。

最后,总结一下Java NIO TCP协议下的C/S结构程序流程图,为彻底理解Java NIO服务。

基于这个例子引出的Java NIO的逻辑过程和思想,再去研读Netty的代码,相信会容易理解Netty的核心reactor模型工作原理。

Java NIO通信的基础,基于TCP C/S例子介绍的更多相关文章

  1. Java NIO通信框架在电信领域的实践

    [http://www.codeceo.com/article/java-nio-communication.html]   华为电信软件技术架构演进 Java NIO框架在技术变迁中起到的关键作用 ...

  2. JAVA Socket 底层是怎样基于TCP/IP 实现的???

    首先必须明确:TCP/IP模型中有四层结构:       应用层(Application Layer).传输层(Transport  Layer).网络层(Internet Layer  ).链路层( ...

  3. Java NIO 网络编程基础

    Java NIO提供了一套网络api,可以用来处理连接数很多的情况.他的基本思想就是用一个线程来处理多个channel. 123456789101112131415161718192021222324 ...

  4. java NIO socket 通信实例

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/zhuyijian135757/article/details/37672151 java Nio 通 ...

  5. python中基于tcp协议的通信(数据传输)

    tcp协议:流式协议(以数据流的形式通信传输).安全协议(收发信息都需收到确认信息才能完成收发,是一种双向通道的通信) tcp协议在OSI七层协议中属于传输层,它上承用户层的数据收发,下启网络层.数据 ...

  6. 海纳百川而来的一篇相当全面的Java NIO教程

    目录 零.NIO包 一.Java NIO Channel通道 Channel的实现(Channel Implementations) Channel的基础示例(Basic Channel Exampl ...

  7. Java NIO的理解和应用

    Java NIO是一种基于通道和缓冲区的I/O方式,已经被广泛的应用,成为解决高并发与大量连接和I/O处理问题的有效方式. Java NIO相关组件 Java NIO主要有三个核心部分组成,分别是:C ...

  8. Java NIO (转)

    Java NIO提供了与标准IO不同的IO工作方式: Channels and Buffers(通道和缓冲区):标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(B ...

  9. 【转】java NIO 相关知识

    原文地址:http://www.iteye.com/magazines/132-Java-NIO Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的 ...

随机推荐

  1. C# 解压

    需要ICSharpCode.SharpZipLib.dll 网上有很多 先添加文件引用 再添加引用 using ICSharpCode.SharpZipLib.Zip; #region 解压 /// ...

  2. slaac

    https://zhidao.baidu.com/question/460186176.html slaac是IPv6中的术语.Stateless address autoconfiguration, ...

  3. python day02作业

  4. 设置idea文件类型

  5. 2016 多校联赛7 Elegant Construction

    Being an ACMer requires knowledge in many fields, because problems in this contest may use physics, ...

  6. gcc/g++ 使用 tricks

    0. 优化级别 -O0,不进行优化的编译后的文件大小反而更小,小于 -O2 的: 1. -std 指定 C 语言标准 -ansi -ansi == -std=c90 -std=c99:(std:sta ...

  7. make命令回显Makefile执行脚本命令

    /********************************************************************** * make命令回显Makefile执行脚本命令 * 说 ...

  8. Linux 双网卡双网段通信

    /********************************************************************************* * Linux 双网卡双网段通信 ...

  9. Angular 201703

    $http vm.auth = function() { return $http({ method: 'POST', url: 'sys/auth.json' }) } service.auth() ...

  10. 洛谷 P3373:【模板】线段树 2(区间更新)

    题目描述 如题,已知一个数列,你需要进行下面三种操作: 1.将某区间每一个数乘上x 2.将某区间每一个数加上x 3.求出某区间每一个数的和 输入输出格式 输入格式: 第一行包含三个整数N.M.P,分别 ...