Java NIO学习8(Selector 补充版SocketChannel)

SocketChannel

public abstract class SocketChannel extends AbstractSelectableChannel implements ByteChannel, ScatteringByteChannel, GatheringByteChannel {
	// This is a partial API listing
	public static SocketChannel open() throws IOException;

	public static SocketChannel open(InetSocketAddress remote) throws IOException;

	public abstract Socket socket();

	public abstract boolean connect(SocketAddress remote) throws IOException;

	public abstract boolean isConnectionPending();

	public abstract boolean finishConnect() throws IOException;

	public abstract boolean isConnected();

	public final int validOps();
}


Socket和SocketChannel类封装点对点、有序的网络连接,类似于我们所熟知并喜爱的TCP/IP网络连接。SocketChannel扮演客户端发起同一个监听服务器的连接。直到连接成功,它才能收到数据并且只会从连接到的地址接收

每个SocketChannel对象创建时都是同一个对等的java.net.Socket对象串联的。静态的open( )方法可以创建一个新的SocketChannel对象,而在新创建的SocketChannel上调用socket( )方法能返回它对等的Socket对象;在该Socket上调用getChannel( )方法则能返回最初的那个SocketChannel。

新创建的SocketChannel虽已打开却是未连接的。在一个未连接的SocketChannel对象上尝试一个I/O操作会导致NotYetConnectedException异常。

我们可以通过在通道上直接调用connect( )方法或在通道关联的Socket对象上调用connect( )来将该socket通道连接。

一旦一个socket通道被连接,它将保持连接状态直到被关闭。

您可以通过调用布尔型的isConnected( )方法来测试某个SocketChannel当前是否已连接。

第二种带InetSocketAddress参数形式的open( )是在返回之前进行连接的便捷方法。这段代码: 
SocketChannel socketChannel = SocketChannel.open (new InetSocketAddress ("somehost", somePort)); 
等价于下面这段代码:
 SocketChannel socketChannel = SocketChannel.open( ); 
socketChannel.connect (new InetSocketAddress ("somehost", somePort));

1 如果您选择使用传统方式进行连接——通过在对等Socket对象上调用connect( )方法,那么传统的连接语义将适用于此。线程在连接建立好或超时过期之前都将保持阻塞。

2 如果您选择通过在通道上直接调用connect( )方法来建立连接并且通道处于阻塞模式(默认模式),那么连接过程实际上是一样的。在SocketChannel上并没有一种connect( )方法可以让您指定超时(timeout)值,当connect( )方法在非阻塞模式下被调用时SocketChannel提供并发连接:它发起对请求地址的连接并且立即返回值

如果返回值是true,说明连接立即建立了(这可能是本地环回连接);

如果连接不能立即建立,connect( )方法会返回false且并发地继续连接建立过程。



面向流的的socket建立连接状态需要一定的时间,因为两个待连接系统之间必须进行包对话以建立维护流socket所需的状态信息。

跨越开放互联网连接到远程系统会特别耗时。假如某个SocketChannel上当前正由一个并发连接,isConnectPending( )方法就会返回true值。调用finishConnect( )方法来完成连接过程,该方法任何时候都可以安全地进行调用。假如在一个非阻塞模式的SocketChannel对象上调用finishConnect( )方法,将可能出现下列情形之一:

 connect( )方法尚未被调用。那么将产生NoConnectionPendingException异常。
 连接建立过程正在进行,尚未完成。那么什么都不会发生,finishConnect( )方法会立即返回false值。
 在非阻塞模式下调用connect( )方法之后,SocketChannel又被切换回了阻塞模式。那么如果有必要的话,调用线程会阻塞直到连接建立完成,finishConnect( )方法接着就会返回true值。
 在初次调用connect( )或最后一次调用finishConnect( )之后,连接建立过程已经完成。那么SocketChannel对象的内部状态将被更新到已连接状态,finishConnect( )方法会返回true值,然后SocketChannel对象就可以被用来传输数据了。
 连接已经建立。那么什么都不会发生,finishConnect( )方法会返回true值。
当通道处于中间的连接等待(connection-pending)状态时,您只可以调用finishConnect( )、isConnectPending( )或isConnected( )方法

一旦连接建立过程成功完成,isConnected( )将返回true值。

InetSocketAddress addr = new InetSocketAddress (host, port);
 SocketChannel sc = SocketChannel.open( ); 
sc.configureBlocking (false);
 sc.connect (addr); 

while ( ! sc.finishConnect( )) { 
doSomethingElse( ); 
}

doSomethingWithChannel (sc);
 sc.close( );


一段用来管理异步连接的可用代码。

public class SocketChannelApp {
	public static void main(String[] args) throws Exception {
		InetSocketAddress addr = new InetSocketAddress("127.0.0.1", 8989);
		SocketChannel sc = SocketChannel.open();
		sc.connect(addr);
		sc.configureBlocking(false);
		
		while (!sc.finishConnect()) {
			doSomethings();
		}
		
		//Do something with the connected socket
		ByteBuffer buffer = ByteBuffer.wrap(new String("Hello server!").getBytes());
		sc.write(buffer);
		sc.close();
		
	}

	private static void doSomethings() {
		System.out.println("do something useless!");
	}
}

server还是采用上篇的 ,我把它简单的改了改

public class ServerSocketChannelApp {
	private static final String MSG = "hello, I must be going \n";

	public static void main(String[] args) throws Exception {

		int port = 8989;
		ServerSocketChannel ssc = ServerSocketChannel.open();
		ServerSocket ss = ssc.socket();
		ss.bind(new InetSocketAddress(port));
		// set no blocking
		ssc.configureBlocking(false);

		ByteBuffer buffer = ByteBuffer.wrap(MSG.getBytes());

		while (true) {
//			System.out.println("wait for connection ……");
			SocketChannel sc = ssc.accept();
			
			if (sc == null) {
				// no connections, snooze a while ...
				Thread.sleep(1000);
			} else {
				System.out.println("Incoming connection from " + sc.socket().getRemoteSocketAddress());
				ByteBuffer readerBuffer = ByteBuffer.allocate(1024);
				sc.read(readerBuffer);
				readerBuffer.flip();
				//output get 
				out(readerBuffer);
				
				buffer.rewind();
				sc.write(buffer);
				sc.close();
			}
		}
	}

	private static void out(ByteBuffer readerBuffer) {
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < readerBuffer.limit(); i++) {
			char c = (char) readerBuffer.get();
			sb.append(new String(new char[]{c}));
		}
		
		System.out.println(sb.toString());
	}
}


ps:

如果尝试异步连接失败,那么下次调用finishConnect( )方法会产生一个适当的经检查的异常以指出问题的性质。通道然后就会被关闭并将不能被连接或再次使用。与连接相关的方法使得我们可以对一个通道进行轮询并在连接进行过程中判断通道所处的状态。第四章中,我们将了解到如何使用选择器来避免进行轮询并在异步连接建立之后收到通知。Socket通道是线程安全的。并发访问时无需特别措施来保护发起访问的多个线程,不过任何时候都只有一个读操作和一个写操作在进行中。请记住,sockets是面向流的而非包导向的。它们可以保证发送的字节会按照顺序到达但无法承诺维持字节分组。某个发送器可能给一个socket写入了20个字节而接收器调用read( )方法时却只收到了其中的3个字节。剩下的17个字节还是传输中。由于这个原因,让多个不配合的线程共享某个流socket的同一侧绝非一个好的设计选择。

connect( )和finishConnect( )方法是互相同步的,并且只要其中一个操作正在进行,任何读或写的方法调用都会阻塞,即使是在非阻塞模式下。如果此情形下您有疑问或不能承受一个读或写操作在某个通道上阻塞,请用isConnected( )方法测试一下连接状态。










  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java NIO 中的 Selector 可以用于多路复用 I/O,它可以同时监控多个 Channel 的 IO 状态,如读写就绪等,从而让你的程序可以同时处理多个网络连接。 使用 Selector 的基本流程如下: 1. 创建 Selector 对象:使用 `Selector.open()` 方法。 2. 创建并配置 Channel:每个 Channel 都必须注册到 Selector 上。 3. 向 Selector 注册感兴趣的事件:使用 `SelectionKey` 对象将 Channel 和感兴趣的事件绑定。 4. 通过 `select()` 方法监控 Channel:该方法会阻塞,直到至少有一个 Channel 处于就绪状态。 5. 处理就绪的 Channel:通过 `selectedKeys()` 方法获取所有就绪的 Channel,然后遍历每一个 Key,并根据 Key 的事件状态进行相应的处理。 6. 关闭 Selector:使用 `close()` 方法关闭 Selector。 以上就是 Selector 的基本使用方法。希望这些信息能帮助你理解和使用 Java NIO 中的 Selector。 ### 回答2: Java NIO(New Input/Output)提供了一种非阻塞I/O的能力,其中的selector是一种重要的组件。它允许程序通过一个单线程来监听多个通道上的事件并做出相应的处理。 使用Selector主要包括以下步骤: 1. 创建Selector实例: Selector selector = Selector.open(); 2. 创建Channel并设置为非阻塞模式: 在使用Selector之前,需要确保Channel处于非阻塞模式,例如SocketChannel或ServerSocketChannelSocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); 3. 将Channel注册到Selector上: 通过SelectionKey来表示Channel的注册状态,包括感兴趣的操作集合及其附加的数据。可以使用以下方法将Channel注册到Selector上: SelectionKey key = socketChannel.register(selector, SelectionKey.OP_READ); 4. 进行事件监听: 使用Selector的select()方法进行事件监听,它会阻塞,直到有一个或多个事件发生: int readyChannels = selector.select(); if (readyChannels == 0) { continue; } 5. 获取已就绪的事件集合: 通过调用selector.selectedKeys()方法获取已经就绪的事件集合: Set<SelectionKey> selectedKeys = selector.selectedKeys(); 6. 遍历已就绪的事件集合并处理: 遍历selectedKeys集合,处理每一个就绪的事件: Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key.isReadable()) { // 可读事件处理逻辑 } if (key.isWritable()) { // 可写事件处理逻辑 } keyIterator.remove(); // 处理完毕后需要手动移除该事件,避免重复处理 } 7. 关闭Selector: 使用完Selector后需要及时关闭: selector.close(); 使用Selector可以实现多个通道的事件监听和处理,极大地提高了应用程序的性能和资源利用率。需要注意的是,在使用Selector时,一个线程可以管理多个Channel,但要谨慎处理每个Channel上的事件,以避免阻塞整个Selector处理线程。 ### 回答3: Java NIO(New I/O)是一种非阻塞I/O操作的Java API。它提供了一组用于高效处理I/O操作的类和接口。其中,SelectorNIO的核心组件之一,用于实现非阻塞I/O。 Selector是一个类似于调度员的对象,它可以同时监视多个通道的I/O事件。使用Selector可以实现单线程同时管理多个通道的I/O操作,提高了系统的效率。 使用Selector的主要步骤如下: 1. 创建一个Selector对象:通过调用Selector.open()方法创建一个Selector对象。 2. 将通道注册到Selector上:将需要监视的通道注册到Selector上,例如SocketChannel、ServerSocketChannel等。通过调用通道的register()方法完成注册。 3. 设置通道的非阻塞模式:通过调用通道的configureBlocking(false)方法将通道设置为非阻塞模式。 4. 选择通道:通过调用Selector的select()方法选择通道,并返回已准备就绪的通道的数量。 5. 处理选择的通道:通过调用Selector的selectedKeys()方法获取选择的通道集合,可以通过遍历通道集合进行相应的读写操作。 6. 取消选择的通道:通过调用SelectionKey的cancel()方法取消选择的通道的注册。 示例代码如下: ```java Selector selector = Selector.open(); SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); socketChannel.connect(new InetSocketAddress("example.com", 80)); socketChannel.register(selector, SelectionKey.OP_CONNECT); while (true) { int readyChannels = selector.select(); if (readyChannels == 0) { continue; } Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key.isConnectable()) { // 处理连接就绪的通道 SocketChannel channel = (SocketChannel) key.channel(); if (channel.isConnectionPending()) { channel.finishConnect(); } channel.configureBlocking(false); channel.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { // 处理读就绪的通道 SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); channel.read(buffer); buffer.flip(); // 处理读取到的数据 } keyIterator.remove(); } } ``` 以上是一个简单的Selector的使用示例,通过这些步骤,可以实现对多个通道的非阻塞I/O操作的监视和处理。需要注意的是,Selector是基于事件驱动的,可以实现高效的I/O操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值