前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >NIO之完成网络通信

NIO之完成网络通信

作者头像
用户4919348
发布2019-04-18 16:31:03
5000
发布2019-04-18 16:31:03
举报
文章被收录于专栏:波波烤鸭波波烤鸭波波烤鸭

  NIO被叫为 no-blocking io,其实是在网络这个层次中理解的,对于FileChannel来说一样是阻塞。对于网络通信是还有如下几个Channel

 java.nio.channels.Channel 接口
	|-- SelectableChannel
		|-- SocketChannel
		|-- ServerSocketChannel
		|-- DatagramChannel
		
		|-- Pipe.SinkChannel
		|-- Pipe.SourceChannel

  我们通常使用NIO是在网络中使用的,网上大部分讨论NIO都是在网络通信的基础之上的!说NIO是非阻塞的NIO也是网络中体现的! nio的核心要素有:

Buffer缓冲区
Channel通道
Selector选择器

我们在网络中使用NIO往往是I/O模型的多路复用模型!

NIO阻塞形态

  为了更好地理解,我们先来写一下NIO在网络中是阻塞的状态代码,随后看看非阻塞是怎么写的就更容易理解了。是阻塞的就没有Selector选择器了,就直接使用Channel和Buffer就完事了。

客户端代码

/**
 * 使用NIO完成网络通信的三个核心
 * 
 * 1.通道(Channel):负责连接
 * 	  java.nio.channels.Channel 接口
 * 			|-- SelectableChannel
 * 				|-- SocketChannel
 * 				|-- ServerSocketChannel
 * 				|-- DatagramChannel
 * 				
 * 				|-- Pipe.SinkChannel
 * 				|-- Pipe.SourceChannel
 * 
 *  2.缓冲区(Buffer):负责数据的存取
 *  
 *  3.选择器(Selector):是SelectableChannel的多路复用器。用于监控SelectableChannel的IO状况
 * 
 * 
 * @author 波波烤鸭
 * @email dengpbs@163.com
 *
 */
public class BlockClient {

	/**
	 * 传递图片
	 * @param args
	 * @throws Exception 
	 */
	public static void main(String[] args) throws Exception {
		// 1.获取通道
		SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
		// 2.发送一张图片给服务器
		FileChannel fileChannel = FileChannel.open(Paths.get("c:/tools/a9.jpg"), StandardOpenOption.READ);
		// 3.要使用NIO,有了Channel必然要使用Buffer,Buffer是与数据打交道的
		ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
		
		// 4.读取本地文件,发送给服务器
		while(fileChannel.read(byteBuffer)!=-1){
			byteBuffer.flip();//读取之前切换成读模式
			socketChannel.write(byteBuffer);
			byteBuffer.clear();
		}
		// 显示的告诉服务器数据写完了
		socketChannel.shutdownOutput();
		int num = 0 ;
		byteBuffer.clear();
		
		while((num = socketChannel.read(byteBuffer)) !=-1){
			// 切回读模式
			byteBuffer.flip();
			System.out.println(new String(byteBuffer.array(),0,num));
			byteBuffer.clear();
		}
		/*// 5.关闭流
		fileChannel.close();
		socketChannel.close();*/
	}
}

服务器代码

public class BlockServer {

	/**
	 * IO阻塞 服务端
	 * @param args
	 * @throws Exception 
	 */
	public static void main(String[] args) throws Exception {
		//1.获取通道
		ServerSocketChannel server = ServerSocketChannel.open();
		//2.得到文件通道,将客户端传递的图片写入到本地
		FileChannel fileChannel = FileChannel.open(Paths.get("c:/tools/bb.jpg"), 
				StandardOpenOption.WRITE
				,StandardOpenOption.CREATE);// 如果不存在就创建
		// 3.绑定连接
		server.bind(new InetSocketAddress(9999));
		// 4.获取客户端的连接(阻塞的)
		SocketChannel client = server.accept();
		// 5.声明Buffer存储图片
		ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
		// 6.将客户端传递的数据保存在本地
		while(client.read(byteBuffer)!=-1){
			byteBuffer.flip();
			fileChannel.write(byteBuffer);
			byteBuffer.clear();
		}
		
		byteBuffer.put("图片接收成功!!".getBytes());
		byteBuffer.flip();
		client.write(byteBuffer);
		byteBuffer.clear();
		// 显示的告诉客户端信息输完了
		client.shutdownOutput();
		/*fileChannel.close();
		client.close();
		server.close();*/
	}
}

NIO非阻塞形态

  如果使用非阻塞模式的话,那么我们就可以不显式告诉服务器已经发完数据了,但是需要显示的指定是非阻塞的

客户端:

public class NoBlockClient {

	public static void main(String[] args) throws Exception {
		// 1. 获取通道
		SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 6666));

		// 1.1切换成非阻塞模式
		socketChannel.configureBlocking(false);

		// 2. 发送一张图片给服务端吧
		FileChannel fileChannel = FileChannel.open(Paths.get("c:/tools/a9.jpg"),
				StandardOpenOption.READ);

		// 3.要使用NIO,有了Channel,就必然要有Buffer,Buffer是与数据打交道的呢
		ByteBuffer buffer = ByteBuffer.allocate(1024);

		// 4.读取本地文件(图片),发送到服务器
		while (fileChannel.read(buffer) != -1) {

			// 在读之前都要切换成读模式
			buffer.flip();

			socketChannel.write(buffer);

			// 读完切换成写模式,能让管道继续读取文件的数据
			buffer.clear();
		}

		// 5. 关闭流
		fileChannel.close();
		socketChannel.close();

	}
}

服务器:

public class NoBlockServer {

	public static void main(String[] args) throws Exception {
		// 1.获取通道
		ServerSocketChannel server = ServerSocketChannel.open();

		// 2.切换成非阻塞模式
		server.configureBlocking(false);

		// 3. 绑定连接
		server.bind(new InetSocketAddress(6666));

		// 4. 获取选择器
		Selector selector = Selector.open();

		// 4.1将通道注册到选择器上,指定接收“监听通道”事件
		server.register(selector, SelectionKey.OP_ACCEPT);

		// 5. 轮训地获取选择器上已“就绪”的事件--->只要select()>0,说明已就绪
		while (selector.select() > 0) {
			// 6. 获取当前选择器所有注册的“选择键”(已就绪的监听事件)
			Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

			// 7. 获取已“就绪”的事件,(不同的事件做不同的事)
			while (iterator.hasNext()) {

				SelectionKey selectionKey = iterator.next();

				// 接收事件就绪
				if (selectionKey.isAcceptable()) {

					// 8. 获取客户端的链接
					SocketChannel client = server.accept();

					// 8.1 切换成非阻塞状态
					client.configureBlocking(false);

					// 8.2 注册到选择器上-->拿到客户端的连接为了读取通道的数据(监听读就绪事件)
					client.register(selector, SelectionKey.OP_READ);

				} else if (selectionKey.isReadable()) { // 读事件就绪

					// 9. 获取当前选择器读就绪状态的通道
					SocketChannel client = (SocketChannel) selectionKey.channel();

					// 9.1读取数据
					ByteBuffer buffer = ByteBuffer.allocate(1024);

					// 9.2得到文件通道,将客户端传递过来的图片写到本地项目下(写模式、没有则创建)
					FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.WRITE,
							StandardOpenOption.CREATE);

					while (client.read(buffer) > 0) {
						// 在读之前都要切换成读模式
						buffer.flip();
						outChannel.write(buffer);
						// 读完切换成写模式,能让管道继续读取文件的数据
						buffer.clear();
					}
				}
				// 10. 取消选择键(已经处理过的事件,就应该取消掉了)
				iterator.remove();
			}
		}
	}
}

简单总结一下使用NIO时的要点:

  1. 将Socket通道注册到Selector中,监听感兴趣的事件
  2. 当感兴趣的时间就绪时,则会进去我们处理的方法进行处理
  3. 每处理完一次就绪事件,删除该选择键(因为我们已经处理完了)

DatagramChannel

发送方

public static void main(String[] args) throws IOException {
	// 获取通道
	DatagramChannel dc = DatagramChannel.open();
	// 非阻塞的
	dc.configureBlocking(false);
	ByteBuffer buf = ByteBuffer.allocate(1024);
	Scanner scan = new Scanner(System.in);
	while(scan.hasNext()){
		String str = scan.next();
		buf.put((new Date().toString()+"\n"+str).getBytes());		
		buf.flip();
		dc.send(buf, new InetSocketAddress("127.0.0.1", 6666));
		buf.clear();
	}
	
	dc.close();
}

接收方

public static void main(String[] args) throws Exception {
	// TODO Auto-generated method stub
	DatagramChannel dc = DatagramChannel.open();
	dc.bind(new InetSocketAddress(6666));
	dc.configureBlocking(false);
	// 获取选择器
	Selector selector = Selector.open();
	dc.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);
				dc.receive(buf);
				buf.flip();
				System.out.println(new String(buf.array(),0,buf.limit()));
				buf.clear();
			}
		}
		it.remove();
	}
}

管道(Pipe)

  Java NIO 管道是2个线程之间的单向数据连接,Pipe有一个source通道和一个sink通道,数据会被写到sink通道,从source通道读取。

在这里插入图片描述
在这里插入图片描述
public static void main(String[] args) throws Exception {
	// 1.获取管道
	Pipe pipe = Pipe.open();
	// 2.将缓冲区数据写入管道
	ByteBuffer buf = ByteBuffer.allocate(1024);
	
	// 3.获取SinkChannel对象
	SinkChannel sinkChannel = pipe.sink();
	buf.put("hello pipe".getBytes());
	buf.flip();
	sinkChannel.write(buf);
	
	// 4.读取缓冲区中的数据
	SourceChannel source = pipe.source();
	buf.flip();
	int num = source.read(buf);
	System.out.println(new String(buf.array(),0,num));
	source.close();
	sinkChannel.close();
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年04月11日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • NIO阻塞形态
  • NIO非阻塞形态
  • DatagramChannel
  • 管道(Pipe)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档