powered by simpleCommunicator - 2.0.61     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / Java [игнор отключен] [закрыт для гостей] / IO/NIO WTF
85 сообщений из 85, показаны все 4 страниц
IO/NIO WTF
    #38415908
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Понадобилось написать TCP "прокси", чем-то аналогичный вот этой тулзе
http://ws.apache.org/tcpmon/tcpmontutorial.html
С сокетами знаком исключительно по форумам, поэтому решил заполнить этот пробел ограничившись для начала Java API.
В качестве тестового варианта написал простейший насос данных в одну и другую сторону на plain IO.
WTF#1 - метод InputStream.available() бесполезен и даже сломан для сокетов.
Читаем JavaDoc
Returns:an estimate of the number of bytes that can be read (or skipped over) from this input stream without blocking or 0 when it reaches the end of the input stream.

На сокетах available будет изначально возвращать 0, если узел с другой стороны ничего не отправляет. Но это ещё не значит что поток завершен.
WTF#2 Дальше хуже. read(), который должен вернуть -1 по окончании чтения тоже никогда -1 не возвращает. В общем случае никакого начала и конца у InputStream для сокетов нет. А использовать available для хоть какой-то эмуляции неблокируемости тоже не выйдет.
Кое как заборол. Работает на браузере.

Начал тестировать на боевом приложении, WTF#3
Клиент отправляет пакет данных. Он прочитан, перенаправлен на другой сервер. После этого мой серверный сокет снова пытается сделать read(). В теории он должен либо заблокироваться, либо вернуть -1.
На практике чтение выкидывает исключение Connection reset! Но это пол беды. Помимо этого клиентская сторона закрывает свой сокет и на запись. Т.е. отклик сервера записать проигнорировав reset уже нельзя.
WTF#4 - не смотря на connection reset и невозможность дальнейшей работы с клиентом со стороны сервера, никакие флаги у сокета не меняются. Всякие connected, closed и пр. находятся в том же состоянии что и в начале работы. Т.е. спросит клиента с сервера живо ли тот ещё никакой возможности не видно.

Психанул. Переписал всё на NIO без селекторов. Т.е. тот же многопоточный подход что и в IO, только через NIO API. И, о чудо!
Работает. Причем независимо от параметра configureBlocking().
Никаких проблем с чтением после первого пакета почему-то нет. Всё до окончания сессии обмена пакетами работает более менее.

WTF#5 - я вообще смотрю на разные проекты и вижу что постоянные исключения в TCP на Java это вообще штатные ситуации. Имеет ли смысл избегать логирования stacktrace для повышения производительности? Ведь если разворачивать stacktrace на каждый пук при многочисленых соединениях и отсоединениях клиентов, то провал в производительности обеспечен.

И напоследок вопрос про NIO - как лучше всего избегать холостых циклов чтения, когда данные ещё не отправленый второй стороной. Они почему-то возникают даже при configureBlocking(true). Перейти исключительно на селекторы? Варианты с Thread.sleep(n) как-то не улыбаются.

Комментарии и нравоучения приветствуются.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38415965
Лагман
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Можно селектор использовать http://tutorials.jenkov.com/java-nio/selectors.html
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38415970
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ЛагманМожно селектор использовать http://tutorials.jenkov.com/java-nio/selectors.html
Прочитано несколько раз. Спасибо.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38415974
Андрей Панфилов
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Blazkowicz,

относительно 1-3. На транспортном вы не можете знать сколько байт должен в итоге передать клиент, потому что это определяется протоколом приложения, поэтому для сокета у вас фактически только три состояния: в буфере есть данные, их нужно считать и отправить дальше, в буфере нет данных - нужно ждать когда появятся, сокет закрыт.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38415983
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Андрей ПанфиловНа транспортном вы не можете знать сколько байт должен в итоге передать клиент, потому что это определяется протоколом приложения
Правильно. Если можно привязаться к протоколу, то всё становится просто.

Андрей ПанфиловПоэтому для сокета у вас фактически только три состояния: в буфере есть данные, их нужно считать и отправить дальше, в буфере нет данных - нужно ждать когда появятся, сокет закрыт.
Именно. Проблема в том что нет какого-то однозначно способа определить состояния и поступить правильно в зависимости от этого в IO.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38415990
Андрей Панфилов
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
BlazkowiczПроблема в том что нет какого-то однозначно способа определить состояния и поступить правильно в зависимости от этого в IO.Не понял проблемы.. д.б. что-то в духе:

Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
while (true) {
    while (clientInput.available() > 0) {
        считать/послать
    }
    while (serverInput.available() > 0) {
        считать/послать
    }
    if (client.isClosed() || server.isClosed()) {
        break;
    }
}


по вкусу вставить sleep, т.е. на одно проксируемое соединение уходит один поток
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38415993
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Спасибо за пример! Попробую на досуге в таком ключе. На первый взгляд, действительно, можно избежать вышеуказаных проблем таким способом.
Я просто сходу в разных потоках это делал. Для NIO понял что лучше объединить. Для IO, вижу, вообще без объединения не обойтись.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416064
Фотография mayton
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Для файлового ввода-вывода имплементация IO должна импортировать в себя пакеты NIO.
Как там с сокетами - ХЗ но странно всё это. Насчёт переписывания.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416086
cdtyjv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Не очень понял, что у вас за проблемы с available(). Вот пример, в котором все абсолютно четко работает:

Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
public class SocketExample {
    public static void main(String[] args) throws Exception {
        final byte[] chunk1 = new byte[] {1, 2, 3, 4};
        final byte[] chunk2 = new byte[] {5, 6, 7, 8};

        final CountDownLatch latch1 = new CountDownLatch(1);
        final CountDownLatch latch2 = new CountDownLatch(1);
        final CountDownLatch latch3 = new CountDownLatch(1);

        new Thread(new Runnable() {
            @Override public void run() {
                try {
                    ServerSocket srvSock = new ServerSocket(8844);

                    Socket cliSock = srvSock.accept();

                    OutputStream os = cliSock.getOutputStream();

                    os.write(chunk1);

                    latch1.await();

                    os.write(chunk2);

                    latch2.await();

                    os.close();

                    latch3.await();

                    cliSock.close();
                    srvSock.close();
                }
                catch (Exception ignore) {
                    // Ignore.
                }
            }
        }).start();

        Thread.sleep(1000);

        Socket sock = new Socket("localhost", 8844);

        InputStream is = sock.getInputStream();

        assert is.available() == 4;

        byte[] buf = new byte[4];

        int read = is.read(buf);

        assert read == 4;
        assert Arrays.equals(buf, chunk1);
        assert is.available() == 0;

        latch1.countDown();
        Thread.sleep(50);

        assert is.available() == 4;

        read = is.read(buf);

        assert read == 4;
        assert Arrays.equals(buf, chunk2);
        assert is.available() == 0;

        latch2.countDown();
        Thread.sleep(50);

        assert is.available() == 0;
        assert is.read() == -1;

        latch3.countDown();
        Thread.sleep(50);

        assert is.read() == -1;
    }
}



Но это "Java из Java". Я легко допускаю, что клиенты/сервера, с которыми вы взаимодействуете могут рвать соединения некорректно. В любом случае, для вычитки никаких проблем быть не должно.
Метод available() имеет лишь ограниченную применимость, и скедулить потоки, полагаясь на него, разумеется, нельзя.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416092
cdtyjv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
По NIO - да, лучше всего сидеть на селекторах. Один тред слушает slector.select(), потом диспатчит это в другие треды - это типичный паттерн для этого дела.
Вот очень хороший туториал по NIO: http://rox-xmlrpc.sourceforge.net/niotut/

P.S.: Если често, не очень понял, как NIO может быть без селекторов
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416098
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
cdtyjvP.S.: Если често, не очень понял, как NIO может быть без селекторов
Тот же IO, только проще, и без блокировки.
http://tutorials.jenkov.com/java-nio/socket-channel.html
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416106
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
cdtyjvНе очень понял, что у вас за проблемы с available(). Вот пример, в котором все абсолютно четко работает:
Я не знаю размеров пакетов. И не могу на них полагаться. Я полагался на то что метод read заблокируется если что или вернет -1.
А ещё я к тому что SocketInputStream нарушает контракт InputStream.

cdtyjvЯ легко допускаю, что клиенты/сервера, с которыми вы взаимодействуете могут рвать соединения некорректно.
Так и есть.

cdtyjvВ любом случае, для вычитки никаких проблем быть не должно.

Вот чтение меня больше всего и озадачило. Как только я пытаюсь в IO читать больше чем длина пакета, то получаю исключение Connection reset, вместо ожидаемой блокировки или -1. И что самое удивительно что в блокирующем режиме через NIO такого не происходит. Я понимаю что частично это и косяк стороннего клиентского сокета. Но как-то слишком много сюрпризов по сравнению с ожиданиями из JavaDoc.


cdtyjvМетод available() имеет лишь ограниченную применимость, и скедулить потоки, полагаясь на него, разумеется, нельзя.
Скедулить потоки я на нем не собирался. Я думал как бы его вообще применить к моей задаче. Но вот до такого 14920507 не додумался, так как всё делал в 2х потоках а не в одном.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416112
DoSOfRedRiver
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Blazkowicz,

Приведите пример Вашего кода работы с IO.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416117
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
DoSOfRedRiverBlazkowicz,
Приведите пример Вашего кода работы с IO.

Две такие асинхронные таски запускаются с каждой стороны.
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
            int read;
            int c = 0;
            byte[] buff = new byte[inSocket.getReceiveBufferSize()];
            try {
                InputStream in = inSocket.getInputStream();
                OutputStream out = outSocket.getOutputStream();
                while (!inSocket.isInputShutdown() && (read = in.read(buff)) >= 0) {
                    if (!outSocket.isOutputShutdown() && read > 0) {
                        out.write(buff, 0, read);
                        c += read;
                        System.out.println(name + " bytes copied " + c);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace(System.out);
            }
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416137
cdtyjv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
BlazkowiczСкедулить потоки я на нем не собирался. Я думал как бы его вообще применить к моей задаче. Но вот до такого 14920507 не додумался, так как всё делал в 2х потоках а не в одном.Настоятельно НЕ рекомендую использовать этот код, ибо это что-то сродни busy loop в многопоточном программировании. Если вы не хотите блокироваться, то смело берите селектор, и вперед. Там есть нормальный Selector.select(), с которым никаких Thread.sleep() и прочего *** не надо делать. Тупо один поток сидит на селекторе. Как только с сокетом происходит какое-то событие - он просыпается, и говорит вам, что это было. А вы в зависимости от этого переадресуете работу в другой поток.
В общем, по ссылке, что я привел выше это очень хорошо расписано. Мне в свое время именно этот материал поставил голову на место по части NIO.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416141
Фотография schwa
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
авторДальше хуже. read(), который должен вернуть -1 по окончании чтения тоже никогда -1 не возвращает.
-1 вернется только в случае, если другая сторона закрыла соединение пока мы висели на read (и все оповещения об этом успешно дошли).
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416143
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Андрей Панфилов,
Спасибо огромное! Работает в лучшем виде! Код на много проще и ещё другуя бага выловилась после рефакторинга.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416146
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
cdtyjvНастоятельно НЕ рекомендую использовать этот код, ибо это что-то сродни busy loop в многопоточном программировании.
Какой именно?

cdtyjvЕсли вы не хотите блокироваться, то смело берите селектор, и вперед.
Это я знаю. Я планировал блокироваться в первой итерации.

cdtyjvТам есть нормальный Selector.select(), с которым никаких Thread.sleep() и прочего *** не надо делать.
Я в курсе. Мануал прочитан. Примеры прочитаны. Thread.sleep() не планировал использовать в продакшне. Собственно это всё никак не отвечает на мои вопросы выше. Было интересно как сделать через IO. И почему именно так.


cdtyjvТупо один поток сидит на селекторе.
Как только с сокетом происходит какое-то событие - он просыпается, и говорит вам, что это было. А вы в зависимости от этого переадресуете работу в другой поток.

На словах я это сам могу рассказывать кому угодно. :)
Так и сделаю когда до нагрузки дойдёт.

cdtyjvВ общем, по ссылке, что я привел выше это очень хорошо расписано. Мне в свое время именно этот материал поставил голову на место по части NIO.
Спасибо прочту.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416148
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
schwa-1 вернется только в случае, если другая сторона закрыла соединение пока мы висели на read (и все оповещения об этом успешно дошли).
Ок. А может мне кто на пальцах объяснить что такое Connection reset и как на это принято реагировать в TCP?
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416150
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Андрей Панфилов
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
while (true) {
    while (clientInput.available() > 0) {
        считать/послать
    }
    while (serverInput.available() > 0) {
        считать/послать
    }
    if (client.isClosed() || server.isClosed()) {
        break;
    }
}



Блин. Рано радовался. Сессия обмена данных протекла успешно. Это супер. Но! Вылез косяк. После сессии оба сокета открыты. Соединение активно. Я не могу выйти из этого цикла! Ведь я не делаю блокирующего read(), который бы мог вернуть -1 или выкинуть исключение.
Так что IO вариант, похоже, в моём случае окончательно сломан.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416155
Андрей Панфилов
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Blazkowicz,

тут беда с isClosed() ровно как и с вашим inSocket.isInputShutdown() - чтобы получить на них true, нужно закрытие сокета инициировать в JVM. По факту жавская реализация сокета предполагает, что чтобы убедиться что сокет закрыт, нужно либо в него что-то записать (тут мы протокол ломаем), либо прочитать (тут зависнем на таймауте), как вариант можно вместо isClosed() ловить эксепшн на sendUrgentData()
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416158
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Андрей Панфиловлибо прочитать (тут зависнем на таймауте)
Или отхватим исключение ещё до окончания записи.

Андрей Панфиловкак вариант можно вместо isClosed() ловить эксепшн на sendUrgentData()
Не вижу разницы с write.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416159
DoSOfRedRiver
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Blazkowicz,

Сразу оговорюсь, что могу в чём-то ошибаться. Ключевые моменты лучше проверить.

Итак,

WTF#1
Действительно, available возвращает количество байт, доступных для чтения. То-есть, если данные на вашу машину ещё не пришли, либо пришли не полностью, то available() вернёт 0.

WTF#2
А какой, собсна, read() вы используете? У меня всё нормально работало, для read() . Советую вам использовать более удобное и эффективное чтение в массив read(byte [] arg), которое возвращает количество считанных байт.
И да,
авториспользовать available для хоть какой-то эмуляции неблокируемости тоже не выйдет

WTF#3
По поводу Connetion reset вот что пишут. Сдаётся мне, проблемы конкретно у вас.

WTF#4
Думается, вытекает из третьего. Всё должно нормально работать.

WTF#5
Избегайте логгирования в местах, где ошибки предсказуемы и некритичны. Можете глянуть на то, как обрабатываются ошибки в Netty, там даже лисенеры специальные есть.

По поводу NIO:
Селекторы достаточно эффективная и удобная вещь, почему вы их избегаете? Вообще, классический IO давно устарел, он неудобен и непрактичен, потому писать лучше сразу с использованием NIO\NIO.2

Приведу здесь пример. Как по мне, в нём отражена вся суть работы IO, кроме, разве что, многопоточного взаимодействия. Пример не мой, кстати.

Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
250.
251.
252.
253.
254.
255.
256.
257.
258.
259.
260.
261.
262.
263.
264.
265.
import java.io.*;
import java.net.*;
import java.text.DateFormat;
import java.util.Date;
import java.util.TimeZone;

class SimpleWEBServer extends Thread
{
    Socket s;

    public static void main(String args[])
    {
        try
        {
            // привинтить сокет на локалхост, порт 80
            ServerSocket server = new ServerSocket(80, 0,
                    InetAddress.getByName("localhost"));

            System.out.println("server is started");

            // слушаем порт
            while(true)
            {
                // ждём нового подключения, после чего запускаем обработку клиента
                // в новый вычислительный поток
                new SimpleWEBServer(server.accept());
            }
        }
        catch(Exception e)
        {System.out.println("init error: "+e);} // вывод исключений
    }

    public SimpleWEBServer(Socket s)
    {
        this.s = s;

        // и запускаем новый вычислительный поток (см. ф-ю run())
        setDaemon(true);
        setPriority(NORM_PRIORITY);
        start();
    }

    public void run()
    {
        try
        {
            // из сокета клиента берём поток входящих данных
            InputStream is = s.getInputStream();
            // и оттуда же - поток данных от сервера к клиенту
            OutputStream os = s.getOutputStream();

            // буффер данных в 64 килобайта
            byte buf[] = new byte[64*1024];
            // читаем 64кб от клиента, результат - кол-во реально принятых данных
            int r = is.read(buf);

            // создаём строку, содержащую полученую от клиента информацию
            String request = new String(buf, 0, r);

            // получаем путь до документа (см. ниже ф-ю "getPath")
            String path = getPath(request);

            // если из запроса не удалось выделить путь, то 
            // возвращаем "400 Bad Request"
            if(path == null)
            {
                // первая строка ответа
                String response = "HTTP/1.1 400 Bad Request\n";

                // дата в GMT
                DateFormat df = DateFormat.getTimeInstance();
                df.setTimeZone(TimeZone.getTimeZone("GMT"));
                response = response + "Date: " + df.format(new Date()) + "\n";

                // остальные заголовки
                response = response
                + "Connection: close\n"
                + "Server: SimpleWEBServer\n"
                + "Pragma: no-cache\n\n";

                // выводим данные:
                os.write(response.getBytes());

                // завершаем соединение
                s.close();

                // выход
                return;
            }


            // если файл существует и является директорией,
            // то ищем индексный файл index.html
            File f = new File(path);
            boolean flag = !f.exists();
            if(!flag) if(f.isDirectory())
            {
                if(path.lastIndexOf(""+File.separator) == path.length()-1)
                    path = path + "index.html";
                else
                    path = path + File.separator + "index.html";
                f = new File(path);
                flag = !f.exists();
            }

            // если по указанному пути файл не найден
            // то выводим ошибку "404 Not Found"
            if(flag)
            {
                // первая строка ответа
                String response = "HTTP/1.1 404 Not Found\n";

                // дата в GMT
                DateFormat df = DateFormat.getTimeInstance();
                df.setTimeZone(TimeZone.getTimeZone("GMT"));
                response = response + "Date: " + df.format(new Date()) + "\n";

                // остальные заголовки
                response = response
                + "Content-Type: text/html\n"
                + "Connection: close\n"
                + "Server: SimpleWEBServer\n"
                + "Pragma: no-cache\n\n";

                // и гневное сообщение
                response = response + "File " + path + " not found!";

                // выводим данные:
                os.write(response.getBytes());

                // завершаем соединение
                s.close();

                // выход
                return;
            }

            // определяем MIME файла по расширению
            // MIME по умолчанию - "text/plain"
            String mime = "text/plain";
            
            // выделяем у файла расширение (по точке)
            r = path.lastIndexOf(".");
            if(r > 0)
            {
                String ext = path.substring(r);
                if(ext.equalsIgnoreCase("html"))
                    mime = "text/html";
                else if(ext.equalsIgnoreCase("htm"))
                    mime = "text/html";
                else if(ext.equalsIgnoreCase("gif"))
                    mime = "image/gif";
                else if(ext.equalsIgnoreCase("jpg"))
                    mime = "image/jpeg";
                else if(ext.equalsIgnoreCase("jpeg"))
                    mime = "image/jpeg";
                else if(ext.equalsIgnoreCase("bmp"))
                    mime = "image/x-xbitmap";
            }

            // создаём ответ

            // первая строка ответа
            String response = "HTTP/1.1 200 OK\n";

            // дата создания в GMT
            DateFormat df = DateFormat.getTimeInstance();
            df.setTimeZone(TimeZone.getTimeZone("GMT"));

            // время последней модификации файла в GMT
            response = response + "Last-Modified: " + df.format(new Date(f.lastModified())) + "\n";

            // длина файла
            response = response + "Content-Length: " + f.length() + "\n";

            // строка с MIME кодировкой
            response = response + "Content-Type: " + mime + "\n";

            // остальные заголовки
            response = response
            + "Connection: close\n"
            + "Server: SimpleWEBServer\n\n";

            // выводим заголовок:
            os.write(response.getBytes());

            // и сам файл:
            FileInputStream fis = new FileInputStream(path);
            r = 1;
            while(r > 0)
            {
                r = fis.read(buf);
                if(r > 0) os.write(buf, 0, r);
            }
            fis.close();

            // завершаем соединение
            s.close();
        }
        catch(Exception e)
        {e.printStackTrace();} // вывод исключений
    }


    // "вырезает" из HTTP заголовка URI ресурса и конвертирует его в filepath
    // URI берётся только для GET и POST запросов, иначе возвращается null
    protected String getPath(String header)
    {
        // ищем URI, указанный в HTTP запросе
        // URI ищется только для методов POST и GET, иначе возвращается null
        String URI = extract(header, "GET ", " "), path;
        if(URI == null) URI = extract(header, "POST ", " ");
        if(URI == null) return null;

        // если URI записан вместе с именем протокола
        // то удаляем протокол и имя хоста
        path = URI.toLowerCase();
        if(path.indexOf("http://", 0) == 0)
        {
            URI = URI.substring(7);
            URI = URI.substring(URI.indexOf("/", 0));
        }
        else if(path.indexOf("/", 0) == 0)
            URI = URI.substring(1); // если URI начинается с символа /, удаляем его

        // отсекаем из URI часть запроса, идущего после символов ? и #
        int i = URI.indexOf("?");
        if(i > 0) URI = URI.substring(0, i);
        i = URI.indexOf("#");
        if(i > 0) URI = URI.substring(0, i);

        // конвертируем URI в путь до документов
        // предполагается, что документы лежат там же, где и сервер
        // иначе ниже нужно переопределить path
        path = "." + File.separator;
        char a;
        for(i = 0; i < URI.length(); i++)
        {
            a = URI.charAt(i);
            if(a == '/')
                path = path + File.separator;
            else
                path = path + a;
        }

        return path;
    }


    // "вырезает" из строки str часть, находящуюся между строками start и end
    // если строки end нет, то берётся строка после start
    // если кусок не найден, возвращается null
    // для поиска берётся строка до "\n\n" или "\r\n\r\n", если таковые присутствуют
    protected String extract(String str, String start, String end)
    {
        int s = str.indexOf("\n\n", 0), e;
        if(s < 0) s = str.indexOf("\r\n\r\n", 0);
        if(s > 0) str = str.substring(0, s);
        s = str.indexOf(start, 0)+start.length();
        if(s < start.length()) return null;
        e = str.indexOf(end, s);
        if(e < 0) e = str.length();
        return (str.substring(s, e)).trim();
    }
}

...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416164
Андрей Панфилов
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Blazkowicz,

sendUrgentData данные не передает а шлет флаг, правда минут что поведение зависит от ОС.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416165
DoSOfRedRiver
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
DoSOfRedRiver,

Алсо для многопоточной обработки внешних соединений рекомендую асинхронное неблокироющее IO с NIO.2 и шаблон Proactor. Ну это если вам надо высокоэффективное приложение, по типу вэб-сервер.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416170
Андрей Панфилов
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Андрей Панфилов,

по факту сишная реализация сокета все что нужно для реализации хотелок у себя имеет: http://stefan.buettcher.org/cs/conn_closed.html
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416171
Фотография schwa
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Blazkowiczschwa-1 вернется только в случае, если другая сторона закрыла соединение пока мы висели на read (и все оповещения об этом успешно дошли).
Ок. А может мне кто на пальцах объяснить что такое Connection reset и как на это принято реагировать в TCP?
Другая сторона нам отправила RST в ответ на нашу операцию записи. Т.е. посчитали что наше соединение не существует - оно могло быть закрыто несколько секунд назад, процесс могли просто убить или уже вообще узел за это время успел рибутнуться и он вообще ничего не знает об этом соединении.
А что в этом случае делать это уже зависит от приложения - либо открывать новое соединение и как-то продолжить работу, либо игра окончена.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416176
Фотография schwa
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Blazkowicz,

Не очень понятно для чего вызываются проверки isInput[Output]Shutdown() в примере?
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416179
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
DoSOfRedRiverСразу оговорюсь, что могу в чём-то ошибаться. Ключевые моменты лучше проверить.

В любом случае спасибо за коментарий.

DoSOfRedRiverДействительно, available возвращает количество байт, доступных для чтения. То-есть, если данные на вашу машину ещё не пришли, либо пришли не полностью, то available() вернёт 0.

Не понимаю зачем повторять то что я написал. Я это просто к тому что такое поведение нарушает контракт описаный в JavaDoc.
Без каких либо видимых причин.


DoSOfRedRiverА какой, собсна, read() вы используете?

Из кода что я привел выше, это очевидно.

DoSOfRedRiverУ меня всё нормально работало, для read() .

У меня реализация должна быть независима от протокола. С протолом HTTP всё нормально работает. С другим кастомным - нет.

DoSOfRedRiverСоветую вам использовать более удобное и эффективное чтение в массив read(byte [] arg), которое возвращает количество считанных байт.

Ваш КО.

DoSOfRedRiverИ да,
авториспользовать available для хоть какой-то эмуляции неблокируемости тоже не выйдет

Вышло по примеру Андрея Панфилова. Но осталась проблема с идентификацией завершения сессии.

DoSOfRedRiverПо поводу Connetion reset вот что пишут. Сдаётся мне, проблемы конкретно у вас.

Полезный коментарий.

DoSOfRedRiverДумается, вытекает из третьего. Всё должно нормально работать.

Мало ли что кому должно. Я описываю что конкретно и как работает на примере произвольного протокола поверх TCP. На HTTP работает. Там пакеты достаточно четко разделены.

DoSOfRedRiverИзбегайте логгирования в местах, где ошибки предсказуемы и некритичны.
Это из чего следует?

DoSOfRedRiverМожете глянуть на то, как обрабатываются ошибки в Netty, там даже лисенеры специальные есть.

Netty я потом посмотрю. Пока только хардкор. Только велосипеды.

DoSOfRedRiverСелекторы достаточно эффективная и удобная вещь, почему вы их избегаете?

Потому что я хочу получить подтверждение или опровержение того на сколько блокирующие сокеты в Java "сломаны".

DoSOfRedRiverВообще, классический IO давно устарел, он неудобен и непрактичен, потому писать лучше сразу с использованием NIO\NIO.2

В теории я знаю очень много и про IO и про NIO и про множество других страшных слов в Java мире. На практике всплывают нюансы. В NIO.2 что хорошего появилось для сокетов чтобы реализовать желаемый TCP прокси?

DoSOfRedRiverПриведу здесь пример. Как по мне, в нём отражена вся суть работы IO, кроме, разве что, многопоточного взаимодействия.

Хрен там что отражено из моих вопросов выше. На конкретном протоколе у меня проблем нет. У меня проблемы на абстракции, которая должна работать на любых протоколах.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416180
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
schwaДругая сторона нам отправила RST в ответ на нашу операцию записи.
У меня на чтение. Это и оказалось большим сюрпризом.

schwaТ.е. посчитали что наше соединение не существует - оно могло быть закрыто несколько секунд назад, процесс могли просто убить или уже вообще узел за это время успел рибутнуться и он вообще ничего не знает об этом соединении.

Процесс открыт. Сокет открыт. Никто не бутается - все на месте.

schwaА что в этом случае делать это уже зависит от приложения - либо открывать новое соединение и как-то продолжить работу, либо игра окончена.
Это уже будет привязка к протоколу.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416182
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
schwaНе очень понятно для чего вызываются проверки isInput[Output]Shutdown() в примере?
Та, просто осталось. Я там вообще все флаги блокирующего сокета перебрал. Ни один из них не меняет состояния ни до, ни после Connection Reset исключения.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416183
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Андрей ПанфиловsendUrgentData данные не передает а шлет флаг, правда минут что поведение зависит от ОС.
Спасибо. Попробую.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416184
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Переписал NIO на 1 поток вместо 2х, по аналогии с этим кодом 14920507 .
Всё нахрен сломалось, теперь и NIO вариант без селектора не работает :(
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416201
cdtyjv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
DoSOfRedRiverПо поводу NIO:
Селекторы достаточно эффективная и удобная вещь, почему вы их избегаете? Вообще, классический IO давно устарел, он неудобен и непрактичен, потому писать лучше сразу с использованием NIO\NIO.2Я бы не был так категоричен. Ничего не устарело, и не устареет. Это просто два разных подхода, которые в определенных местах пересекаются, а в определенных местах нет, тем самым дополняя друг друга.
NIO с селекторами удобен, если нужен неблокирующий режим (как у автора), или если нужно обслуживать очень много соединений, когда подход "одно соединение - один поток" не справляется. Платой за использование NIO является бОльшая сложность его использования. Хотя в случае автора, когда нужно прост опрокидывать данные из одного сокета в другой, эта "добавленная сложность" будет не сильно выше, ибо не надо запариваться с размерами сообщений и выискивать их границы.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416221
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Получилось на блокирующем NIO (SocketChannel.configureBlocking(true))
вот в таком виде.

ВНИМАНИЕ. КОД НЕ РЕКОМЕДУЕТСЯ К ИСПОЛЬЗОВАНИЮ.

Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
class PumpTask implements Callable<Void> {
    SocketChannel inSocket;
    SocketChannel outSocket;

    public PumpTask(SocketChannel inSocket,
                    SocketChannel outSocket) {
        this.inSocket = inSocket;
        this.outSocket = outSocket;
    }

    @Override
    public Void call() throws IOException {
        ByteBuffer buff = ByteBuffer.allocate(inSocket.socket().getReceiveBufferSize());

        try {
            while (inSocket.isConnected() && outSocket.isConnected()) {
                if (!inSocket.isOpen() || !outSocket.isOpen()) Thread.sleep(1);
                pump(buff, inSocket, outSocket);
                pump(buff, outSocket, inSocket);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            inSocket.close();
            outSocket.close();
        }
        return null;
    }

    private int pump(ByteBuffer buff, SocketChannel in, SocketChannel out) throws IOException, InterruptedException {
        int read = 0;
        buff.clear();
        if ((read = in.read(buff)) > 0) {
            buff.flip();
            while (buff.hasRemaining()) {
                out.write(buff);
            }
        }
        return read;
    }
}



Дополнительные Thread.sleep() не нужны. Так как чтение блокирующее лишних циклов не возникает. Правда меня это теперь немного озадачивает. Ведь pump в одну сторону может заблокироваться и следующий уже не будет вызван. Почему вообще работает не понятно. Может один из сокетов таки сделать неблокирующим на всякий случай?
В любом случае потом перепишу на selector.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416224
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
И ещё один косяк, что выход из цикла осуществляется по исключению. Этого забороть не удалось.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416238
DoSOfRedRiver
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
cdtyjv,

Я даже не могу придумать ни одного примера, в котором использование классического IO дало бы мне преимущество перед NIO.

авторНичего не устарело, и не устареет.
Точно так же можно сравнивать AWT и JavaFX. Я не хочу сказать, что эти технологии мертвы, и не нужны никому. Просто придумали уже вещи удобней и красивей.

Blazkowicz,

авторВышло по примеру Андрея Панфилова.
Дак в примере выходит по соединению на поток, не? Тогда вообще не понимаю, какие могут быть проблемы.

авторНо осталась проблема с идентификацией завершения сессии.
По-моему самым эффективным способ будет посылать heartbeat, который проверяет жив ли клиент. Да и других вариантов не могу найти, потому как клиент может просто шутдаунуться.

авторПотому что я хочу получить подтверждение или опровержение того на сколько блокирующие сокеты в Java "сломаны".
Сломаны? Как они могут быть "сломаны"? Вроде куча народу юзает блокинг-ио, никто не жаловался особо.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416302
cdtyjv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
DoSOfRedRiverДак в примере выходит по соединению на поток, не? Тогда вообще не понимаю, какие могут быть проблемы.Два блокирующих сокета на поток. Потому у автора и проблемы.

DoSOfRedRiverПо-моему самым эффективным способ будет посылать heartbeat, который проверяет жив ли клиент. Да и других вариантов не могу найти, потому как клиент может просто шутдаунуться.Так и есть. Состояние, когда клиент подох, а сервер этого не увидел (или наоборот) - называется half-open socket. И хартбиты позволяют успешно детектировать эту ситуацию. Но автору нужно сделать прокси, вклиниться между клиентом и сервером, и просто форвардить данные из одной стороны в другую. Так что хартбиты тут не помогут.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416399
maxkar
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Blazkowiczметод InputStream.available() бесполезен и даже сломан для сокетов.
Он всегда был бесполезен для клиентского кода. Из разумных его применений я вижу только его использование в различных Buffered* обертках. Там обертка может увидеть, что данных много, и реалоцировать буфер.

BlazkowiczЧитаем JavaDoc
Returns:an estimate of the number of bytes that can be read (or skipped over) from this input stream without blocking or 0 when it reaches the end of the input stream.

На сокетах available будет изначально возвращать 0, если узел с другой стороны ничего не отправляет. Но это ещё не значит что поток завершен.

Вы javadoc неправильно понимаете. Javadoc и не обещает, что 0 - это только конец потока. У вас возвращается "number of bytes that can be read without blocking". Ну нет там байтов, чтобы читать. И ноль - вполне корректное значение для первой половины (даже когда конец потока еще не достигнут). Причем "0 в случае конце файла" - это всего лишь уточнение. Оно вполне следует из первой половины контракта. По той же самой причине - "из потока можно прочитать не более 0 байт без блокирования". Разница с read есть, но для данного места оно вполне оправдана. Файловые потоки тоже могут возвращать 0 раньше достижения конца файла.

BlazkowiczWTF#2 Дальше хуже. read(), который должен вернуть -1 по окончании чтения тоже никогда -1 не возвращает. В общем случае никакого начала и конца у InputStream для сокетов нет. А использовать available для хоть какой-то эмуляции неблокируемости тоже не выйдет.

Начало есть. Если сделать Socket.shutdownOutput на стороне клиента, то у сервера и конец потока будет. А если не делать, то и конца не будет. Что логично - по одному соединению можно обмениваться многими соединениями. И даже тот же браузер может не закрывать сокет (поэтому -1 и не будет), а использовать его для послдеюущих запросов.

BlazkowiczНачал тестировать на боевом приложении, WTF#3
Клиент отправляет пакет данных. Он прочитан, перенаправлен на другой сервер. После этого мой серверный сокет снова пытается сделать read(). В теории он должен либо заблокироваться, либо вернуть -1.
На практике чтение выкидывает исключение Connection reset! Но это пол беды. Помимо этого клиентская сторона закрывает свой сокет и на запись. Т.е. отклик сервера записать проигнорировав reset уже нельзя.

Код смотреть надо. Вы там никакой из сокетных потоков или оберток над ними вручную не закрываете? Закрытие любого из них будет приводить к закрытию сокета. Или на сервере вместо shutdown сокету делается close. Для надежной доставки данных нельзя "просто закрыть потоки и сокет". Сначала нужно сделать shutdown (один или два, в зависимости от того, что делаете). И только потом закрывать сокет. В случае исключений shutdown делать уже не имеет смысла.

BlazkowiczWTF#4 - не смотря на connection reset и невозможность дальнейшей работы с клиентом со стороны сервера, никакие флаги у сокета не меняются. Всякие connected, closed и пр. находятся в том же состоянии что и в начале работы. Т.е. спросит клиента с сервера живо ли тот ещё никакой возможности не видно.

Это плохая документация. "isConnected" вооще обозначает, что "соединение хоть когда-то было установлено (смотрите документацию)", а не "сейчас соединение установлено". При использовании большей части конструкторов isConnected всегда будет true. С isClosed ситуация чем-то похожая. Этот флаг всего лишь обозначает, что "сокет был закрыт вызовом метода close или закрытием одного из сокетных потоков". Он не обозначает, что клиент на другой стороне закрыл соединение и т.п. Да, завершение соединения с другой стороны/ошибки/сброс соединения сокет не закрывают. Его все равно нужно закрыть вручную.

BlazkowiczWTF#5 - я вообще смотрю на разные проекты и вижу что постоянные исключения в TCP на Java это вообще штатные ситуации. Имеет ли смысл избегать логирования stacktrace для повышения производительности? Ведь если разворачивать stacktrace на каждый пук при многочисленых соединениях и отсоединениях клиентов, то провал в производительности обеспечен.

Да, достаточно штатная. Логировать ли - зависит от приложения и нагрузки. Во многих случаях протокол имеет явные начало/конец (с соответствующими shutdown и т.п.), поэтому исключения при допустимой нагрузке логировать стоит (мало ли что там). А вот если вы какой-нибудь streaming server пишете, где клиент в какой-то момент просто исчезает (приложение закрыли), там можно и не логировать ничего.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416401
Фотография schwa
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
BlazkowiczschwaДругая сторона нам отправила RST в ответ на нашу операцию записи.
У меня на чтение. Это и оказалось большим сюрпризом.

schwaТ.е. посчитали что наше соединение не существует - оно могло быть закрыто несколько секунд назад, процесс могли просто убить или уже вообще узел за это время успел рибутнуться и он вообще ничего не знает об этом соединении.

Процесс открыт. Сокет открыт. Никто не бутается - все на месте.

Операция записи завершается, когда переданные данные были скопированы в буфер отправки. На следующей операции записи/чтения будет получен connection reset, если за это время произошел обрыв соединения и эта сторона соединения об этом узнала.
Без закрытия соединения на другой стороне RST получить никак нельзя.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416445
maxkar
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
В спойлере пример. Проверял на www.apache.org порт 80. Никаких исключений. Никаких проблем с сокетами. Радостно общается с браузером потоков в 10 и при этом с HTTP keep-alive (видно по логам и сообщениям о закрытии сокетов). Соединения могут закрываться и при неактивности (т.е. конец файла определяется с обеих сторон), тоже видно по сообщениям.

Если делать совсем правильно, читалку и писалку тоже нужно разделить на два потока (на три потока, в примере две записи). Читалка складывает данные в буфер. Если данных в буфере слишком много, закрывает сокет со второй стороны и свой сокет (генерируя попутно кучу исключений в духе SocketClosedException, ConnectionReset и т.п.).


Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
import java.io.*;
import java.net.*;

public class Proxy {

  private static class Copier implements Runnable {
    final OutputStream logStream;
    final OutputStream copyStream;
    final InputStream is;
    final Socket osock;

    Copier(OutputStream logStream, OutputStream copyStream, 
        InputStream is, Socket osock) {
      this.logStream = logStream;
      this.copyStream = copyStream;
      this.is = is;
      this.osock = osock;
    }

    @Override
    public void run() {
      final byte[] buffer = new byte[65536];
      try {
        try {
          while (true) {
            final int readed = is.read(buffer);
            if (readed < 0) {
              osock.shutdownOutput();
              return;
            }

            logStream.write(buffer, 0, readed);
            copyStream.write(buffer, 0, readed);
          }
        } finally {
          logStream.close();
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  private static class Killer implements Runnable {
    final Thread t1;
    final Thread t2;
    final Socket s1;
    final Socket s2;

    Killer(Thread t1, Thread t2, Socket s1, Socket s2) {
      this.t1 = t1;
      this.t2 = t2;
      this.s1 = s1;
      this.s2 = s2;
    }

    @Override
    public void run() {
      try {
        try {
          t1.join();
          t2.join();
          System.out.println("Killing the sockets!");
        } finally {
          try {
            s1.close();
          } finally {
            s2.close();
          }
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  public static void main(String[] args) throws Exception {
    final ServerSocket ss = new ServerSocket(8888);
    int log = 0;
    while (true) {
      final Socket cconn =  ss.accept();
      final Socket sconn = new Socket(args[0], Integer.parseInt(args[1]));
      
      final Thread t1 = new Thread(new Copier(new FileOutputStream("c2s" + log), 
            sconn.getOutputStream(), cconn.getInputStream(), sconn));
      final Thread t2 = new Thread(new Copier(new FileOutputStream("s2c" + log), 
            cconn.getOutputStream(), sconn.getInputStream(), cconn));
      t1.start();
      t2.start();
      new Thread(new Killer(t1, t2, cconn, sconn)).start();
      log += 1;
    }
  }
}


...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416487
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
DoSOfRedRiverЯ даже не могу придумать ни одного примера, в котором использование классического IO дало бы мне преимущество перед NIO.

Google -> IO faster than NIO.


DoSOfRedRiverТочно так же можно сравнивать AWT и JavaFX. Я не хочу сказать, что эти технологии мертвы, и не нужны никому. Просто придумали уже вещи удобней и красивей.
Аналогия не уместна.

DoSOfRedRiverДак в примере выходит по соединению на поток, не? Тогда вообще не понимаю, какие могут быть проблемы.
Проблемы я объяснил в первом посте и по вашей просьбе привел текст кода, который эти проблемы вскрывает.

DoSOfRedRiverПо-моему самым эффективным способ будет посылать heartbeat, который проверяет жив ли клиент. Да и других вариантов не могу найти, потому как клиент может просто шутдаунуться.
Конкретику, пожалуйста. sendUrgentData() или какой ещё heartbeat?

DoSOfRedRiverСломаны? Как они могут быть "сломаны"? Вроде куча народу юзает блокинг-ио, никто не жаловался особо.
IO работает когда протокол заранее оговорен. У меня нет протокола. Мне нужно прокачивать любые данные любых протоколов поверх TCP. Конечно, если протокол защищен он атаки man-in-the-middle, то работать никакой вариант не будет.
Но на IO у меня не получается даже обычный протокол прокачать без защиты. Почему? - описал в первом посте и привел код.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416490
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
cdtyjvДва блокирующих сокета на поток. Потому у автора и проблемы.
У меня и по сокету на поток проблемы и два сокета на поток тоже проблемы.

cdtyjvТак и есть. Состояние, когда клиент подох, а сервер этого не увидел (или наоборот) - называется half-open socket. И хартбиты позволяют успешно детектировать эту ситуацию. Но автору нужно сделать прокси, вклиниться между клиентом и сервером, и просто форвардить данные из одной стороны в другую. Так что хартбиты тут не помогут.
А на sendUrgentData()?
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416500
cdtyjv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
BlazkowiczУ меня и по сокету на поток проблемы и два сокета на поток тоже проблемы.А вы код "сокет-на-поток" выкладывали? А то я уже запутался, где что.

BlazkowiczА на sendUrgentData()?В общем случае sendUrgentData() не работает. Что бы спокойно ее использовать, необходимо быть уверенным, что принимающая сторона знает, как с этой самой urgent data быть. В противном случае вы по сути будете отсылать на другую стороны какие-то левые байты, который запорят протокол, и приведут к непредсказуемым последствиям.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416501
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
maxkarВы javadoc неправильно понимаете. Javadoc и не обещает, что 0 - это только конец потока. У вас возвращается "number of bytes that can be read without blocking". Ну нет там байтов, чтобы читать. И ноль - вполне корректное значение для первой половины (даже когда конец потока еще не достигнут). Причем "0 в случае конце файла" - это всего лишь уточнение. Оно вполне следует из первой половины контракта. По той же самой причине - "из потока можно прочитать не более 0 байт без блокирования". Разница с read есть, но для данного места оно вполне оправдана. Файловые потоки тоже могут возвращать 0 раньше достижения конца файла.
Спасибо! Теперь понял основную ошибку.

maxkarНачало есть. Если сделать Socket.shutdownOutput на стороне клиента, то у сервера и конец потока будет. А если не делать, то и конца не будет. Что логично - по одному соединению можно обмениваться многими соединениями. И даже тот же браузер может не закрывать сокет (поэтому -1 и не будет), а использовать его для послдеюущих запросов.
У меня 3rd party клиент. Он шлет reset вместо всего остального.

maxkarКод смотреть надо.

14920906

maxkarВы там никакой из сокетных потоков или оберток над ними вручную не закрываете? Закрытие любого из них будет приводить к закрытию сокета. Или на сервере вместо shutdown сокету делается close. Для надежной доставки данных нельзя "просто закрыть потоки и сокет". Сначала нужно сделать shutdown (один или два, в зависимости от того, что делаете). И только потом закрывать сокет. В случае исключений shutdown делать уже не имеет смысла.
Проблема ни в shutdown/close. Проблема в том что когда сервер делает read с клиентского сокета, вылетает Connection Reset и предотвратить это можно только через available().


maxkarЭто плохая документация. "isConnected" вооще обозначает, что "соединение хоть когда-то было установлено (смотрите документацию)", а не "сейчас соединение установлено". При использовании большей части конструкторов isConnected всегда будет true. С isClosed ситуация чем-то похожая. Этот флаг всего лишь обозначает, что "сокет был закрыт вызовом метода close или закрытием одного из сокетных потоков". Он не обозначает, что клиент на другой стороне закрыл соединение и т.п. Да, завершение соединения с другой стороны/ошибки/сброс соединения сокет не закрывают. Его все равно нужно закрыть вручную.

Клиент послал Reset и у клиентского сокета на сервере никакие флаги вообще не поменялись. Это и смущает.

maxkarДа, достаточно штатная. Логировать ли - зависит от приложения и нагрузки. Во многих случаях протокол имеет явные начало/конец (с соответствующими shutdown и т.п.), поэтому исключения при допустимой нагрузке логировать стоит (мало ли что там). А вот если вы какой-нибудь streaming server пишете, где клиент в какой-то момент просто исчезает (приложение закрыли), там можно и не логировать ничего.
Мне нужно решение не привязаное к конкретным протоколам. Если я использую available(), то я не могу выйти из цикла. И клиентский и серверный сокет живут себе даже после окончания сессии обмена данными. Если я не использую available(), то я выхватываю исключение на методе read() и дальнейшая работа с этим сокетом не возможна. Может надо было ещё и со стороны сервера reset() вызвать чтобы обновить сокет? Не очевидно как-то.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416507
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
schwaОперация записи завершается, когда переданные данные были скопированы в буфер отправки. На следующей операции записи/чтения будет получен connection reset, если за это время произошел обрыв соединения и эта сторона соединения об этом узнала.
Вот такой вот хитрый протокол попался. Если делать read больше нужного - отгребаешь RST. Если предотвратить RST через available(), то не происходит выхода из цикла.
Ещё удивительно то что абсолютно аналогичный код на NIO API отрабатыват. Там нет available, но там и лишний read не приводит к RST.

schwaБез закрытия соединения на другой стороне RST получить никак нельзя.
Спасибо. Не знал. А почему бы просто не закрыть клиенту соединение тогда? Или это такой способ для одной стороны сообщить другой, что она планово закрылась? Помогите понять суть RST. Ткните носом в нужный мануал, что ли.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416515
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
maxkarВ спойлере пример. Проверял на www.apache.org порт 80. Никаких исключений. Никаких проблем с сокетами. Радостно общается с браузером потоков в 10 и при этом с HTTP keep-alive (видно по логам и сообщениям о закрытии сокетов). Соединения могут закрываться и при неактивности (т.е. конец файла определяется с обеих сторон), тоже видно по сообщениям.

На HTTP у меня тоже всё работало без проблем. На другом TCP протоколе метод read из вашего примера выкинет Exception до окончания сессии обмена данными. При этом сокет ещё и закрывается, так что в него нельзя записать отклик.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416522
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
cdtyjvА вы код "сокет-на-поток" выкладывали? А то я уже запутался, где что.
Вот мой первоначальный код.
14920906
Запускается два потока. Один работает с клиентским сокетом. Второй с серверным.
(третий сидит на ServerSocket.accept(), но это не важно)


cdtyjvА на sendUrgentData()?В общем случае sendUrgentData() не работает. Что бы спокойно ее использовать, необходимо быть уверенным, что принимающая сторона знает, как с этой самой urgent data быть. В противном случае вы по сути будете отсылать на другую стороны какие-то левые байты, который запорят протокол, и приведут к непредсказуемым последствиям.[/quot]
Понял. Спасибо.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416531
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Всем большое спасибо за обсуждение. Возник ещё вопрос по NIO.
Вот в этом примере 14921347 , по логике один из двух pump может заблокироваться, так как данные в эту сторону уже не идут.
И второй pump не вызовется. Ведь поток заблокирован.
Но на практике этого не происходит. Кто-нибудь может объяснить почему? Или мне просто повезло с протоколом? Вечером залогирую более детально. Но методы вызываются строго по очереди. Возможно в каких-то случаях они качают 0 байт, но при этом лишний вызов на TCP не происходит (смотрел снифером)
Возможно я не понимаю Blocking NIO.
Надо, наверное, ещё свои собственные клиент-сервер написать для теста хитрых протоколов.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416603
zalexaka
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Blazkowicz…
Вот в этом примере 14921347 , по логике один из двух pump может заблокироваться, так как данные в эту сторону уже не идут.
И второй pump не вызовется. Ведь поток заблокирован.
Но на практике этого не происходит.

И в catch не попадает при этом?
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416609
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
zalexakaBlazkowicz…
Вот в этом примере 14921347 , по логике один из двух pump может заблокироваться, так как данные в эту сторону уже не идут.
И второй pump не вызовется. Ведь поток заблокирован.
Но на практике этого не происходит.

И в catch не попадает при этом?
Не понял вопроса. Вышеприведенный код отлично работает. Выкидывает исключения, когда комуникация завершена и это нормально.

Вопрос в том, что если клиент шлет один пакет. Сервер шлет второй и третий. Но, после 2го пакета, метод должен блокироваться на чтении из клиента. Но клиент ничего не шлет. Ждет третьего пакета с сервера. А третий пакет не передаётся, потому что поток заблокирован. Почему этого не происходит?
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416626
zalexaka
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Blazkowicz,
Упс, тупанул вопрос действительно не в тему.
BlazkowiczПочему этого не происходит? Возможно pump прокачивает пакеты по одному, других объяснений не нахожу.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38416689
Фотография schwa
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
BlazkowiczЕщё удивительно то что абсолютно аналогичный код на NIO API отрабатыват. Там нет available, но там и лишний read не приводит к RST.
В исходниках SocketInput/OuputStream очень много проверок на isReset[Pending]. Видимо в nio проверки иначе. Но все равно без закрытия на другой стороне RST просто так возникнуть все же не может.
BlazkowiczСпасибо. Не знал. А почему бы просто не закрыть клиенту соединение тогда? Или это такой способ для одной стороны сообщить другой, что она планово закрылась? Помогите понять суть RST. Ткните носом в нужный мануал, что ли.
Если нужно закрывать только убедившись, что другая сторона прочитала данные, то можно сделать следующим образом.
На стороне, которая хочет инициировать закрытие.
Код: java
1.
2.
3.
4.
socket.shutdownOutput();//закрытие исходящего потока. также инициирует протокол закрытия этого сокета.
int n = socket.read();//ждем получения подтверждения о том, что соединение было закрыто на той стороне.
assert n == -1;//EOF
socket.close();


А другой cтороне
Код: java
1.
2.
3.
4.
int n = socket.getInputStream().read();
if (n == -1) {//EOF другая сторона закрыла сокет.
  socket.close();
} else  ... // обычный случай когда получили данные.


Или сделать специальное сообщение, которое будет говорить другой стороне, что после его получения надо бы завершить соединение.

А про мануал - Сетевое программирование в Unix Стивенса.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38417273
DoSOfRedRiver
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Blazkowicz,

авторGoogle -> IO faster than NIO.
И когда дело доходить до многопоточности, мы проигрываем в производительности из-за концепции thread-per-connection. Дело случая.

авторКонкретику, пожалуйста. sendUrgentData() или какой ещё heartbeat?
В вашем случае думается только эксепшены ловить.

авторо на IO у меня не получается даже обычный протокол прокачать без защиты.
Попробуйте. Там рядом ещё многопоточная реализация была.

авторВозможно я не понимаю Blocking NIO.
http://stackoverflow.com/questions/17615272/java-selector-is-asynchronous-or-non-blocking-architecture

В качестве мультиплексора выступают селекторы.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38417304
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
DoSOfRedRiverИ когда дело доходить до многопоточности, мы проигрываем в производительности из-за концепции thread-per-connection. Дело случая.
Зависит исключительно от того на сколько эффективно ОС управляет потоками. Переключение контекста уже совсем не дорогая операция в современных системах.

DoSOfRedRiverВ вашем случае думается только эксепшены ловить.

available() не выкидывает исключения. А read() выкидывает его раньше времени. Так где мне его ловить?

DoSOfRedRiver Попробуйте. Там рядом ещё многопоточная реализация была.
Повторяю. Вот этот гениальный код не работает для 3rd party клиента, который конектиться к моему серверу.
Код: java
1.
while((bytes_read = from_server.read(reply)) != -1) 



DoSOfRedRiver http://stackoverflow.com/questions/17615272/java-selector-is-asynchronous-or-non-blocking-architecture
Там ни слова про configureBlocking(true) в NIO.

DoSOfRedRiverВ качестве мультиплексора выступают селекторы.
Холодно.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38417368
Андрей Панфилов
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Blazkowiczavailable() не выкидывает исключения. А read() выкидывает его раньше времени. Так где мне его ловить?
так не выйдет?

Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
    Socket s = new Socket("host", port);
    PushbackInputStream inStream = new PushbackInputStream(s.getInputStream());

    boolean isClosed(PushbackInputStream stream) {
        try {
            byte[] r = new byte[1];
            int result = stream.read(r);
            if (result > -1) {
                if (result > 0) {
                    stream.unread(r);
                }
                return false;
            }
            return true;
        } catch (SocketTimeoutException te) {
            return false;
        } catch (IOException ie) {
            return true;
        }
    }
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38417369
Андрей Панфилов
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
забыл...

Код: java
1.
2.
3.
    Socket s = new Socket("host", port);
    s.setSoTimeout(1);
    PushbackInputStream clientInStream = new PushbackInputStream(s.getInputStream());
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38417383
Basil A. Sidorov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
BlazkowiczПроблема в том что нет какого-то однозначно способа определить состояния и поступить правильно в зависимости от этого в IO. Сокет :
Привязан ?
Закрыт ?
Подключен ?
Клиент больше не пишет ?
Клиент больше не читает ?

Или что?
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38417388
DoSOfRedRiver
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Blazkowicz,

авторТам ни слова про configureBlocking(true) в NIO.
Что? Там картинка даже есть специальная, Blocking I/O называется.

авторЗависит исключительно от того на сколько эффективно ОС управляет потоками. Переключение контекста уже совсем не дорогая операция в современных системах.
Да. Но когда дело доходит до высоко-нагруженных приложений, почему-то предпочитают Node.js, а не Апачи всякие.

авторavailable() не выкидывает исключения. А read() выкидывает его раньше времени. Так где мне его ловить?
А если в код вроде if ((read = in.read(buff)) > 0) трюкача поставить и на эксепшене закрывать соединение конечным адресатом?
Или вы про паузы во время передачи данных?
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38417393
Basil A. Sidorov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
BlazkowiczschwaДругая сторона нам отправила RST в ответ на нашу операцию записи.
У меня на чтение. Это и оказалось большим сюрпризом.Ошибки могут возникать в любой момент.
Если некто на любом участке между нами и ними уже знает, что райком закрыт и с фронта никто не вернулся, то есть всего три вариант:
1. Штатно закрыть подключение, прислав FIN-итный пакет и, возможно, соблюсти ещё какие-то формальности;
2. Тупо молчать, пока IP-стек не известит прикладуху о connection timeout;
3. Сбросить подключение прислав RST.

P.S. Стек сетевых ошибок нахер никому не нужен - он всегда один и тот же.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38417459
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Basil A. SidorovBlazkowiczПроблема в том что нет какого-то однозначно способа определить состояния и поступить правильно в зависимости от этого в IO. Сокет :
Привязан ?
Закрыт ?
Подключен ?
Клиент больше не пишет ?
Клиент больше не читает ?
Или что?
Значения этих свойств одни и тоже на серверном сокете
- До начала чтения пакета с клиента.
- По окончании чтения пакета, до следующего чтения.
- И самое обидное: после исключения connection reset вызваного чтением.
То есть можно было бы поймать исключение, посмотреть свойства, что-то предпринять, а фигушки. Ни одно из этих свойств значения не меняет, когда даже в сокет нельзя ни читать, ни писать.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38417468
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Андрей Панфиловтак не выйдет?

Я не понял чем это отличается от использования available() по поведению. На вскидку - тоже самое. Я также отхвачу исключение, при попытке чтения и тем самым "закрою" сокет на запись (клиент закроет его сам), которую мне ещё предстоит произвести.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38417469
Basil A. Sidorov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Хм-м-м ...
Надо будет поразвлекаться на досуге ...
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38417472
Фотография Blazkowicz
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
DoSOfRedRiverавторТам ни слова про configureBlocking(true) в NIO.
Что? Там картинка даже есть специальная, Blocking I/O называется.

Это картинка для классического IO, а не для Blocking NIO.

DoSOfRedRiverДа. Но когда дело доходит до высоко-нагруженных приложений, почему-то предпочитают Node.js, а не Апачи всякие.

Сотни леммингов не могут ошбаться.

DoSOfRedRiverА если в код вроде if ((read = in.read(buff)) > 0) трюкача поставить и на эксепшене закрывать соединение конечным адресатом?
"трюкача поставить"?

DoSOfRedRiverИли вы про паузы во время передачи данных?
Нет. Я про два примера кода в начале темы. Один - мой. Не работает, потому что read выкидывает исключения и клиент закрывает сокет. Второй Андрея Панфилова - работает нормально, но нет никакого признака, чтобы выйти из цикла.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38417539
Андрей Панфилов
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Blazkowicz,

это не отличается от available, оно в дополнение - пока в буфере данные есть читаем available, если нет висим секунду на таймауте.

Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
public class Test implements Runnable {

    private Socket _i;
    private Socket _o;

    private Test(Socket i, Socket o) {
        _i = i;
        _o = o;
    }

    @Override
    public void run() {
        try {
            pump(_i, _o);
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    static void pump(Socket i, Socket o) throws IOException {
        o.setSoTimeout(1);
        i.setSoTimeout(1);
        PushbackInputStream out = new PushbackInputStream(o.getInputStream());
        PushbackInputStream in = new PushbackInputStream(i.getInputStream());
        while (true) {
            while (in.available() > 0) {
                pump(in, o);
            }
            while (out.available() > 0) {
                pump(out, i);
            }
            if (checkClosed(in, o)) {
                o.close();
                break;
            }
            if (checkClosed(out, i)) {
                i.close();
                break;
            }
        }
    }

    static boolean checkClosed(PushbackInputStream in, Socket o)
        throws IOException {
        boolean closed = false;
        try {
            byte[] r = new byte[1];
            int result = in.read(r);
            if (result > -1) {
                if (result > 0) {
                    in.unread(r);
                }
            } else {
                closed = true;
            }
        } catch (SocketTimeoutException te) {
            // ignore
        } catch (IOException ie) {
            closed = true;
        } finally {
            try {
                if (in.available() > 0) {
                    pump(in, o);
                }
            } finally {
                if (closed) {
                    in.close();
                }
            }
        }
        return closed;
    }

    static void pump(InputStream s, Socket o) throws IOException {
        byte[] r = new byte[s.available()];
        s.read(r);
        o.getOutputStream().write(r);
    }

    public static void main(String[] argv) throws Exception {
        ServerSocket ss = new ServerSocket(22);
        Socket i = null;
        while ((i = ss.accept()) != null) {
            Socket o = new Socket("192.168.2.49", 22);
            new Thread(new Test(i, o)).start();
        }
    }
}
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38417663
cdtyjv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Вот нормальный пример TCP-туннеля на блокирующем IO, один поток на сервер, один поток на клиент:
http://svn.apache.org/viewvc/webservices/tcpmon/trunk/modules/tcpmon-core/src/main/java/org/apache/ws/commons/tcpmon/Relay.java?view=markup
http://svn.apache.org/viewvc/webservices/tcpmon/trunk/modules/tcpmon-core/src/main/java/org/apache/ws/commons/tcpmon/TcpTunnel.java?view=markup

Когда я вижу, что два соединения сидят в одном потоке, как в примере выше, у меня едва ли не кровь из глаз идет, без шуток. Вот простейший пример, когда такой подход просто катастрофически неверен. Пишете вы, значит, такой клиент-серверную игру. Написали, и вот хотите посмотреть на то, какой трафик она генерирует. Подключаете свой "Пушбэк", и начинаете смотреть. Смотрите-смотрите, вроде все в порядке. Но в какой-то момент у вас начинается "война гильдий", и сервера начинает переть дофига траффика. И вы вдруг замечаете, что клиент перестал отсылать траффик на сервер. То есть игрок видит, что происходит, но сам ничего сделать не может. Отключаете свою тулзу - все в порядке. Подключаете опять - снова игрок ничего сделать не может. А в чем проблема? Да в том, что вы никак не можете выйти из цикла, если трафика слишком много:
Код: java
1.
2.
3.
while (out.available() > 0) {
    pump(out, i);
}



А что вы будете делать, если сервер устроен так, что вычитывает из клиента бинарные данные, и сериализует их в какой-нибудь условный ServerObject, но делает это до тех пор, пока не пример, скажем, 3 таких объекта, после чего перестает читать клиента, пока что-то не запишет в него? У вас опять все зависнет, теперь уже не в цикле, а внутри метода pump.

Про busy-loop я уже молчу, ибо уж этот косяк должен быть всем очевиден.

Поэтому я еще раз хочу заакцентировать внимание, что код, подобный этому 14927138 - это демонстрация того, как ни в коем случае нельзя организовывать TCP-туннели, особенно учитывая то, что мы тут стремимся выработать общее решение, которое будет работать для любых клиент-серверных протоколов.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38417717
Андрей Панфилов
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
cdtyjv,

Спасибо, К.О., до вас никто не знал, что двунаправленный канал лучше обрабатывать двумя потоками, осталось только понять как это относится к:

BlazkowiczWTF#1 - метод InputStream.available() бесполезен и даже сломан для сокетов.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38417730
cdtyjv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Андрей Панфилов ,
Да точно так же, как и ваш код :-)
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38417734
maxkar
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
BlazkowiczschwaБез закрытия соединения на другой стороне RST получить никак нельзя.
Спасибо. Не знал. А почему бы просто не закрыть клиенту соединение тогда? Или это такой способ для одной стороны сообщить другой, что она планово закрылась? Помогите понять суть RST. Ткните носом в нужный мануал, что ли.

RST - это как раз не планово а аварийно. Ключевые слова - "tcp termination sequence". Можно еще по SO_LINGER поискать, там завершение TCP-сессии тоже подробно рассматривается (лингеринг - это ручная настройка tcp termination sequence). У вас клиент делает close без shutdown (или shutdown с SO_LINGER в 0), и это большая ошибка. Правильно было бы трясти производителя, чтобы закрытие поправили, а не искать workaround.

На пальцах картина примерно следующая (может быть, ошибаюсь где-то).

RST - это аварийное завершение соединения. Если OS получила RST, значит, на другой стороне сокета (пары от нашего локального) больше нет ни в каком виде. Т.е. удаленная OS о "сокете" ничего не знает. Типичное использование RST - в ответ на пришедший пакет. Вот стоит у вас машина, ничего плохого не делает, а ей вдруг приходит какой-то пакет "на порт 4321 от 8.8.9.9" и якобы "в середине текущего соединения". Вот на такой пакет ваша OS пошлет RST удаленной стороне. Удаленная сторона его получит (если получит) и прекратит посылать сообщения (те же повторные отправки пакетов из-за отсутствия подтвержднеия и т.п.).

Т.е. если мы получили RST, с сокетом уже ничего сделать нельзя. Нет второй стороны уже ни в каком виде (все последующие пакеты только RST в ответ и могут получить). Только открывать новый сокет.

Далее. Закрытие сокета без предварительного shutdown тоже приводит к RST. Т.е. вызов "socket.close() без socket.shutdownOutput()" - это аварийное завершение, при котором, в частности, не нужно гарантировать доставку уже отправленных пакетов (да, это именно так!). Поэтому ОС посылает RST и освобождает ресурсы сразу же. Все дальнейшие входящие пакеты (как данные, так и служебные) по данному сокету получат "RST" (все структуры данных от сокета уже освобождены). Логика в этом поведении есть. Приложение решило, что "клиент уже не тот" и дальше с ним общаться смысла нет. Соответственно, данные "неправильному" клиенту доставлять тоже не нужно, поэтому можно сокет (и все структуры данных) освободить сразу же.

С shutdownOuptut ситуация иная. Во-первых, вызов shutdown блокирующий и ожидает потверждения доставки всех данных от протиовоположной стороны (т.е. если что-то "не дошло", будет исключение). После shutdown и close сокет какое-то время остается в системной таблице в состоянии TIME_WAIT. В этом состоянии ОС отправляет подтверждения второй стороне о данных с нее. Нужно это, чтобы вторая (удаленная) сторона на свой socket.shutdown не получила ошибок. Пусть A - наша машина, (на ней мы сделали shutdown+close). Б - какая-то удаленная машина. Когда-то Б отправляла машине А данные. Машина А получила их, отправила подтвеждение, и это подтверждение потерялось. Т.е. с точки зрения машины А все хорошо. А с точки зрения машины Б - проблемы со связью. Поэтому Б будет какое-то время повторно посылать пакеты (retransmit). Если бы после close сокет удалялся из таблиц, машина A в ответ на retransmit послала бы RST (см. выше, это стандартный ответ если адресат не найден) и сторона Б считала бы, что данные не дошли. А так как сокет находится в TIME_WAIT, OS о нем знает и повторно отправляет подтверждение (тех данных, которые код на А уже давно обработал).

Так что если shutdown отсутствует, вторая сторона мало того что получит ошибку (в любом языке!), так еще и "последние отправленные данные" могут потеряться (потому что retransmit делать уже некому). И никаких гарантий о поведении второй стороны после RST уже нет.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38417737
cdtyjv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
maxkarДалее. Закрытие сокета без предварительного shutdown тоже приводит к RST. Т.е. вызов "socket.close() без socket.shutdownOutput()" - это аварийное завершение, при котором, в частности, не нужно гарантировать доставку уже отправленных пакетов (да, это именно так!).Нет, это не так. Читаем JavaDoc метода close():

JavaDocCloses this socket.
Any thread currently blocked in an I/O operation upon this socket will throw a SocketException.
Once a socket has been closed, it is not available for further networking use (i.e. can't be reconnected or rebound). A new socket needs to be created.
Closing this socket will also close the socket's InputStream and OutputStream.
If this socket has an associated channel then the channel is closed as well.
Более того, на первой странице я приводил пример. Вот его небольшая модификация:
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
public class SocketExample {
    public static void main(String[] args) throws Exception {
        final byte[] chunk1 = new byte[] {1, 2, 3, 4};
        final byte[] chunk2 = new byte[] {5, 6, 7, 8};
        final byte[] chunk3 = new byte[] {9, 10, 11, 12};

        final CountDownLatch latch1 = new CountDownLatch(1);
        final CountDownLatch latch2 = new CountDownLatch(1);
        final CountDownLatch latch3 = new CountDownLatch(1);

        new Thread(new Runnable() {
            @Override public void run() {
                try {
                    ServerSocket srvSock = new ServerSocket(8844);

                    Socket cliSock = srvSock.accept();

                    OutputStream os = cliSock.getOutputStream();

                    os.write(chunk1);

                    latch1.await();

                    os.write(chunk2);

                    latch2.await();

                    os.write(chunk3);
                    cliSock.close();
                    srvSock.close();

                    latch3.await();
                }
                catch (Exception ignore) {
                    // Ignore.
                }
            }
        }).start();

        Thread.sleep(1000);

        Socket sock = new Socket("localhost", 8844);

        InputStream is = sock.getInputStream();

        assert is.available() == 4;

        byte[] buf = new byte[4];

        int read = is.read(buf);

        assert read == 4;
        assert Arrays.equals(buf, chunk1);
        assert is.available() == 0;

        latch1.countDown();
        Thread.sleep(50);

        assert is.available() == 4;

        read = is.read(buf);

        assert read == 4;
        assert Arrays.equals(buf, chunk2);
        assert is.available() == 0;

        latch2.countDown();
        Thread.sleep(50);

        assert is.available() == 4;

        read = is.read(buf);

        assert read == 4;
        assert Arrays.equals(buf, chunk3);
        assert is.available() == 0;

        latch3.countDown();
        Thread.sleep(50);

        assert is.read() == -1;
    }
}


Как видите, никакого RST в виде Exception не возникает.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38417791
Фотография schwa
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Закрытие SocketOutput/InputStream-ов при вызове socket.close() в java не имеет никакого отношения к shutdownOutput, который является вызовом shutdown с флагом SHUT_WR. Т.к. close и shutdown это разные операции.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38417924
cdtyjv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
В общем, вот как это работает, например, на Windows.

Сначала читаем в MSDN статью о том, как делать gracefull shutdown сокета: http://msdn.microsoft.com/en-us/library/windows/desktop/ms738547(v=vs.85).aspx
Из этой статьи следует, что сокет можно вырубать двумя способами: shutdown() + closesocket() или WSASendDisconnect() + closesocket() .

Теперь смотрим на имлепементацию дефолтного джавовского дуал-сокета: http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/windows/native/java/net/DualStackPlainSocketImpl.c

Из него мы видим, что при вызове shutdown() из Java происходит вызов shutdown() в Windows.
А когда мы вызываем close() из Java, то это делегируется в http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/windows/native/java/net/net_util_md.c в метод NET_SocketClose, который в свою очередь вызывает как раз таки WSASendDisconnect() + closesocket() , как и написано в WinAPI.

Таким образом, Socket.close() является безопасным способом вырубать сокет. По крайней мере на Windows. Что и доказывает приведенный выше код.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38417986
maxkar
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
cdtyjvКак видите, никакого RST в виде Exception не возникает.
Здесь все еще хуже. Оно банально не работает:
Код: powershell
1.
2.
3.
4.
5.
6.
7.
8.
9.
maxkar@progressor ~/test/proxy $ java -ea SocketExample
Exception in thread "main" java.lang.AssertionError
	at SocketExample.main(SocketExample.java:51)
^Cmaxkar@progressor ~/test/proxy $ java -version
java version "1.7.0_40"
Java(TM) SE Runtime Environment (build 1.7.0_40-b43)
Java HotSpot(TM) 64-Bit Server VM (build 24.0-b56, mixed mode)
maxkar@progressor ~/test/proxy $ uname -a
Linux progressor 3.10.7-gentoo-r1 #1 SMP Sun Sep 29 20:06:02 MSK 2013 x86_64 Intel(R) Core(TM) i7-2700K CPU @ 3.50GHz GenuineIntel GNU/Linux



Я еще немного поигрался с сокетами. В общем, "по-умолчанию" socket.shutdown делает все нормально на любом языке (с точки зрения удаленной стороны). В документации я не нашел, что такое поведение гарантируется. Так что shutdown желательно делать (он еще и ошибку даст, если какие-то данные не дошли). Но если "специально постараться", отправить RST не составит большого труда:

Сервер:
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;

public class SE {
    public static void main(String[] args) throws Exception {
        final byte[] chunk1 = new byte[] {1, 2, 3, 4};

        try {
            ServerSocket srvSock = new ServerSocket(8844);
            Socket cliSock = srvSock.accept();

            Thread.sleep(1000);
            InputStream is = cliSock.getInputStream();

            assert is.available() == 4;
            byte[] buf = new byte[4];

            int read = is.read(buf);

            System.out.println(Arrays.toString(buf));
            assert read == 4;
            assert Arrays.equals(buf, chunk1);
            assert is.available() == 0;

            Thread.sleep(1000);

            assert is.read() == -1;

            cliSock.close();
            srvSock.close();

        }
        catch (Throwable ignore) {
          ignore.printStackTrace();
        }

    }
}


Клиент:
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
import java.io.*;
import java.net.*;

public class SES {
  public static void main(String[] args) throws Exception {
    final Socket s = new Socket("localhost", 8844);
    final byte[] buf = {1,2,3,4};
    s.setSoLinger(true, 0);
    final OutputStream ss = s.getOutputStream();
    ss.write(buf);
    s.close();
  }
}



Сервер с ключиком -ea нужно запускать (это я с вашего примера брал).

Зачем стороннее приложение использует SO_LINGER? Ну не знаю. Может, оно тысячами в секунду соединения плодит и не хочет, чтобы эти сокеты висели в TIME_WAIT.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38417993
cdtyjv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
На Windows работает нормально. Где именно вылез assert? Что на SocketExample.java:51 строке?

А с SO_LINGER это некорректное рассуждение просто. Если вы его выставляете, значит для вас нормально, что другая сторона может получить RST.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38417996
Фотография schwa
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
cdtyjvТаким образом, Socket.close() является безопасным способом вырубать сокет. По крайней мере на Windows. Что и доказывает приведенный выше код.
Нет. Но в конце этой статьи написано, что нужно сделать, чтобы закрыть "безопасно" нормально.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38417998
Фотография schwa
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
http://msdn.microsoft.com/en-us/library/windows/desktop/ms738547(v=vs.85).aspx

Вот этой статье.
авторOne technique that can be used to minimize the chance of problems occurring during connection teardown is to avoid relying on an implicit shutdown being initiated by closesocket. Instead, use one of the two explicit shutdown functions, shutdown or WSASendDisconnect. This in turn causes an FD_CLOSE indication to be received by the peer application indicating that all pending data has been received.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38417999
cdtyjv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
schwacdtyjvТаким образом, Socket.close() является безопасным способом вырубать сокет. По крайней мере на Windows. Что и доказывает приведенный выше код.
Нет. Но в конце этой статьи написано, что нужно сделать, чтобы закрыть "безопасно" нормально.Что нет? В статье написано, что надо делать shutdown/WSASendDisconnect + socketclose(). Нативный код JVM именно это и делает:
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
JNIEXPORT int JNICALL NET_SocketClose(int fd) {
    struct linger l;
    int ret;
    nt len = sizeof (l);
    if (getsockopt(fd, SOL_SOCKET, SO_LINGER, (char *)&l, &len) == 0) {
        if (l.l_onoff == 0) {
            WSASendDisconnect(fd, NULL);
        }
    }
    ret = closesocket (fd);
    return ret;
}


А выставление SO_LINGER говорит о том, что мы готовы (а может быть даже намеренно хотим) закрывать сокет через RST.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38418009
Фотография schwa
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
shutdown инициирует закрытие соединения.
close закрывает дескриптор сокета также инициируя последовательность закрытия соединения, если оно еще не было закрыто.
Без положительного значения в SO_LINGER обе функции завершаются сразу же, не дожидаясь получения подтверждений отправленных FIN сегментов.

Так что после завершения последовательности вызовов
Код: java
1.
2.
write(socket, data)
close(socket)


у приложения нет никаких сведений о том, а были ли доставлены данные, которые мы отправили.

В конце этой статьи же написано
http://msdn.microsoft.com/en-us/library/windows/desktop/ms738547(v=vs.85).aspx One technique that can be used to minimize the chance of problems occurring during connection teardown is to avoid relying on an implicit shutdown being initiated by closesocket. Instead, use one of the two explicit shutdown functions, shutdown or WSASendDisconnect. This in turn causes an FD_CLOSE indication to be received by the peer application indicating that all pending data has been received. To illustrate this, the following table shows the functions that would be invoked by the client and server components of an application, where the client is responsible for initiating a graceful shutdown.

Client

(1) Invokes shutdown(s, SD_SEND) to signal end of session and that client has no more data to send.

(local timing significance only) Gets FD_READ and calls recv to get any response data sent by server

Server

(2) Receives FD_CLOSE, indicating graceful shutdown in progress and that all data has been received.

(3) Sends any remaining response data.

(local timing significance only) Invokes closesocket .

Client

(5) Receives FD_CLOSE indication.

(6) Invokes closesocket.

А теперь тоже самое, но для unix
http://blog.netherlabs.nl/articles/2009/01/18/the-ultimate-so_linger-page-or-why-is-my-tcp-not-reliable
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38418011
Фотография schwa
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Пропустил (4) для сервера
(4) Invokes shutdown(s, SD_SEND) to indicate server has no more data to send.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38418014
cdtyjv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Я так и не могу понять, к чему вы это пишете. Мы изначально говорили про то, безопасен ли Socket.close().
Так вот, я утверждаю, что безопасен. В доказательство этого я привел нативный код JVM, из которого видно, что на Windows он вызывает WSASendDisconnect + closesocket, что соответствует рекомендациям из MSDN.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38418481
maxkar
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
cdtyjvНа Windows работает нормально. Где именно вылез assert? Что на SocketExample.java:51 строке?
Это первый же assert. Вы ключик -ea нигде не забыли при локальном тестировании? А то без него "все работает".
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38418508
cdtyjv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
maxkarЭто первый же assert. Вы ключик -ea нигде не забыли при локальном тестировании? А то без него "все работает".Серверный порт поменяйте, он у вас занят.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38419722
maxkar
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
cdtyjvСерверный порт поменяйте, он у вас занят.
Зачем? Я никаких исключений из-за занятости не получаю. Серверный сокет нормально поднимается. Клиентский сокет тоже исключений не дает. Если assert'ы не включать, то и какое-то чтение идет (все успешно завершается в этом случае). На этом же порту потом работают другие примеры (естественно, после остановки вашего).

Хотя порт я поменял. Как и ожидалось - результат ровно тот же. AssertionError на первом is.available() = 4.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38419746
cdtyjv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
maxkar ,
Ну продебажьте, и выясните, с чем это связано. Очевидно же, что до закрытия даже дело не доходит. В моем случае на рабочей машине не биндился серверный сокет, так как он был занят.
Как бы то ни было, вопрос по Socket.close() уже закрыт, нативный код все показал.
...
Рейтинг: 0 / 0
IO/NIO WTF
    #38419751
cdtyjv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
maxkar ,
В моем коде блок catch пуст. Добавьте туда print, и увидите, в чем причина.
...
Рейтинг: 0 / 0
85 сообщений из 85, показаны все 4 страниц
Форумы / Java [игнор отключен] [закрыт для гостей] / IO/NIO WTF
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]