Java入门系列-25-NIO(实现非阻塞网络通信)
还记得之前介绍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(实现非阻塞网络通信)的更多相关文章
- JAVA基础知识之网络编程——-基于NIO的非阻塞Socket通信
		阻塞IO与非阻塞IO 通常情况下的Socket都是阻塞式的, 程序的输入输出都会让当前线程进入阻塞状态, 因此服务器需要为每一个客户端都创建一个线程. 从JAVA1.4开始引入了NIO API, NI ... 
- Java并发包源码学习系列:基于CAS非阻塞并发队列ConcurrentLinkedQueue源码解析
		目录 非阻塞并发队列ConcurrentLinkedQueue概述 结构组成 基本不变式 head的不变式与可变式 tail的不变式与可变式 offer操作 源码解析 图解offer操作 JDK1.6 ... 
- 4.NIO的非阻塞式网络通信
		/*阻塞 和 非阻塞 是对于 网络通信而言的*/ /*原先IO通信在进行一些读写操作 或者 等待 客户机连接 这种,是阻塞的,必须要等到有数据被处理,当前线程才被释放*/ /*NIO 通信 是将这个阻 ... 
- Java NIO 同步非阻塞
		同步非阻塞IO (NIO) NIO是基于事件驱动思想的,实现上通常采用Reactor(http://en.wikipedia.org/wiki/Reactor_pattern)模式,从程序角度而言,当 ... 
- Java NIO Socket 非阻塞通信
		相对于非阻塞通信的复杂性,通常客户端并不需要使用非阻塞通信以提高性能,故这里只有服务端使用非阻塞通信方式实现 客户端: package com.test.client; import java.io. ... 
- JAVA NIO学习三:NIO 的非阻塞式网络通信
		紧接着上一章,我们继续来研究NIO,上一章中我们讲了NIO 中最常见的操作即文件通道的操作,但实际上NIO的主要用途还是在于网络通信,那么这个时候就会涉及到选择器,这一章我们就会对其进行讲解操作. 一 ... 
- java nio实现非阻塞Socket通信实例
		服务器 package com.java.xiong.Net17; import java.io.IOException; import java.net.InetSocketAddress; imp ... 
- java的高并发IO原理,阻塞BIO同步非阻塞NIO,异步非阻塞AIO
		原文地址: IO读写的基础原理 大家知道,用户程序进行IO的读写,依赖于底层的IO读写,基本上会用到底层的read&write两大系统调用.在不同的操作系统中,IO读写的系统调用的名称可能不完 ... 
- 【Java并发编程】9、非阻塞同步算法与CAS(Compare and Swap)无锁算法
		转自:http://www.cnblogs.com/Mainz/p/3546347.html?utm_source=tuicool&utm_medium=referral 锁(lock)的代价 ... 
随机推荐
- Arcgis Android 手动搭建开发环境
			前言 本文为大家分享arcgis android 环境的手动搭建过程,默认你懂一定的java和android 基础知识,已经有android的开发环境.如缺乏以上环境和知识,请自行补充. 版本介绍 A ... 
- Java算法 -- 顺序表
			顺序表结构定义:就是按照顺序存储方式存储的线性表 1.定义一个顺序表的基本数据: static final int MAXLEN = 100; Class Student{ private Strin ... 
- C++ STL详解
			C++ STL详解 转载自:http://www.cnblogs.com/shiyangxt/archive/2008/09/11/1289493.html 一.STL简介 STL(Standard ... 
- ZKEACMS 的两种发布方式
			前言 如果你还不知道ZKEACMS,不妨先了解一下. ASP.NET MVC 开源建站系统 ZKEACMS 推荐,从此网站“拼”起来 官方地址:http://www.zkea.net/zkeacms ... 
- Android 打开URL中的网页和拨打电话、发送短信功能
			拨打电话需要的权限 <uses-permission android:name="android.permission.CALL_PHONE"/> 为了省事界面都写一起 ... 
- 序列(DP)(组合数)
			这是一个DP题. 我们设\(f[i][j][k]\)表示\(i\)序列长度中放入了\(j\)个元素,其中\(k\)是限定的众数的个数:状态转移方程是 \[f[k][i][j]=f[k][i-1][j- ... 
- css3 hover效果
			html代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset="U ... 
- [ActionScript 3.0]  常用的正则表达式
			as 3.0常用的正则表达式: /* * 去除字符串前面的空格和跳格符 */ var src:String=" Hello! "; trace(src); //原文本 trace( ... 
- [ActionScript 3.0] 十进制与二进制,十六进制等数据之间的相互转换
			将十进制转换为二进制,方法是:将数字除以2,根据余数来从右往左排列二进制的位数,如下以十进制数10为例 10除以2得5,余数为0,故第一个位置为0: 5除以2得2,余数为1,故第二个位置为1: 2除以 ... 
- KVM 安装 VMware 虚拟机
			去掉了“双引号”改为:vmx.allowNested = TRUE 打开在其中创建虚拟机的文件夹VMDISK和搜索与您的虚拟机的名称. vmx 文件. 用记事本打开它,并添加上述条目. 所以 vmx. ... 
