Java
September 2009 30

Доступ к 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. Не хватает кармы для переноса статьи в соотвествующий блог.
+15
21.1k 58
Comments 14