Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >NIO之Channel通道(二)-SelectableChannel、SocketChannel、ServerSocketChannel

NIO之Channel通道(二)-SelectableChannel、SocketChannel、ServerSocketChannel

作者头像
云飞扬
发布于 2022-04-25 06:20:43
发布于 2022-04-25 06:20:43
60400
代码可运行
举报
文章被收录于专栏:星汉技术星汉技术
运行总次数:0
代码可运行

NIO之Channel通道(二)-SelectableChannel、SocketChannel、ServerSocketChannel

1、SelectableChannel

SelectableChannel是一个抽象类,它实现了Channel接口,这个类比较特殊。

SelectableChannel可以被Selector用来多路复用,不过首先需要调用selectableChannel.configureBlocking(false)调整为非阻塞模式。

可选通道的使用大致过程如下:

  • 1.新建通道。
  • 2.将通道的事件注册到选择器Selector上。
  • 3.通过SelectKey获得需要处理的通道,然后对通道进行处理。

SelectableChannel的实现类,可以看出主要分为几大块:

  • 有关UDP协议的:DatagramChannel。
  • 有关SCTP协议的:SctpChannel、SctpMultiChannel、SctpServerChannel。
  • 有关TCP协议的:ServerSocketChannel、SocketChannel。
  • 有关管道的:SinkChannel、SourceChannel这两个抽象类定义在java.nio.channels.Pipe类中。

1.1重要方法

1.1.1register()

注册通道到选择器。在ServerSocketChannle和SocketChannel上提供了register方法来实现注册,通过SelectionKey来实现选择。

此方法有两个重载:

  • SelectionKey register(Selector sel, int ops)
  • SelectionKey register(Selector sel, int ops, Object att)

参数释义:

  • 第一个参数代表要注册的Selector实例。
  • 第二个参数代表本通道感兴趣的操作,这些都定义在SelectionKey类中。
  • 第三个参数Object att是注册时的附件,也就是可以在注册的时候带点什么东西过去。
1.1.2isRegistered()

获取通道是否注册到选择器,新创建的通道没有注册,返回true表示已经注册,false表示没有注册。

1.1.3configureBlocking(boolean)

这个是设置channel的阻塞模式的,true代表block;false为non-block。一般用non-block配合Selector多路复用

1.1.4isBlocking()

检测当前通道的阻塞状态。默认直接返回Channel.blocking属性的值。

2、SocketCannel

用于Socket的TCP连接的数据读写,既可以从Channel读数据,也可以向Channle中写入数据。

2.1重要方法

2.1.1open()

获取SocketCannel通道。

有两个重载:

  • open()
  • open(SocketAddress)
2.1.2validOps()

返回一个本通道支持的操作方式,支持三种,读、写连接。

2.1.3bind(SocketAddress)

绑定一个本地的套接字地址。

2.1.4setOption(SocketOption<T>, T)

设置套接字的操作方式。

2.1.5shutdownInput()

停止当前连接的读操作,并且关闭通道。

2.1.6shutdownOutput()

停止当前连接的写操作,并且关闭通道。

2.1.7socket()

获取一个和当前通道有关的套接字。

2.1.8isConnected()

判断是否建立了连接。

2.1.9isConnectionPending()

当前通道上是否存在正在连接的操作。

2.1.10connect(SocketAddress)

建立连接。

2.1.11finishConnect()

完成连接。

2.1.12getRemoteAddress()

返回一个此通道的套接字已经连接的远程地址。

2.1.13read()

读数据。

此方法有三个重载。

  • read(ByteBuffer)
  • read(ByteBuffer[], int, int)
  • read(ByteBuffer[])
2.1.14write()

写数据。

此方法有三个重载。

  • write(ByteBuffer)
  • write(ByteBuffer[], int, int)
  • write(ByteBuffer[])
2.1.15getLocalAddress()

获取此通道的套接字绑定的地址。

2.2案例

Socket客户端代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package xyz.xujd.testcase.nio;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class SocketChannelTest {

	public static void main(String[] args) {
		try {
			SocketChannel sc = SocketChannel.open();
			sc.configureBlocking(true);
			System.out.println(sc.connect(new InetSocketAddress("localhost", 8090)));
			ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
			while (true) {
				int count = 0;
				while ((count = sc.read(byteBuffer)) > 0) {
					byteBuffer.flip();
					while (byteBuffer.hasRemaining()) {
						System.out.println((char) byteBuffer.get());
					}
					byteBuffer.clear();
				}
				sc.write(byteBuffer.put("return".getBytes()));
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

3、ServerSocketChannel

针对面向流的侦听套接字的可选择通道。多个并发线程可安全地使用服务器套接字通道。

通过ServerSocketChannel可以监听TCP连接,服务端监听到连接之后,会为每个请求创建一个SocketChannel。

3.1重要方法

3.1.1accept()

接受连接。

3.1.2bind()

将通道的套接字与本地地址绑定,并且配置套接字监听连接。

此方法有两个重载。

  • bind(SocketAddress)
  • bind(SocketAddress, int)
3.1.3open()

获取ServerSocketChannel通道。

3.1.4socket()

获取服务器关联的套接字。

3.1.5validOps()

返回一个操作集,表示次通道支持的操作。

3.1.6setOption(SocketOption<T>, T)

设置服务器操作集。

3.1.7getLocalAddress()

获取服务器套接字关联的地址。

3.2案例

Socket服务端代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package xyz.xujd.testcase.nio;

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 ServerSocketChannelTest {

	private ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

	public static void main(String[] args) {
		ServerSocketChannelTest tssc = new ServerSocketChannelTest();
		tssc.run();
	}

	public void run() {
		try {
			// 创建一个选择器。
			Selector selector = Selector.open();
			// 打开一个通道。
			ServerSocketChannel ssc = ServerSocketChannel.open();
			// 设置为非阻塞模式
			ssc.configureBlocking(false);
			// 在通道上绑定一个地址
			ssc.socket().bind(new InetSocketAddress(8090));
			// 将通道注册到选择器上,并设置选择器的时间是OP_ACCET
			// 一个通道可以注册到多个选择器上,但是一个通道在一个选择器上只能注册一次。
			// 不同的通道在选择器中注册的事件不一样,第二个参数是有限制的,由channel类型决定。
			ssc.register(selector, SelectionKey.OP_ACCEPT);
			// 服务器端准备就绪。
			System.out.println("------------------服务器开始等待客户端连接----------------------------------------");
			while (true) {
				// 选择器阻塞,等待事件到来,事件不来就阻塞,事件到来返回时间到来的通道个数。
				int i = selector.select();
				if (i == 0) {
					continue;
				}
				// 遍历事件。
				Iterator<SelectionKey> it = selector.selectedKeys().iterator();
				while (it.hasNext()) {
					SelectionKey selectionKey = it.next();
					// 如果是OP_ACCEPT,表示这个事件是ServerSocketChannel的事件,因为SocketChannel没有这个事件。
					if (selectionKey.isAcceptable()) {
						ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
						// 建立连接
						SocketChannel channel = server.accept();
						// 将建立的链接设置为准备读,并注册到选择器上。
						registerChannel(selector, channel, SelectionKey.OP_READ);
						// 向建立的通道写入内容。
						hello(channel);
					}
					// 如果可读时间,没有在ServerScoketChannel上注册可读,值在SokectChannel上注册了可读。
					if (selectionKey.isReadable()) {
						// 从通道中读取数据,只有通道中有数据,才会进入这个条件中。
						readFromSocket(selectionKey);
					}
					// 此时只是将当前出发的事件从list中移除,在下一次选择时,还会查看改通道是否有操作事件发生。
					it.remove();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 读取数据
	 * 
	 * @param selectionKey
	 * @throws IOException
	 */
	private void readFromSocket(SelectionKey selectionKey) throws IOException {
		SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
		buffer.clear();
		int count = 0;
		// 当有数据可读时,第一次读到的数据不应该是0,它会读取到0或-1
		while ((count = socketChannel.read(buffer)) > 0) {
			buffer.flip();
			// 将数据回写到通道中
			while (buffer.hasRemaining()) {
				socketChannel.write(buffer);
			}
			buffer.clear();
		}
		if (count < 0) {
			socketChannel.close();
		}
	}

	/**
	 * 向通道中写入数据。
	 *
	 * @param channel
	 * @throws IOException
	 */
	private void hello(SocketChannel channel) throws IOException {
		buffer.clear();
		buffer.put("Hello".getBytes());
		buffer.flip();
		channel.write(buffer);
	}

	/**
	 * 将channel注册到选择器上。
	 * 
	 * @param selector 选择器
	 * @param channel  通道
	 * @param opRead   操作
	 * @throws IOException
	 */
	private void registerChannel(Selector selector, SocketChannel channel, int opRead) throws IOException {
		if (channel == null) {
			return;
		}
		channel.configureBlocking(false);
		channel.register(selector, opRead);
	}

}

4、测试阻塞

利用ServerSocketChannel、SocketChannel实现NIO方式先的TCP通信的类,在非阻塞模式下ACCEPT CONNECT READ WRITE方法都不产生阻塞。

4.1ACCEPT的非阻塞验证

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	public static void main(String[] args) throws IOException {
		ServerSocketChannel ssc = ServerSocketChannel.open();
		ssc.configureBlocking(false);
		ssc.bind(new InetSocketAddress(44444));
		ssc.accept();
	}

执行程序,执行过程一闪而过,没有出现阻塞。

4.2CONNECT的非阻塞验证

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	public static void main(String[] args) throws IOException {
		SocketChannel sc = SocketChannel.open();
		sc.configureBlocking(false);
		sc.connect(new InetSocketAddress("127.0.0.1", 44444));
	}

执行程序,执行过程一闪而过,没有出现阻塞。

4.3READ的非阻塞验证

服务端:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	public static void main(String[] args) throws IOException {
		// 创建通道
		ServerSocketChannel ssc = ServerSocketChannel.open();
		// 开启非阻塞模式
		ssc.configureBlocking(false);
		// 绑定监听端口
		ssc.bind(new InetSocketAddress(44444));
		SocketChannel sc = null;
		// 确保已经建立连接
		while (sc == null) {
			sc = ssc.accept();
		}
		// 开启非阻塞模式
		sc.configureBlocking(false);
		// 创建缓存区
		ByteBuffer buf = ByteBuffer.allocate(5);
		// 读取数据
		sc.read(buf);
	}

客户端:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	public static void main(String[] args) throws IOException {
		// 创建通道
		SocketChannel sc = SocketChannel.open();
		// 开启非阻塞模式
		sc.configureBlocking(false);
		// 建立连接,确定连接目标,返回是否连接成功
		boolean isConn = sc.connect(new InetSocketAddress("127.0.0.1", 44444));
		// 如果没有连接成功,重复连接,直到连接建立
		while (!isConn) {
			isConn = sc.finishConnect();
		}
		// 模拟写阻塞
		while (true) {
		}
	}

首先执行服务端代码,然后执行客户端代码,当客户端代码执行过后,服务端代码就结束执行了,说明服务端代码读操作不阻塞。

4.4WRITE的非阻塞验证

客户端:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	public static void main(String[] args) throws IOException {
		// 创建通道
		SocketChannel sc = SocketChannel.open();
		// 开启非阻塞模式
		sc.configureBlocking(false);
		// 建立连接,确定连接目标,返回是否连接成功
		boolean isConn = sc.connect(new InetSocketAddress("127.0.0.1", 44444));
		// 如果没有连接成功,重复连接,直到连接建立
		while (!isConn) {
			isConn = sc.finishConnect();
		}
		// 写操作计数
		int conut=0;
		while (true) {
			//模拟写操作
			int i=sc.write(ByteBuffer.wrap("a".getBytes()));
			conut+=i;
			System.out.println(conut);
		}
	}

服务器端:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	public static void main(String[] args) throws IOException {
		// 创建通道
		ServerSocketChannel ssc = ServerSocketChannel.open();
		// 开启非阻塞模式
		ssc.configureBlocking(false);
		// 绑定监听端口
		ssc.bind(new InetSocketAddress(44444));
		SocketChannel sc = null;
		// 确保已经建立连接
		while (sc == null) {
			sc = ssc.accept();
		}
		// 开启非阻塞模式
		sc.configureBlocking(false);
		//模拟读操作阻塞
		while(true){}
	}

执行服务器端代码,执行客户端代码,可以看到客户端一直在输出数字,大概到两万三千多的时候一直不停的打印出这个数字,而服务器什么都没有打印,说明没有接收,但是客户端仍然可以一直写,证明写操作也没有阻塞。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022/04/16 ,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Java NIO深入理解ServerSocketChannel
JAVA NIO有两种解释:一种叫非阻塞IO(Non-blocking I/O),另一种也叫新的IO(New I/O),其实是同一个概念。它是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,已经被越来越多地应用到大型应用服务器,成为解决高并发与大量连接、I/O处理问题的有效方式。
用户1251985
2019/07/02
1.5K0
Java NIO深入理解ServerSocketChannel
Bio、Nio、Aio的用法系列之NIO服务端(二)
技术圈有很多人说NIO是new IO,是因为他是新增的接口,这也是官方说法,但是,我们知道,以前都是阻塞IO,详细见上文BIO详解,而NIO是非阻塞的,所以说,NIO更确切的说法 是non-block IO,当然关于说法,大家可以根据自己的理解,不过多做解释。 首先在讲解NIO之前我们先了解几个概念
用户1257393
2018/07/30
3440
【Netty】NIO 选择器 ( Selector ) 通道 ( Channel ) 缓冲区 ( Buffer ) 网络通信案例
NIO 网络通信 服务器端 操作流程 , 与 BIO 原理类似 , 基本流程是 启动服务器套接字通道 , 创建选择器 , 将服务器套接字通道注册给选择器 , 监听客户端连接事件 , 客户端连接成功后 , 创建套接字通道 , 将新创建的通道注册给选择器 , 然后监听该通道的读取事件 ;
韩曙亮
2023/03/27
7320
【Netty】NIO 选择器 ( Selector ) 通道 ( Channel ) 缓冲区 ( Buffer ) 网络通信案例
NIO学习(二)Channel通道与Selectors选择器
引用上一篇文章的区别。IO是传统的面向流的阻塞IO,而NIO是面向缓冲区的非阻塞式IO。在NIO中使用了一个线程来作为Selectors-选择器,来管理多个输入通道,即在使用时只需要将通道注册到选择器中,即可处理输入的通道和选择已经准备好的通道进行管理。
虞大大
2020/08/26
6360
NIO学习(二)Channel通道与Selectors选择器
【Netty】「NIO」(三)剖析 Selector
本篇博文是《从0到1学习 Netty》中 NIO 系列的第三篇博文,主要内容是介绍通过使用 Selector,一个单独的线程可以有效地监视多个通道,从而提高应用程序的处理效率,往期系列文章请访问博主的 Netty 专栏,博文中的所有代码全部收集在博主的 GitHub 仓库中;
sidiot
2023/08/30
3220
【Netty】「NIO」(三)剖析 Selector
NIO 之 Channel实现原理
相关文章 IO、NIO、AIO 内部原理分析 NIO 之 Selector实现原理 NIO 之 ByteBuffer实现原理 NIO概述 Java NIO 由以下几个核心部分组成: Channels Buffers Selectors 在传统IO中,流是基于字节的方式进行读写的。 在NIO中,使用通道(Channel)基于缓冲区数据块的读写。 Channel 和 IO 流的区别 Java NIO的通道类似IO中的流,但又有些不同: 既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。
java404
2018/05/18
1.2K0
Netty 入门篇 Day 3---网络编程
在阻塞模式下,会导致 线程暂停 ssc.accept(); // 阻塞的方法 会导致线程暂停,一直等到有client连接 才继续工作 channel.read(buffer); // 阻塞的方法 会导致线程暂停,一直等client发送信息 才继续进行读操作 服务器端的单线程模式下,阻塞方法会导致这个线程暂停(闲置); 同时 多个client相到受影响,几乎不能正确工作,需要服务器端的多线程支持 服务器端的多线程模式缺点:1) 占用内存多 2)多线程切换,带来比较大的内存开销
猫头虎
2024/04/08
1160
Netty 入门篇 Day 3---网络编程
nio学习之Selector选择器
如果程序打开了多个连接通道,每个连接的流量都比较低,可以使用Selector对通道进行管理
大忽悠爱学习
2021/12/13
5290
nio学习之Selector选择器
「高并发通信框架Netty4 源码解读(六)」NIO通道之Socket通道
全部 socket 通道类(DatagramChannel、 SocketChannel 和ServerSocketChannel)都是由位于 java.nio.channels.spi 包中的 AbstractSelectableChannel 引申而来。这意味着我们可以用一个 Selector 对象来执行 socket 通道的有条件的选择。选择器下一篇再讲。
源码之路
2020/09/04
7280
「高并发通信框架Netty4 源码解读(六)」NIO通道之Socket通道
Netty: NIO Selector选择器(C/S demo详细注释与源码)
三个元素: Selector选择器、SelectableChannel可选择的通道、SelectionKey选择键
冷环渊
2021/11/17
2800
Netty: NIO Selector选择器(C/S demo详细注释与源码)
java nio_(一) Java NIO 概述[通俗易懂]
通常在进行同步I/O操作时,如果读取数据,代码会阻塞直至有 可供读取的数据。同样,写入调用将会阻塞直至数据能够写入。传统的Server/Client模式会基于TPR(Thread per Request),服务器会为每个客户端请求建立一个线程,由该线程单独负责处理一个客户请求。这种模式带来的一个问题就是线程数量的剧增,大量的线程会增大服务器的开销。大多数的实现为了避免这个问题,都采用了线程池模型,并设置线程池线程的最大数量,这由带来了新的问题,如果线程池中有200个线程,而有200个用户都在进行大文件下载,会导致第201个用户的请求无法及时处理,即便第201个用户只想请求一个几KB大小的页面。传统的 Server/Client模式如下图所示:
全栈程序员站长
2022/09/08
5720
java nio_(一) Java NIO 概述[通俗易懂]
Java NIO 实现网络通信
Java NIO 的相关资料很多,对 channel,buffer,selector 如何相关概念也有详细的阐述。但是,不亲自写代码调试一遍,对这些概念的理解仍然是一知半解。
水货程序员
2018/11/13
1.1K0
NIO之Selector解读
Selector 一般称 为选择器 ,也可以翻译为 多路复用器 。它是 Java NIO 核心组件中 的一个,用于检查一个或多个 NIO Channel(通道)的状态是否处于可读、可写。如 此可以实现单线程管理多个 channels,也就是可以管理多个网络链接。
一个风轻云淡
2023/10/15
2360
NIO之Selector解读
从零讲解搭建一个NIO消息服务端
假设你已经了解并实现过了一些OIO消息服务端,并对异步消息服务端更有兴趣,那么本文或许能带你更好的入门,并了解JDK部分源码的关系流程,正如题目所说,笔者将竟可能还原,以初学者能理解的角度,讲诉并构建一个NIO消息服务端。
Java猫说
2019/04/11
5260
从零讲解搭建一个NIO消息服务端
NIO学习四-Selector
前面我们已经简单的学习了channel,知道channel作为通道,可以在通道中进行读写操作,同时知道ByteChannel是双向的。对于NIO的优势在于多路复用选择器上,在Nginx、Redis、Netty中都有多路复用的体现。因此学习Selector是有必要的。
路行的亚洲
2020/07/16
3930
Netty --Selector选择器
Channel(read/write) 多个连接 Event决定切换到那个channel
疯狂的KK
2020/02/19
1K0
庖丁解牛:NIO核心概念与机制详解 06 _ 连网和异步 I/O
在 Java NIO 中,连网操作与其他操作一样,依赖于通道(Channel)和缓冲区(Buffer)。通道是用于读取和写入数据的途径,而缓冲区则用于暂存数据。
小小工匠
2023/11/21
1900
庖丁解牛:NIO核心概念与机制详解 06 _ 连网和异步 I/O
掌握NIO,程序人生
就像新IO为java带来的革新那样,让我们也开启一段新的程序人生。 关键字:NIO,BIO,伪IO,AIO,多路复用选择器,通道,缓冲区,jdk研究,回调函数,高并发 java.nio 概述 历史背景 在java nio出现之前,java网络IO是只有输入输出流操作的基于同步阻塞的Socket编程,这在实际应用中效率低下,因此当时高性能服务器开发领域一直被拥有更接近UNIX操作系统的Channel概念的C++和C长期占据。我们知道现代操作系统的根源都是来自于UNIX系统,它代表了操作系统层面底层
文彬
2018/05/08
1.3K1
Netty-nio
channel 有一点类似于 stream,它就是读写数据的双向通道,可以从 channel 将数据读入 buffer,也可以将 buffer 的数据写入 channel,而之前的 stream 要么是输入,要么是输出,channel 比 stream 更为底层
sgr997
2022/11/10
7180
Netty-nio
Java NIO与IO 区别和比较
NIO包(java.nio.*)引入了四个关键的抽象数据类型,它们共同解决传统的I/O类中的一些问题。
IT工作者
2022/03/30
2170
相关推荐
Java NIO深入理解ServerSocketChannel
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验