Доступ к COM-порту из Java-апплета

    Привет всем. Хочу поделиться решением одной задачи, которая встала передо мной некоторое время назад. Состояла она в том, что было необходимо дать веб-сервису возможность получать доступ и обмениваться информацией с некоторым устройством (в моем случае карт-ридер) через COM-порт на компьютере пользователя. Для этого было решено использовать Java-апплет, и в процессе реализации возникало несколько затруднений, решение которых я и хочу описать в статье. Замечу, что в интернете мне не удалось найти единого руководства «от начала и до конца», как решить мою задачу, так что надеюсь, кому-нибудь моя статья может сэкономить несколько часов времени.



    Постановка задачи



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

    Требования к компьютеру пользователя:
    1. Microsoft Windows XP, Vista, Seven
    2. Любой современных браузер, поддерживающий Java-апплеты, основной упор на IE, Firefox
    3. Установленная JRE1.6.

    Обзор имеющихся методов доступа к COM-порту из Java-апплетов



    Как известно, в стандартную комплектацию JRE не входит какой-либо класс или метод, позволяющий осуществить доступ к COM-порту. Мною были найдены две наиболее популярные внешние библиотеки: javax.comm и RXTX. Посмотрев информацию о них, я принял решение о применении RXTX, т.к. javax.comm больше не развивается, к тому же у RXTX шире спектр поддерживаемых операционных систем, что может быть полезно мне в будущем.

    Обе библиотеки осуществляют доступ к COM-порту через использование native-библиотек. Для RXTX — это библиотека rxtxSerial.dll.

    В плане функций по работе с COM-портом обе библиотеки имеют идентичный набор классов.

    Тестовый апплет для работы с COM-портом



    Приведу код апплета, который мы будем «распространять». Этот апплет, естественно не тот, что я применял в своем проекте, его я сделал специально для данной статьи. Итак:

    package cardreaders;

    import java.applet.Applet;
    import java.awt.Graphics;

    import java.net.URL;
    import java.io.InputStream;
    import java.io.File;
    import java.io.FileOutputStream;

    import gnu.io.CommPort;
    import gnu.io.CommPortIdentifier;
    import gnu.io.SerialPort;

    public class CardReader extends Applet {

      Graphics gg;
      int pos;

      public void init() {
        // TODO start asynchronous download of heavy resources
      }

      public void paint(Graphics g)
      {
        gg = g;
        pos = 14;
        
        text("INITIALIZATION...");

        java.util.Enumeration<CommPortIdentifier> portEnum = CommPortIdentifier.getPortIdentifiers();
        while ( portEnum.hasMoreElements() )
        {
          CommPortIdentifier portIdentifier = portEnum.nextElement();
          if( portIdentifier.getPortType() == CommPortIdentifier.PORT_SERIAL)
            text("FOUND PORT " + portIdentifier.getName());
        }
      }

      public void text(String t)
      {
        gg.drawString(t, 10, pos);
        pos += 14;
      }
    }

    * This source code was highlighted with Source Code Highlighter.


    Этот код просто выводит список всех найденных в системе COM-портов (при чем эта операция выполняется каждый раз при перерисовке апплета).

    Добавление библиотеки RXTX



    Исходя из постановки задачи, необходимо сделать наш апплет как можно более простым для пользователя. А нет ничего проще, чем один единственный jar-файл, включающий в себя и наш апплет и библиотеку RXTX. Для того чтобы интегрировать библиотеку в jar, прежде всего скачаем ее исходный код.

    В скаченном архиве из папки src возьмем все .java файлы и добавим к нашему проекту. Если вы, как и я, работаете с NetBeans, то просто создайте в src-папке проекта структуру каталогов gnu/io, куда и поместите вышеуказанные файлы.

    После компиляции в одном апплете сразу будут объеденены наш код и библиотека RXTX.

    Добавление native-библиотеки rxtxSerial.dll



    Как уже было отмечено выше, RXTX использует native-библиотеку rxtxSerial.dll для доступа к COM-порту под Windows. Эта библиотека подгружается классами RXTX через метод System.loadLibrary().
    Поэтому для работы нашего апплета библиотека rxtxSerial.dll должна находиться на компьютере пользователя, причем в одной из директорый, откуда ее сможет загрузить RXTX, т.е. в директориях, прописаных в java.library.path. Одна из таких директорий — это директория bin в папке установки JRE.

    Наиболее очевидным методом является предварительная инсталяция библиотеки rxtxSerial.dll в указанную директорию, но это требует дополнительных действий от пользователя (далеко не всегда квалифицированного), поэтому было применено другое решение.

    Любой файл, в том числе и dll-библиотека может быть включен в jar-файл как ресурс. Чтобы сделать это в NetBeans просто создайте папку resources в директории, где находится .java файл вашего апплета, и поместите туда rxtxSerial.dll. Но как теперь подключить ее? На помощь приходит метод, который описан здесь.

    С учетом описанного метода, исходный код нашего апплета становится таким:

    package cardreaders;

    import java.applet.Applet;
    import java.awt.Graphics;

    import java.net.URL;
    import java.io.InputStream;
    import java.io.File;
    import java.io.FileOutputStream;

    import gnu.io.CommPort;
    import gnu.io.CommPortIdentifier;
    import gnu.io.SerialPort;

    public class CardReader extends Applet {

      Graphics gg;
      int pos;

      static {
        try {
          System.out.println("CardReader {}");
          /* Get DLL from JAR file */
          URL res = CardReader.class.getResource("resources/rxtxSerial.dll");
          InputStream is = res.openStream();

          /* Define the destination file */
          File dll = File.createTempFile("rxtxSerial",".dll");

          /* Open the destination file */
          FileOutputStream fos = new FileOutputStream(dll);

          /* Copy the DLL fro the JAR to the filesystem */
          byte[] array = new byte[1024];
          for(int i=is.read(array);
            i!=-1;
            i=is.read(array)
          ) {
            fos.write(array,0,i);
          }

          /* Close all streams */
          fos.close();
          is.close();

          /* Load the DLL from the filesystem */
          System.load(dll.getAbsolutePath());
          System.out.println("CardReader loaded");
        }
        catch(Throwable e)
        {
          e.printStackTrace();
        }
      }

      public void init() {
        // TODO start asynchronous download of heavy resources
      }

      public void paint(Graphics g)
      {
        gg = g;
        pos = 14;
        
        text("INITIALIZATION...");

        java.util.Enumeration<CommPortIdentifier> portEnum = CommPortIdentifier.getPortIdentifiers();
        while ( portEnum.hasMoreElements() )
        {
          CommPortIdentifier portIdentifier = portEnum.nextElement();
          if( portIdentifier.getPortType() == CommPortIdentifier.PORT_SERIAL)
            text("FOUND PORT " + portIdentifier.getName());
        }
      }

      public void text(String t)
      {
        gg.drawString(t, 10, pos);
        pos += 14;
      }
    }

    * This source code was highlighted with Source Code Highlighter.



    Но это не все. Библиотека RXTX по-прежнему пытается загрузить rxtxSerial.dll с помощью System.loadLibrary(), что ей конечно не удается. Для решения этой проблемы можно было бы сохранять dll в одну из директорий java.library.path, но я пошел более простым путем: в исходных кодах RXTX закомментировал строчку загрузки библиотеки (она все равно грузится классом CardReader).

    Подписывание апплета



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

    Итог



    Распространяемый нами апплет получил возможность работы с COM-портом, и при этом все необходимое содержится в одном jar-файле. На пользовательском компьютере не требуется установки никаких библиотек, т.е. пользователю достаточно просто зайти на страницу с апплетом (и в нашем случае подтвердить в появившемся окне разрешение на запуск апплета).

    P.S. Не хватает кармы для переноса статьи в соотвествующий блог.
    Поделиться публикацией

    Комментарии 14

      +2
      А предупреждение безопасности при запуске апплета показывается?
        0
        Ну разумеется, ведь он подписан неподтвержденным сертификатом
        0
        > NetBeance
        NetBeans

        > у RXTX шире спектр поддерживаемых операционных систем
        Но данный пример работает только под Windows, верно?
        • НЛО прилетело и опубликовало эту надпись здесь
            0
            Поправил, спасибо.

            Да, пример работает под виндоус, поскольку на клиентских терминалах (точки продаж) стоит именно он, поэтому я не тратил время на адаптацию под другие системы, но тем не менее такое возможно. Например, для MacOS можно посмотреть здесь.

            +1
            Посмотрите в сторону Java Web Start (jnlp), там native библиотеки без проблем вроде подключаются стандартным путём. Начиная с Java 6 Update 10 для апплетов тот же jnlp используют.
              0
              Да, вы правы, это хороший вариант, обязательно его рассмотрю.
              –2
              Я решил точно такую же проблему через довольно простой javascript с применением почти стандартной OCX.
              Работает только через IE, зато не нужно джаву городить.
                0
                Как я написал, одно из требований было в максимальной простоте. В данном случае от пользователя не требуется вообще никаких действий (разве что подтвердить запуск апплета), никакой установки.
                  +3
                  Работает только через IE


                  Дальше можно не читать.
                  +1
                  Ну вообще-то есть куда более стандартный способ добавления jar'ов к своему коду:
                  — кладем либу rxtx.jar рядом с applet.jar
                  — редактируем манифест-файл applet.jar, добавляя к нему строчку Class-Path:rxtx.jar

                  Вуаля, либа подкачается сама. Разумеется, можно не класть их рядом, тогда нужно будет добавить путь к класспафу.
                    0
                    Ну я согласен, только проблема была в том, что rxtx пытается загрузить библиотеку с помощью System.loadLibrary, а саму dll-ку я распаковываю во временную папку, от куда она с помозью данной функции не может быть загруженной. Поэтому пришлось слегка подправить код библиотеки (в трех классах удалить соответствующий вызов). Хотя, конечно можно было бы инсталлировать библиотеку прямо в место установки JDK
                    +1
                    С версии java 1.6 u10 можно использовать возможности Java Web Start для запуска аплетов.
                    В таком случае просто прописываете свою .dll в jnlp-дескриптор (тэг nativelib) и вуаля.
                    java.sun.com/javase/6/docs/technotes/guides/javaws/developersguide/syntax.html
                      0
                      Ну да, это тема. Пожалуй, можно будет переделать =)

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

                    Самое читаемое