java nio通信_Java NIO通信的基础,基于TCP C/S例子介绍

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

Java NIO的核心概念有三个:Channel,Selector,ByteBuffer。

而这当中,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到这三个具体之类之间的继承/实现关系(该图来自网络,若有不妥,请告知,谢谢)。

0cc815e172dadf7322012a0af557ffd8.png

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。

13447f5c064622b6ef794821ede22c82.png

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

cada4e2f378eda5519aec2ca00337a83.png

这三个重要的概念,做了一番解释和描述后,就以一个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 classTcpServer {/**

* @param args*/

public static voidmain(String[] args) {try{

startServer(Constants.SERVER_PORT);

}catch(IOException e) {

e.printStackTrace();

}

}public static void startServer(intport) throws IOException{/**开启一个服务channel,

*A selectable channel for stream-oriented listening sockets.*/ServerSocketChannel serverChannel=ServerSocketChannel.open();

serverChannel.configureBlocking(false);

serverChannel.bind(newInetSocketAddress(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 <= 0) {continue;

}/** 从selector上获取到了IO事件,可能是accept,也有可能是read*/Set SelectonKeySet =selector.selectedKeys();

Iterator 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= newStringBuilder();

ByteBuffer byteBuffer= ByteBuffer.allocate(1024);int readBytes = 0;int ret = 0;/** 注意读数据的时候,ByteBuffer的操作,需要flip,clear进行指针位置的调整*/

while ((ret = socketChannel.read(byteBuffer)) > 0) {

readBytes+=ret;

byteBuffer.flip();

sb.append(Charset.forName("UTF-8").decode(byteBuffer).toString());

byteBuffer.clear();

}if (readBytes == 0) {

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(0);

}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 classTcpClient {/**

* @param args*/

public static voidmain(String[] args) {try{

startClient(Constants.SERVER_IP, Constants.SERVER_PORT);

}catch(IOException e) {

e.printStackTrace();

}

}public static void startClient(String serverIp, intserverPort) throws IOException{/** 创建一个SocketChannel,指定为非阻塞模式

* A selectable channel for stream-oriented connecting sockets.*/SocketChannel socketChannel=SocketChannel.open();

socketChannel.configureBlocking(false);/** 连接到指定的服务地址*/socketChannel.connect(newInetSocketAddress(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(0);

}

}/** 设置1sec的超时时间,进行IO事件选择操作*/

int nSelectedKeys = selector.select(5000);if(nSelectedKeys > 0){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= newStringBuilder();/** 定义一个ByteBuffer的容器,容量为1k*/ByteBuffer byteBuffer= ByteBuffer.allocate(1024);int readBytes = 0;int ret = 0;/** 注意,对ByteBuffer的操作,需要关心的是flip,clear等。*/

while ((ret = readChannel.read(byteBuffer)) > 0) {

readBytes+=ret;

byteBuffer.flip();

sb.append(Charset.forName("UTF-8").decode(byteBuffer).toString());

byteBuffer.clear();

}if (readBytes == 0) {

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服务。

48e7bd56a5fda0d860f413b70db9f83f.png

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值