• Кэширование редиректа?

    Vamp
    @Vamp
    Все заголовки относятся исключительно к своему ответу. После получения редиректа барузер сгенерирует новый отдельный запрос к ресурсу /my/file.svg и сервер вместе с ним вернёт свои заголовки, относящиеся только к этому ресурсу и в этих заголовках кеширующей директивы не будет.

    Так что в вашем случае будет закеширован редирект. То есть браузер запомнит что урл "file.php?path=/my/file.svg" редиректится на "/my/file.svg" (в принципе, он это запомнит и без дополнительного Cache-Control) и если встретит где-то на странице первый урл, то отправит запрос сразу на второй урл, минуя запрос на первый.

    Вам нужно отдавать контент картинки непосредственно самим скриптом:
    header('Content-Type: image/svg+xml');
    // public явным образом разрешает кешировать контент не только на
    // устройстве пользователя, но и на любом промежуточном кеширующем http сервере,
    // если такой будет стоять между вами и пользователем
    // например, провайдерский или в офисах
    header('Cache-Control: public, max-age=604800');
    
    // ОСТОРОЖНО!!! Сначала произведите валидацию
    // параметра path перед передачей его в readfile()!
    readfile($_GET['path']);

    Это не должно быть напряжно по ресурсам. Всё-таки кешируется на неделю.
    Ответ написан
    Комментировать
  • Как обезопасить загрузку изображений на сайт?

    Vamp
    @Vamp
    Суть уязвимости в том, что в метаданные картинки вставляется произвольный php код. Далее, используя уязвимости сайта, злоумышленник заставляет сервер исполнить эту картинку как php скрипт.

    Чтобы избавиться от внедрённых таким образом шеллов, применяют ресайз, в ходе которого все метаданные теряются. Дополнительным бонусом идёт проверка валидности - imagecreatefromstring() и прочие аналогичные функции вернут ошибку, если пользователь залил битый/неподдерживаемый формат или вообще не картинку.

    В принципе - это годный способ, если вам не нужен оригинал загруженного изображения. Используйте его без опасений.

    Если оригинал всё же нужен, то следует удалить метаданные во время загрузки. Здесь уже будет посложнее, так как у каждого формата свои особенности в этом плане. Например, у jpeg это секции COM и APP1 (EXIF). У png - tEXt, iTXt и zTXt. У других форматов ещё какие-то свои особенности. То есть нужно разбираться в форматах изображений и использовать библиотеки, позволяющие работать с форматом на таком низком уровне. Причём надо удалять все метаданные, не относящиеся непосредственно к изображению, так как php код могут засунуть и в нестандартный чанк - браузер его проигнорирует и покажет картинку корректно, но php код там всё-таки присутствовать будет.

    Можно ещё попробовать сделать поиск подстроки <? в сыром контенте файла. Если подстрока найдена, то, возможно, там есть какой-то php код. Способ так себе и не рекомендуется к применению - будет много ложноположительных срабатываний.

    И ещё никогда не сохраняйте изображение в файловой системе под тем именем, с которым пользователь его загрузил. Генерируйте свои имена файлам. Если пользовательское имя файла нужно - сохраните его отдельно в базе.
    Ответ написан
    Комментировать
  • Как обезопаситься от шеллов на сервере?

    Vamp
    @Vamp
    Для начала необходимо разделить сайты друг от друга физически. Сами скрипты сайта, временные файлы и файлы сессий.

    Например, структура может быть такая:
    /var/www
      + toster.ru
      |  + site
      |  + sessions
      |  + temp
      + example.com
      |  + site
      |  + sessions
      |  + temp
      + остальные сайты


    В папке site расположены скрипты сайта. Обычно эта папка (или одна из вложенных) является document root сайта.

    В sessions находятся файлы сессий посетителей сайта. Если их не разделять, то взлом одного сайта автоматически дискредитирует сессии всех пользователей остальных сайтов. То есть, взломав example.com, можно стырить сессию администратора toster.ru. Путь к этой папке настраивается директивой session.save_path.

    А в temp находятся временные файлы, загружаемые пользователями через html формы (директива upload_tmp_dir). Туда же неплохо направить и остальные временные файлы, генерируемые функциями tmpfile(), tempnam(), самостоятельным образом (используя результат функции sys_get_temp_dir() для построения пути) или любой другой функцией, которая использует системную временную директорию. К сожалению, директива sys_temp_dir, ответственная за это, появилась только в php 5.5.0. Если у вас более ранняя версия, тогда нужно учитывать момент, что сайты могут ходить в /tmp и нельзя блокировать доступ к этой папке.

    ---

    Далее необходимо настроить нормальные права доступа. Часто в интернете можно видеть советы "делайте chmod 777 на всё". Так делать нельзя. Подобные права доступа как раз наихудшим образом сказываются на безопасности, потому что дают возможность отредактировать любой скрипт на сайте и встроить маленький бэкдор в самый незаметный скрипт, принадлежащий CMS. Надежно избавиться от такого довеска можно будет только полной переустановкой CMS. Или можно загнать весь сайт в систему контроля версий, как предложил Максим Гречушников, и одной командой узнать какие скрпиты были заражены. Тем не менее, одно другое не исключает. Система контроля версий позволит выявить заражение постфактум, а нормальные права не дадут возможности заразить файл в принципе.

    Я рекомендую на папки устанавливать права 755, а на файлы 644. При этом, владельцем всех папок и файлов должен быть кто угодно, но не пользователь, из-под которого запущен веб-сервер или php (например, пользователь, под которым вы сами заходите на сервер по ssh). Но такие права на всё устанавливать тоже нельзя. Сайт в процессе своей работы может генерировать свои временные файлы (например, кешировать что-то в файл, компилировать шаблоны, хранить какие-нибудь настройки), поэтому для папок, в которые сайт может что-то записать, права должны быть другие. Вот здесь уже можно ставить 777 для папок и 666 для файлов. Это довольно кропотливая работа, потому что у каждого сайта (особенно если они на разных CMS) свои папки и часто определение таких папок нетрвиально.

    И права доступа не решают всех проблем. Злоумышленник может встроить вредоносный код в скомпилированный шаблон или отравить кеш. Тогда не помогут ни права (так как для нормальной работы сайту нужно иметь права на запись в такие файлы), ни система контроля версий (так как подобные файлы слишком часто меняются и, как правило, не хранятся в репозитории). Хотя надо сказать, этот способ внедрения сложнее и недолговечнее, чем встраивание в нормальный скрипт.

    И ещё автоматическое обновление сайта перестанет работать. Если CMS позволяет обновить себя через кнопку в веб-интерфейсе админки, то такое обновление не будет работать, так как права доступа не разрешают веб-серверу модифицировать скрипты. Обновлять скрипты теперь можно только вручную от имени пользователя, которому принадлежат все файлы. Безопасность и удобство - разные концы одной палки.

    Даже с учётом всех недостатков - нормально настроенные права доступа на файлы и папки значительно сокращают вектор возможных атак. Так что обязательно разберитесь с этим вопросом.

    ---

    После раскидывания сайтов по своим папкам и настройки прав, необходимо ограничить доступ сайтов друг к другу. Проще всего это сделать настройкой php директивы open_basedir. Эта директива определяет список директорий, внутри которых (а так же во всех вложенных папках) скрипты могут читать и писать. Доступ за пределы этих директорий будет пресекаться. То есть нужно каждому сайту прописать в open_basedir путь к своей папке и к /tmp (если версия php < 5.5.0 и нельзя установить sys_temp_dir).

    Для Apache с mod_php конфигурация прописывается в httpd.conf:

    <VirtualHost *:80>
      ServerName example.com
      php_admin_value open_basedir /var/www/example.com/:/tmp/
      php_admin_value upload_tmp_dir /var/www/example.com/temp/
      php_admin_value sys_temp_dir /var/www/example.com/temp/
      php_value session.save_path /var/www/example.com/sessions/
    </VirtualHost>


    Для php-fpm в конец php.ini файла вписывается специальная секция, определяющая индивидуальную конфигурацию для каждого сайта:

    [HOST=example.com]
    open_basedir /var/www/example.com/:/tmp/
    upload_tmp_dir /var/www/example.com/temp/
    sys_temp_dir /var/www/example.com/temp/
    session.save_path /var/www/example.com/sessions/
    
    [HOST=toster.ru]
    ; конфиг для toster.ru и т.д.


    У open_basedir есть недостатки:

    1. Замедление файловых операций, так как необходимо проверить вхождение каждого открываемого файла в список open_basedir. Если проекты не highload, то некритично.
    2. Есть варианты обхода. Но их далеко не всегда удаётся эксплуатировать. Тем более они закрываются со временем в новых версиях php.
    3. Realpath cache не работает вместе с включенной open_basedir.

    ---

    Есть вариант запускать каждый сайт от своего пользователя. В apache это делается опцией AssignUserId. В php-fpm - отдельной конфигурацией pool. Но в любом случае нужно для каждого сайта создавать не только отдельного юзера, но и группу. При этом права на основную папку сайта (/var/www/example.com) должны быть 750, а группа должна соответствовать той, от которой будет запускаться процесс apache (или пул php-fpm), ответственный за обслуживание сайта.

    Проще показать на примере. После имени файла я буду указывать права, владельца и группу в формате (права, владелец:группа)
    /var/www         (755, root:root)
      + toster.ru    (750, my_user:toster)
      |  + site      (755, my_user:toster)
      |  + sessions  (777, my_user:toster)
      |  + temp      (777, my_user:toster)
      + example.com  (750, my_user:example)
      |  + site      (755, my_user:example)
      |  + sessions  (777, my_user:example)
      |  + temp      (777, my_user:example)
      + остальные сайты - всё аналогично


    my_user - это пользователь, под которым ни в коем случае нельзя запускать веб-сервер и php. Это может быть ваш собственный пользователь, под которым вы заходите на сервер по ssh. В этом случае у вас будут все права на сайт, а у сайта только необходимый минимум.

    <VirtualHost *:80>
      ServerName example.com
      AssignUserId exapmle example
    </VirtualHost>
    <VirtualHost *:80>
      ServerName toster.ru
      AssignUserId toster toster
    </VirtualHost>


    Разумеется, в системе должны быть заранее созданы пользователи с именами toster, example и с одноимёнными основными группами.

    Вобщем, рекомендую более подробно ознакомиться с системой прав и пользователей в линуксе. Пригодится всегда.

    ---

    Более сложным и предпочтительным вариантом является упаковка каждого сайта в свой отдельный виртуальный контейнер lxc/docker. Я не смогу в двух словах описать как это добро настраивать, но эффект будет такой, как будто каждый сайт работает на своём собственном VPS сервере.

    ---

    Ещё немного про базу. Для доступа к базе у каждого сайта должен быть свой собственный логин с паролем. Причем, доступ должен ограничиваться исключительно той базой, в которой находятся данные сайта. И права должны быть не все подряд, а только SELECT, INSERT, UPDATE, DELETE. В редких случаях CMS может самостоятельно создавать таблицы для своих нужд в процессе своей работы (не в процессе первичной установки). В этом случае можно добавить права CREATE, ALTER, INDEX юзеру этого сайта. Иногда бывает в базе есть какие-то процедуры/функции. Для их вызова требуются права EXECUTE.

    ---

    Безопасность - дело большое и очень разнообразное. И уж точно не простое. Всё что я здесь написал - это далеко не полный список. Есть ещё настройки уровня операционной системы (особенно фаервол) и прочего установленного в системе софта. Так же отдельным пунктом идёт система мониторинга и оповещения, которые почему-то никто не делает - не считают нужным разбираться ещё и в этом. Так что если у вас нет хорошего админа в запасе, то проще будет заказать услугу администрирования прямо у того же хостера, у которого вы арендуете VPS. Или найти админа-фрилансера. Или же перевезти сайты на shared хостинг, где администрирование уже включено в ценник, да и сам переезд хостеры часто предлагают сделать бесплатно силами своих админов.
    Ответ написан
    Комментировать
  • Как правильно передать такой запрос через jdbc?

    Vamp
    @Vamp
    У вас в переменной sql сразу 4 разных запроса. Вам нужно выполнить каждый запрос отдельным вызовом executeUpdate(). Workbench же сам определяет количество переданных ему запросов, разделяет их и выполняет последовательно, один за другим. А в консоли вы ввели каждую команду отдельно, поэтому никаких проблем с workbench и консольным клиентом у вас нет.
    Ответ написан
    1 комментарий
  • Как правильно работать с MySQL в Java?

    Vamp
    @Vamp
    Первый вопрос: скажите пожалуйста правильно ли я построил схему работу с базой?

    Такой подход имеет право на жизнь и часто встречается у новичков. Но он не очень удобен в использовании и поддержке.

    Второй вопрос: у меня после каждого запроса в терминале висит куча Sleep соединений

    Сложно сказать в чём дело по тем данным что вы предоставили. Возможно con.close() выдал исключение и не закрыл соединение, а у вас это исключение тупо игнорируется.

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

    package hello.world;
    
    import java.sql.*;
    
    public class Main {
    
        // один коннект на всё приложение
        private static Connection conn;
    
        // в данном случае так же можно использовать и единственный Statement
        private static Statement stmt;
    
        public static void main(String[] args) throws SQLException {
            conn = DriverManager.getConnection("jdbc:mysql://localhost/hello", "user", "password");
            stmt = conn.createStatement();
            doTheJob();
        }
    
        private static void doTheJob() throws SQLException {
    
            // здесь используется фишка Java7 под названием try-with-resources,
            // которая освобождает нас от необходимости закрывать ResultSet руками
            // он будет закрыт автоматически в конце try блока
            try ( ResultSet rs = stmt.executeQuery("SHOW TABLES") ) {
                while ( rs.next() ) {
                    System.out.println(rs.getString(1));
                }
            }
    
            // переиспользуем Statement для другого запроса - это допустимо
            try ( ResultSet rs = stmt.executeQuery("SELECT column FROM table") ) {
                while ( rs.next() ) {
                    System.out.println(rs.getString(1));
                }
            }
    
            // вариант без try-with-resources для Java6 и ниже
            ResultSet rs2 = null;
            try {
                rs2 = stmt.executeQuery("SELECT column2 FROM table2");
                // делаем что-то с rs2
            } finally {
                close(rs2);
            }
    
        }
    
        private static void close(ResultSet rs) {
            if ( rs != null ) {
                try {
                    rs.close();
                } catch ( SQLException e ) {
                    e.printStackTrace();
                }
            }
        }
    
    }


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

    Для многопоточных программ часто используется схожая техника, где у каждого потока есть свой объект Connection, с которым он работает (например, хранит свой экземпляр коннекта в ThreadLocal поле).

    Но в многопоточных приложениях удобнее пользоваться пулом коннектов. Пул берёт на себя ответственность по раздаче соединений всем страждущим. Он на старте создаёт сразу несколько соединений к базе и сохраняет в свою внутреннюю коллекцию. Далее, по мере необходимости (вызова метода getConnection на пуле), пул удаляет из внутренней коллекции первый по очереди коннект и возвращает его запросившему. Если колекция пуста, то создаётся новый коннект и сразу возвращается. Основная фишка в том, что возвращаемые этим пулом соединения при закрытии на самом деле на закрываются, а возвращаются обратно во внутреннюю коллекцию пула и могут быть переиспользованы другими потоками. Таким образом достигается более высокая производительность путём уменьшения количества открытых коненктов к базе и увеличения интенсивности их использования. Код приложения при этом выглядит так, будто на каждый запрос строится новое соединение к базе и закрывается сразу после завершения работы с результатом этого запроса.

    Разумеется, описание в предыдущем абзаце довольно поверхностное и неполное, но обрисовывает общую для всех пулов концепцию.

    Если будете заниматься многопоточным программированием, а это маст хэв для backend (в особенности, highload), то без пула коннектов не обойтись. Хотя конечно можно и обойтись, но по мере развития проекта вы будете пилить собственную обёртку и в итоге реализуете самый простейший пул коннектов. Оно того не стоит - проще сразу взять готовый.

    Я не рекомендую сразу браться за изучение каких-либо пулов. Сначала попрактикуйтесь с голым JDBC в однопоточных приложениях. Пулы коннектов - это уже вторая ступень. К ним следует приступать только когда будете уверенно себя чувствовать в JDBC. На начальном этапе они будут только мешать.

    Когда будете готовы освоить какой-нибудь пул, то советую обратить внимание на HikariCP. Он давно уже работает у меня в хайлоад проектах и ни разу не подводил. Ниже пример кода с его использованием:

    package hello.world;
    
    import com.zaxxer.hikari.HikariConfig;
    import com.zaxxer.hikari.HikariDataSource;
    
    import java.sql.*;
    import java.util.Properties;
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class Main {
    
        // пул, в котором содержатся все соединения
        private static HikariDataSource dbPool;
    
        // в этом сервисе будем параллельно выполнять запросы
        private static ExecutorService executor = Executors.newFixedThreadPool(5);
    
        public static void main(String[] args) throws SQLException {
    
            // конфигурируем пул
            Properties props = new Properties();
            props.setProperty("dataSourceClassName", "com.mysql.jdbc.jdbc2.optional.MysqlDataSource");
            props.setProperty("dataSource.url", "jdbc:mysql://localhost/hello");
            props.setProperty("dataSource.user", "user");
            props.setProperty("dataSource.password", "password");
            props.setProperty("poolName", "MyFirstPool");
            props.setProperty("maximumPoolSize", "5"); // в этом пуле будет максимум 5 соединений
            props.setProperty("minimumIdle", "1"); // как минимум одно активное соединение там будет жить постоянно
            dbPool = new HikariDataSource(new HikariConfig(props));
            doTheJob();
        }
    
        private static void doTheJob() throws SQLException {
    
            // сколько запросов будем делать параллельно
            int selects = 5;
    
            // этот объект позволит нам дождаться выполнения всех запросов,
            // выполняющихся в параллельных потоках чтобы подсчитать общее время
            // выполнения всех запросов
            CountDownLatch waitLatch = new CountDownLatch(selects);
    
            long startTime = System.nanoTime();
            for ( int i = 0; i < selects; ++i ) {
                executor.submit(new ThreadPoolJob(dbPool, waitLatch));
            }
    
            try {
                // ждём когда все воркеры закончат
                waitLatch.await();
            } catch ( InterruptedException e ) {
                System.out.println("latch was broken by interruption request");
            }
            long timeElapsed = System.nanoTime() - startTime;
            System.out.println("All queries was executed in: " + (timeElapsed / 1000000000) + " sec");
    
        }
    
        // класс-воркер, который будет выполнять запрос
        private static class ThreadPoolJob implements Runnable {
    
            private final HikariDataSource dbPool;
    
            private final CountDownLatch waitLatch;
    
            ThreadPoolJob(HikariDataSource dbPool, CountDownLatch waitLatch) {
                this.dbPool = dbPool;
                this.waitLatch = waitLatch;
            }
    
            @Override
            public void run() {
                String tName = Thread.currentThread().getName();
                System.out.println(tName + ": Start query");
                try (
                    Connection conn = dbPool.getConnection();
                    Statement stmt = conn.createStatement();
                 ) {
                    // здесь мы не получаем и не работаем с ResultSet, поэтому не
                    // описываем его в try-with-resources
                    stmt.execute("SELECT SLEEP(5)");
                    System.out.println(tName + ": Query completed");
                } catch ( SQLException e ) {
                    e.printStackTrace();
                } finally {
                    waitLatch.countDown();
                }
            }
        }
    
    }
    Ответ написан
    Комментировать
  • Как настроить Maven на копирование во время сборки?

    Vamp
    @Vamp
    Это делается при помощи плагинов. Вот на выбор antrun и maven-resources-plugin:
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-antrun-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>validate</phase>
                        <configuration>
                            <tasks>
                                <copy todir="${project.basedir}/target/jfx/speedmap">
                                    <fileset dir="/home/fluent/mapGr"/>
                                </copy>
                            </tasks>
                        </configuration>
                        <goals>
                            <goal>run</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
    
            <plugin>
                <artifactId>maven-resources-plugin</artifactId>
                <version>3.0.1</version>
                <executions>
                    <execution>
                        <id>copy-resources-foreign</id>
                        <phase>validate</phase>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.basedir}/target/jfx/speedmap</outputDirectory>
                            <resources>
                                <resource>
                                    <directory>/home/fluent/mapGr</directory>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>


    Есть директории, лежащие вне Java -проекта. Это не ресурсы (их надо копировать в директорию, отличную от target\classes).

    В вашем случае это всё-таки ресурсы. Просто по умолчанию maven считает ресурсами только контент из директорий src/main/resources и src/test/resources. Вам просто нужно сказать где ещё располагаются ресурсы, необходимые для сборки проекта.

    Вообще говоря, не очень хорошо завязываться на внешние по отношению к проекту ресурсы. Это усложняет вхождение новых участников, так как теперь уже недостаточно просто запустить mvn install и получить готовый артефакт. Да и сами потом забудете/запутаетесь, будете тратить время на вспоминание как правильно собирать проект. Хотя, конечно, хозяин - барин.
    Ответ написан
    1 комментарий
  • Можно ли в Java 8 устанавливать лимит памяти исходя из системной памяти, а не памяти java процесса?

    Vamp
    @Vamp
    В вашем случае решения чисто на java нет. Здесь необходимо зарядить регулярную задачу (cron), которая будет смотреть свободную физическую память в системе и убивать самое толстое java приложение при достижении 95% лимита памяти. Например, так:
    #!/usr/bin/env bash
    
    # pid'ы отслеживаемых java процессов
    # задачку определения актуальных pid'ов оставлю на вас
    PID1=1234
    PID2=5678
    
    # минимальное допустимое количество свободной памяти в процентах
    MIN_MEM_SIZE=5
    
    function get_java_mem {
      jstat -gc $1 | awk 'NR==1 {print ($3 + $4 + $6 + $8 + $10)}'
    }
    
    function get_free_mem {
      free | awk 'NR==2 {print ($7 * 100) / $2}'
    }
    
    if [ $(echo "`get_free_mem` < $MIN_MEM_SIZE" | bc) -eq "1" ]; then
      if [ $(echo "`get_java_mem $PID1` > `get_java_mem $PID2`" | bc) -eq "1" ]; then
        PID_TO_KILL=$PID1;
      else
        PID_TO_KILL=$PID2
      fi
      echo "Memory shortage detected. Taking heap dump and killing process $PID_TO_KILL"
      jmap  -dump:format=b,file=${PID_TO_KILL}.heapdump.bin $PID_TO_KILL
      kill $PID_TO_KILL
    fi


    если я пропишу -Xmx2g то оба влезут, но если один процесс использует все 2 гб, то второй не может эту память использовать.

    Не очень ясно зачем нужны такие качели. Если приложение нормально работает с -Xmx2g, то с -Xmx4g сильно лучше не станет. Разве что full gc будет происходить немного чаще, только и всего.
    Ответ написан
    Комментировать
  • Как красиво завершить работу с NIO при прерывании потока?

    Vamp
    @Vamp Автор вопроса
    Проблема оказалась в изначально неверном предположении.

    Во всех книгах написано, что jvm завершает свою работу только после завершения всех не daemon потоков. Но нигде не уточняется при каких условиях поток получает interrupted status. Мне всегда казалось, что при закрытии приложения внешним образом (Crtl+C, SIGTERM, рестарт системы), jvm сначала установит interrupted статус всем потокам, дождётся завершения последнего не daemon потока, после чего закроется. И достаточно просто продумать адекватную реакцию на прерывание потока.

    Как оказалось, единственный способ установить interrupted status - вызвать Thread.interrupt() из пользовательского кода. А все остальные способы завершения тупо останавливают потоки jvm без возможности как-то отреагировать на ситуацию. И единственный способ отследить это - установить shutdown hook.
    В принципе, это решение меня вполне устраивает.
    Ответ написан
    Комментировать
  • В какой ситуации я получаю writable SelectorKey из nio Selektor'a в Java?

    Vamp
    @Vamp
    Writable получается всегда, когда есть свободное место в output буфере у какого-либо сокета.

    Технически, метод write() всего лишь записывает данные в специальный буфер в памяти, привязанный к сетевому сокету. Размер этого буфера регулируется опцией SO_SNDBUF. Это in-memory операция. Реальная работа с сетью на этом этапе не происходит.

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

    На OP_WRITE нужно подписываться только когда есть данные, которые можно отправить удаленной стороне и отписываться сразу, как только вся порция данных будет записана в сокет. В противном случае, в результате каждого вызова Selector.select() вы будете получать список всех сокетов, у которых есть свободное место в буфере. А так как данных для записи у вас нет - только впустую потратите ресурс процессора.

    Образно говоря, на OP_READ вы подписываетесь, когда хотите послушать что говорят другие, а на OP_WRITE, когда вам самим есть что сказать.
    Ответ написан
    Комментировать