logo

Socket 源码分析

作者:千魔啸夜2021.07.07 20:17浏览量:307

简介:Java Socket源码分析

套接字为一个普通的java类,但是它的逻辑实现却主要依赖了一个以SocketImpl抽象类为基类的系列类,并且它的相关逻辑是默认托管给一个SocksSocketImpl(SocketImpl的后代类)的,同时SocksSocketImpl又将主要的逻辑托管给父类,最终由其兄弟类DualStackSocketImpl或者是TwoStackSocketImpl实现核心的业务代码,事实上是调用了native方法。

SocketImpl这个类是套接字的实现类。Impl是套接字的方法的实现。而在Socket套接字类中,里面的构造方法实现了代理和空参构造的生成。构造方法有如下几个构造方法:

1.代理参数构造方法:

public Socket(Proxy proxy) {
// Create a copy of Proxy as a security measure
if (proxy == null) {
throw new IllegalArgumentException(“Invalid Proxy”);
}
Proxy p = proxy == Proxy.NO_PROXY ? Proxy.NO_PROXY
: sun.net.ApplicationProxy.create(proxy);
Proxy.Type type = p.type();
if (type == Proxy.Type.SOCKS || type == Proxy.Type.HTTP) {
SecurityManager security = System.getSecurityManager();
InetSocketAddress epoint = (InetSocketAddress) p.address();
if (epoint.getAddress() != null) {
checkAddress (epoint.getAddress(), “Socket”);
}
if (security != null) {
if (epoint.isUnresolved())
epoint = new InetSocketAddress(epoint.getHostName(), epoint.getPort());
if (epoint.isUnresolved())
security.checkConnect(epoint.getHostName(), epoint.getPort());
else
security.checkConnect(epoint.getAddress().getHostAddress(),
epoint.getPort());
}
impl = type == Proxy.Type.SOCKS ? new SocksSocketImpl(p)
: new HttpConnectSocketImpl(p);
impl.setSocket(this);
} else {
if (p == Proxy.NO_PROXY) {
if (factory == null) {
impl = new PlainSocketImpl();
impl.setSocket(this);
} else
setImpl();
} else
throw new IllegalArgumentException(“Invalid Proxy”);
}
}
创建一个未连接的套接字,根据proxy的类型来判断代理的类型。如果proxy的类型是Socks或者Http就根据这个类型来创建代理实现类SocksSocketImpl,HttpConnectSocketImpl。如果是直接连接,就创建一个PlainSocketImpl进行直接连接。

2.无参构造

public Socket() {
setImpl();
}
直接创建一个SocketImpl,设置实现类的地址Socket的默认实现类。

3.根据主机地址和端口来创建一个套接字

public Socket(String host, int port)
    throws UnknownHostException, IOException
{
    this(host != null ? new InetSocketAddress(host, port) :
         new InetSocketAddress(InetAddress.getByName(null), port),
         (SocketAddress) null, true);
}

根据主机名和端口号来生成一个InetSocketAddress,通过InetSocketAddress来进行套接字的构造。

4.根据InetSocketAddress和端口号来生成套接字

public Socket(InetAddress address, int port) throws IOException {
    this(address != null ? new InetSocketAddress(address, port) : null,
         (SocketAddress) null, true);
}

判断地址是否为空,如果地址不为空生成新的InetSocketAddress。

5.根据目标地址和回环地址来创建套接字

public Socket(String host, int port, InetAddress localAddr,
              int localPort) throws IOException {
    this(host != null ? new InetSocketAddress(host, port) :
           new InetSocketAddress(InetAddress.getByName(null), port),
         new InetSocketAddress(localAddr, localPort), true);
}

套接字的构造类里面传入目标地址和本地地址这两个地址这两个地址创建两个InetSocketAddress实体类,传入创建套接字。

6.根据是否创建一个Stream的套接字

public Socket(String host, int port, boolean stream) throws IOException {
this(host != null ? new InetSocketAddress(host, port) :
new InetSocketAddress(InetAddress.getByName(null), port),
(SocketAddress) null, stream);
}

Stream套接字和Datagram套接字的区别在于一个是进行UDP协议,一个是TCP协议。他们一个发送ACK数据包连续数据确保数据的完整性,一个是UDP发送无序的数据包,不确保数据是否被传送到。这两个套接字的创建根据是stream是否是true来创建。

connect方法里面传入终点地址,根据终点地址来进行连接。默认的连接超时为0。

public void connect(SocketAddress endpoint) throws IOException {
connect(endpoint, 0);
}
public void connect(SocketAddress endpoint, int timeout) throws IOException {
if (endpoint == null)
throw new IllegalArgumentException(“connect: The address can’t be null”);

    if (timeout < 0)
      throw new IllegalArgumentException("connect: timeout can't be negative");

    if (isClosed())
        throw new SocketException("Socket is closed");

    if (!oldImpl && isConnected())
        throw new SocketException("already connected");

    if (!(endpoint instanceof InetSocketAddress))
        throw new IllegalArgumentException("Unsupported address type");

    InetSocketAddress epoint = (InetSocketAddress) endpoint;
    InetAddress addr = epoint.getAddress();
    int port = epoint.getPort();
    checkAddress(addr, "connect");

    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        if (epoint.isUnresolved())
            security.checkConnect(epoint.getHostName(), port);
        else
            security.checkConnect(addr.getHostAddress(), port);
    }
    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;
}

如果没有创建实现类没有使用旧的实现类,就直接创建一个实现类和连接到目标地址并且设置超时延时。如果开启了安全管理器,那就检查连接然后连接。

绑定方法

public void bind(SocketAddress bindpoint) throws IOException {
    if (isClosed())
        throw new SocketException("Socket is closed");
    if (!oldImpl && isBound())
        throw new SocketException("Already bound");

    if (bindpoint != null && (!(bindpoint instanceof InetSocketAddress)))
        throw new IllegalArgumentException("Unsupported address type");
    InetSocketAddress epoint = (InetSocketAddress) bindpoint;
    if (epoint != null && epoint.isUnresolved())
        throw new SocketException("Unresolved address");
    if (epoint == null) {
        epoint = new InetSocketAddress(0);
    }
    InetAddress addr = epoint.getAddress();
    int port = epoint.getPort();
    checkAddress (addr, "bind");
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkListen(port);
    }
    getImpl().bind (addr, port);
    //实现类绑定地址和端口。
bound = true;
}

使用Nagle算法来进行数据的传输,Nagle算法可以让数据直接延迟发送,数据设置套接字的配置为TCP不延时。

public void setTcpNoDelay(boolean on) throws SocketException {
    if (isClosed())
        throw new SocketException("Socket is closed");
    getImpl().setOption(SocketOptions.TCP_NODELAY, Boolean.valueOf(on));
}

整个类对套接字重要的方法进行了实现,每个方法都对套接字的底层的实现起了重要的作用。后面还有trafficClass等等各种不常用的方法。

相关文章推荐

发表评论