3.5.3 SocketChannel 下面开始学习SocketChannel,它是使用最多的socket通道类: public abstract class SocketChannel extends AbstractSelectableChannel implements ByteChannel, ScatteringByteChannel, GatheringByteChannel { // 这里仅列出部分API 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扮演客户端发起同一个监听服务器的连接。直到连接成功,它才能收到数据并且只会从连接到的地址接收。(对于 ServerSocketChannel,由于涉及到validOps()方法,我们将在第四章检查选择器时进行讨论。通用的 read/write 方法也未在此列出,详情请参考 3.1.2节。) 每个SocketChannel对象创建时都是同一个对等的java.net.Socket对象串联的。静态的open()方法可以创建一个新的SocketChannel对象,而在新创建的SocketChannel上调用socket()方法能返回它对等的Socket对象;在该Socket上调用getChannel()方法则能返回最初的那个SocketChannel。 虽然每个SocketChannel对象都会创建一个对等的Socket对象,反过来却不成立。直接创建的Socket对象不会关联SocketChannel对象,它们的getChannel()方法只返回null。 新创建的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)); 如果您选择使用传统方式进行连接——通过在对等Socket对象上调用connect()方法,那么传统的连接语义将适用于此。线程在连接建立好或超时过期之前都将保持阻塞。如果您选择通过在通道上直接调用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(); 例 3-8 是一段用来管理异步连接的可用代码。 /* *例 3-8 建立并发连接 */ package com.ronsoft.books.nio.channels; import java.nio.channels.SocketChannel; import java.net.InetSocketAddress; /** * Demonstrate asynchronous connection of a SocketChannel. * @author Ron Hitchens (ron@ronsoft.com) */ public class ConnectAsync { public static void main (String [] argv) throws Exception { String host = "localhost"; int port = 80; if (argv.length == 2) { host = argv[0]; port = Integer.parseInt (argv[1]); } InetSocketAddress addr = new InetSocketAddress(host, port); SocketChannel sc = SocketChannel.open(); sc.configureBlocking (false); System.out.println ("initiating connection"); sc.connect (addr); while ( !sc.finishConnect()) { doSomethingUseful(); } System.out.println ("connection established"); // Do something with the connected socket // The SocketChannel is still nonblocking sc.close(); } private static void doSomethingUseful() { System.out.println ("doing something useless"); } } 如果尝试异步连接失败,那么下次调用finishConnect()方法会产生一个适当的经检查的异常以指出问题的性质。通道然后就会被关闭并将不能被连接或再次使用。 与连接相关的方法使得我们可以对一个通道进行轮询并在连接进行过程中判断通道所处的状态。第四章中,我们将了解到如何使用选择器来避免进行轮询并在异步连接建立之后收到通知。 Socket通道是线程安全的。并发访问时无需特别措施来保护发起访问的多个线程,不过任何时候都只有一个读操作和一个写操作在进行中。请记住,sockets 是面向流的而非包导向的。它们可以保证发送的字节会按照顺序到达但无法承诺维持字节分组。某个发送器可能给一个socket写入了20 个字节而接收器调用read()方法时却只收到了其中的3个字节。剩下的17个字节还是传输中。由于这个原因,让多个不配合的线程共享某个流 socket 的同一侧绝非一个好的设计选择。 connect()和finishConnect()方法是互相同步的,并且只要其中一个操作正在进行,任何读或写的方法调用都会阻塞,即使是在非阻塞模式下。如果此情形下您有疑问或不能承受一个读或写操作在某个通道上阻塞,请用isConnected()方法测试一下连接状态。 Java nio入门教程详解(二十七)
|