Как реализован ServerSocket JVM (Часть 1)

Java задняя часть Операционная система Windows
Как реализован ServerSocket JVM (Часть 1)

Обзор

JDK предоставляет нам класс ServerSocket как реализацию серверного сокета, через который хост может прослушивать определенный порт и получать запросы с других концов, а также может отвечать запрашивающему после обработки. Его внутренняя реальная реализация достигается через класс SocketImpl, который обеспечивает фабричный режим, поэтому, если вам нужны другие реализации, вы также можете изменить его через фабричный режим.

структура наследования

--java.lang.Object
  --java.net.ServerSocket

Схема связанного класса

Выступая перед истинной реализацией класса Serversocket на классе SockeImpl, чтобы вы могли видеть, что он использует класс Socketimpl, но Windows и Unix-подобные системы разные, а разные версии Windows также должны делать разные лечения, поэтому два Типы систем разных классов.

На следующем рисунке показана взаимосвязь диаграмм классов окон. Класс SocketImpl реализует интерфейс SocketOptions, а затем выводится ряд подклассов. Среди них AbstractPlainSocketImpl — некоторая абстракция реализации исходного сокета, а класс PlainSocketImpl — прокси. класс, в котором Proxy TwoStacksPlainSocketImpl и DualStackPlainSocketImpl реализованы по-разному. Причина существования двух реализаций заключается в том, что одна работает с версиями ниже Windows Vista, а другая — с Windows Vista и выше.

image

По сравнению с реализацией windows, unix-подобная реализация не такая громоздкая, у нее нет проблемы с версией, поэтому она напрямую реализуется классом PlainSocketImpl, кроме того, видно, что есть класс SocksSocketImpl для обеих операционных систем. Он в основном реализует протоколы преобразования сеансов безопасности брандмауэра, включая SOCKS V4 и V5.

image

Из вышеизложенного видно, что на самом деле к разным системам нужно относиться по-разному, и они в основном одинаковы.Следующие реализации сокетов анализируются на примере 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;
  • created указывает, был ли создан объект SocketImpl, и ServerSocket должен полагаться на этот объект для реализации операций сокета.
  • bound Связаны ли адрес и порт.
  • закрытый Был ли сокет закрыт.
  • closeLock Блокировка, используемая при закрытии сокета.
  • impl Реальный объект реализации сокета.
  • oldImpl не использует старую реализацию.

основной метод

Конструктор

Существует пять типов конструкторов, вы можете не передавать параметры или передавать SocketImpl, порт, невыполненную работу и адрес. Давайте в основном рассмотрим последний конструктор, метод setImpl используется для установки объекта реализации, затем проверяет правильность размера порта, проверяет, меньше ли отставание 0 и делает его равным 50, и, наконец, выполняет порт и адрес операция привязки.

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);
    }
    
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;
        }
    }    

метод setImpl

Установите объект реализации сокета.Фабричный шаблон предоставляется здесь для облегчения стыковки с другими реализациями.По умолчанию фабричный объект отсутствует, поэтому реализацией шаблона является объект SocksSocketImpl.

private void setImpl() {
        if (factory != null) {
            impl = factory.createSocketImpl();
            checkOldImpl();
        } else {
            impl = new SocksSocketImpl();
        }
        if (impl != null)
            impl.setServerSocket(this);
    }

метод createImpl

Этот метод используется для создания объекта реализации сокета.Если объект реализации пуст, он будет вызван первымsetImplУстановите метод, а затем вызовите объект реализации сокета.createспособ создания сокета.

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

createЧто дал метод? Логика его реализации находится в классе AbstractPlainSocketImpl, куда передается переменная логического типа потока, которая фактически используется для определения того, является ли это протоколом udp или tcp, потоком является поток, tcp основан на соединении, и существует естественная абстракция потока. А udp не подключен не потоковый.

Два типа соединений идентифицируются логическим типом, true — это tcp, false — это udp, а затем передаются в локальную реализацию через метод socketCreate, перед этим оба они создадут объект FileDescriptor как ссылку на сокет, а FileDescriptor — это описание файла, которое можно использовать для описания файлов, сокетов, ресурсов и т. д. Кроме того, протокол udp также будет проходитьResourceManager.beforeUdpCreate()Для подсчета количества сокетов udp виртуальной машины будет создано исключение, если будет превышено указанное максимальное значение, а значение по умолчанию равно 25. Наконец, установите созданный флаг сокета в значение true, что соответствует абстрактному объекту Socket клиентского сокета и объекту ServerSocket сокета сервера в Java.

protected synchronized void create(boolean stream) throws IOException {
        this.stream = stream;
        if (!stream) {
            ResourceManager.beforeUdpCreate();
            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();
    }
    

Посмотрите вниз на вызов вышеsocketCreateМетод логики определяет, что файловый дескриптор не пуст, затем вызывает локальныйsocket0метод и, наконец, связать полученный дескриптор с объектом файлового дескриптора.

void socketCreate(boolean stream) throws IOException {
        if (fd == null)
            throw new SocketException("Socket closed");

        int newfd = socket0(stream, false /*v6 Only*/);

        fdAccess.set(fd, newfd);
    }
    
static native int socket0(boolean stream, boolean v6Only) throws IOException;

Затем посмотрите на локальный методsocket0реализация, логика такова:

  1. позвонивNET_Socketфункция для создания дескриптора сокета, которыйsocketфункция создает дескриптор и передаетSetHandleInformationФункция устанавливает флаг наследования дескриптора. Здесь вы можете видеть, что категория, соответствующая идентификатору потока,SOCK_STREAMа такжеSOCK_DGRAM. Выдает исключение создания, если дескриптор недействителен.
  2. затем пройтиsetsockoptФункция устанавливает значения опций для сокета и выдает исключение create в случае возникновения ошибки.
  3. наконец пройти сноваSetHandleInformationУстанавливает флаг наследования дескриптора, возвращая дескриптор.
JNIEXPORT jint JNICALL Java_java_net_DualStackPlainSocketImpl_socket0
  (JNIEnv *env, jclass clazz, jboolean stream, jboolean v6Only /*unused*/) {
    int fd, rv, opt=0;

    fd = NET_Socket(AF_INET6, (stream ? SOCK_STREAM : SOCK_DGRAM), 0);
    if (fd == INVALID_SOCKET) {
        NET_ThrowNew(env, WSAGetLastError(), "create");
        return -1;
    }

    rv = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &opt, sizeof(opt));
    if (rv == SOCKET_ERROR) {
        NET_ThrowNew(env, WSAGetLastError(), "create");
    }

    SetHandleInformation((HANDLE)(UINT_PTR)fd, HANDLE_FLAG_INHERIT, FALSE);

    return fd;
}

int NET_Socket (int domain, int type, int protocol) {
    SOCKET sock;
    sock = socket (domain, type, protocol);
    if (sock != INVALID_SOCKET) {
        SetHandleInformation((HANDLE)(uintptr_t)sock, HANDLE_FLAG_INHERIT, FALSE);
    }
    return (int)sock;
}

метод связывания

Этот метод используется для привязки сокета к указанному адресу и порту.Если SocketAddress пусто, это означает, что ни адрес, ни порт не указаны, тогда система привяжет сокет ко всем допустимым локальным адресам, и сгенерирует порт динамически . Логика следующая:

  1. Определяем закрыто ли оно, если закрыто кидаемSocketException("Socket is closed").
  2. Судя по тому, было ли оно связано, связывание брошеноSocketException("Already bound").
  3. Определите, является ли адрес пустым, создайте InetSocketAddress, если он пуст, по умолчанию все действительные локальные адреса, соответствующие0.0.0.0И 0 - порт по умолчанию, динамически генерируется операционной системой.
  4. Определить, относится ли объект к типу InetSocketAddress, если нет, броситьIllegalArgumentException("Unsupported address type").
  5. Определяем, имеет ли адрес уже значение, если нет, кидаемSocketException("Unresolved address").
  6. Отставание устанавливается равным 50, если оно меньше 1.
  7. Проверьте порт через менеджер безопасности.
  8. Вызов объекта по сокетуbindа такжеlistenметод.
  9. Связанный флаг установлен в значение true.
public void bind(SocketAddress endpoint) throws IOException {
        bind(endpoint, 50);
    }
    
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());
            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;
        }
    }

объект реализации сокетаbindметод будет вызываться косвенноsocketBindметод, логика следующая:

  1. Получите дескриптор локального файла nativefd.
  2. Проверьте, не пуст ли адрес.
  3. передачаbind0Родной метод.
  4. Если порт 0, он также вызоветlocalPort0Собственный метод получает локальный порт и присваивает его атрибуту localport объекта реализации сокета, чтобы получить порт, динамически сгенерированный операционной системой.
void socketBind(InetAddress address, int port) throws IOException {
        int nativefd = checkAndReturnNativeFD();

        if (address == null)
            throw new NullPointerException("inet address argument is null.");

        bind0(nativefd, address, port, exclusiveBind);
        if (port == 0) {
            localport = localPort0(nativefd);
        } else {
            localport = port;
        }

        this.address = address;
    }
static native void bind0(int fd, InetAddress localAddress, int localport, boolean exclBind)
                             
static native int localPort0(int fd) throws IOException;

bind0Логика локального метода следующая:

  1. пройти черезNET_InetAddressToSockaddrФункция заполняет значение свойства объекта InetAddress уровня Java в объединение SOCKETADDRESS, которое соответствует структуре библиотеки Winsock, и целью является их заполнение.
typedef union {
    struct sockaddr     sa;
    struct sockaddr_in  sa4;
    struct sockaddr_in6 sa6;
} SOCKETADDRESS;
  1. NET_WinBindЛогика функции заключается в том, чтобы сначала проверить, требуется ли эксклюзивный порт по флагу exclBind, и при необходимости передать библиотеку WinsocksetsockoptНастройки функцийSO_EXCLUSIVEADDRUSEВыбор на уровне Java, чтобы решить, можно ли передать эксклюзивный портsun.net.useExclusiveBindпараметр для настройки, по умолчанию он монопольный. Затем через операционную системуbindФункция завершает операцию привязки.
  2. Выдает исключение, если привязка не удалась.
JNIEXPORT void JNICALL Java_java_net_DualStackPlainSocketImpl_bind0
  (JNIEnv *env, jclass clazz, jint fd, jobject iaObj, jint port,
   jboolean exclBind)
{
    SOCKETADDRESS sa;
    int rv, sa_len = 0;

    if (NET_InetAddressToSockaddr(env, iaObj, port, &sa,
                                  &sa_len, JNI_TRUE) != 0) {
      return;
    }

    rv = NET_WinBind(fd, &sa, sa_len, exclBind);

    if (rv == SOCKET_ERROR)
        NET_ThrowNew(env, WSAGetLastError(), "NET_Bind");
}

localPort0Реализация нативных методов в основном осуществляется через библиотеку Winsock.getsocknameфункция, чтобы получить адрес сокета, а затем передатьntohsФункция преобразует сетевые байты в байты хоста и в int.

JNIEXPORT jint JNICALL Java_java_net_DualStackPlainSocketImpl_localPort0
  (JNIEnv *env, jclass clazz, jint fd) {
    SOCKETADDRESS sa;
    int len = sizeof(sa);

    if (getsockname(fd, &sa.sa, &len) == SOCKET_ERROR) {
        if (WSAGetLastError() == WSAENOTSOCK) {
            JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
                    "Socket closed");
        } else {
            NET_ThrowNew(env, WSAGetLastError(), "getsockname failed");
        }
        return -1;
    }
    return (int) ntohs((u_short)GET_PORT(&sa));
}

объект реализации сокетаlistenметод будет вызываться косвенноsocketListenМетод, логика проста, получите локальные файловые дескрипторы, а затем вызовыlisten0Родной метод. Вы можете видеть, что собственный метод очень прост, просто вызывая библиотеку Winsock.listenфункцию для завершения операции мониторинга.

void socketListen(int backlog) throws IOException {
        int nativefd = checkAndReturnNativeFD();

        listen0(nativefd, backlog);
    }
    
static native void listen0(int fd, int backlog) throws IOException;

JNIEXPORT void JNICALL Java_java_net_DualStackPlainSocketImpl_listen0
  (JNIEnv *env, jclass clazz, jint fd, jint backlog) {
    if (listen(fd, backlog) == SOCKET_ERROR) {
        NET_ThrowNew(env, WSAGetLastError(), "listen failed");
    }
}

принять метод

Этот метод используется для получения подключений к сокету. После открытия сокета для прослушивания он будет блокировать ожидание подключения к сокету. Как только появится подключение для получения, этот метод будет использоваться для получения операции. Логика такова,

  1. Определите, был ли закрыт сокет.
  2. Определите, привязан ли сокет.
  3. Создайте объект Socket и вызовитеimplAcceptметод,
  4. Возврат объекта Socket.
public Socket accept() throws IOException {
        if (isClosed())
            throw new SocketException("Socket is closed");
        if (!isBound())
            throw new SocketException("Socket is not bound yet");
        Socket s = new Socket((SocketImpl) null);
        implAccept(s);
        return s;
    }

implAcceptЛогика метода такова,

  1. Если реализация сокета во входящем объекте Socket пуста, она будет передана черезsetImplМетод устанавливает реализацию сокета и выполняется, если он не пуст.resetработать.
  2. Вызов объекта реализации сокетаacceptМетод завершает операцию получения.Этот шаг выполнен потому, что в объекте SocketImpl в нашем объекте Socket по-прежнему отсутствует файловый дескриптор, соответствующий базовому сокету операционной системы.
  3. Позвоните менеджеру по безопасности, чтобы проверить разрешения.
  4. Получите полный объект SocketImpl, назначьте его объекту Socket и вызовитеpostAcceptМетод устанавливает для объекта Socket значение created,connected,bound.
protected final void implAccept(Socket s) throws IOException {
        SocketImpl si = null;
        try {
            if (s.impl == null)
              s.setImpl();
            else {
                s.impl.reset();
            }
            si = s.impl;
            s.impl = null;
            si.address = new InetAddress();
            si.fd = new FileDescriptor();
            getImpl().accept(si);

            SecurityManager security = System.getSecurityManager();
            if (security != null) {
                security.checkAccept(si.getInetAddress().getHostAddress(),
                                     si.getPort());
            }
        } catch (IOException e) {
            if (si != null)
                si.reset();
            s.impl = si;
            throw e;
        } catch (SecurityException e) {
            if (si != null)
                si.reset();
            s.impl = si;
            throw e;
        }
        s.impl = si;
        s.postAccept();
    }

объект реализации сокетаacceptСпособ в основном называется следующим образомsocketAcceptметод, логика такова,

  1. Получить файловый дескриптор операционной системы.
  2. Вызывается, если объект SocketImpl пуст.NullPointerException("socket is null").
  3. Если тайм-аут меньше или равен 0, вызовите локальный напрямуюaccept0метод, который продолжает блокировать.
  4. И наоборот, если тайм-аут больше 0, то есть установлен тайм-аут, он сначала вызоветconfigureBlockingЛокальный метод используется для установки указанного сокета в неблокирующий режим. Тогда позвониwaitForNewConnectionСобственный метод, если новый сокет может быть получен в течение периода ожидания, он будет вызванaccept0Метод получает дескриптор нового сокета и снова вызывает его после успешного получения.configureBlockingСобственный метод устанавливает новый сокет в режим блокировки. Наконец, если неблокирующий режим дает сбой, исходный сокет будет установлен в фиолетовый режим штекера и, наконец, используется здесь, поэтому его выполнение может быть гарантировано даже в случае возникновения исключения.
  5. Наконец, назначьте полученный новый файловый дескриптор объекту SocketImpl, а также назначьте удаленный порт, удаленный адрес, локальный порт и т. д. связанным с ним переменным.
void socketAccept(SocketImpl s) throws IOException {
        int nativefd = checkAndReturnNativeFD();
        if (s == null)
            throw new NullPointerException("socket is null");
        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);
            }
        }
        fdAccess.set(s.fd, newfd);
        InetSocketAddress isa = isaa[0];
        s.port = isa.getPort();
        s.address = isa.getAddress();
        s.localport = localport;
    }

configureBlockingЛогика локального метода очень проста, суть в том, чтобы вызвать библиотеку Winsock.ioctlsocketФункция для установки сокета как блокирующего или неблокирующего в соответствии с флагом блокировки.

JNIEXPORT void JNICALL Java_java_net_DualStackPlainSocketImpl_configureBlocking
  (JNIEnv *env, jclass clazz, jint fd, jboolean blocking) {
    u_long arg;
    int result;

    if (blocking == JNI_TRUE) {
        arg = SET_BLOCKING;    // 0
    } else {
        arg = SET_NONBLOCKING;   // 1
    }

    result = ioctlsocket(fd, FIONBIO, &arg);
    if (result == SOCKET_ERROR) {
        NET_ThrowNew(env, WSAGetLastError(), "configureBlocking");
    }
}

waitForNewConnectionЛогика локального метода следующая, ядро ​​через библиотеку WinsockselectОн будет ожидать истечения времени ожидания, чтобы узнать, активен ли указанный файловый дескриптор.Если время ожидания истекло, он вернет 0 и выдаст исключение SocketTimeoutException на уровень Java. И если возвращается -1, это означает, что сокет был закрыт и выброшено исключение SocketException. Выдает InterruptedIOException, если возвращается -2.

JNIEXPORT void JNICALL Java_java_net_DualStackPlainSocketImpl_waitForNewConnection
  (JNIEnv *env, jclass clazz, jint fd, jint timeout) {
    int rv;

    rv = NET_Timeout(fd, timeout);
    if (rv == 0) {
        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException",
                        "Accept timed out");
    } else if (rv == -1) {
        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "socket closed");
    } else if (rv == -2) {
        JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
                        "operation interrupted");
    }
}

JNIEXPORT int JNICALL
NET_Timeout(int fd, long timeout) {
    int ret;
    fd_set tbl;
    struct timeval t;
    t.tv_sec = timeout / 1000;
    t.tv_usec = (timeout % 1000) * 1000;
    FD_ZERO(&tbl);
    FD_SET(fd, &tbl);
    ret = select (fd + 1, &tbl, 0, 0, &t);
    return ret;
}

accept0Логика реализации локального метода такова:

  1. через язык СmemsetФункция устанавливает значение в структуре, соответствующей объединению SOCKETADDRESS, в 0.
  2. через библиотеку Winsockacceptфункция для получения адреса сокета.
  3. Определите, является ли полученный дескриптор сокета недопустимым, и может ли он генерировать InterruptedIOException или SocketException соответственно.
  4. пройти черезSetHandleInformationФункция устанавливает флаг наследования дескриптора.
  5. NET_SockaddrToInetAddressФункция используется для преобразования результирующего сокета в объект InetAddress уровня Java.
  6. Используйте сгенерированный объект InetAddress для создания объекта InetSocketAddress уровня Java.
  7. Объект массива InetSocketAddress, назначенный слою Java.
  8. Возвращает файловый дескриптор только что полученного сокета.
JNIEXPORT jint JNICALL Java_java_net_DualStackPlainSocketImpl_accept0
  (JNIEnv *env, jclass clazz, jint fd, jobjectArray isaa) {
    int newfd, port=0;
    jobject isa;
    jobject ia;
    SOCKETADDRESS sa;
    int len = sizeof(sa);

    memset((char *)&sa, 0, len);
    newfd = accept(fd, &sa.sa, &len);

    if (newfd == INVALID_SOCKET) {
        if (WSAGetLastError() == -2) {
            JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
                            "operation interrupted");
        } else {
            JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
                            "socket closed");
        }
        return -1;
    }

    SetHandleInformation((HANDLE)(UINT_PTR)newfd, HANDLE_FLAG_INHERIT, 0);

    ia = NET_SockaddrToInetAddress(env, &sa, &port);
    isa = (*env)->NewObject(env, isa_class, isa_ctorID, ia, port);
    (*env)->SetObjectArrayElement(env, isaa, 0, isa);

    return newfd;
}

------------- Рекомендуем прочитать ------------

Резюме моей статьи за 2017 год — машинное обучение

Краткое изложение моих статей за 2017 год — Java и промежуточное ПО

Резюме моих статей 2017 года — глубокое обучение

Краткое изложение моих статей за 2017 год — исходный код JDK

Резюме моей статьи за 2017 год — обработка естественного языка

Резюме моих статей 2017 года — Java Concurrency

------------------рекламное время----------------

Планета знаний: путешествие по океану

Меню официальной учетной записи было разделено на «распределенное», «машинное обучение», «глубокое обучение», «НЛП», «глубина Java», «ядро параллелизма Java», «исходный код JDK», «ядро Tomcat», и т.д. Там может быть один стиль, чтобы удовлетворить ваш аппетит.

Зачем писать «Анализ проектирования ядра Tomcat»

Добро пожаловать, чтобы следовать:

这里写图片描述