Рассмотрим следующие клиентские и серверные компоненты:
import java.io.InputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
public class client {
public static void main(String[] args) throws IOException {
while (true) {
URL url = new URL("http://localhost:8000");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
int statusCode = connection.getResponseCode();
System.out.println("Status Code: " + statusCode);
connection.disconnect();
}
}
}
import java.io.OutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8000);
while (true) {
Socket clientSocket = serverSocket.accept();
OutputStream outputStream = clientSocket.getOutputStream();
outputStream.write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n".getBytes());
outputStream.flush();
clientSocket.close();
}
}
}
Запустив клиент во время работы сервера, вы вскоре увидите, что клиент начинает зависать SYN_SENT
на уровне TCP (всего около 30 секунд):
$ watch -n 0.1 "ss -on state syn-sent '( dport = :8000 )'"
Every 0.1s: ss -on state syn-sent '( dport = :8000 )' myhost: Tue Jul 16 04:08:52 2024
Netid Recv-Q Send-Q Local Address:Port Peer Address:Port Process
tcp 0 1 [::ffff:127.0.0.1]:60418 [::ffff:127.0.0.1]:8000 timer:(on,3.731ms,2)
$ pkill -3 java
# Stack trace of client's main thread while hanging outputted in Java terminal...
"main" #1 prio=5 os_prio=0 cpu=2429.68ms elapsed=40.96s tid=0x000079e6c40266c0 nid=0x18a1c6 runnable [0x000079e6cb9fd000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.Net.connect0([email protected]/Native Method)
at sun.nio.ch.Net.connect([email protected]/Net.java:579)
at sun.nio.ch.Net.connect([email protected]/Net.java:568)
at sun.nio.ch.NioSocketImpl.connect([email protected]/NioSocketImpl.java:593)
at java.net.Socket.connect([email protected]/Socket.java:633)
at java.net.Socket.connect([email protected]/Socket.java:583)
at sun.net.NetworkClient.doConnect([email protected]/NetworkClient.java:183)
at sun.net.www.http.HttpClient.openServer([email protected]/HttpClient.java:533)
at sun.net.www.http.HttpClient.openServer([email protected]/HttpClient.java:638)
at sun.net.www.http.HttpClient.<init>([email protected]/HttpClient.java:281)
at sun.net.www.http.HttpClient.New([email protected]/HttpClient.java:386)
at sun.net.www.http.HttpClient.New([email protected]/HttpClient.java:422)
at sun.net.www.protocol.http.HttpURLConnection.setNewClient([email protected]/HttpURLConnection.java:831)
at sun.net.www.protocol.http.HttpURLConnection.setNewClient([email protected]/HttpURLConnection.java:819)
at sun.net.www.protocol.http.HttpURLConnection.writeRequests([email protected]/HttpURLConnection.java:759)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0([email protected]/HttpURLConnection.java:1708)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream([email protected]/HttpURLConnection.java:1611)
at java.net.HttpURLConnection.getResponseCode([email protected]/HttpURLConnection.java:529)
at client.main(client.java:13)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0([email protected]/Native Method)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke([email protected]/NativeMethodAccessorImpl.java:77)
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke([email protected]/DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke([email protected]/Method.java:568)
at com.sun.tools.javac.launcher.Main.execute([email protected]/Main.java:419)
at com.sun.tools.javac.launcher.Main.run([email protected]/Main.java:192)
at com.sun.tools.javac.launcher.Main.main([email protected]/Main.java:132)
Я создаю Java-приложение (когда я столкнулся с этой проблемой, я пытался использовать интервал опроса 10 мс - мне показалось, что 30 мс работает), где мне нужно быстро отправлять HTTP-запросы, подобные этому, в течение определенного периода времени (я не могу использовать веб-сокеты). Итак, мой вопрос: почему происходит это зависание и как это исправить?
На данный момент моей лучшей попыткой исправить это было увеличение количества доступных дескрипторов файлов с обеих сторон (uname -n unlimitied
), но безрезультатно.
Сейчас тестирую больше... Я также могу воспроизвести то же самое с Python:
import requests
while True:
response = requests.get("http://localhost:8000")
print(f"Status Code: {response.status_code}")
Тогда python -m http.server
на сервер и вы SYN_SENT
повесите. Итак, похоже, что проблема может быть глубже, чем я изначально предполагал, но мне любопытно и я готов услышать любое потенциальное решение.
Я хочу, чтобы эта SYN_SENT
проблема с зависанием не возникала. Я хочу иметь возможность настроить интервал опроса HTTP-запросов так, чтобы он был очень низким (даже 1 мс между запросами в локальной сети; пока нет утечки ресурсов и все запросы выполняются последовательно, я не понимаю, почему это не так. Это достижимо), при этом мое Java или другое приложение работает совершенно надежно. Однако я также хочу понять проблему. Вид из Wireshark меня сбивает с толку, поскольку он показывает, что сервер вернул ответ HTTP 200 OK, но по какой-то причине клиент зависает во время его чтения (я пытался включить все необходимое, чтобы воспроизвести то, что я вижу, поскольку я занимался отладкой это часами). Спасибо за ваше время.
Я обновил вопрос, чтобы указать свое «желаемое поведение», как описано в разделе «Необходимы сведения об отладке».
похоже, что что-то на компьютере, либо брандмауэр, либо антивирус/антивредоносное ПО или тому подобное, блокирует завершение соединения. Возникает ли у вас такая же проблема, если вы используете стандартный HTTP-порт 80 вместо 8000? А что, если клиент и сервер работают на разных компьютерах в одной локальной сети, а не на одном компьютере?
Я рад сообщить, что нашел причину этой проблемы! Я заметил, что всякий раз, когда у меня возникали эти SYN_SENT
зависания (как показано ss
), в моем dmesg
также регистрировалось следующее:
nf_conntrack: nf_conntrack: table full, dropping packet
Таблица отслеживания соединений заполнялась! Каждый запрос HTTP 1.1, который я делал, выполнялся в собственном потоке TCP. Теперь все это имеет смысл.
Я использую довольно уникальный дистрибутив Linux, поэтому не уверен, что моя таблица отслеживания соединений меньше, чем в среднем. В моем приложении также было несколько проблем с утечкой сокетов/ресурсов, которые усугубляли проблему даже между отдельными программами в моей системе. Я еще не рассматривал возможность увеличения размера этой таблицы. Если вы столкнулись с этой проблемой, помните, что проблема также может заключаться в небольшой таблице подключений другого устройства в сети, например маршрутизатора, коммутатора или брандмауэра.
Я оставил свое приложение работающим на ночь с очень коротким интервалом опроса, чтобы посмотреть, что произойдет, и когда я проснулся, компилятор Java давал сбой, потому что systemd, кажется, заполнил /tmp
этими nf_conntrack
журналами. Вот тогда я подумал проверить dmesg
, и вуаля! Затем я перезагрузился, чтобы очиститься tmpfs
.
Почему -1? И почему голосование закрыто из-за «необходимости деталей отладки», которые, как мне кажется, я предоставил? Очевидно, это непростая проблема с синтаксическим сахаром, но это определенно проблема, которая меня интересует. Однако я обновляю тег Java для сети, поскольку эта проблема оказывается более общей. Спасибо.