java.nio.channels.SocketChannel
1. SocketChannel的父子继承关系
2. SocketChannel功能
从SocketChannel类的注释可以看到以下几点:
1). socket channel是面向流的连接socket的一个可选通道。
2). 通过调用#open()方法创建socket channel。根据不同参数,为不同的socket创建channel。
3). socket channel支持非阻塞连接。创建套接字通道,和建立到远程套接字的链接的过程可以通过#connect方法初始化,再由#finishConnect方法完成。
4). socket channel支持异步关闭,类似于在Channel类中指定的异步关闭操作。
5). socket channel对于多个并发线程来说是安全的。如果方法正在执行,读或写的操作会被阻塞。
/**
* A selectable channel for stream-oriented connecting sockets.
* socket channel是面向流的连接套接字的一个可选通道。
*
* <p> A socket channel is created by invoking one of the {@code open} methods of
* this class. The no-arg {@link #open() open} method opens a socket channel
* for an <i>Internet protocol</i> socket. The {@link #open(ProtocolFamily)}
* method is used to open a socket channel for a socket of a specified protocol
* family. It is not possible to create a channel for an arbitrary, pre-existing
* socket. A newly-created socket channel is open but not yet connected. An
* attempt to invoke an I/O operation upon an unconnected channel will cause a
* {@link NotYetConnectedException} to be thrown. A socket channel can be
* connected by invoking its {@link #connect connect} method; once connected,
* a socket channel remains connected until it is closed. Whether or not a
* socket channel is connected may be determined by invoking its {@link #isConnected()
* isConnected} method.
* 通过调用#open()方法创建socket channel
* #open()无参方法给网络协议socket创建socket channel
* #open(ProtocolFamily)方法被用来去给一个特殊的协议族的socket去创建一个socket channel
* 不能去给一个任意预先存在的socket创建channel
* 一个新创建的socket channel 是open状态,但是还没有连接
* 在还没有连接的channel上尝试去调用I/O操作会抛出一个NotYetConnectedException异常
* socket channel可以通过调用#connect()方法来进行连接
* 一旦连接上,socket channel就会维持链接状态知道它被关闭
* socket channel 是否处于连接状态,可以通过调用#isConnected()方法判断。
*
* <p> Socket channels support <i>non-blocking connection:</i> A socket
* channel may be created and the process of establishing the link to the
* remote socket may be initiated via the {@link #connect connect} method for
* later completion by the {@link #finishConnect finishConnect} method.
* Whether or not a connection operation is in progress may be determined by
* invoking the {@link #isConnectionPending isConnectionPending} method.
* socket channel支持非阻塞连接
* 创建套接字通道,和建立到远程套接字的链接的过程可以通过#connect方法初始化,再由#finishConnect方法完成。
* 连接操作是否正在进行可以通过调用#isConnectionPending方法来确定。
*
* <p> Socket channels support <i>asynchronous shutdown</i>, which is similar
* to the asynchronous close operation specified in the {@link Channel} class.
* If the input side of a socket is shut down by one thread while another
* thread is blocked in a read operation on the socket's channel, then the read
* operation in the blocked thread will complete without reading any bytes and
* will return {@code -1}. If the output side of a socket is shut down by one
* thread while another thread is blocked in a write operation on the socket's
* channel, then the blocked thread will receive an {@link
* AsynchronousCloseException}.
* socket channel支持异步关闭,类似于在Channel类中指定的异步关闭操作。
*
* <p> Socket options are configured using the {@link #setOption(SocketOption,Object)
* setOption} method. Socket channels for <i>Internet protocol</i> sockets support
* following options:
* <blockquote>
* <table class="striped">
* <caption style="display:none">Socket options</caption>
* <thead>
* <tr>
* <th scope="col">Option Name</th>
* <th scope="col">Description</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <th scope="row"> {@link java.net.StandardSocketOptions#SO_SNDBUF SO_SNDBUF} </th>
* <td> The size of the socket send buffer </td>
* </tr>
* <tr>
* <th scope="row"> {@link java.net.StandardSocketOptions#SO_RCVBUF SO_RCVBUF} </th>
* <td> The size of the socket receive buffer </td>
* </tr>
* <tr>
* <th scope="row"> {@link java.net.StandardSocketOptions#SO_KEEPALIVE SO_KEEPALIVE} </th>
* <td> Keep connection alive </td>
* </tr>
* <tr>
* <th scope="row"> {@link java.net.StandardSocketOptions#SO_REUSEADDR SO_REUSEADDR} </th>
* <td> Re-use address </td>
* </tr>
* <tr>
* <th scope="row"> {@link java.net.StandardSocketOptions#SO_LINGER SO_LINGER} </th>
* <td> Linger on close if data is present (when configured in blocking mode
* only) </td>
* </tr>
* <tr>
* <th scope="row"> {@link java.net.StandardSocketOptions#TCP_NODELAY TCP_NODELAY} </th>
* <td> Disable the Nagle algorithm </td>
* </tr>
* </tbody>
* </table>
* </blockquote>
*
* <p> Socket channels for <i>Unix domain</i> sockets support:
* <blockquote>
* <table class="striped">
* <caption style="display:none">Socket options</caption>
* <thead>
* <tr>
* <th scope="col">Option Name</th>
* <th scope="col">Description</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <th scope="row"> {@link java.net.StandardSocketOptions#SO_SNDBUF SO_SNDBUF} </th>
* <td> The size of the socket send buffer </td>
* </tr>
* <tr>
* <th scope="row"> {@link java.net.StandardSocketOptions#SO_RCVBUF SO_RCVBUF} </th>
* <td> The size of the socket receive buffer </td>
* </tr>
* <tr>
* <th scope="row"> {@link java.net.StandardSocketOptions#SO_LINGER SO_LINGER} </th>
* <td> Linger on close if data is present (when configured in blocking mode
* only) </td>
* </tr>
* </tbody>
* </table>
* </blockquote>
*
* <p> Additional (implementation specific) options may also be supported.
*
* <p> Socket channels are safe for use by multiple concurrent threads. They
* support concurrent reading and writing, though at most one thread may be
* reading and at most one thread may be writing at any given time. The {@link
* #connect connect} and {@link #finishConnect finishConnect} methods are
* mutually synchronized against each other, and an attempt to initiate a read
* or write operation while an invocation of one of these methods is in
* progress will block until that invocation is complete. </p>
* socket channel对于多个并发线程来说是安全的。
* 尽管在任何给定的时间内最多有一个线程在读,最多有一个线程在写。但是他们仍然支持并发读和并发写。
* #connect()方法#finishConnect()方法是相互同步的,
* 当尝试调用正在执行的#connect()方法#finishConnect()方法时,读或写的操作都会阻塞,直到这个调用完成。
*
* @author Mark Reinhold
* @author JSR-51 Expert Group
* @since 1.4
*/
public abstract class SocketChannel
extends AbstractSelectableChannel
implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel
{
......
}
3. SocketChannel提供的方法
1). open() 方法
open()方法实现逻辑:
(1). 新的SocketChannel通过调用系统默认的java.nio.channels.spi.SelectorProvider对象的openSocketChannel方法创建的
(2). 第一次获取SelectorProvider对象按照一下顺序:
a. 查看系统属性java.nio.channels.spi.selectorprovider是否存在,存在即它将当作为具体provider类的完全限定名。
b. 如果一个provider类已经打包安装在一个jar文件中,同时这个jar文件对系统类加载器是可见的,并且这个jar文件在它的资源目录META-INF/services中包含一个名为java.nio.channels.spi.SelectorProvider的provider配置文件,然后取该文件中指定的第一个类名。
c. 最后,如果在上面没有指定provider,系统默认的provider类就会实例化。
(3). 调用SocketChannelImpl.openSocketChannel()方法,new SocketChannelImpl(this),对自身进行实例构造。
/**
* Opens a socket channel.
*
* <p> The new channel is created by invoking the {@link
* java.nio.channels.spi.SelectorProvider#openSocketChannel
* openSocketChannel} method of the system-wide default {@link
* java.nio.channels.spi.SelectorProvider} object. </p>
* 新的channel通过调用系统默认的java.nio.channels.spi.SelectorProvider对象的openSocketChannel方法创建的
*
* @return A new socket channel
*
* @throws IOException
* If an I/O error occurs
*/
public static SocketChannel open() throws IOException {
return SelectorProvider.provider().openSocketChannel();
}
/**
* Returns the system-wide default selector provider for this invocation of
* the Java virtual machine.
* 对这次虚拟机的调用,返回一个系统范围内默认的selcetor provider
*
* <p> The first invocation of this method locates the default provider
* object as follows: </p>
* 第一次调用这个方法的时候会按照下列次序定位默认的provider对象
*
* <ol>
*
* <li><p> If the system property
* <tt>java.nio.channels.spi.SelectorProvider</tt> is defined then it is
* taken to be the fully-qualified name of a concrete provider class.
* The class is loaded and instantiated; if this process fails then an
* unspecified error is thrown. </p></li>
* 如果定义了系统属性java.nio.channels.spi.selectorprovider,那么它将当作为具体provider类的完全限定名。
* 加载并且实例化这个类;如果执行失败了,就抛出未指定的错误。
*
* <li><p> If a provider class has been installed in a jar file that is
* visible to the system class loader, and that jar file contains a
* provider-configuration file named
* <tt>java.nio.channels.spi.SelectorProvider</tt> in the resource
* directory <tt>META-INF/services</tt>, then the first class name
* specified in that file is taken. The class is loaded and
* instantiated; if this process fails then an unspecified error is
* thrown. </p></li>
* 如果一个provider类已经打包安装在一个jar文件中,同时这个jar文件对系统类加载器是可见的,
* 并且这个jar文件在它的资源目录META-INF/services中包含一个名为java.nio.channels.spi.SelectorProvider的provider配置文件,
* 然后取该文件中指定的第一个类名。加载并且实例化这个类;如果执行失败了,就抛出未指定的错误。
*
* <li><p> Finally, if no provider has been specified by any of the above
* means then the system-default provider class is instantiated and the
* result is returned. </p></li>
* 最后,如果在上面没有指定provider,系统默认的provider类就会实例化,然后返回
*
* </ol>
*
* <p> Subsequent invocations of this method return the provider that was
* returned by the first invocation. </p>
* 之后调用这个方法返回的provider就是第一次调用返回的那个provider
*
* @return The system-wide default selector provider
*/
public static SelectorProvider provider() {
synchronized (lock) {
if (provider != null)
return provider;
return AccessController.doPrivileged(
new PrivilegedAction<SelectorProvider>() {
public SelectorProvider run() {
if (loadProviderFromProperty())
return provider;
if (loadProviderAsService())
return provider;
provider = sun.nio.ch.DefaultSelectorProvider.create();
return provider;
}
});
}
}
public SocketChannel openSocketChannel() throws IOException {
return new SocketChannelImpl(this);
}
2). bind()方法
bind()方法实现逻辑:
(1). SocketChannel抽象类将bind()方法重写为抽象方法,采用模版方法的设计模式要求子类必须实现
@Override
public abstract SocketChannel bind(SocketAddress local)
throws IOException
@Override
(2). SocketChannelImpl类对bind()方法的实现
// Lock held by current reading or connecting thread
private final ReentrantLock readLock = new ReentrantLock();
// Lock held by current writing or connecting thread
private final ReentrantLock writeLock = new ReentrantLock();
public SocketChannel bind(SocketAddress local) throws IOException {
// 上读锁
readLock.lock();
try {
// 上写锁
writeLock.lock();
try {
synchronized (stateLock) {
ensureOpen();
if (state == ST_CONNECTIONPENDING)
throw new ConnectionPendingException();
if (localAddress != null)
throw new AlreadyBoundException();
if (isUnixSocket()) {
localAddress = unixBind(local);
} else {
// 网络绑定
localAddress = netBind(local);
}
}
} finally {
writeLock.unlock();
}
} finally {
readLock.unlock();
}
return this;
}
private SocketAddress netBind(SocketAddress local) throws IOException {
InetSocketAddress isa;
if (local == null) {
isa = new InetSocketAddress(Net.anyLocalAddress(family), 0);
} else {
// 对SocketAddress进行地址检查
isa = Net.checkAddress(local, family);
}
@SuppressWarnings("removal")
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkListen(isa.getPort());
}
NetHooks.beforeTcpBind(fd, isa.getAddress(), isa.getPort());
// 地址绑定
Net.bind(family, fd, isa.getAddress(), isa.getPort());
return Net.localAddress(fd);
}
(3). 进行地址绑定
static void bind(ProtocolFamily family, FileDescriptor fd,
InetAddress addr, int port) throws IOException
{
boolean preferIPv6 = isIPv6Available() &&
(family != StandardProtocolFamily.INET);
if (addr.isLinkLocalAddress()) {
addr = IPAddressUtil.toScopedAddress(addr);
}
bind0(fd, preferIPv6, exclusiveBind, addr, port);
}
// native方法
private static native void bind0(FileDescriptor fd, boolean preferIPv6,
boolean useExclBind, InetAddress addr,
int port)
throws IOException;