Обзор
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 и выше.
По сравнению с реализацией windows, unix-подобная реализация не такая громоздкая, у нее нет проблемы с версией, поэтому она напрямую реализуется классом PlainSocketImpl, кроме того, видно, что есть класс SocksSocketImpl для обеих операционных систем. Он в основном реализует протоколы преобразования сеансов безопасности брандмауэра, включая SOCKS V4 и V5.
Из вышеизложенного видно, что на самом деле к разным системам нужно относиться по-разному, и они в основном одинаковы.Следующие реализации сокетов анализируются на примере 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
реализация, логика такова:
- позвонив
NET_Socket
функция для создания дескриптора сокета, которыйsocket
функция создает дескриптор и передаетSetHandleInformation
Функция устанавливает флаг наследования дескриптора. Здесь вы можете видеть, что категория, соответствующая идентификатору потока,SOCK_STREAM
а такжеSOCK_DGRAM
. Выдает исключение создания, если дескриптор недействителен. - затем пройти
setsockopt
Функция устанавливает значения опций для сокета и выдает исключение create в случае возникновения ошибки. - наконец пройти снова
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 пусто, это означает, что ни адрес, ни порт не указаны, тогда система привяжет сокет ко всем допустимым локальным адресам, и сгенерирует порт динамически . Логика следующая:
- Определяем закрыто ли оно, если закрыто кидаем
SocketException("Socket is closed")
. - Судя по тому, было ли оно связано, связывание брошено
SocketException("Already bound")
. - Определите, является ли адрес пустым, создайте InetSocketAddress, если он пуст, по умолчанию все действительные локальные адреса, соответствующие
0.0.0.0
И 0 - порт по умолчанию, динамически генерируется операционной системой. - Определить, относится ли объект к типу InetSocketAddress, если нет, бросить
IllegalArgumentException("Unsupported address type")
. - Определяем, имеет ли адрес уже значение, если нет, кидаем
SocketException("Unresolved address")
. - Отставание устанавливается равным 50, если оно меньше 1.
- Проверьте порт через менеджер безопасности.
- Вызов объекта по сокету
bind
а такжеlisten
метод. - Связанный флаг установлен в значение 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
метод, логика следующая:
- Получите дескриптор локального файла nativefd.
- Проверьте, не пуст ли адрес.
- передача
bind0
Родной метод. - Если порт 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
Логика локального метода следующая:
- пройти через
NET_InetAddressToSockaddr
Функция заполняет значение свойства объекта InetAddress уровня Java в объединение SOCKETADDRESS, которое соответствует структуре библиотеки Winsock, и целью является их заполнение.
typedef union {
struct sockaddr sa;
struct sockaddr_in sa4;
struct sockaddr_in6 sa6;
} SOCKETADDRESS;
-
NET_WinBind
Логика функции заключается в том, чтобы сначала проверить, требуется ли эксклюзивный порт по флагу exclBind, и при необходимости передать библиотеку Winsocksetsockopt
Настройки функцийSO_EXCLUSIVEADDRUSE
Выбор на уровне Java, чтобы решить, можно ли передать эксклюзивный портsun.net.useExclusiveBind
параметр для настройки, по умолчанию он монопольный. Затем через операционную системуbind
Функция завершает операцию привязки. - Выдает исключение, если привязка не удалась.
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");
}
}
принять метод
Этот метод используется для получения подключений к сокету. После открытия сокета для прослушивания он будет блокировать ожидание подключения к сокету. Как только появится подключение для получения, этот метод будет использоваться для получения операции. Логика такова,
- Определите, был ли закрыт сокет.
- Определите, привязан ли сокет.
- Создайте объект Socket и вызовите
implAccept
метод, - Возврат объекта 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
Логика метода такова,
- Если реализация сокета во входящем объекте Socket пуста, она будет передана через
setImpl
Метод устанавливает реализацию сокета и выполняется, если он не пуст.reset
работать. - Вызов объекта реализации сокета
accept
Метод завершает операцию получения.Этот шаг выполнен потому, что в объекте SocketImpl в нашем объекте Socket по-прежнему отсутствует файловый дескриптор, соответствующий базовому сокету операционной системы. - Позвоните менеджеру по безопасности, чтобы проверить разрешения.
- Получите полный объект 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
метод, логика такова,
- Получить файловый дескриптор операционной системы.
- Вызывается, если объект SocketImpl пуст.
NullPointerException("socket is null")
. - Если тайм-аут меньше или равен 0, вызовите локальный напрямую
accept0
метод, который продолжает блокировать. - И наоборот, если тайм-аут больше 0, то есть установлен тайм-аут, он сначала вызовет
configureBlocking
Локальный метод используется для установки указанного сокета в неблокирующий режим. Тогда позвониwaitForNewConnection
Собственный метод, если новый сокет может быть получен в течение периода ожидания, он будет вызванaccept0
Метод получает дескриптор нового сокета и снова вызывает его после успешного получения.configureBlocking
Собственный метод устанавливает новый сокет в режим блокировки. Наконец, если неблокирующий режим дает сбой, исходный сокет будет установлен в фиолетовый режим штекера и, наконец, используется здесь, поэтому его выполнение может быть гарантировано даже в случае возникновения исключения. - Наконец, назначьте полученный новый файловый дескриптор объекту 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
Логика реализации локального метода такова:
- через язык С
memset
Функция устанавливает значение в структуре, соответствующей объединению SOCKETADDRESS, в 0. - через библиотеку Winsock
accept
функция для получения адреса сокета. - Определите, является ли полученный дескриптор сокета недопустимым, и может ли он генерировать InterruptedIOException или SocketException соответственно.
- пройти через
SetHandleInformation
Функция устанавливает флаг наследования дескриптора. -
NET_SockaddrToInetAddress
Функция используется для преобразования результирующего сокета в объект InetAddress уровня Java. - Используйте сгенерированный объект InetAddress для создания объекта InetSocketAddress уровня Java.
- Объект массива InetSocketAddress, назначенный слою Java.
- Возвращает файловый дескриптор только что полученного сокета.
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»
Добро пожаловать, чтобы следовать: