java sockets源码一瞥

作者ChihMinh,原作链接https://chihminh.github.io/2016/07/28/java-socket-sc/,转载请注明出处

前言

一个IP地址和一个端口号称为一个套接字(socket)。此术语出现在最早的TCP规范(RFC793, Page 5)中,后来也作为伯克利版的编程接口。Berkeley套接字应用程序接口形成了事实上的网络套接字的标准精髓,以下列表是一个Berkeley套接字API库提供的函数或者方法的概要:

  • socket() 创建一个新的确定类型的套接字,类型用一个整型数值标识(文件描述符),并为它分配系统资源。
  • bind() 一般用于服务器端,将一个套接字与一个套接字地址结构相关联,比如,一个指定的本地端口和IP地址。
  • listen() 用于服务器端,使一个绑定的TCP套接字进入监听状态。
  • connect() 用于客户端,为一个套接字分配一个自由的本地端口号。 如果是TCP套接字的话,它会试图获得一个新的TCP连接。
  • accept() 用于服务器端。 它接受一个从远端客户端发出的创建一个新的TCP连接的接入请求,创建一个新的套接字,与该连接相应的套接字地址相关联。
  • send()和recv(),或者write()和read(),或者recvfrom()和sendto(), 用于往/从远程套接字发送和接受数据。
  • close() 用于系统释放分配给一个套接字的资源。 如果是TCP,连接会被中断。
    gethostbyname()和gethostbyaddr() 用于解析主机名和地址。
  • select() 用于修整有如下情况的套接字列表: 准备读,准备写或者是有错误。
  • poll() 用于检查套接字的状态。 套接字可以被测试,看是否可以写入、读取或是有错误。
  • getsockopt() 用于查询指定的套接字一个特定的套接字选项的当前值。
  • setsockopt() 用于为指定的套接字设定一个特定的套接字选项。

Java sockets使用了伯克利套接字接口。

Java sockets源码分析

Socket和ServerSocket

Socket、ServerSocket、SocketImplFactory类图如下:

Socket和ServerSocket都是通过SocketImpl实现的,二者很相似,这里只分析Socket
创建一个未连接的socket,使用系统默认的SocketImpl

1
2
3
public Socket() {
setImpl();
}

创建一个未连接的socket,用户指定SocketImpl

1
2
3
4
5
6
7
protected Socket(SocketImpl impl) throws SocketException {
this.impl = impl;
if (impl != null) {
checkOldImpl();
this.impl.setSocket(this);
}
}

再来看setImpl方法,默认使用SocksSocketImpl类,也可以使用工厂方法来设置

1
2
3
4
5
6
7
8
9
10
11
12
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.setSocket(this);
}

connect方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void connect(SocketAddress endpoint, int timeout) throws IOException {
// 省略部分代码
if (!created)
createImpl(true);
if (!oldImpl)
impl.connect(epoint, timeout);
else if (timeout == 0) {
if (epoint.isUnresolved())
impl.connect(addr.getHostName(), port);
else
impl.connect(addr, port);
} else
throw new UnsupportedOperationException("SocketImpl.connect(addr, timeout)");
connected = true;
/*
* If the socket was not bound before the connect, it is now because
* the kernel will have picked an ephemeral port & a local address
*/
bound = true;
}

可见Socket的connect方法是用SocketImpl的connect方法实现的,再看看close方法

1
2
3
4
5
6
7
8
9
public synchronized void close() throws IOException {
synchronized(closeLock) {
if (isClosed())
return;
if (created)
impl.close();
closed = true;
}
}

close方法同样是通过SocketImpl实现的,通过查看源代码也很容易分析其他方法是怎样实现的。

SocketImpl的类图