还记得之前介绍NIO时对比传统IO的一大特点吗?就是NIO是非阻塞式的,这篇文章带大家来看一下非阻塞的网络操作。

补充:以数组的形式使用缓冲区

package testnio;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; public class TestBufferArray { public static void main(String[] args) throws IOException {
RandomAccessFile raf1=new RandomAccessFile("D:/1.txt","rw"); //1.获取通道
FileChannel channel1=raf1.getChannel(); //2.创建缓冲区数组
ByteBuffer buf1=ByteBuffer.allocate(512);
ByteBuffer buf2=ByteBuffer.allocate(512);
ByteBuffer[] bufs= {buf1,buf2};
//3.将数据读入缓冲区数组
channel1.read(bufs); for (ByteBuffer byteBuffer : bufs) {
byteBuffer.flip();
}
System.out.println(new String(bufs[0].array(),0,bufs[0].limit()));
System.out.println("-----------");
System.out.println(new String(bufs[1].array(),0,bufs[1].limit())); //写入缓冲区数组到通道中
RandomAccessFile raf2=new RandomAccessFile("D:/2.txt","rw");
FileChannel channel2=raf2.getChannel();
channel2.write(bufs); }
}

使用NIO实现阻塞式网络通信

TCP协议的网络通信传统实现方式是通过套接字编程(Socket和ServerSocket),NIO实现TCP网络通信需要用到 Channel 接口的两个实现类:SocketChannel和ServerSocketChannel

使用NIO实现阻塞式网络通信

客户端

package com.jikedaquan.blockingnio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption; public class Client { public static void main(String[] args) { SocketChannel sChannel=null; FileChannel inChannel=null;
try {
//1、获取通道
sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 1666));
//用于读取文件
inChannel = FileChannel.open(Paths.get("F:/a.jpg"), StandardOpenOption.READ); //2、分配指定大小的缓冲区
ByteBuffer buf=ByteBuffer.allocate(1024); //3、读取本地文件,发送到服务器端 while(inChannel.read(buf)!=-1) {
buf.flip();
sChannel.write(buf);
buf.clear();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭通道
if (inChannel!=null) {
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
} if(sChannel!=null) {
try {
sChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

new InetSocketAddress("127.0.0.1", 1666) 用于向客户端套接字通道(SocketChannel)绑定要连接地址和端口

服务端

package com.jikedaquan.blockingnio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption; public class Server { public static void main(String[] args) { ServerSocketChannel ssChannel=null; FileChannel outChannel=null; SocketChannel sChannel=null;
try {
//1、获取通道
ssChannel = ServerSocketChannel.open();
//用于保存文件的通道
outChannel = FileChannel.open(Paths.get("F:/b.jpg"), StandardOpenOption.WRITE,StandardOpenOption.CREATE); //2、绑定要监听的端口号
ssChannel.bind(new InetSocketAddress(1666));
//3、获取客户端连接的通道
sChannel = ssChannel.accept(); //4、分配指定大小的缓冲区
ByteBuffer buf=ByteBuffer.allocate(1024); //5、接收客户端的数据,并保存到本地
while(sChannel.read(buf)!=-1) {
buf.flip();
outChannel.write(buf);
buf.clear();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//6、关闭通道
if(sChannel!=null) {
try {
sChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(outChannel!=null) {
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(ssChannel!=null) {
try {
ssChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

服务端套接字仅绑定要监听的端口即可 ssChannel.bind(new InetSocketAddress(1666));

上面的代码使用NIO实现的网络通信,可能有同学会问,没有看到阻塞效果啊,确实是阻塞式的看不到效果,因为客户端发送一次数据就结束了,服务端也是接收一次数据就结束了。那如果服务端接收完成数据后,再向客户端反馈呢?

能够看到阻塞效果的网络通信

客户端

package com.jikedaquan.blockingnio2;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption; public class Client { public static void main(String[] args) {
SocketChannel sChannel=null;
FileChannel inChannel=null;
try {
sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 1666));
inChannel = FileChannel.open(Paths.get("F:/a.jpg"), StandardOpenOption.READ); ByteBuffer buf=ByteBuffer.allocate(1024); while(inChannel.read(buf)!=-1) {
buf.flip();
sChannel.write(buf);
buf.clear();
} //sChannel.shutdownOutput();//去掉注释掉将不会阻塞 //接收服务器端的反馈
int len=0;
while((len=sChannel.read(buf))!=-1) {
buf.flip();
System.out.println(new String(buf.array(),0,len));
buf.clear();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(inChannel!=null) {
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(sChannel!=null) {
try {
sChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

服务端

package com.jikedaquan.blockingnio2;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption; public class Server { public static void main(String[] args) { ServerSocketChannel ssChannel=null;
FileChannel outChannel=null;
SocketChannel sChannel=null;
try {
ssChannel = ServerSocketChannel.open();
outChannel = FileChannel.open(Paths.get("F:/a.jpg"),StandardOpenOption.WRITE,StandardOpenOption.CREATE); ssChannel.bind(new InetSocketAddress(1666));
sChannel = ssChannel.accept();
ByteBuffer buf=ByteBuffer.allocate(1024); while(sChannel.read(buf)!=-1) {
buf.flip();
outChannel.write(buf);
buf.clear();
} //发送反馈给客户端
buf.put("服务端接收数据成功".getBytes());
buf.flip();
sChannel.write(buf);
} catch (IOException e) {
e.printStackTrace();
}finally {
if(sChannel!=null) {
try {
sChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(outChannel!=null) {
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(ssChannel!=null) {
try {
ssChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

服务端将向客户端发送两次数据

选择器(Selector)

想要实现非阻塞的IO,必须要先弄懂选择器。Selector 抽象类,可通过调用此类的 open 方法创建选择器,该方法将使用系统的默认选择器提供者创建新的选择器。

将通道设置为非阻塞之后,需要将通道注册到选择器中,注册的同时需要指定一个选择键的类型 (SelectionKey)。

选择键(SelectionKey)可以认为是一种标记,标记通道的类型和状态。

SelectionKey的静态字段:

OP_ACCEPT:用于套接字接受操作的操作集位

OP_CONNECT:用于套接字连接操作的操作集位

OP_READ:用于读取操作的操作集位

OP_WRITE:用于写入操作的操作集位

用于检测通道状态的方法:

方法名称 说明
isAcceptable() 测试此键的通道是否已准备好接受新的套接字连接
isConnectable() 测试此键的通道是否已完成其套接字连接操作
isReadable() 测试此键的通道是否已准备好进行读取
isWritable() 测试此键的通道是否已准备好进行写入

将通道注册到选择器:

ssChannel.register(selector, SelectionKey.OP_ACCEPT);

IO操作准备就绪的通道大于0,轮询选择器

while(selector.select()>0) {
//获取选择键,根据不同的状态做不同的操作
}

实现非阻塞式TCP协议网络通信

非阻塞模式:channel.configureBlocking(false);

客户端

package com.jikedaquan.nonblockingnio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Scanner; public class Client { public static void main(String[] args) {
SocketChannel sChannel=null;
try {
//1、获取通道
sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",1666)); //2、切换非阻塞模式
sChannel.configureBlocking(false); //3、分配指定大小的缓冲区
ByteBuffer buf=ByteBuffer.allocate(1024);
//4、发送数据给服务端
Scanner scanner=new Scanner(System.in);
//循环从控制台录入数据发送给服务端
while(scanner.hasNext()) { String str=scanner.next();
buf.put((new Date().toString()+"\n"+str).getBytes());
buf.flip();
sChannel.write(buf);
buf.clear();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//5、关闭通道
if(sChannel!=null) {
try {
sChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

服务端

package com.jikedaquan.nonblockingnio;

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; public class Server { public static void main(String[] args) throws IOException { //1、获取通道
ServerSocketChannel ssChannel=ServerSocketChannel.open();
//2、切换非阻塞模式
ssChannel.configureBlocking(false);
//3、绑定监听的端口号
ssChannel.bind(new InetSocketAddress(1666));
//4、获取选择器
Selector selector=Selector.open();
//5、将通道注册到选择器上,并指定“监听接收事件”
ssChannel.register(selector, SelectionKey.OP_ACCEPT); //6、轮询式的获取选择器上已经 “准备就绪”的事件
while(selector.select()>0) {
//7、获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
Iterator<SelectionKey> it=selector.selectedKeys().iterator();
while(it.hasNext()) {
//8、获取准备就绪的事件
SelectionKey sk=it.next();
//9、判断具体是什么事件准备就绪
if(sk.isAcceptable()) {
//10、若“接收就绪”,获取客户端连接
SocketChannel sChannel=ssChannel.accept();
//11、切换非阻塞模式
sChannel.configureBlocking(false);
//12、将该通道注册到选择器上
sChannel.register(selector, SelectionKey.OP_READ);
}else if(sk.isReadable()) {
//13、获取当前选择器上“读就绪”状态的通道
SocketChannel sChannel=(SocketChannel)sk.channel();
//14、读取数据
ByteBuffer buf=ByteBuffer.allocate(1024);
int len=0;
while((len=sChannel.read(buf))>0) {
buf.flip();
System.out.println(new String(buf.array(),0,len));
buf.clear();
}
}
//15、取消选择键 SelectionKey
it.remove();
} }
}
}

服务端接收客户端的操作需要在判断 isAcceptable() 方法内将就绪的套接字通道以读操作注册到 选择器中

在判断 isReadable() 内从通道中获取数据

实现非阻塞式UDP协议网络通信

发送端

package com.jikedaquan.nonblockingnio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.Scanner; public class TestDatagramSend { public static void main(String[] args) throws IOException {
//获取通道
DatagramChannel dChannel=DatagramChannel.open();
//非阻塞
dChannel.configureBlocking(false);
ByteBuffer buf=ByteBuffer.allocate(1024);
Scanner scanner=new Scanner(System.in);
while(scanner.hasNext()) {
String str=scanner.next();
buf.put(str.getBytes());
buf.flip();
//发送数据到目标地址和端口
dChannel.send(buf,new InetSocketAddress("127.0.0.1", 1666));
buf.clear();
}
dChannel.close();
}
}

接收端

package com.jikedaquan.nonblockingnio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator; public class TestDatagramReceive {
public static void main(String[] args) throws IOException {
//获取通道
DatagramChannel dChannel=DatagramChannel.open();
dChannel.configureBlocking(false);
//绑定监听端口
dChannel.bind(new InetSocketAddress(1666));
//获取选择器
Selector selector=Selector.open();
//读操作注册通道
dChannel.register(selector, SelectionKey.OP_READ);
while(selector.select()>0) {
Iterator<SelectionKey> it=selector.selectedKeys().iterator();
//迭代选择键
while(it.hasNext()) {
SelectionKey sk=it.next();
//通道可读
if(sk.isReadable()) {
ByteBuffer buf=ByteBuffer.allocate(1024);
//接收数据存入缓冲区
dChannel.receive(buf);
buf.flip();
System.out.println(new String(buf.array(),0,buf.limit()));
buf.clear();
}
} it.remove();
}
}
}

Java入门系列-25-NIO(实现非阻塞网络通信)的更多相关文章

  1. JAVA基础知识之网络编程——-基于NIO的非阻塞Socket通信

    阻塞IO与非阻塞IO 通常情况下的Socket都是阻塞式的, 程序的输入输出都会让当前线程进入阻塞状态, 因此服务器需要为每一个客户端都创建一个线程. 从JAVA1.4开始引入了NIO API, NI ...

  2. Java并发包源码学习系列:基于CAS非阻塞并发队列ConcurrentLinkedQueue源码解析

    目录 非阻塞并发队列ConcurrentLinkedQueue概述 结构组成 基本不变式 head的不变式与可变式 tail的不变式与可变式 offer操作 源码解析 图解offer操作 JDK1.6 ...

  3. 4.NIO的非阻塞式网络通信

    /*阻塞 和 非阻塞 是对于 网络通信而言的*/ /*原先IO通信在进行一些读写操作 或者 等待 客户机连接 这种,是阻塞的,必须要等到有数据被处理,当前线程才被释放*/ /*NIO 通信 是将这个阻 ...

  4. Java NIO 同步非阻塞

    同步非阻塞IO (NIO) NIO是基于事件驱动思想的,实现上通常采用Reactor(http://en.wikipedia.org/wiki/Reactor_pattern)模式,从程序角度而言,当 ...

  5. Java NIO Socket 非阻塞通信

    相对于非阻塞通信的复杂性,通常客户端并不需要使用非阻塞通信以提高性能,故这里只有服务端使用非阻塞通信方式实现 客户端: package com.test.client; import java.io. ...

  6. JAVA NIO学习三:NIO 的非阻塞式网络通信

    紧接着上一章,我们继续来研究NIO,上一章中我们讲了NIO 中最常见的操作即文件通道的操作,但实际上NIO的主要用途还是在于网络通信,那么这个时候就会涉及到选择器,这一章我们就会对其进行讲解操作. 一 ...

  7. java nio实现非阻塞Socket通信实例

    服务器 package com.java.xiong.Net17; import java.io.IOException; import java.net.InetSocketAddress; imp ...

  8. java的高并发IO原理,阻塞BIO同步非阻塞NIO,异步非阻塞AIO

    原文地址: IO读写的基础原理 大家知道,用户程序进行IO的读写,依赖于底层的IO读写,基本上会用到底层的read&write两大系统调用.在不同的操作系统中,IO读写的系统调用的名称可能不完 ...

  9. 【Java并发编程】9、非阻塞同步算法与CAS(Compare and Swap)无锁算法

    转自:http://www.cnblogs.com/Mainz/p/3546347.html?utm_source=tuicool&utm_medium=referral 锁(lock)的代价 ...

随机推荐

  1. Backup--修改实例级别是否使用压缩备份的默认值

    --========================================= --修改实例级别是否使用压缩备份的默认值 USE master; GO EXEC sp_configure 'b ...

  2. Android Post方式发送信息和获取URL中的图片

    需要Internet权限,AndroidManifest.xml <uses-permission android:name="android.permission.INTERNET& ...

  3. 使用PHPstudy在Windows服务器下部署PHP系统

    PHP网站一般运行在Apache服务器上,IIS服务器上也可以运行,但是不推荐,windows服务器上没有集成Apache跟PHP的运行环境,但是借助第三方的软件就能很好的搞定,一般这样的软件有很多, ...

  4. iis 部署webapi常见错误及解决方案

    iis 部署webapi常见错误及解决方案 错误一: 原因:asp.net web api部署在Windows服务器上后,按照WebAPI定义的路由访问,老是出现404,但定义一个静态文件从站点访问, ...

  5. adt-bundle-windows不显示ADK Manage和其它图标的解决方法?

    我今天下载了包含ADT的eclipse,运行后发现在工具栏中居然没有ADK Manage和其它Android相关图标,这是为什么啊?上网搜索了一下,最终解决了!解决方法,把ADK的tool路径加入到p ...

  6. leetcode 33 Search in Rotated Sorted Array JAVA

    题目: 假设按照升序排序的数组在预先未知的某个点上进行了旋转. ( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] ). 搜索一个给定的目标值,如果数组中存在这个 ...

  7. vue element-ui 表格筛选,点击筛选数据

    <el-table-column prop="productEnvVersion" label="运行环境" :filters="this.ru ...

  8. iptables总结

        iptables: 包过滤型防火墙      Firewall: 防火墙,隔离工具:工作于主机或网络的边缘,对于进出本主机或网络的报文根据事先定义好的检查规则作匹配检测,对于能够被规则所匹配到 ...

  9. js的事件机制二

    js的事件机制二 1.给合适的HTML标签添加合适的事件 onchange-----select下拉框 onload-----body标签 单双击-----用户会进行点击动作的HTML元素 鼠标事件 ...

  10. [spring] spring 装配Bean

    Bean的配置信息是Bean的元数据信息.包括1.Bean的实现类.2.Bean的属性信息.3.Bean的依赖关系.4.Bean的行为配置(比如控制Bean的生命周期中init,destory方法). ...