3. Java Socket实现

1. SocketImpl的类树如下:

java.net.SocketImpl(抽象类)
    --> java.net.AbstractPlainSocketImpl(抽象实现类)
        --> java.net.TwoStacksPlainSocketImpl(真正实现类,Vista以下版本)
        --> java.net.DualStackPlainSocketImpl(真正实现类,Vista及以上版本)
        --> java.net.PlainSocketImpl(上述实现类的代理类)
            --> java.net.SocksSocketImpl(SOCKS TCP套接字实现类)

2. 类字段

  • SocketImpl
// 指向服务器或客户端实例,只能有一个非空
Socket socket = null;
ServerSocket serverSocket = null;

// 套接字文件描述符
FileDescriptor fd;
// 远端IP地址
InetAddress address;
// 远端端口
int port;
// 本地端口
int localport;
  • PlainSocketImpl
// 代理真正的套接字实例
// TwoStacksPlainSocketImpl或DualStackPlainSocketImpl
AbstractPlainSocketImpl impl;
  • ServerSocket
// 指向套接字代理类实例(SocksSocketImpl)
SocketImpl impl;

// 状态
// 是否创建套接字
boolean created = false;
// 是否绑定本地地址
boolean bound = false;
// 是否关闭套接字
boolean closed = false;
  • Socket
// 指向套接字代理类实例(SocksSocketImpl)
SocketImpl impl;

// 状态
// 是否创建套接字
boolean created = false;
// 是否绑定本地地址
boolean bound = false;
// 是否建立连接
boolean connected = false;
// 是否关闭读
boolean shutIn = false;
// 是否关闭写
boolean shutOut = false;
// 是否关闭套接字
boolean closed = false;

3. 创建套接字

  • ServerSocket

创建真正套接字实例:

public ServerSocket() throws IOException {
    setImpl();
}

// ServerSocket
private void setImpl() {
    // SocketImpl#impl
    impl = new SocksSocketImpl();
    impl.setServerSocket(this);
}

SocksSocketImpl() {
    // Nothing needed
}

// SocksSocketImpl的父类
PlainSocketImpl() {
    if (useDualStackImpl) {
        // AbstractPlainSocketImpl#impl
        impl = new DualStackPlainSocketImpl(exclusiveBind);
    } else {
        impl = new TwoStacksPlainSocketImpl(exclusiveBind);
    }
}

public DualStackPlainSocketImpl(boolean exclBind) {
    // 套接字是否互斥绑定,Windows为true
    exclusiveBind = exclBind;
}

初始化套接字:

// ServerSocket
SocketImpl getImpl() throws SocketException {
    if (!created)
        createImpl();
    return impl;
}

// ServerSocket
void createImpl() throws SocketException {
    if (impl == null)
        setImpl();
    try {
        impl.create(true);
        created = true;
    } catch (IOException e) {
        throw new SocketException(e.getMessage());
    }
}

// AbstractPlainSocketImpl
void create(boolean stream) throws IOException {
    this.stream = stream;
    if (!stream) {
        // SocketImpl#fd
        fd = new FileDescriptor();
        try {
            // 创建数据报套接字UDP
            socketCreate(false);
        } catch (IOException ioe) {
            fd = null;
            throw ioe;
        }
    } else {
        fd = new FileDescriptor();
        // 创建流式套接字TCP
        socketCreate(true);
    }

    // Socket或ServerSocket置为已创建状态
    if (socket != null)
        socket.setCreated();
    if (serverSocket != null)
        serverSocket.setCreated();
}

// DualStackPlainSocketImpl
void socketCreate(boolean stream) throws IOException {
    // 调用native方法创建套接字
    int newfd = socket0(stream, false /*v6 Only*/);
    // SocketImpl#fd
    fdAccess.set(fd, newfd);
}

// ServerSocket:已创建
void setCreated() {
    created = true;
}
  • Socket
public Socket() {
    setImpl();
}

void setImpl() {
    impl = new SocksSocketImpl();
    impl.setSocket(this);
}

SocketImpl getImpl() {
    if (!created)
        createImpl(true);
    return impl;
}

// 后续同ServerSocket

4. 服务器绑定本地地址

// ServerSocket
void bind(SocketAddress endpoint, int backlog) {
    // getImpl()触发套接字初始化
    // 绑定本地地址
    getImpl().bind(epoint.getAddress(), epoint.getPort());
    getImpl().listen(backlog);
}

//AbstractPlainSocketImpl
void bind(InetAddress address, int lport) {
    socketBind(address, lport);

    // Socket或ServerSocket置为已绑定状态
    if (socket != null)
        socket.setBound();
    if (serverSocket != null)
        serverSocket.setBound();
}

// DualStackPlainSocketImpl
void socketBind(InetAddress address, int port) {
    //  套接字文件描述符
    int nativefd = checkAndReturnNativeFD();
    // 调用native方法绑定本地地址
    bind0(nativefd, address, port, exclusiveBind);
    // 本地端口
    if (port == 0) {
        localport = localPort0(nativefd);
    } else {
        localport = port;
    }
}

5. 服务器监听连接请求

// ServerSocket
void bind(SocketAddress endpoint, int backlog) {
    // getImpl()触发套接字初始化
    getImpl().bind(epoint.getAddress(), epoint.getPort());
    // 监听连接请求
    getImpl().listen(backlog);
}

// AbstractPlainSocketImpl
void listen(int count) {
    socketListen(count);
}

// DualStackPlainSocketImpl
void socketListen(int backlog) {
    // 套接字文件描述符
    int nativefd = checkAndReturnNativeFD();
    // 调用native方法监听请求
    listen0(nativefd, backlog);
}

6. 客户端请求连接

// Socket
void connect(SocketAddress endpoint, int timeout) {
    // 远程地址和端口
    InetSocketAddress epoint = (InetSocketAddress) endpoint;
    InetAddress addr = epoint.getAddress ();
    int port = epoint.getPort();

    // 连接请求
    impl.connect(addr.getHostName(), port);

    // 已连接
    connected = true;
    // 如果套接字在连接之前没有绑定,内核将选择临时端口和本地地址
    bound = true;
}

// AbstractPlainSocketImpl
void connect(String host, int port){
    InetAddress address = InetAddress.getByName(host);
    if (address.isAnyLocalAddress()) {
        doConnect(InetAddress.getLocalHost(), port, timeout);
    } else {
        doConnect(address, port, timeout);
    }
}

// AbstractPlainSocketImpl
void doConnect(InetAddress address, int port, int timeout) {
    socketConnect(address, port, timeout);
    if (socket != null) {
        // Socket设置为已绑定、已连接
        socket.setBound();
        socket.setConnected();
    }
}

// DualStackPlainSocketImpl
void socketConnect(InetAddress address, int port, int timeout) {
    //  套接字文件描述符
    int nativefd = checkAndReturnNativeFD();

    int connectResult;
    if (timeout <= 0) {
        // 阻塞直到连接成功
        connectResult = connect0(nativefd, address, port);
    } else {
        // 阻塞,直到连接成功或超时
        configureBlocking(nativefd, false);
        try {
            connectResult = connect0(nativefd, address, port);
            if (connectResult == WOULDBLOCK) {
                // 调用native方法超时等待连接
                waitForConnect(nativefd, timeout);
            }
        } finally {
            configureBlocking(nativefd, true);
        }
    }
}

7. 服务器接受连接

// ServerSocket
public Socket accept() {
    Socket s = new Socket((SocketImpl) null);
    implAccept(s);
    return s;
}

// ServerSocket
void implAccept(Socket s) {
    SocketImpl si = null;

    // 创建SocketImpl实例
    s.setImpl();
    
    si = s.impl;
    s.impl = null;
    si.address = new InetAddress();
    si.fd = new FileDescriptor();

    getImpl().accept(si);
    
    s.impl = si;
    s.connected = true;
    s.created = true;
    s.bound = true;
}

// AbstractPlainSocketImpl
void accept(SocketImpl s) {
    socketAccept(s);
}

// DualStackPlainSocketImpl
void socketAccept(SocketImpl s) {
    // ServerSocket监听的文件描述符
    int nativefd = checkAndReturnNativeFD();

    // 与客户端连接的文件描述符
    // 如果复用监听fd,则只能与一个客户端连接通信
    // 为连接新建fd,使服务器能够连接多个客户端
    int newfd = -1;
    // 保存客户端的地址和端口
    InetSocketAddress[] isaa = new InetSocketAddress[1];
    
    if (timeout <= 0) {
        // 调用本地方法与客户端建立连接
        newfd = accept0(nativefd, isaa);
    } else {
        // 超时连接
        configureBlocking(nativefd, false);
        try {
            waitForNewConnection(nativefd, timeout);
            newfd = accept0(nativefd, isaa);
            if (newfd != -1) {
                configureBlocking(newfd, true);
            }
        } finally {
            configureBlocking(nativefd, true);
        }
    }
    
    // 将新SocketImpl的fd设置为连接的文件描述符
    // 使用该fd与客户端进行通信
    fdAccess.set(s.fd, newfd);
    
    // 远程,即客户端的地址和端口
    InetSocketAddress isa = isaa[0];
    s.port = isa.getPort();
    s.address = isa.getAddress();
    // 新SocketImpl的本地端口即ServerSocket监听的端口
    s.localport = localport;
}

8. 通信的InputStream和OutputStream

// Socket
InputStream getInputStream() {
    return impl.getInputStream();
}
OutputStream getOutputStream() {
    return impl.getOutputStream();
}

// AbstractPlainSocketImpl
InputStream getInputStream() {
    socketInputStream = new SocketInputStream(this);
    return socketInputStream;
}
OutputStream getOutputStream() {
    socketOutputStream = new SocketOutputStream(this);
    return socketOutputStream;
}
  • SocketInputStream extends FileInputStream
// 指向连接的文件描述符
FileDescriptor fd;
// 指向连接的真正套接字
AbstractPlainSocketImpl impl;
// 指向连接的Socket实例
Socket socket = null;

SocketInputStream(AbstractPlainSocketImpl impl) {
    fd = impl.getFileDescriptor();
    this.impl = impl;
    socket = impl.getSocket();
}

int read(byte b[], int off, int length, int timeout) {
    int n = socketRead(fd, b, off, length, timeout);
}

private int socketRead(FileDescriptor fd, byte b[], int off, int len, int timeout) {
    // 使用套接字接受原语读取
    return socketRead0(fd, b, off, len, timeout);
}

// 流的关闭会导致Socket关闭
public void close() {
    if (socket != null) {
        if (!socket.isClosed()) {
            socket.close();
        }
    } else {
        impl.close();
    }
}
  • SocketOutputStream extends FileOutputStream
// 指向连接的文件描述符
FileDescriptor fd;
// 指向连接的真正套接字
AbstractPlainSocketImpl impl;
// 指向连接的Socket实例
Socket socket = null;

SocketOutputStream(AbstractPlainSocketImpl impl) {
    fd = impl.getFileDescriptor();
    this.impl = impl;
    socket = impl.getSocket();
}

void write(int b) {
    temp[0] = (byte)b;
    socketWrite(temp, 0, 1);
}

void socketWrite(byte b[], int off, int len) {
    // 写到套接字
    socketWrite0(fd, b, off, len);
}

// 流的关闭会导致Socket关闭
public void close() {
    if (socket != null) {
        if (!socket.isClosed()) {
            socket.close();
        }
    } else {
        impl.close();
    }
}
  • shutdownInput()和shutdownOutput()
// Socket
void shutdownInput() {
    getImpl().shutdownInput();
    shutIn = true;
}
void shutdownOutput() {
    getImpl().shutdownOutput();
    shutOut = true;
}

// AbstractPlainSocketImpl
protected void shutdownInput() throws IOException {
    if (fd != null) {
        // SHUT_RD = 0
        socketShutdown(SHUT_RD);
        if (socketInputStream != null) {
            socketInputStream.setEOF(true);
        }
      shut_rd = true;
    }
}
protected void shutdownOutput() {
    if (fd != null) {
        // SHUT_WR = 1
        socketShutdown(SHUT_WR);
        shut_wr = true;
    }
}

// DualStackPlainSocketImpl
// howto:0,input;1,output;
void socketShutdown(int howto) throws IOException {
    int nativefd = checkAndReturnNativeFD();
    shutdown0(nativefd, howto);
}

9. 关闭

// ServerSocket/Socket
public void close() {
    synchronized(closeLock) {
        if (isClosed())
            return;
        if (created)
            impl.close();
        closed = true;
    }
}

// AbstractPlainSocketImpl
protected void close() throws IOException {
    synchronized(fdLock) {
        if (fd != null) {
            if (fdUseCount == 0) {
                if (closePending) {
                    return;
                }
                closePending = true;
                // 我们分两步关闭文件描述符——首先是“预关闭”,它关闭套接字,但不释放底层文件描述符。
                // 由于未传输数据和长时间的逗留间隔,此操作可能很长。
                // 一旦预关闭完成,我们做实际的套接字来释放fd。
                try {
                    socketPreClose();
                } finally {
                    socketClose();
                }
                fd = null;
                return;
                
            } else {
                // 还需要递减fdUseCount来通知释放fd的最后一个线程关闭它。
                if (!closePending) {
                    closePending = true;
                    fdUseCount--;
                    socketPreClose();
                }
            }
        }
    }
}
void socketPreClose() {
    socketClose0(true);
}
void socketClose() {
    socketClose0(false);
}

// DualStackPlainSocketImpl
void socketClose0(boolean useDeferredClose/*unused*/) throws IOException {
    final int nativefd = fdAccess.get(fd);
    fdAccess.set(fd, -1);
    close0(nativefd);
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352

推荐阅读更多精彩内容

  • Java知识点1、==和equals的区别基本类型比较==比较内容 equals比较地址值引用类型比较==比较地址...
    压抑的内心阅读 591评论 0 0
  • 1 网络编程----TCPNo24 【 public class Server { public static...
    征程_Journey阅读 1,254评论 0 4
  • 一、基础知识:1、JVM、JRE和JDK的区别:JVM(Java Virtual Machine):java虚拟机...
    杀小贼阅读 2,376评论 0 4
  • TCP协议是面向连接的通信协议,即在传输数据前先在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之...
    CQ_TYL阅读 1,250评论 0 2
  • 那么多年的学习生涯,遇见了无数的老师,像你们一样。 或许也和你们一样,总归会遇见一个或者几个,无论什么时候想起都满...
    箬茶阅读 373评论 0 5