Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Секреты программирования для Internet на Java

.pdf
Скачиваний:
160
Добавлен:
02.05.2014
Размер:
3.59 Mб
Скачать

собственном классе-упаковщике. Многие тайны, связанные с тем, как это сделать, можно открыть, если посмотреть на файл LocalProfile.h.

Пример 12-2. Файл LocalProfile.h.

/* DO NOT EDIT THIS FILE - it is machine generated */ #include <native.h>

/* Header for class LocalProfile */ #ifndef _Included_LocalProfile #define _Included_LocalProfile typedef struct ClassLocalProfile {

long CfilePointer; } ClassLocalProfile; HandleTo(LocalProfile);

extern long LocalProfile_openProfile(struct HLocalProfile *);

extern void LocalProfile_setPublicKey(struct HLocalProfile *,HArrayOfByte *); struct Hjava_lang_String;

extern struct Hjava_lang_String

*LocalProfile_getAttrib(struct HLocalProfile *,struct Hjava_lang_String *); extern struct Hjava_awt_Color

*LocalProfile_getFavoriteColor(struct HLocalProfile *); extern struct Hjava_awt_String

*LocalProfile_setAttrib(struct HLocalProfile *,struct Hjava_lang_String *,struct Hjava_lang_String *);

#endif

Первая структура (struct) - это представление класса Java на C. Как вы помните, собственные методы ведут себя так же, как другие методы, и потому могут обращаться к частным элементам класса, который их содержит. Структура "ClassLocalProfile" делает это возможным. Заметьте, что эта структура передается всем нашим функциям в добавление к тем параметрам, которые содержат эквивалентный собственный метод Java. Это гарантирует правильное скрытие данных и в то же время позволяет нашим функциям C быть истинными элементами нашего класса.

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

Типы Java должны быть конвертированы в правильные типы C.

Мы должны иметь возможность обращаться к другим элементам класса из DLL.

Нам нужны механизмы перевода массивов Java в C.

Мы должны иметь возможность создавать объекты Java из DLL и обращаться к методам общих элементов и переменным в существующих объектах Java.

Нам нужно выдавать исключения из DLL.

Внашем примере все это делается. Давайте начнем с простейших функций и постепенно перейдем к самым трудным для выполнения на C. Два наших собственных метода, определенных

смодификатором private, validProfile и openProfile, наиболее просты для выполнения, потому что для них нужно знать только, как переводить примитивные типы Java в типы C. В табл. 12-9 показано, как делать этот перевод:

Таблица 12-9. Перевод примитивных типов Java в типы C

Примитивный тип Java Эквивалент в C

boolean

long

char

unicode

byte

char

short

short

int

long

long

int64_t

float

float

double

double

Ⱦɚɧɧɚɹ ɜɟɪɫɢɹ ɤɧɢɝɢ ɜɵɩɭɳɟɧɚ ɷɥɟɤɬɪɨɧɧɵɦ ɢɡɞɚɬɟɥɶɫɬɜɨɦ %RRNV VKRS Ɋɚɫɩɪɨɫɬɪɚɧɟɧɢɟ ɩɪɨɞɚɠɚ ɩɟɪɟɡɚɩɢɫɶ ɞɚɧɧɨɣ ɤɧɢɝɢ ɢɥɢ ɟɟ ɱɚɫɬɟɣ ɁȺɉɊȿɓȿɇɕ Ɉ ɜɫɟɯ ɧɚɪɭɲɟɧɢɹɯ ɩɪɨɫɶɛɚ ɫɨɨɛɳɚɬɶ ɩɨ ɚɞɪɟɫɭ piracy@books-shop.com

Поняв, как переводить примитивные типы из Java в C, мы можем теперь выполнить небольшую работу с помощью LocalProfile_openProfile. Этот метод просто открывает файл, описанный в переменной окружения APPLETPROFILE:

long LocalProfile_openProfile(struct HLocalProfile *this) { char profileName[1024];

strcpy(profileName, getenv(APPLETPROFILE)); return (long) fopen(profileName, r);}

Как вы помните, этот метод, определенный с модификатором private, используется для того, чтобы присвоить значение переменной CFilePointer. Поскольку этот файл потребуется и другим нашим собственным методам, имеет смысл сохранить его как часть объекта. Когда нужно будет к нему обратиться, мы сделаем это с помощью указателя struct HLocalProfile, передаваемом при любом выполнении собственного метода. При компиляции собственного класса-упаковщика генерируется структура struct. Ее имя начинается с "H" и заканчивается именем класса.

Не беспокойтесь, нам не придется использовать эту структуру для того, чтобы добраться до элементов экземпляра собственного упаковщика. Для этого достаточно реализовать функцию _dynamic_method. Ее можно использовать как для экземпляра собственного упаковщика, так и для других экземпляров (вкратце мы это обсудим ниже). Сейчас нас больше всего заботит доступ к элементам данных экземпляра собственного упаковщика. Для того чтобы обратиться к переменной CFilePointer в LocalProfile, воспользуемся приведенным ниже внешним макросом.

Пример 12-3. Простая реализация собственного метода.

long LocalProfile_getAttrib(struct HLocalProfile *this, HJava_lang_String *key) {

FILE *profile = (FILE *)unhand(this)->CFilePointer; /* Остальная часть функции*/

}

Для того чтобы действительно реализовать остальные функции, мы должны уметь обращаться с массивами и строками Java в C. Это объясняется в следующем разделе.

Массивы и строки Java в C

В нашей библиотеке нам часто будут нужны массивы и строки, и файл java/include/*.h содержит необходимые средства для работы с ними. Начнем со строк и с функции LocalProfile_getAttrib. Эта функция будет рассматривать наш файл profile как хеш-таблицу. Она будет искать строку вида key=value, ключом в которой является переданная нам строка. Если функция не сможет найти ключ, она вернет ноль. В противном случае она вернет строку, стоящую сразу после знака = и заканчивающуюся перед первым встреченным пробелом.

Чтобы это сделать, нужно прежде всего задать символьный массив, который описывает нашу строку Java. Если мы найдем искомое значение, его нужно будет перевести из символьного массива в структуре Hjava_lang_String, который оболочка времени выполнения конвертирует в значение типа String. Функции, делающие это, описаны в табл. 12-10, там же приведены еще некоторые доступные нам функции.

Таблица 12-10. Функции Java для работы со строками

Функция Описание

void javaString2CString(Hjava_lang_String *jString, char *buf, int size)

char *makeCString(Hjava_lang_String *jString)

Конвертирует jString в символьный массив buf; последний параметр задает длину.

Возвращает параметр массива на основе jString, который числится иполняющей системой как мусор.

char *allocCString(Hjava_lang_String *jString)

int javaStringLength(Hjava_lang_String *jString)

Hjava_lang_String *makeJavaString (char *s, int size)

void javaString2unicode (Hjava_lang_String *jString, unicode *buf,int, size)

Аналогичен makeCString за исключением того, что используется malloc и вы отвечаете за перераспределение памяти.

Возвращает длину jString.

Делает представление строки Java на C на основе массива s; последний параметр задает длину.

Конвертирует jString в массив buf формата unicode; последний параметр задает размер.

www.books-shop.com

Теперь мы можем написать функцию LocalProfile_getAttrib.

Пример 12-4. Использование строк Java в C.

struct Hjava_lang_String * LocalProfile_getAttrib(struct HlocalProfile *this, Hjava_lang_String *key) {

char Ckey[1024]; char Cvalue[1024];

Hjava_lang_String *Jvalue;

FILE *profile = (FILE *)unhand(this)->CfilePointer; int keyLength = javaStringLength(key); javaString2CString(key, Ckey, keyLength); strcat(Ckey, "=%s");

rewind(profile);

if (fscanf(profile, Ckey, Cvalue) != EOF) { return makeJavaString(Cvalue, strlen(Cvalue));

}

else {

return NULL;}

}

В примере 12-4 мы смогли преодолеть различия между строками Java и C. Теперь пойдем дальше и посмотрим на функции, которые делают массивы Java и C совместимыми. В то время как элементы массивов C представляют собой просто упорядоченный набор указателей, массивы Java являются упорядоченными наборами данных особого типа. Мы не можем просто создать массив C и ожидать, что наша программа на Java поймет, что с ним делать. Мы должны определить, какой тип массива Java мы пытаемся описать в нашей библиотеке. В табл. 12-11 приведена такая информация для простых типов. Объяснения по поводу объектов будут даны, когда мы поймем, как их создавать. Во второй колонке представлена заранее определенная структура, описывающая тип массива. Как и раньше, для того чтобы обратиться к части данных массива, мы можем использовать внешний макрос. В третьей колонке даны константы, описывающие этот тип. Для правильного распределения места константа передается макросу

ArrayAlloc.

Таблица 12-11. Простые массивы Java на C

Массив Java Представление на C Обозначение типа

boolean[]

HArrayOfInt *

T_BOOLEAN

byte[]

HArrayOfByte *

T_BYTE

char[]

HArrayOfChar *

T_CHAR

short[]

HArrayOfShort *

T_SHORT

int[]

HArrayOfInt *

T_INT

long[]

HArrayOfLong *

T_LONG

float[]

HArrayOfFloat *

T_FLOAT

double[]

HArrayOfDouble *

T_DOUBLE

Object

HArrayOfObject *

T_CLASS

Когда мы хотим создать массив Java, мы используем функцию ArrayAlloc, зависящую от двух параметров. Первый параметр определяет тип массива, который описывается в нашей второй колонке. Второй параметр определяет требуемый размер массива. Затем мы снова применяем внешний макрос для того, чтобы реально обратиться к части данных массива. Следующий раздел программы создает массив из десяти байтов и приписывает ему значения от 0 до 9. Как вы помните, байт Java является символом на C, и, следовательно, мы можем заместить байт символом в приведенном ниже фрагменте:

HArrayOfByte *bytes=ArrayAlloc(T_BYTE,10); for (int i=0;i;i++) { unhand(bytes)->data[i]=(char)i;}

Теперь можно перейти к написанию нашей функции LocalProfile_getPubKey(). Воспользуемся переменной окружения APPLETPUBKEY для того, чтобы узнать, где находится файл, и библиотечной функцией stat для выяснения размера. Если файл найти не удается, эта функция возвращает нуль.

www.books-shop.com

Пример 12-5. Массивы Java на C.

HArrayOfByte *LocalProfile_getPubKey(struct HLocalProfile *this) { int i;

FILE *pubKeyFile=NULL; char pubKeyFileName[1024]; char *buf=NULL; HArrayOfByte *pubKey=NULL; int counter=0;

strcpy(pubKeyFileName,getenv("APPLETPUBKEY"));

pubKeyFile=fopen(pubKeyFileName,"r"); if (pubKeyFile==NULL) return NULL;

pubKey=(HArrayOfByte *)ArrayAlloc(T_BYTE,256); buf=unhand(pubKey)-body;

while (!feof(pubKeyFile) && counter) { buf[counter] = (char)fgetc(pubKeyFile); counter++;}

return pubKey;}

Создание и обработка объектов Java

Получив представление о том, как обращаться с простыми типами, мы можем теперь начать создание объектов Java внутри наших библиотек. Это самый запутанный этап написания DLL, потому что именно здесь сильнее всего выявляются различия между Java и C. Разрешить эти проблемы можно с помощью структур и функций данных, приведенных в табл. 12-12 и 12-13 соответственно.

 

Таблица 12-12. Вспомогательные структуры Java

Структура

Описание

ExecEnv Оболочка выполнения. NULL задает текущую оболочку, обычно как раз она и нужна. ClassClass Структура данных, содержащая информацию о классе Java.

Signature Строка C, описывающая, какие параметры имеет данный конструктор/метод.

Таблица 12-13. Вспомогательные функции Java

Функция

Описание

Hobject *execute_java_constructor(ExecEnv *E, char

Создает экземпляр className с

*className,ClassClass *cb, char *signature

параметром set, задаваемым

 

сигнатурой.

ClassClass *FindClass(ExecEnv, char *className,bool_t

Возвращает представление C класса

resolve)

Java className.

long execute_java_static_method(ExecEnv *E, ClassClass

Реализует статический метод

*cb,char *methodName, char signature,...)

methodName класса Java, описанного

 

с помощью cb.

long execute_java_dynamic_method(ExecEnv *E, HObject

Реализует динамический метод

*obj,char *methodName,char signature)

объекта Java obj.

Как можно заметить, то, что очень легко на Java, требует большой работы на C. Из табл. 12-13 видно, что необходимо вызывать различные функции для конструкторов, статических методов и для динамических методов.

Разумеется, язык Java также заставляет нас различать типы функций. Но в Java это делается гораздо изящнее, так же как и просмотр оболочкой времени выполнения Java определений класса прежде его создания, что является еще одним способом проверки корректности действий программиста. Когда мы работаем с библиотеками DLL, мы должны иметь дело с FindClass и с функциями execute_java_constructor.

Чтобы заставить наши параметры передаваться правильно, придется совершить некоторые неочевидные действия. Поскольку методы могут перезагружаться, нужно будет явно описать набор параметров, который нам понадобится передавать. Кроме того, необходимо описать типы параметров и возвращаемые значения, чтобы построить соответствующие представления C. Вот тут-то нам и пригодятся строки сигнатур (signature). Наши вспомогательные функции определяют по сигнатуре, что можно ожидать в качестве параметров и что должно быть возвращено.

www.books-shop.com

Сигнатура состоит из ключей, определенных в табл. 12-13, а также внешних и внутренних скобок, содержащих параметры.

Таблица 12-14. Ключи для сигнатур Java

Ключ Тип Java

Z boolean

Bbyte

Cchar

F float S short V void

[ массив любого типа

L*; любой объект Java; * заменяется на classname

Синтаксис сигнатуры имеет следующий вид: (Parameter Keys) Return Key

Например, метод, не содержащий параметров и ничего не возвращающий, будет иметь сигнатуру ()V. В табл. 12-15 приведены несколько примеров использования сигнатур. Заметьте, что вам не нужно определять тип возвращаемого значения для конструкторов. Будем считать, что класс someClass находится в нашем рабочем каталоге.

Таблица 12-15. Примеры сигнатур

Конструктор/метод Сигнатура

Hashtable

()

Integer(int j)

(I)

Integer(String S)

(Ljava/lang/String;)

boolean S.beginsWith(String S1, int len) (Ljava/lang/String;I)Z

someClass(Hashtable H)

(Ljava/util/Hashtable;)

someClass sC.reverse(someClass s)

(LsomeClass;)LsomeClass;

Получив представление о том, как работают сигнатуры, попробуем воспользоваться динамическим методом класса String. Следующая функция производит сравнение со String с помощью функции compareTo из DLL. Будем считать, что "sample" - это имя нашего собственного класса-упаковщика.

long sample_CbeginsWith(sample *this,Hjava_lang_String *S1,Hjava_lang_String *S2,long len) {

char sig[]="(Ljava/lang/String;I)Z"; return execute_java_dynamic_method((ExecEnv *)NULL,(HObject *) S1, "beginsWith@,sig,S2,len);}

Отметим некоторые особенности, связанные с нашими параметрами для выполнения метода execute_java_dynamic_method. Во-первых, мы передаем NULL в качестве первого параметра. При передаче NULL метод execute_java_dynamic_method будет использовать текущий поток в качестве оболочки выполнения почти всегда, когда это будет нужно. Нашим следующим параметром будет объект, который нужно использовать. При использовании S1 этот собственный метод равносилен S1.compareTo(S2). Следующим параметром является имя метода. Если метод не существует или является статическим, эта функция выдаст исключение. Далее идет сигнатура, которую мы определяем по табл. 12-15. Нашим следующим аргументом будет параметр, который нужно передать, в данном случае S2. Если вы - квалифицированный программист на C, вы заметили по табл. 12-13, что метод execute_java_dynamic_method - функция с переменным числом аргументов. Поскольку мы передаем S2 и len, мы просто приписываем их в конце. Естественно, они должны идти в том же порядке, что и соответствующие ключи в сигнатуре.

ExecEnv и оболочка выполнения

Если вам захочется, вы можете сменить оболочку выполнения и ваш метод будет исполнен не на текущем потоке. Прежде чем вы начнете это делать, помните, что вы имеете доступ к данному потоку Java как к объекту. Это может немного упростить вашу жизнь. Просто обратитесь к

www.books-shop.com

потоку, передав ваш собственный метод как параметр.

Мы передаем параметры конструкторам, динамическим методам и статическим методам одним и тем же способом - создаем сигнатуру и прикрепляем в конце реальные параметры. Конечно, с помощью конструкторов и статических методов мы не можем создать представление объекта, потому что ни одним объект не был реализован. Поэтому методам execute_java_constructor и execute_java_static_method нужна информация о классах.

Структура данных ClassClass содержит информацию о том, какие элементы содержатся в данном классе, и об их атрибутах. Функция FindCLass перенесет эти данные для использования в структуру ClassClass. Применим класс java.awt.Color следующим образом:

ClassClass *cb;

cb=FindClass((ExecEnv *)NULL,"java/awt/Color",TRUE);

Здесь мы снова устанавливаем значение ExecEnv в нуль. Последний параметр позволяет функции FindClass разрешить этот класс со всеми суперклассами, которые он может иметь. Нет смысла этого не делать. Если мы присвоим классу значение false, мы можем не получить все наши элементы.

Когда у нас есть указатель ClassClass для какого-то класса, мы можем применить методы execute_java_static_method и execute_java_constructor. Ниже показано, как с их помощью преобразуется переменная типа String в int:

long sample_strToInt(Hsample *this, Hjava_lang_String *S) { ClassClass *cb;

char sig[]="(Ljava/lang/String;)I"; cb=FindClass((ExecEnv *)NULL,"java/lang/String",TRUE); return (long)

execute_java_static_method((ExecEnv *)NULL, "parseInt",sig,S);}

Теперь мы можем вернуться к библиотеке DLL, которую мы строим, и реализовать собственный метод favoriteColor. Этот метод возвращает объект Color, а это значит, что мы должны создать его в LocalProfile_favoriteColor. Конструктор, которым мы будем пользоваться, выглядит следующим образом:

Color(int red, int green, int blue)

Пример 12-6. Построение объекта Java на C. struct Hjava_awt_Color *

LocalProfile_favoriteColor(HLocalProfile *this) { Hjava_lang_String *RedColorParam; Hjava_lang_String *BlueColorParam; Hjava_lang_String *GreenColorParam;

long red; long green; long blue; char sig[]="(III)LHjava_awt_Color";

char intSig[]="(LHjava_lang_String)I"; ClassClass *Colorcb;

ClassClass *Integercb;

Colorcb=FindClass((ExecEnv *)NULL,"java/lang/Color",TRUE); Integercb=FindClass((ExecEnv *)NULL,"java/lang/Integer",TRUE); RedColorParam=LocalProfile_getAttrib(this,makeJavaString("red",3)); BlueColorParam=LocalProfile_getAttrib(this,makeJavaString("blue",4)); GreenColorParam=LocalProfile_getAttrib(this,makeJavaString("green",5)); red=(long) execute_java_static_method(

(ExecEnv *)NULL, Integercb, "parseInt", intSig, RedColorParam); blue=(long) execute_java_static_method(

(ExecEnv *)NULL, Integercb, "parseInt", intSig, BlueColorParam); green=(long) execute_java_static_method(

(ExecEnv *)NULL, Integercb, "parseInt", intSig, GreenColorParam); return (struct Hjava_awt_Color *) execute_java_constructor((ExecEnv *)NULL,

"Color", Colorcb, sig, red, green, blue);

}

www.books-shop.com

Выше мы построили объект и сразу же вернули его. При желании мы могли бы вызвать динамические методы объекта, присвоив его структуре Hjava_lang_Color и затем передав ее методу execute_java_dynamic_method.

Конструкторы и сборка мусора

Сборка мусора осуществляется внутри DLL. Когда вы создаете экземпляр с помощью execute_java_constructor, вам нужно привести его и присвоить соответствующему указателю структуры. Это требование применимо также и к массивам. Если вы используете не ArrayAlloc, а массивы C, сборщик мусора не знает, что он должен сохранять распределенную память.

Выдача исключений

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

Для того чтобы выдавать исключения из DLL, будем использовать функцию SignalError. Она содержит следующие аргументы:

void SignalError(ExecEnv *e, char *ExceptionName, char *description);

Теперь мы можем написать последнюю функцию нашего класса LocalProfile - setAttrib. Этот метод должен читать атрибут "write" из файла profile и проверять, разрешена ли запись. Если запись запрещена, метод выдает исключение.

Пример 12-7. Выдача исключений из DLL.

void LocalProfile_setAttrib(struct HLocalProfile *this, Hjava_lang_String * key, Hjava_lang_String * value Hjava_lang_String *writeAllowed; writeAllowed=LocalProfile_getAttrib(this, makeJavaString("write",5));

if (strcmp("yes",makeCString(writeAllowed))!=0) { SignalError(0, "profileWriteUnAllowedException", NULL); } else {writeToProfile(this,key,value);}

}

Компиляция и использование DLL

Мы уже написали всю программу для собственных методов; теперь пора ее откомпилировать. Для начала покажем, как это делать в Windows 95/NT. Для компиляции DLL вам понадобится Visual C++ версии 2.0 или выше. Просто перейдите в командную строку DOS и введите следующее:

C:cl LocalProfile.c profile.c -Feprofile.dll -MD -LD javai.lib

Затем добавьте местоположение profile.dll в переменной окружения LIB.

Пользователи Solaris должны ввести следующее (если Java находится в /usr/local/java):

cc-G -I/usr/local/java/include -I/usr/local/java/include/solaris LocalProfile.c profile.c -o libprofile.so

СОВЕТ Третий аргумент должен быть приспособлен к различным объектам UNIX. Подробное описание того, как это сделать, вы найдете в документации к вашему JDK.

Когда библиотека DLL откомпилирована, вы готовы к тому, чтобы использовать класс LocalProfile в своей программе. Если вы хотите использовать его с апплетами, класс и библиотека DLL должны быть внутри CLASSPATH на локальной машине. Помните, что возможности связаться с DLL будут зависеть от того, разрешает ли это поддерживающий Java Web-броузер.

Что дальше?

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

www.books-shop.com

Глава 13

Работа с сетью на уровне сокетов и потоков

Сокеты Несвязываемые датаграммы Потоки

Входные потоки Выходные потоки Разнообразие потоков Потоки данных

Разбор данных текстового потока Взаимодействие InterApplet c каналами

До сих пор мы обсуждали основные возможности работы с самим языком Java, пакеты, включенные в Java API, работу с графикой и окнами, а также основные свойства потоков в Java. Теперь пора немного расширить наш кругозор и обсудить пакет java.net, который позволит вам сделать свои апплеты и программы на Java открытыми для взаимодействия по сети с другими компьютерами.

www.books-shop.com

Интерес к Java в большой степени объясняется тем, что два свободно доступных Webброузера, Netscape и Hotjava, могут загружать и выполнять Java-апплеты по Интернет. Апплеты очень тесно связаны с Интернет и вообще с сетями. Поэтому совсем не удивительно, что Java API включает в себя большой пакет сетевых классов. Разумеется, поскольку апплеты не могут загружать данные из файлов локального компьютера, они должны хранить данные в сети и возвращать их оттуда. Сетевые классы, содержащиеся в API, можно разделить на две основные группы: классы, занимающиеся сокетами, и классы, занимающиеся URL. Мы обсудим использование сокетов в этой главе, а использование URL - в главе 14, "Связь по сети с помощью

URL".

Сокеты (sockets) не реализуют метод передачи данных - они создают экземпляры более общих классов ввода/вывода, называемых потоками (threads), которые выполняют эту работу. Сокеты можно сравнить с телефонными проводами. Продолжая эту аналогию, можно сравнить выходные потоки с голосом, а входные потоки - с ухом. Сокет переносит данные (ваш голос) по сети; потоки занимаются тем, что закладывают данные в сокет и выдают их наружу. Потоки полезны в своем деле, хотя если вы будете их использовать при программировании апплетов, то скорее всего в комбинации с сокетом или при взаимодействии между апплетами.

СОВЕТ Фрагменты кода, приводимые в качестве примеров в этой главе, помещены на диск CDROM, прилагаемый к книге. Этим диском могут пользоваться те из читателей, кто работает с Windows 95/NT или Macintosh; пользователи UNIX должны обращаться к Web-странице Online Companion, на которой собраны сопроводительные материалы к этой книге (адрес http://www.vmedia.com/java.html).

Сокеты

Идея сокета неразрывно связана с TCP/IP - протоколом, использующимся в Интернет. По существу, сокет является непрерывной связью данных между двумя хостами сети. Он определяется сетевым адресом конечного компьютера (endpoint), а также портом на каждом хосте. Компьютеры в сети направляют приходящие из сети потоки данных в специальные принимающие программы, присваивая каждой программе отдельный номер - порт программы. Аналогично, когда генерируются выходные данные, программе, инициирующей передачу данных, присваивается номер порта для транзакции. Удаленный компьютер не может ответить на ввод данных. В TCP/IP резервируются определенные номера портов для специальных протоколов - например, 25 для SMTP и 80 для HTTP. Все номера портов, меньшие 1024, зарезервированы на каждом хосте для системного администратора.

Java API реализует класс для осуществления взаимодействия сокетов - java.net.Socket. Следующий фрагмент программы использует самый простой конструктор:

try {

// создание связи между сокетами

Socet s = new Socket("www.vmedia.com",25) /*

эта часть программы взаимодействует с сокетом

*/

//закрытие связи между сокетами s.close();

}catch (UnknownHostException e) {

//хост неизвестен

}catch (IOException e) {

//ошибка ввода/вывода во время связи

Таблица 13-1. Конструкторы класса Socket

Конструктор

Описание

Socket(String, int)

Имя хоста и порт для связи.

Socket(String, int, boolean) Имя хоста, порт и булевский указатель сокета: для потоков (true) или для датаграмм (false).

Socket(InetAddress, int)

Интернетовский адрес и порт для связи.

Socket (InetAddress, int,

Интернетовский адрес, порт и булевский указатель сокета: для

boolean)

потоков (true) или для датаграмм (false).

www.books-shop.com

При создании сокета можно указать, с каким хостом соединяться: либо с помощью переменной типа String, содержащей имя хоста, либо заданием специального класса, java.net.InetAddress. Какая разница между этими двумя способами? Для того чтобы полностью это понять, вам понадобятся некоторые знания о TCP/IP.

Каждому хосту в сети, осуществляющей связь по протоколу TCP/IP, в том числе Интернет, присвоен уникальный численный идентификатор, называемый IP-адресом. IP-адрес состоит из четырех байтов и в общем случае представляется восьмеричным числом с точками - например, 127.0.0.1. Хотя IP-адреса очень удобны для общения между собой компьютеров, людям запоминать их и регулярно использовать очень трудно. Чтобы облегчить использование ресурсов сети, в Интернет используется система DNS (Domain Name System). Человек задает имя хоста, и его компьютер запрашивает местный DNS-сервер, который определяет по данному имени IPадрес. Класс Socket позволяет задать либо имя хоста в форме строки, либо IP-адрес в форме

InetAddress.

СОВЕТ InetAddress для данного сокета можно определить, воспользовавшись методом getInetAddress, определенном на классе Socket. Если вы хотите открыть новое соединение с той же машиной, возможно, немного быстрее будет вместо имени хоста воспользоваться InetAddress, чтобы избежать дополнительного преобразования DNS.

Вас, возможно, удивляет, почему пакет java.net использует класс InetAddress, а не 4-битовый массив, содержащий IP-адрес. Дело в том, что класс InetAddress дополнительно предоставляет методы, которые действуют на IP-адрес; например, метод getHostName выполняет обратное преобразование DNS на классе InetAddress и возвращает имя хоста. InetAddress не содержит общих конструкторов, но зато он дает две статические функции: getByName и getAllByName, которые берут имя хоста и возвращают все InetAddress, относящиеся к этому хосту.

Сокеты и обеспечение безопасности апплета

Netscape Navigator 2.0 не разрешает ненадежным апплетам, загруженным с удаленных серверов, открывать сокеты к любой машине в Интернет. Апплет может открыть только сокеты к хосту, с которого он был загружен. Это свойство не позволяет апплетам генерировать нежелательное или незамеченное перекачивание данных с каждой машины, на которой они запускаются. Надежные апплеты, проверенные цифровой авторизацией, имеют меньше ограничений, чем ненадежные. Они получают возможность взаимодействовать с любым хостом в Интернет. Однако к моменту написания этой книги компания Sun еще не изобрела и не разработала механизма идентификации надежных апплетов, так что все удаленные апплеты пока считаются ненадежными.

Ограничения, связанные с обеспечением безопасности апплетов, не применяются к апплетам, загруженным из каталога, относящегося к локальному классу данного пользователя. Один из способов, с помощью которого пользователи могут обойти это ограничение, - установить апплет в каталогах своих локальных классов. Другой способ - написать сервер, который запускается на том хосте, где находится апплет, и перенаправляет данные, приходящие на определенный порт, по новому назначению. Третий способ - иметь на одной Web-странице множество апплетов, каждый из которых будет находиться на том хосте, с которым должен взаимодействовать апплет. Поскольку апплеты, находящиеся на одной Web-странице, могут взаимодействовать между собой, каждый апплет сможет связаться со всеми хостами других апплетов. Правда, многие операционные системы ограничивают число поддерживаемых соединений, так что последний метод может не сработать.

Втабл. 13-12 перечислены методы для класса Socket. Особенно важны методы getInputStream

иgetOutputStream, потому что их используют для реальной связи с удаленным хостом. Вообще потоки осуществляют перенос данных; в примере 13-1 мы используем их для переноса данных между хостами по сети.

 

Таблица 13-2. Методы класса Socket

Метод

Описание

close()

Закрывает сокет.

InetAddress getInetAddress()

Возвращает интернетовский адрес компьюте-ра на

 

другом конце сокета.

int getLocalPort()

Возвращает номер локального порта, с кото-рым связан

 

данный сокет.

InputStream getInputStream

Возвращает InputStream, относящийся к данному сокету.

www.books-shop.com