Socket网络通信之NIO

NIO:new io ,java1.4开始推出的可非阻塞IO。

java.nio 包,可解决BIO阻塞的不足 但比BIO学习、使用复杂。

可以以阻塞、非阻塞两种方式工作。

可以在非阻塞模式下,可以用少量(甚至一个)线程处理大量IO连接。

Java7推出了 Nio.2  (又称AIO,异步IO)。

一、NIO工作流程如下图:

流程图如上所示,要理解NioSocket的使用必须先理解三个概念:Selector,Channel和Buffer。举个例子。大学时有人卖电话卡,提供送货上门服务,只要有人打电话,他就送过去、收钱在回去,然后等下一个电话,这就相当于普通的Socket处理请求的模式。如果请求不是很多,这是没有问题的。而像现在的电商配送模式——送快递就类似于NioSocket。快递并不会一件一件的送,而是将很多件货一起拿去送,而且在中转站都有专门的分拣员负责按配送范围把货物分给不同的送货员,这样效率就提高了很多。Selector就是中转战的分拣员,Channel就是送货员(或者开往某个区域的配货车),Buffer就是所要送的货物。
NioSocket使用中首先要创建ServerSocketChannel,然后注册Selector,接下来就可以用Selector接收请求并处理了。 
ServerSocketChannel可以使用自己的静态工程方法open创建。每个ServerSocketChannel对应一个ServerSocket,可以调用其socket方法来获取,不过如果直接使用获取到ServerSocket来监听请求,那还是原来的处理模式,一般使用获取到的ServerSocket来绑定端口。ServerSocketChannel可以通过configureBlocking方法来设置是否采用阻塞模式,如果要采用非阻塞模式可以用configureBlocking(false)来设置,设置了非阻塞模式之后就可以调用register方法注册Selector来使用了(阻塞模式不可以使用Selector)。 
Selector可以使用自己的静态工程方法open创建,创建后通过Channel的register方法注册到ServerSocketChannel或者SocketChannel上,注册完之后Selector就可以通过select方法来等等请求,select方法有一个long类型的参数,代表最长等待时间,如果在这段时间里接收到了相应操作的请求则返回可以处理的请求的数量,否则在超时后返回0,程序继续往下走,如果传入的参数为0或者调用无参数的重载方法,select方法会采用阻塞模式直到有相应操作的请求出现。当接收到请求后Selector调用selectedKeys方法返回SelectedKey的集合。 
selectedKey保存了处理当前请求的Channel和Selector,并且提供了不同的操作类型。Channel在注册Selector的时候可以通过register的第二个参数选择特定的操作,这里的操作就是在selectedKey中定义的,一共有4种: 
SelectionKey.OP_ACCEPT 
SelectionKey.OP_CONNECT 
SelectionKey.OP_READ 
SelectionKey.OP_WRITE 
分别表示接收请求操作、连接操作、读操作和写操作,只有在register方法中注册了相应的操作Selector才会关心相应类型操作的请求。 
Channel和Selector并没有谁属于谁的关系,就像数据库里的多对多的关系,不过Selecor这个分拣员分拣的更细,它可以按不同类型来分拣,分拣后的结果保存在SelectionKey中,可以分别通过SelectionKey的channel方法和selector方法来获取对应的Channel和Selector,而且还可以通过isAcceptable、isConnectable、isReadable和isWritable方法来判断是什么类型的操作。 
NioSocket中服务端的处理过程可以分为5步: 
1、创建ServerSocketChannel并设置相应参数 。
2、创建Selector并注册到ServerSocketChannel上 。
3、调用Selector的select方法等待请求 。
4、Selector接收到请求后使用selectionKeys返回SelectionKey集合 。
5、使用SelectionKey获取到Channel、Selector和操作类型并进行具体操作。

下面具体说说Selector,Channel和Buffer的用法。

 二、Selector 选择器 :

Selector 选择器 :非阻塞模式下,一个选择器可检测多个SelectableChannel,获得为读写等操作准备好的通道。就不需要我们用循环去判断了。通过Selector,一个线程就可以处理多个Channel,可极大减少线程数。 用cpu核心数量的线程,充分利用cpu资源,又减少线程切换。

Selector 用法:1,创建Selector。Selector selector = Selector.open();

       2,将要交给Selector检测的SelectableChannel注册进来。

          (1)channel.configureBlocking(false);   // 注意:一定要设为非阻塞模式

          (2)SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

          channel.register方法的第二个参数指定要selector帮忙监听的就绪操作:SelectionKey.OP_CONNECT(可连接);SelectionKey.OP_ACCEPT(可接受);SelectionKey.OP_READ(可读);SelectionKey.OP_WRITE(可写)。

       3,通过Selector来选择就绪的Channel,有三个select方法。int n = selector.select();

        (1) int select() //阻塞直到有就绪的Channel。
        (2)int select(long timeout) //阻塞最长多久。
        (3)int selectNow() //不阻塞。 

          三个方法返回值:就绪的Channel数量。 
          注意:select()方法返回当前的就绪数量。
          例:第一次select返回1;第二次select,又一个channel就绪,如果第一个就绪的channel还未被处理,则此时就绪的channel是2个,会返回2。在用线程池异步处理任务时需特别小心,重复选择!

       4,获得就绪的SelectionKey集合(当有就绪的Channel时)。

        Set<SelectionKey> selectedKeys = selector.selectedKeys();

       5,处理selectedKeys set。  

        Set<SelectionKey> selectedKeys = selector.selectedKeys();
        Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
        while(keyIterator.hasNext()) {
        SelectionKey key = keyIterator.next();
        if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.
        } else if (key.isConnectable()) {
        // a connection was established with a remote server.
        } else if (key.isReadable()) {
        // a channel is ready for reading
        } else if (key.isWritable()) {
        // a channel is ready for writing
        }
        keyIterator.remove(); //处理了,一定要从selectedKey集中移除
        }

 三、Channel 通道:数据的来源或去向目标

Java NIO: Channels read data into Buffers, and Buffers write data into Channels。

1、Channel的实现

  FileChannel 文件通道
  DatagramChannel UDP协议的通道
  SocketChannel 通常通道
  ServerSocketChannel 服务通道

2、各Channel的API方法

  open():创建通道
  read(Buffer):从通道中读数据放入到buffer
  write(Buffer):将buffer中的数据写给通道

三、Buffer   缓冲区:数据的临时存放区

Buffer类型:ByteBuffer、MappedByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer

Buffer的基本使用步骤:

(1)调用xxxBuffer.allocate(int)创建Buffer
(2)调用put方法往Buffer中写数据
(3)调用buffer.flip()将buffer转为读模式
(4)读取buffer中的数据
(5)Call buffer.clear() or buffer.compact()

1、Buffer的操作API

(1)调用xxxBuffer.allocate(int)创建Buffer
  ByteBuffer buf = ByteBuffer.allocate(48);
  CharBuffer buf = CharBuffer.allocate(1024);
(2)往Buffer中写数据
  int bytesRead = inChannel.read(buf); //read into buffer.
  buf.put(127);
(3)调用buffer.flip()将buffer转为读模式
  buf.flip(); // 转为读模式,position变为0
(4)读取buffer中的数据
  //read from buffer into channel.
  int bytesWritten = inChannel.write(buf);
  byte aByte = buf.get();
(5)读完后,调用clear()或compact()为下次写做好准备
  buf.clear(); //position=0 limit = capacity
  buf.compact(); //整理,将未读的数据移动到头部

如下所示为NioServer和NioClient的代码

public class NioServer {
private static Charset charset = Charset.forName("UTF-8");
private static CharsetDecoder decoder = charset.newDecoder();
public static void main(String[] args) throws IOException {
int port = 1104;
// 极少的线程
int threads = 3;
ExecutorService tpool = Executors.newFixedThreadPool(threads);
// 1、得到一个selector
Selector selector = Selector.open();
try (ServerSocketChannel ssc = ServerSocketChannel.open()) {
ssc.bind(new InetSocketAddress(port));
// 2 注册到selector
// 要非阻塞
ssc.configureBlocking(false);
// ssc向selector 注册,监听连接到来。
ssc.register(selector, SelectionKey.OP_ACCEPT);
// 连接计数
int connectionCount = 0;
// 3、循环选择就绪的通道
while (true) {
// 阻塞等待就绪的事件
int readyChannels = selector.select();
// 因为select()阻塞可以被中断
if (readyChannels == 0) {
continue;
} // 取到就绪的key集合
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
// a connection was accepted by a ServerSocketChannel.
ServerSocketChannel sssc = (ServerSocketChannel) key.channel();
// 接受连接
SocketChannel cc = sssc.accept();
// 请selector 帮忙检测数据到了
// 设置非阻塞
cc.configureBlocking(false);
// 向selector 注册
cc.register(selector, SelectionKey.OP_READ, ++connectionCount);
} else if (key.isConnectable()) {
// a connection was established with a remote server.
} else if (key.isReadable()) {
// a channel is ready for reading
// 4、读取数据进行处理
// 交各线程池去处理
tpool.execute(new SocketReadProcess(key));
// 取消一下注册,防止线程池处理不及时,没有注销掉
key.cancel();
} else if (key.isWritable()) {
// a channel is ready for writing
}
keyIterator.remove(); // 处理了,一定要从selectedKey集中移除
}
}
}
} static class SocketReadProcess implements Runnable {
SelectionKey key;
public SocketReadProcess(SelectionKey key) {
super();
this.key = key;
}
@Override
public void run() {
try {
System.out.println("连接" + key.attachment() + "发来:" + readFromChannel());
// 如果连接不需要了,就关闭
key.channel().close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} private String readFromChannel() throws IOException {
SocketChannel sc = (SocketChannel) key.channel();
int bfsize = 1024;
ByteBuffer rbf = ByteBuffer.allocateDirect(bfsize);
// 定义一个更大的buffer
ByteBuffer bigBf = null;
// 读的次数计数
int count = 0;
while ((sc.read(rbf)) != -1) {
count++;
ByteBuffer temp = ByteBuffer.allocateDirect(bfsize * (count + 1));
if (bigBf != null) {
// 将buffer有写转为读模式
bigBf.flip();
temp.put(bigBf);
}
bigBf = temp;
// 将这次读到的数据放入大buffer
rbf.flip();
bigBf.put(rbf);
// 为下次读,清理。
rbf.clear();
// 读出的是字节,要转为字符串
}
if (bigBf != null) {
// 转为读模式
bigBf.flip();
// 转成CharBuffer,再转为字符串。
return decoder.decode(bigBf).toString();
}
return null;
}
}
}

NioServer

public class NioClient {
static Charset charset = Charset.forName("UTF-8");
public static void main(String[] args) {
try (SocketChannel sc = SocketChannel.open();) {
// 连接 会阻塞
boolean connected = sc.connect(new InetSocketAddress("localhost", 1104));
System.out.println("connected=" + connected);
// 写
Scanner scanner = new Scanner(System.in);
System.out.println("请输入:");
String mess = scanner.nextLine();
ByteBuffer bf = ByteBuffer.wrap(mess.getBytes(charset));
while (bf.hasRemaining()) {
int writedCount = sc.write(bf);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

NioClient

如上代码所示,先执行NioServer再执行NioClient,用debug模式分步执行,多个客户端连接的时候,会发现不会阻塞。

当然对于NIO通信,还可以使用non-blockin模式和更加稳定的java开源框架Netty和MINA。

Socket网络通信之NIO的更多相关文章

  1. Socket网络通信之BIO

    Socket网络通信之BIO 如果要让两台计算机实现通信,需要的条件:ip,port,协议. 目前我们用的最多的就是TCP/IP协议和UDP协议.TCP三次握手,所以比较慢,且安全:UDP速度快,但是 ...

  2. Socket网络通信编程(二)

    1.Netty初步 2.HelloWorld 3.Netty核心技术之(TCP拆包和粘包问题) 4.Netty核心技术之(编解码技术) 5.Netty的UDP实现 6.Netty的WebSocket实 ...

  3. Socket 网络通信

    Socket 网络通信 1.OSI (Open System Interconnect Reference Model)(开放系统互联参考模型) 从下低到高 :物理层.数据链路层.网络层.传输层.会话 ...

  4. Socket网络通信——IO、NIO、AIO介绍以及区别

    一 基本概念 Socket又称"套接字",应用程序通常通过"套接字"向网路发出请求或者应答网络请求. Socket和ServerSocket类位于java.ne ...

  5. Socket Server-基于NIO的TCP服务器

    NIO主要原理及使用 NIO采取通道(Channel)和缓冲区(Buffer)来传输和保存数据,它是非阻塞式的I/O,即在等待连接.读写数据(这些都是在一线程以客户端的程序中会阻塞线程的操作)的时候, ...

  6. socket网络通信

    1.socket通常也称作"套接字",用于描述IP地址和端口.在internet上的主机一般运行了多个服务软件,同时提供几种服务,每种服务都打开一个socket,并绑定到一个端口上 ...

  7. java实现最基础的socket网络通信

    一.网络通信基础 网络中存在很多的通信实体,每一个通信实体都有一个标识符就是IP地址. 而现实中每一个网络实体可以和多个通信程序同时进行网络通信,这就需要使用端口号进行区分. 二.java中的基本网络 ...

  8. 【Java TCP/IP Socket】基于NIO的TCP通信(含代码)

    NIO主要原理及使用 NIO采取通道(Channel)和缓冲区(Buffer)来传输和保存数据,它是非阻塞式的I/O,即在等待连接.读写数据(这些都是在一线程以客户端的程序中会阻塞线程的操作)的时候, ...

  9. 【Java TCP/IP Socket】Java NIO Socket VS 标准IO Socket

    简介 Java  NIO从JDK1.4引入,它提供了与标准IO完全不同的工作方式. NIO包(java.nio.*)引入了四个关键的抽象数据类型,它们共同解决传统的I/O类中的一些问题.    1. ...

随机推荐

  1. nginx架构与基础概念

    1       Nginx架构 Nginx 高性能,与其架构有关. Nginx架构: nginx运行时,在unix系统中以daemon形式在后台运行,后台进程包含一个master进程和多个worker ...

  2. JSONArray.fromObject Date显示问题

    原文链接:http://www.cnblogs.com/Nbge/archive/2012/07/31/2617127.html 使用JSONArray.fromObject,Date类型打出来的完全 ...

  3. 论文阅读笔记五十六:(ExtremeNet)Bottom-up Object Detection by Grouping Extreme and Center Points(CVPR2019)

    论文原址:https://arxiv.org/abs/1901.08043 github: https://github.com/xingyizhou/ExtremeNet 摘要 本文利用一个关键点检 ...

  4. Django 解答 01 (pycharm创建项目)

    pycharm创建项目 1. 2. 3.Tools --->Deployment--->Options 这一条由always 改为 On explicit save action(Ctrl ...

  5. DropDownList年份的添加

    http://blog.sina.com.cn/s/blog_4b9e030e01007sc3.html

  6. 关于在JS中设置标签属性

    Attribute 该属性主要是用来在标签行内样式,添加.删除.获取属性.且适用于自定义属性. setAttribute("属性名",属性值“”):这个是用来设置标签属性的: re ...

  7. ubuntu同时装有MXNet和Caffe框架

    我阐述一下我遇到的问题:因为之前装过caffe,最近装了MXNet.MXNet可以运行,但import caffe就不行了,找不到模块. 那应该怎么处理呢??? 参考了一下这个网站:https://i ...

  8. connect to 10.104.11.128 port 9999 (tcp) failed: No route to host

    问题: iptables当找到匹配的规则时,就会执行相应的动作,而不会向下继续匹配. 可以看到https没有添加,匹配不到规则,所以就会包错 解决方法: iptables -I INPUT -p tc ...

  9. hbase数据库操作

    .实验内容与完成情况:(实验具体步骤和实验截图说明) (一)编程实现以下指定功能,并用 Hadoop 提供的 HBase Shell 命令完成相同任务: () 列出 HBase 所有的表的相关信息,例 ...

  10. my.ZC

    1.100级,裸身,满技能,属性模拟 数据:   大唐 方寸 化生 龙宫 普陀 地府 狮驼 魔王   气血 1200 1900 2600 1200 2600 2600 1900 1900   魔法 7 ...