public
class ServerSocket implements java.io.Closeable {
/**
* Various states of this socket.
*/
private boolean created = false;
private boolean bound = false;
private boolean closed = false;
private Object closeLock = new Object();
/**
* The implementation of this Socket.
*/
private SocketImpl impl;
Socket 类:
public
class Socket implements java.io.Closeable {
/**
* Various states of this socket.
*/
private boolean created = false;
private boolean bound = false;
private boolean connected = false;
private boolean closed = false;
private Object closeLock = new Object();
private boolean shutIn = false;
private boolean shutOut = false;
/**
* The implementation of this Socket.
*/
SocketImpl impl;
类的继承图:
实体类图:
由于 windows 和 unix-like 系统有差异,而 windows 不同的版本也需要做不同的处理, 所以两类系统的类不尽相同。 SocketImpl 类实现了 SocketOptions 接口,内部的方法基本上都是抽象方法,接着还派生出了一系列的子类,其中 AbstractPlainSocketImpl 是默认套接字的实现的一些抽象,而 PlainSocketImpl 类是一个代理类。 windows 下 PlainSocketImpl 代理 TwoStacksPlainSocketImpl 和 DualStackPlainSocketImpl 两种不同实现。存在两种实现的原因是一个用于处理 Windows Vista 以下的版本,另一个用于处理 Windows Vista 及以上的版本。 unix-like 不存在版本的问题,所以它直接由 PlainSocketImpl 类实现。 这两类操作系统都还存在一个 SocksSocketImpl 类,它其实主要是实现了防火墙安全会 话转换协议,包括 SOCKS V4 和 V5(SOCKS:防火墙安全会话转换协议 (Socks: Protocol for sessions traversal across firewall securely) SOCKS 协议提供一个框架,为在 TCP 和 UDP 域中的客户机/服务器应用程序能更方便安全地使用网络防火墙所提供的服务,并提供一个通用框架来使这些协议安全透明地穿过防火墙 。
/**
* Creates a socket with a boolean that specifies whether this
* is a stream socket (true) or an unconnected UDP socket (false).
*/
protected synchronized void create(boolean stream) throws IOException {
this.stream = stream;
if (!stream) {
ResourceManager.beforeUdpCreate();
// only create the fd after we know we will be able to create the socket
fd = new FileDescriptor();
try {
socketCreate(false);
} catch (IOException ioe) {
ResourceManager.afterUdpClose();
fd = null;
throw ioe;
}
} else {
fd = new FileDescriptor();
socketCreate(true);
}
if (socket != null)
socket.setCreated();
if (serverSocket != null)
serverSocket.setCreated();
}
会根据通信类型分 UDP 和 TCP 进行 socket 创建,而具体的创建方法 socketCreate,是个抽象方法
abstract void socketCreate(boolean isServer) throws IOException;
/**
* Various states of this socket.
*/
private boolean created = false;//created 表示是否已经创建了 SocketImpl 对象,ServerSocket 需要依赖该对象实现套接字操作。
private boolean bound = false;//bound 是否已绑定地址和端口。
private boolean closed = false;//closed 是否已经关闭套接字。
private Object closeLock = new Object();//closeLock 关闭套接字时用的锁。
/**
* The implementation of this Socket.
*/
private SocketImpl impl;//impl 真正的套接字实现对象。
/**
* Are we using an older SocketImpl?
*/
private boolean oldImpl = false;//oldImpl 是不是使用旧的实现。构造方法
ServerSocket(SocketImpl impl) {
this.impl = impl;
impl.setServerSocket(this);
}
public ServerSocket() throws IOException {
setImpl();
}
public ServerSocket(int port) throws IOException {
this(port, 50, null);
}
public ServerSocket(int port, int backlog) throws IOException {
this(port, backlog, null);
}
//setImpl 方法用于设置实现对象,然后检查端口大小是否正确,检查 backlog 小于 0 就让它等于 50,最后进行端口和地址绑定操作。
public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException {
setImpl();
if (port < 0 || port > 0xFFFF)
throw new IllegalArgumentException(
"Port value out of range: " + port);
if (backlog < 1)
backlog = 50;
try {
bind(new InetSocketAddress(bindAddr, port), backlog);
} catch(SecurityException e) {
close();
throw e;
} catch(IOException e) {
close();
throw e;
}
}
这五类构造函数,可以什么参数都不传,也可以传入 SocketImpl、端口、backlog 和地址等。
private void setImpl() {
if (factory != null) {
impl = factory.createSocketImpl();
checkOldImpl();
} else {
// No need to do a checkOldImpl() here, we know it's an up to date
// SocketImpl!
impl = new SocksSocketImpl();
}
if (impl != null)
impl.setServerSocket(this);
}
不过 ServerSocket 和 Socket 为何要使用 SocksSocketImpl ?这是很古怪的行为,具体原因没有官方解释,目前比较合理的推测是: 这是从 JDK1.4 保持下来的传统,对代理服务器连接的实验性支持。在实际环境中,JRE 可能已经通过系统属性-DsocksProxyHost 和-DsocksProxyPort 或 ProxySelector.setDefault()或通过 JRE 安装的默认 ProxySelect 从外部配置为使用 SOCKS 进行代理。但是 PlainSocketImpl 不会参考这些属性或类,所以这些外部配置将被忽略不起作用,而 SocksSocketImpl 则会进行检查。
public void bind(SocketAddress endpoint, int backlog) throws IOException {
if (isClosed())
throw new SocketException("Socket is closed");
if (!oldImpl && isBound())
throw new SocketException("Already bound");
if (endpoint == null)
endpoint = new InetSocketAddress(0);
if (!(endpoint instanceof InetSocketAddress))
throw new IllegalArgumentException("Unsupported address type");
InetSocketAddress epoint = (InetSocketAddress) endpoint;
if (epoint.isUnresolved())
throw new SocketException("Unresolved address");
if (backlog < 1)
backlog = 50;
try {
SecurityManager security = System.getSecurityManager();
if (security != null)
security.checkListen(epoint.getPort());
//bind 方法里比较关键的就是两行,
getImpl().bind(epoint.getAddress(), epoint.getPort());
getImpl().listen(backlog);
bound = true;
} catch(SecurityException e) {
bound = false;
throw e;
} catch(IOException e) {
bound = false;
throw e;
}
}
点击进入:
SocketImpl getImpl() throws SocketException {
if (!created)
createImpl();
return impl;
}
getImpl 方法中会创建 SocketImpl
void createImpl() throws SocketException {
if (impl == null)
setImpl();
try {
impl.create(true);
created = true;
} catch (IOException e) {
throw new SocketException(e.getMessage());
}
}
impl.create 方法实际的实现者是 AbstractPlainSocketImpl,在 AbstractPlainSocketImpl 抽象类中 create 方法对 UDP 和 TCP 协议分别做了处理,创建 socket 套接字的代码就一句 socketCreate(true),由具体的实现类完成。当然,在 windows 下我们调试,会进入 windows 下 PlainSocketImpl 代理TwoStacksPlainSocketImpl 和 DualStackPlainSocketImpl。 在 Linux 下 JDK 的 PlainSocketImpl 非常简单,直接调用 JDK 中的 native 方法。并最终调用了操作系统的相关方法。
linux环境的java代码PlainSocketImpl 类(win环境的大家自己看) :
package java.net;
import java.io.IOException;
import java.io.FileDescriptor;
import java.util.Set;
import java.util.HashSet;
import java.util.Collections;
import jdk.net.*;
import static sun.net.ExtendedOptionsImpl.*;
/*
* On Unix systems we simply delegate to native methods.
*
* @author Chris Hegarty
*/
class PlainSocketImpl extends AbstractPlainSocketImpl
{
static {
initProto();
}
/**
* Constructs an empty instance.
*/
PlainSocketImpl() { }
/**
* Constructs an instance with the given file descriptor.
*/
PlainSocketImpl(FileDescriptor fd) {
this.fd = fd;
}
protected <T> void setOption(SocketOption<T> name, T value) throws IOException {
if (!name.equals(ExtendedSocketOptions.SO_FLOW_SLA)) {
super.setOption(name, value);
} else {
if (isClosedOrPending()) {
throw new SocketException("Socket closed");
}
checkSetOptionPermission(name);
checkValueType(value, SocketFlow.class);
setFlowOption(getFileDescriptor(), (SocketFlow)value);
}
}
protected <T> T getOption(SocketOption<T> name) throws IOException {
if (!name.equals(ExtendedSocketOptions.SO_FLOW_SLA)) {
return super.getOption(name);
}
if (isClosedOrPending()) {
throw new SocketException("Socket closed");
}
checkGetOptionPermission(name);
SocketFlow flow = SocketFlow.create();
getFlowOption(getFileDescriptor(), flow);
return (T)flow;
}
protected void socketSetOption(int opt, boolean b, Object val) throws SocketException {
try {
socketSetOption0(opt, b, val);
} catch (SocketException se) {
if (socket == null || !socket.isConnected())
throw se;
}
}
native void socketCreate(boolean isServer) throws IOException;
native void socketConnect(InetAddress address, int port, int timeout)
throws IOException;
native void socketBind(InetAddress address, int port)
throws IOException;
native void socketListen(int count) throws IOException;
native void socketAccept(SocketImpl s) throws IOException;
native int socketAvailable() throws IOException;
native void socketClose0(boolean useDeferredClose) throws IOException;
native void socketShutdown(int howto) throws IOException;
static native void initProto();
native void socketSetOption0(int cmd, boolean on, Object value)
throws SocketException;
native int socketGetOption(int opt, Object iaContainerObj) throws SocketException;
native void socketSendUrgentData(int data) throws IOException;
}
然后直接调用底层操作系统C语言代码对应socketCreate方法创建socket:
/*
* Class: java_net_PlainSocketImpl
* Method: socketCreate
* Signature: (ZZ)V */
JNIEXPORT void JNICALL
Java_java_net_PlainSocketImpl_socketCreate(JNIEnv *env, jobject this,
jboolean stream, jboolean isServer) {
jobject fdObj, ssObj;
int fd;
int type = (stream ? SOCK_STREAM : SOCK_DGRAM);
int domain = ipv6_available() ? AF_INET6 : AF_INET;
if (socketExceptionCls == NULL) {
jclass c = (*env)->FindClass(env, "java/net/SocketException");
CHECK_NULL(c);
socketExceptionCls = (jclass)(*env)->NewGlobalRef(env, c);
CHECK_NULL(socketExceptionCls);
}
fdObj = (*env)->GetObjectField(env, this, psi_fdID);
if (fdObj == NULL) {
(*env)->ThrowNew(env, socketExceptionCls, "null fd object");
return;
}
if ((fd = socket(domain, type, 0)) == -1) {
/* note: if you run out of fds, you may not be able to load
* the exception class, and get a NoClassDefFoundError
* instead.
*/
NET_ThrowNew(env, errno, "can't create socket");
return;
}
/*
* If IPv4 is available, disable IPV6_V6ONLY to ensure dual-socket support.
*/
if (domain == AF_INET6 && ipv4_available()) {
int arg = 0;
if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&arg,
sizeof(int)) < 0) {
NET_ThrowNew(env, errno, "cannot set IPPROTO_IPV6");
close(fd);
return;
}
}
/*
* If this is a server socket then enable SO_REUSEADDR
* automatically and set to non blocking.
*/
if (isServer) {
int arg = 1;
SET_NONBLOCKING(fd);
if (NET_SetSockOpt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&arg,
sizeof(arg)) < 0) {
NET_ThrowNew(env, errno, "cannot set SO_REUSEADDR");
close(fd);
return;
}
}
(*env)->SetIntField(env, fdObj, IO_fd_fdID, fd);
}
或者此版本代码:
到此,JDK 中的 BIO 实现原理分析完毕,下篇分析Linux 环境下的 IO 复用编程原理,敬请期待!