JDK 为咱们提供了 ServerSocket 类做为服务端套接字的实现,经过它可让主机监听某个端口而接收其余端的请求,处理完后还能够对请求端作出响应。它的内部真正实现是经过 SocketImpl 类来实现的,它提供了工厂模式,因此若是本身想要其余的实现也能够经过工厂模式来改变的。java
--java.lang.Object
--java.net.ServerSocket
复制代码
前面说到 ServerSocket 类真正的实现是经过 SocketImpl 类,因而能够看到它使用了 SocketImpl 类,但因为 windows 和 unix-like 系统有差别,而 windows 不一样的版本也须要作不一样的处理,因此两类系统的类不尽相同。windows
下图是 windows 的类图关系, SocketImpl 类实现了 SocketOptions 接口,接着还派生出了一系列的子类,其中 AbstractPlainSocketImpl 是原始套接字的实现的一些抽象,而 PlainSocketImpl 类是一个代理类,由它代理 TwoStacksPlainSocketImpl 和 DualStackPlainSocketImpl 两种不一样实现。存在两种实现的缘由是一个用于处理 Windows Vista 如下的版本,另外一个用于处理 Windows Vista 及以上的版本。 安全
比起 windows 的实现,unix-like 的实现则不会这么繁琐,它不存在版本的问题,因此它直接由 PlainSocketImpl 类实现,此外,能够看到两类操做系统都还存在一个 SocksSocketImpl 类,它其实主要是实现了防火墙安全会话转换协议,包括 SOCKS V4 和 V5 。bash
根据上面能够看到其实对于不一样系统就是须要作差别处理,基本都是大同小异,下面涉及到套接字实现均以 Windows Vista 及以上的版本为例进行分析。数据结构
public class ServerSocket implements java.io.Closeable
复制代码
ServerSocket 类的声明很简单,实现了 Closeable 接口,该接口只有一个close
方法。并发
private boolean created = false;
private boolean bound = false;
private boolean closed = false;
private Object closeLock = new Object();
private SocketImpl impl;
private boolean oldImpl = false;
复制代码
该方法用于关闭套接字,逻辑以下,机器学习
close
方法。public void close() throws IOException {
synchronized(closeLock) {
if (isClosed())
return;
if (created)
impl.close();
closed = true;
}
}
复制代码
套接字实现对象的close
方法逻辑为,socket
ResourceManager.afterUdpClose()
操做将 UDP 套接字计数器减一,前面说过 Java 是有控制 UDP 套接字数量的。socketPreClose
和socketClose
两个方法完成关闭操做,而且将文件描述符设为 null,最后返回。socketPreClose
方法。protected void close() throws IOException {
synchronized(fdLock) {
if (fd != null) {
if (!stream) {
ResourceManager.afterUdpClose();
}
if (fdUseCount == 0) {
if (closePending) {
return;
}
closePending = true;
try {
socketPreClose();
} finally {
socketClose();
}
fd = null;
return;
} else {
if (!closePending) {
closePending = true;
fdUseCount--;
socketPreClose();
}
}
}
}
}
复制代码
socketPreClose
方法调用了socketClose0
方法,它的逻辑很简单,判断文件描述符为空则抛出SocketException("Socket closed")
异常,判断文件描述符无效则直接返回,接着获取本地文件描述符,经过调用close0
本地方法完成关闭操做。分布式
private void socketPreClose() throws IOException {
socketClose0(true);
}
void socketClose0(boolean useDeferredClose/*unused*/) throws IOException {
if (fd == null)
throw new SocketException("Socket closed");
if (!fd.valid())
return;
final int nativefd = fdAccess.get(fd);
fdAccess.set(fd, -1);
close0(nativefd);
}
复制代码
close0
本地方法以下,逻辑为,ide
getsockopt
函数获取 SO_LINGER 选项的值。WSASendDisconnect
来启动关闭链接操做,达到优雅关闭。closesocket
函数进行关闭操做,这里额外说下 SO_LINGER 选项,若是 linger 结构体的第一个元素为0,此时表示关闭操做当即返回,操做系统接管套接字而且保证将全部数据发送给对端。JNIEXPORT void JNICALL Java_java_net_DualStackPlainSocketImpl_close0
(JNIEnv *env, jclass clazz, jint fd) {
NET_SocketClose(fd);
}
JNIEXPORT int JNICALL
NET_SocketClose(int fd) {
struct linger l = {0, 0};
int ret = 0;
int len = sizeof (l);
if (getsockopt(fd, SOL_SOCKET, SO_LINGER, (char *)&l, &len) == 0) {
if (l.l_onoff == 0) {
WSASendDisconnect(fd, NULL);
}
}
ret = closesocket (fd);
return ret;
}
复制代码
该方法用于设置套接字的选项,它经过套接字实现对象的setOption
方法来设置,
public <T> ServerSocket setOption(SocketOption<T> name, T value)
throws IOException
{
getImpl().setOption(name, value);
return this;
}
复制代码
套接字实现对象的setOption
方法实现以下,对不一样的选项的合法性判断,只有SO_KEEPALIVE
SO_SNDBUF
SO_RCVBUF
SO_REUSEADDR
SO_REUSEPORT
SO_LINGER
IP_TOS
TCP_NODELAY
这些选项属于 Java 支持的选项,而其余选项则抛出不支持异常。最后会再调另一个setOption
方法,其中选项参数值由 SocketOptions 接口定义。
protected <T> void setOption(SocketOption<T> name, T value) throws IOException {
if (name == StandardSocketOptions.SO_KEEPALIVE &&
(getSocket() != null)) {
setOption(SocketOptions.SO_KEEPALIVE, value);
} else if (name == StandardSocketOptions.SO_SNDBUF &&
(getSocket() != null)) {
setOption(SocketOptions.SO_SNDBUF, value);
} else if (name == StandardSocketOptions.SO_RCVBUF) {
setOption(SocketOptions.SO_RCVBUF, value);
} else if (name == StandardSocketOptions.SO_REUSEADDR) {
setOption(SocketOptions.SO_REUSEADDR, value);
} else if (name == StandardSocketOptions.SO_REUSEPORT &&
supportedOptions().contains(name)) {
setOption(SocketOptions.SO_REUSEPORT, value);
} else if (name == StandardSocketOptions.SO_LINGER &&
(getSocket() != null)) {
setOption(SocketOptions.SO_LINGER, value);
} else if (name == StandardSocketOptions.IP_TOS) {
setOption(SocketOptions.IP_TOS, value);
} else if (name == StandardSocketOptions.TCP_NODELAY &&
(getSocket() != null)) {
setOption(SocketOptions.TCP_NODELAY, value);
} else {
throw new UnsupportedOperationException("unsupported option");
}
}
复制代码
setOption
方法逻辑以下,
SocketException("Socket Closed")
异常。socketSetOption
方法。public void setOption(int opt, Object val) throws SocketException {
if (isClosedOrPending()) {
throw new SocketException("Socket Closed");
}
boolean on = true;
switch (opt) {
case SO_LINGER:
if (val == null || (!(val instanceof Integer) && !(val instanceof Boolean)))
throw new SocketException("Bad parameter for option");
if (val instanceof Boolean) {
on = false;
}
break;
case SO_TIMEOUT:
if (val == null || (!(val instanceof Integer)))
throw new SocketException("Bad parameter for SO_TIMEOUT");
int tmp = ((Integer) val).intValue();
if (tmp < 0)
throw new IllegalArgumentException("timeout < 0");
timeout = tmp;
break;
case IP_TOS:
if (val == null || !(val instanceof Integer)) {
throw new SocketException("bad argument for IP_TOS");
}
trafficClass = ((Integer)val).intValue();
break;
case SO_BINDADDR:
throw new SocketException("Cannot re-bind socket");
case TCP_NODELAY:
if (val == null || !(val instanceof Boolean))
throw new SocketException("bad parameter for TCP_NODELAY");
on = ((Boolean)val).booleanValue();
break;
case SO_SNDBUF:
case SO_RCVBUF:
if (val == null || !(val instanceof Integer) ||
!(((Integer)val).intValue() > 0)) {
throw new SocketException("bad parameter for SO_SNDBUF " +
"or SO_RCVBUF");
}
break;
case SO_KEEPALIVE:
if (val == null || !(val instanceof Boolean))
throw new SocketException("bad parameter for SO_KEEPALIVE");
on = ((Boolean)val).booleanValue();
break;
case SO_OOBINLINE:
if (val == null || !(val instanceof Boolean))
throw new SocketException("bad parameter for SO_OOBINLINE");
on = ((Boolean)val).booleanValue();
break;
case SO_REUSEADDR:
if (val == null || !(val instanceof Boolean))
throw new SocketException("bad parameter for SO_REUSEADDR");
on = ((Boolean)val).booleanValue();
break;
case SO_REUSEPORT:
if (val == null || !(val instanceof Boolean))
throw new SocketException("bad parameter for SO_REUSEPORT");
if (!supportedOptions().contains(StandardSocketOptions.SO_REUSEPORT))
throw new UnsupportedOperationException("unsupported option");
on = ((Boolean)val).booleanValue();
break;
default:
throw new SocketException("unrecognized TCP option: " + opt);
}
socketSetOption(opt, on, val);
}
复制代码
继续看socketSetOption
方法,逻辑以下,
SO_TIMEOUT
选项则直接返回,由于SO_TIMEOUT
选项属于 Java 层本身定义出来的,并不须要传递到操做系统中,因此只要在 Java 层进行维护便可。SO_REUSEPORT
选项则直接抛出UnsupportedOperationException("unsupported option")
异常,由于 windows 并无该选项。setIntOption
本地方法。void socketSetOption(int opt, boolean on, Object value)
throws SocketException {
int nativefd = checkAndReturnNativeFD();
if (opt == SO_TIMEOUT) {
return;
}
if (opt == SO_REUSEPORT) {
throw new UnsupportedOperationException("unsupported option");
}
int optionValue = 0;
switch(opt) {
case SO_REUSEADDR :
if (exclusiveBind) {
isReuseAddress = on;
return;
}
case TCP_NODELAY :
case SO_OOBINLINE :
case SO_KEEPALIVE :
optionValue = on ? 1 : 0;
break;
case SO_SNDBUF :
case SO_RCVBUF :
case IP_TOS :
optionValue = ((Integer)value).intValue();
break;
case SO_LINGER :
if (on) {
optionValue = ((Integer)value).intValue();
} else {
optionValue = -1;
}
break;
default :/* shouldn't get here */ throw new SocketException("Option not supported"); } setIntOption(nativefd, opt, optionValue); } 复制代码
setIntOption
方法的逻辑主要是组装好 Winsock 库接口须要的数据结构,根据 Java 层对应的选项映射成本地对应的选项,接着经过NET_SetSockOpt
函数设置该选项的值。
JNIEXPORT void JNICALL
Java_java_net_DualStackPlainSocketImpl_setIntOption
(JNIEnv *env, jclass clazz, jint fd, jint cmd, jint value)
{
int level = 0, opt = 0;
struct linger linger = {0, 0};
char *parg;
int arglen;
if (NET_MapSocketOption(cmd, &level, &opt) < 0) {
JNU_ThrowByName(env, "java/net/SocketException", "Invalid option");
return;
}
if (opt == java_net_SocketOptions_SO_LINGER) {
parg = (char *)&linger;
arglen = sizeof(linger);
if (value >= 0) {
linger.l_onoff = 1;
linger.l_linger = (unsigned short)value;
} else {
linger.l_onoff = 0;
linger.l_linger = 0;
}
} else {
parg = (char *)&value;
arglen = sizeof(value);
}
if (NET_SetSockOpt(fd, level, opt, parg, arglen) < 0) {
NET_ThrowNew(env, WSAGetLastError(), "setsockopt");
}
}
复制代码
NET_SetSockOpt
函数核心逻辑是调用 Winsock 库的setsockopt
函数对选项进行设置,另外,对于一些选项会作额外处理,好比当SO_REUSEADDR
选项时,会先查询操做系统的SO_EXCLUSIVEADDRUSE
选项的值是否为1,便是否开启了独占地址功能,若是开启了则不用进一步调用setsockopt
函数而直接返回。
JNIEXPORT int JNICALL
NET_SetSockOpt(int s, int level, int optname, const void *optval,
int optlen)
{
int rv = 0;
int parg = 0;
int plen = sizeof(parg);
if (level == IPPROTO_IP && optname == IP_TOS) {
int *tos = (int *)optval;
*tos &= (IPTOS_TOS_MASK | IPTOS_PREC_MASK);
}
if (optname == SO_REUSEADDR) {
rv = NET_GetSockOpt(s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char *)&parg, &plen);
if (rv == 0 && parg == 1) {
return rv;
}
}
rv = setsockopt(s, level, optname, optval, optlen);
if (rv == SOCKET_ERROR) {
...
}
return rv;
}
复制代码
SocketOptions 接口在 Java 层定义了如下的选项,并非每一个选项名都在 Winsock 库中有选项与之对应,但能对应上的选项的值都相同。
public interface SocketOptions {
@Native public static final int TCP_NODELAY = 0x0001;
@Native public static final int SO_BINDADDR = 0x000F;
@Native public static final int SO_REUSEADDR = 0x04;
@Native public static final int SO_REUSEPORT = 0x0E;
@Native public static final int SO_BROADCAST = 0x0020;
@Native public static final int IP_MULTICAST_IF = 0x10;
@Native public static final int IP_MULTICAST_IF2 = 0x1f;
@Native public static final int IP_TOS = 0x3;
@Native public static final int SO_LINGER = 0x0080;
@Native public static final int SO_TIMEOUT = 0x1006;
@Native public static final int SO_SNDBUF = 0x1001;
@Native public static final int SO_RCVBUF = 0x1002;
@Native public static final int SO_KEEPALIVE = 0x0008;
@Native public static final int SO_OOBINLINE = 0x1003;
}
复制代码
Winsock 库的相关的大部分选项的定义以下,好比TCP_NODELAY
选项在 Java 层和 C/C++ 层的值是相同的。其余选项也相似,在 Java 层能找到对应的选项则在本地也能找到与之对应的选项。
#define SO_DEBUG 0x0001 /* turn on debugging info recording */
#define SO_ACCEPTCONN 0x0002 /* socket has had listen() */
#define SO_REUSEADDR 0x0004 /* allow local address reuse */
#define SO_KEEPALIVE 0x0008 /* keep connections alive */
#define SO_DONTROUTE 0x0010 /* just use interface addresses */
#define SO_BROADCAST 0x0020 /* permit sending of broadcast msgs */
#define SO_USELOOPBACK 0x0040 /* bypass hardware when possible */
#define SO_LINGER 0x0080 /* linger on close if data present */
#define SO_OOBINLINE 0x0100 /* leave received OOB data in line */
#define SO_SNDBUF 0x1001 /* send buffer size */
#define SO_RCVBUF 0x1002 /* receive buffer size */
#define SO_SNDLOWAT 0x1003 /* send low-water mark */
#define SO_RCVLOWAT 0x1004 /* receive low-water mark */
#define SO_SNDTIMEO 0x1005 /* send timeout */
#define SO_RCVTIMEO 0x1006 /* receive timeout */
#define SO_ERROR 0x1007 /* get error status and clear */
#define SO_TYPE 0x1008 /* get socket type */
#define SO_BSP_STATE 0x1009 /* get socket 5-tuple state*/
#define SO_GROUP_ID 0x2001 /* ID of a socket group*/
#define SO_GROUP_PRIORITY 0x2002 /* the relative priority within a group*/
#define SO_MAX_MSG_SIZE 0x2003 /* maximum message size*/
#define SO_CONDITIONAL_ACCEPT 0x3002 /* enable true conditional accept: connection is not ack-ed to the other side until conditional function returns CF_ACCEPT*/
#define SO_PAUSE_ACCEPT 0x3003 /* pause accepting new connections*/
#define SO_COMPARTMENT_ID 0x3004 /* get/set the compartment for a socket*/
#define WSK_SO_BASE 0x4000 /* */
#define TCP_NODELAY 0x0001 /* Options to use with [gs]etsockopt at the IPPROTO_TCP level.*/
复制代码
该方法主要用于设置 ServerSocket 的 accept
方法,也就是接收套接字链接的等待超时时间,与之对应的为 SO_TIMEOUT 选项,它的单位是毫秒,一旦达到该超时时间则会抛出 SocketTimeoutException 异常,但该 ServerSocket 对象仍然是有效,也就是说若是捕获到以上抛出的异常的话仍是能够继续使用它的。
public synchronized void setSoTimeout(int timeout) throws SocketException {
if (isClosed())
throw new SocketException("Socket is closed");
getImpl().setOption(SocketOptions.SO_TIMEOUT, timeout);
}
复制代码
套接字实现对象的setOption
方法上面有详细的讲解,注意看到SO_TIMEOUT
的状况,判断设置的值必须为整型,且将其转换成整型并赋给 timeout 变量,由于SO_TIMEOUT
选项属于 Java 层本身定义出来的,并不须要传递到操做系统中,因此只要在 Java 层进行维护便可。最后调用的socketSetOption
方法也是直接返回并不作什么操做。
该方法能够容许屡次绑定同个地址端口,它的做用主要是某个地址端口关闭后会有一段时间处于 TIME_WAIT 状态,该状态下可能不在容许套接字绑定该端口,必需要等到彻底关闭才容许再次绑定,经过设置该方法可让其重复绑定。另外,该方法实际与sun.net.useExclusiveBind
系统参数有紧密联系,默认状况下该参数值为 true,因此操做系统默认是使用了排他绑定的,这种状况下,调用setReuseAddress
方法不会真正去改变操做系统。
public void setReuseAddress(boolean on) throws SocketException {
if (isClosed())
throw new SocketException("Socket is closed");
getImpl().setOption(SocketOptions.SO_REUSEADDR, Boolean.valueOf(on));
}
复制代码
setReuseAddress
方法调用套接字实现对象的setOption
方法,该方法前面有详细的讲解,其中能够看到case SO_REUSEADDR
时将其值转换成 boolean 值而后调用socketSetOption
方法。
socketSetOption
方法中,当case SO_REUSEADDR
时能够看到 exclusiveBind 为 true 时则直接设置完标识就返回了,不会继续作其余操做,而这里的 exclusiveBind 默认为 true,能够经过sun.net.useExclusiveBind
参数来改变。
若是sun.net.useExclusiveBind
参数设置为 false,则会调用setIntOption
本地方法,该函数会间接调用NET_SetSockOpt
函数,主要逻辑是先判断是否是已经设置了SO_EXCLUSIVEADDRUSE
选项,若是设置了则无需再作操做了,直接返回。不然经过 Winsock 库的setsockopt
函数来设置SO_REUSEADDR
选项。
返回 ServerSocket 对象字符串,若是还没绑定则返回ServerSocket[unbound]
,若是绑定了则根据安全管理器为不为空分别获取回送地址或IP地址,最后返回形如ServerSocket[addr=xxx,localport=xxx]
的字符串。
public String toString() {
if (!isBound())
return "ServerSocket[unbound]";
InetAddress in;
if (System.getSecurityManager() != null)
in = InetAddress.getLoopbackAddress();
else
in = impl.getInetAddress();
return "ServerSocket[addr=" + in +
",localport=" + impl.getLocalPort() + "]";
}
复制代码
该方法用于设置接收的缓冲区大小,设置后做为 ServerSocket 接收到的套接字的接收缓冲区的默认值,默认值为64K,在 ServerSocket 绑定以前设置才能生效。该方法主要逻辑是先判断大小必须大于0且套接字不处于关闭状态,而后调用套接字实现对象的setOption
方法。
public synchronized void setReceiveBufferSize (int size) throws SocketException {
if (!(size > 0)) {
throw new IllegalArgumentException("negative receive size");
}
if (isClosed())
throw new SocketException("Socket is closed");
getImpl().setOption(SocketOptions.SO_RCVBUF, size);
}
复制代码
setOption
方法前面有详细讲解,这里再也不赘述。
-------------推荐阅读------------
------------------广告时间----------------
知识星球:远洋号
公众号的菜单已分为“分布式”、“机器学习”、“深度学习”、“NLP”、“Java深度”、“Java并发核心”、“JDK源码”、“Tomcat内核”等,可能有一款适合你的胃口。
欢迎关注: