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

Программирование в сетях Windows

.pdf
Скачиваний:
538
Добавлен:
11.03.2015
Размер:
3.02 Mб
Скачать

68

ЧАСТЬ I Устаревшие сетевые API

Как видно, для разработки серверного приложения почтового ящика требуется совсем немного вызовов API-функций.

Серверные процессы создают почтовые ящики с помощью вызова APIфункции CreateMailslot, которая определена так:

HANDLECreateMailslot(

LPCTSTRIpName,

DWORDnMaxMessageSize, DWORD/ReadTimeout,

LPSECURITY_ATTRIBUTES IpSecurltyAttrlbutes

Параметр IpName задает имя почтового ящика. Оно должно иметь следующий ВИД:

\\.\Mailslot\[путь]имя

Заметьте: имя сервера представлено точкой, что означает локальный компьютер. Это необходимо, поскольку нельзя создать почтовый ящик на удаленном компьютере. В параметре IpName имя обязательно уникально, но может быть простым или включать полный путь.

Параметр nMaxMessageSize задает максимальный размер (в байтах) сообщения, которое может быть записано в почтовый ящик. Если клиент записывает сообщение большего размера, сервер не видит это сообщение. Если задать значение 0, то сервер будет принимать сообщения любого размера.

Операции чтения (подробнее — далее в этой главе) могут выполняться с блокировкой почтового ящика или без таковой, в зависимости от параметра IReadTimeout, задающего количество времени (в миллисекундах), в течение которого операции чтения ждут входящих сообщений. Значение MAILSLOT_ WAIT_FOREVER позволит заблокировать операции чтения и заставит их бесконечно ожидать, пока входящие данные не станут доступны для чтения. Если задать значение 0, операции чтения возвращаются немедленно.

Параметр IpSecurityAttibutes определяет права доступа к почтовому ящику. В Windows 95 и Windows 98 он должен иметь значение NULL, потому что в этих ОС система безопасности к объектам не применима. В Windows NT и Windows 2000 этот параметр реализован частично, поэтому следует также присвоить ему NULL.

Почтовый ящик относительно безопасен только при локальном вводевыводе, когда клиент пытается открыть этот ящик, используя точку (.) в качестве имени сервера. Клиент может обойти систему безопасности, задав вместо точки фактическое имя севера, как при удаленном вызове ввода-вы- вода. Параметр IpSecurityAttibutes не реализован для удаленного ввода-выво- да в Windows NT и Windows 2000 из-за больших издержек, связанных с формированием аутентифицированного сеанса между клиентом и сервером при каждой отправке сообщения. Таким образом, почтовые ящики только частично соответствуют модели безопасности Windows NT и Windows 2000, реализованной в стандартных файловых системах. В итоге, любой клиент почтового ящика в сети может отправлять данные серверу.

ГЛАВА 3 Почтовые ящики

69

После создания почтового ящика с действительным описателем можно читать данные. Сервер — единственный процесс, который может читать данные из почтового ящика. Для он должен вызвать "№т32-функцию ReadFile, определенную так:

BOOL ReadFile(

. HANDLE hFile,

LPVOID ipBuffer, DWORDnNumberOfBytesToRead, LPDWORDlpNumberOfBytesRead,

LPOVERLAPPEDlpOverlapped

);

CreateMailslot возвращает описатель hFile. Параметры IpBuffer и nNumberOJBytesToRead определяют, сколько данных может быть считано из почтового ящика. Важно задать размер этого буфера большим, чем параметр nMaxMessageSize из API-вызова CreateMailslot. Кроме того, размер буфера должен превышать размеры входящих сообщений, иначе ReadFile выдаст код ошибки ERROR_INSUFFICIET_BUFFER. Параметр IpNumberOJBytes возвращает количество считанных байтов по завершении работы ReadFile.

Параметр lpOverlapped позволяет считывать данные из почтового ящика асинхронно. Он использует Win32-MexaHH3M перекрытого ввода-вывода (overlapped I/O), подробно описанный в главе 4. По умолчанию, выполнение ReadFile блокирует (ожидает) ввод-вывод, пока данные не станут доступны для чтения. Перекрытый ввод-вывод применим только в Windows NT и Windows 2000, в Windows 95 или Windows 98 присвойте параметру lpOverlapped значение NULL. В листинге 3-1 показано, как написать простой сервер почтовых ящиков.

Листинг 3-1. Пример сервера почтовых ящиков

// Served.срр

«include <windows.h> «include <stdio.h>

void main(void)

{

HANDLE Mailslot; char buffer[256];

DWORD NumberOfBytesRead;

// Создание почтового ящика

if ((Mailslot = CreateMailslot("\\\V\\Mailslot\\Myslot", o,

MAILSLOT_WAIT_FOREVER, NULL)) == INVALID_HANDLE_VALUE)

{

printf("Failed to create a mailslot Xd\n", GetLastErrorO);

см.след.стр.

-1МЧ* i o i устаревшие сетевые API

Листинг 3-1. (продолжение)

/I Бесконечное чтение данных из почтового ящика while(ReadFile(Mailslot, buffer, 256, &NumberOfBytesRead,

NULL) i= 0)

{

p n n t f ( X *s\n , NumberOfBytesRead, buffer),

Клиент почтовых ящиков

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

1Открыть описатель-ссылку на почтовый ящик, в который нужно отправить данные, с помощью API-функции CreateFile

2Записать данные в почтовый ящик, вызвав API-функцию WnteFile

3Закрыть описатель почтового ящика с помощью API-функции CloseHandle

Как уже говорилось, клиенты почтовых ящиков соединяются с серверами без установления соединения Когда клиент открывает описатель-ссыл- ку на почтовый ящик, он не устанавливает связь с сервером почтового ящика На почтовые ящики ссылаются путем вызова API-функции CreateFile, определенной так

HANDLE CreateFile( LPCTSTR lpFlleName, DWORD dwDeslredAccess, DWORD dwShareMode,

LPSECURITY.ATTRIBUTES lpSecurityAttributes,

DWORD dwCreationDisposltion, DWORD dwFlagsAndAttrlbutes, HANDLE hTemplateFlle

),

Параметр lpFlleName описывает один или несколько почтовых ящиков, в которые можно поместить данные Для этого укажите имя ящика в одном из форматов, описанных в этой главе Правила именования почтовых ящиков таковы

И \\.\mailslot\«umi — определяет локальный почтовый ящик на том же компьютере,

Ш\\wmi_cepeepa\mailslot\uMn определяет удаленный сервер почтового ящика с именем имя_сервера,

Ш\\имя_домена\та.Ив\о?\имя — определяет все почтовые ящики с именем имя в домене имя_домена,

Ш\V\mailslot\ii)»wi — определяетвсепочтовыеящикис именем имя восновном домене системы

jl A DM J I ЮЧТОВЫе ЯЩИКИ

И

Параметр dwDestredAccess должен иметь значение GENERICJWR1ТЕ, потому что клиент может только записывать данные на сервер Параметр dwShareMode обязан иметь значение FILE SHARE_READ, позволяя серверу открывать и выполнять операции чтения из почтового ящика Значение параметра ipSecuntyAttnbutes не влияет на почтовые ящики — следует задать A7 LL Флаг dwCreationDisposition должен быть равен OPEN_EXISTING Это удобно, ког/ клиент и сервер функционируют на одном и том же компьютере Если се^ вер не создал почтовый ящик, API-функция CreateFile вернет ошибку Параметр dwCreationDisposition не имеет значения, если сервер работает уд щенно Параметр divFlagsAndAttnbutes должен иметь значение FILE_ ATTRIBUTE NORMAL, a hTemplateFile — значение NULL

После успешного создания описателя можно помещать данные в почтовый ящик Помните, что клиент может только записывать данные в почтовый ящик с помощью Win32^yHK4HH WnteFile

BOOL WnteFile( HANDLE hFlle,

LPCVOID lpBuffer,

DWORD nNumberOfBytesToWnte, LPDWORD lpNumberOfBytesWntten,

LPOVERLAPPED lpOverlapped

),

Параметр hFile — это описатель-ссылка, возвращаемый функцией CreateFile Параметры lpBuffer и nNumberOJBytesToWnte определяют, сколько байт будет отправлено от клиента серверу Максимальный размер сообщения — 64 кб Если описатель почтового ящика создан с указанием домена или звездочки, размер сообщения не должен превышать 424 байта в Windows NT и Windows 2000, или 64 кб — Windows 95 и Windows 98 Если клиент попытается отправить сообщение большего размера, функция WnteFile вернет ошибку ERROR_BAD_NETPATH (чтобы узнать код ошибки, вызовите функцию GetLastErrof) Это происходит потому, что сообщение посылается всем серверам сети как широковещательная дейтаграмма Параметр ipNumberOfBytesWritten возвращает количество байт, отправленных серверу после завершения функции WnteFile

Параметр lpOverlapped позволяет записывать данные в почтовый ящик асинхронно Поскольку почтовые ящики обмениваются данными без установления соединения, функция WnteFile не блокирует ввод-вывод На клиенте этот параметр должен быть равен NULL В листинге 3-2 приведен пример простого клиента почтового ящика

Листинг 3-2. Пример клиента почтового ящика // Client cpp

"include <windows h> «include <stdio h>

m n ( l n t argc, char .argv[]>

ш с/шд

72

ЧАСТЬ I Устаревшие сетевые API

Листинг 3-2.

{продолжение)

{

 

 

,

HANDLE

Mailslot;

DWORD

BytesWritten;

CHAR ServerName[256];

//

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

if

(argc <

2)

{

 

 

 

printf("Usage: client <server name>\n"); return;

}

sprintf(ServerName, "\\\\Xs\\Mailslot\\Myslot", argv[1]);

if ((Mailslot = CreateFile(ServerName, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE)

{

printf("CreateFile failed with error Xd\n", GetLastErrorO); return;

if (WriteFile(Mailslot, "This is a test", 14, &BytesWritten, NULL) == 0)

{

printf("WriteFile failed with error Xd\n", GetLastErrorO); return;

printf("Wrote Xd bytes\n", BytesWritten);

CloseHandle(Mailslot);

}

Дополнительные API-функции почтовых ящиков

Сервер почтового ящика может использовать две дополнительные API-фун- кции для взаимодействия с почтовым ящиком: GetMailslotlnfo и SetMailslotlnfo. Функция GetMailslotlnfo возвращает размер сообщения, когда оно прибывает в почтовый ящик. Приложения используют эту возможность, чтобы динамически настраивать свои буферы для входящих сообщений изменяющейся длины. GetMailslotlnfo может также применяться для опроса наличия входящих данных:

BOOL GetMailslotInfo(

 

 

HANDLE hMailslot,

 

 

LPDWORD

lpMaxMessageSize,

 

 

LPDWORD

lpNextSize,

'*"

ПТ' ^ ' < « - » •"»«'

Г Л А ВА 3 Почтовые ящики

73

LPDWORD lpMessageCount,

LPDWORD lpReadTimeout

);

Параметр hMailslot указывает почтовый ящик, возвращенный вызовом др!_функции CreateMailslot. Параметр ipMaxMessageSize задает, сообщение какого размера (в байтах) можно записать в почтовый ящик. Параметр 1рNextSize указывает на размер следующего сообщения (в байтах). Параметр

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

Если вы хотите предотвратить блокирование при вызове функции Rea- dFile,используйтеперекрытыйввод-выводWin32.ПараметрIpMesssageCount задает буфер, куда записывается общее количество сообщений, ожидающих прочтения. Этот параметр также задействуют для проверки наличия данных. Параметр lpReadTimeout указывает на буфер, возвращающий время таймаута (в миллисекундах), в течение которого операция чтения ждет записи сообщения в почтовый ящик.

API-функция SetMailslotlnfo задает значение тайм-аута для почтового ящика, в течение которого операция чтения ожидает входящих сообщений. Таким образом, приложение может изменить способ чтения от блокирующего к неблокирующему или наоборот. Параметр SetMailslotlnfo определен так:

BOOL SetMailslotInfo( HANDLEhMailslot, DWORDIReadTlmeout

);

Параметр hMailslot указывает почтовый ящик, возвращаемый вызовом APIфункции CreateMailsot. Параметр IReadTimeout определяет количество времени (в миллисекундах), в течение которого операция чтения ожидает записи сообщения в почтовый ящик. Если оно равно 0, то в отсутствие сообщений операциичтениявозвращаютсянемедленно,еслиMA1LSLOT_WAIT_FOREVER— будут ждать бесконечно долго.

Платформа и производительность

Почтовые ящики в Windows 95 и Windows 98 имеют ограничения: они именуются по правилу «8.3», не позволяют отменить блокирующие запросы вво- Да-вывода, вызывают утечки памяти по истечении тайм-аута.

Правила именования «8.3»

indows 95 и Windows 98 ограничивают размеры имен почтовых ящиков форматом «8.3». Это создает проблемы совместимости между Windows 95

74

ЧАСТЬ I Устаревшие сетевые API

или Windows 98 и Windows NT или Windows 2000 Например, если вы попытаетесь создать или открыть почтовый ящик с именем \\ \Mailslot\Mymailslot, Windows 95 создаст почтовый ящик \\ \Mailslot\Mymailsl и будет ссылаться на него Функции CreateMmlslot и CreateFile выполнятся успешно, несмотря на усечение имени Если затем вы отправите сообщение от Windows 2000 к Windows 95 или наоборот, оно не будет получено из-за несоответствия имен почтовых ящиков Если клиент и сервер работают на компьютерах с Windows 95, проблем не возникает — имя усекается как на клиенте, так и на сервере Чтобы избежать осложнений, не создавайте почтовые ящики с длинными именами

Неспособность отменить блокирующие запросы ввода-вывода

Эта проблема существует в Windows 95 и Windows 98 Серверы почтовых ящиков для получения данных вызывают функцию ReadFile Если почтовый ящик создается с флагом MAILSLOT_WAIT_FOREVER, запросы блокируются на неопределенное время, пока данные не станут доступны При невыполненном запросе функции ReadFile серверное приложение при завершении зависает Единственный способ снять приложение — перезагрузить Windows Для решения этой проблемы заставьте сервер открыть описатель его почтового ящика в отдельном потоке и отправить данные, чтобы прервать блокирующий запрос чтения (см листиш 3-3)

Листинг 3-3. Исправленный сервер почтовых ящиков // Serveг2 срр

((include <windows h> «include <stdio h> «include <conio h>

BOOL StopProcessmg,

DWORD WINAPI ServeMailslot(LPVOID lpParameter), void SendMessageToMallslot(void),

void raam(void) {

DWORD Threadld,

 

HANDLE

MailslotThread,

StopProcessmg

= FALSE,

MailslotThread = CreateThread(NULL, 0, ServeMailslot, NULL,

0,

&ThreadId),

 

 

i Mi] itji

printf(

Press a

key to stop the server\n );_, „

_getch(),

Б.8» I*

// Флагу StopProcessmg присваивается TRUE/'чТООМ при завершении

Г Л А ВА 3 Почтовые ящики

75

Листинг3-3. (продолжение)

Ц функции ReadFile поток сервера закончился StopProcessing = TRUE,

//Отправка сообщения почтовому ящику, чтобы прервать

//вызов функции ReadFile на сервере

"* SendMessageToMailslotO,

// Ожидание завершения выполнения потока сервера

if (WaitForSingleObject(MailslotThread, INFINITE) == WAIT_FAILED)

{

pnntf( WaitForSingleOb]ect failed with error Xd\n , GetLastErrorO),

return,

//Функция ServeMailslot

//Описание

//Эта рабочая функция сервера почтового ящика

//для обработки всего входящего ввода-вывода ящика

DWORD WINAPI ServeMailslot(LPVOID lpParameter)

{

char buffer[2048], DWORD NumberOfBytesRead, DWORD Ret,

HANDLE Mailslot,

if ((Mailslot = CreateMailslot( \\\\ \\mailslot\\myslof, 2048, MAILSLOT_WAIT_FOREVER, NULL)) == INVALID_HANDLE_VALUE)

{

pnntf( Failed to create a MailSlot Xd\n , GetLastErrorO); return 0,

while((Ret = ReadFile(Mailslot, buffer, 2048, &NumberOfBytesRead, NULL)) i= 0)

{

if (StopProcessing) break,

printf( Received %d bytes\n , NumberOfBytesRead);

CloseHandle(Mailslot),

смследстр

76

ЧАСТЬ I Устаревшие сетевые API

Листинг 3-2. (продолжение)

return 0;

>

//Функция SendMessageToMailslot

//Описание.

//Функция SendMessageToMailslot отправляет простое сообщение

//

серверу,

чтобы прервать блокирующий вызов API-функции ReadFile

//

 

 

 

void

SendMessageToMailslot(void)

{

 

 

 

 

HANDLE

Mailslot;

 

DWORD

BytesWritten;

 

if ((Mailslot

= CreateFile("\\\\.\\mailslot\\inyslot",

GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE)

{

printf("CreateFile failed with error Kd\n", GetLastErrorO); return;

}

if (WnteFile(Mailslot, "STOP", 4, &BytesWntten, NULL) == 0)

{

pnntf("WriteFile failed with error %d\n", GetLastErrorO); return;

CloseHandle(Mailslot);

/V)1Ol9l£i<

Утечки памяти

Утечки памяти в Windows 95 и Windows 98 происходят при использовании значений тайм-аута в почтовых ящиках. Если почтовый ящик создается функцией CreateMailslot со значением тайм-аута, большим 0, функция ReadFile вызывает утечку памяти, когда время ожидания истекает, и функция возвращает FALSE. После многократных вызовов функции ReadFile система становится нестабильной и последующие вызовы этой функции, время ожидания которых истекает, начинают возвращать TRUE. В результате система больше не может выполнять другие MS-DOS-приложения Для решения этой проблемы создавайте почтовый ящик со временем ожидания, равным 0 или MAILSLOT_WAIT_FOREVER. Это не позволит приложению использовать механизм тайм-аутов, вызывающий утечки памяти.

В базе знаний Microsoft, к которой можно обратиться по адресу http // support.microsoft.com/support/search, описаны следующие проблемы и ограничения.

Г Л А ВА 3 Почтовые ящики

77

Я Q139715 — функция ReadFile возвращает неверный код о ш и б к и

для почтовых ящиков. Когда сервер открывает почтовый ящик функцией CreateMmlslot, задает тайм-аут и затем получает данные функцией ReadFile, последняя выдает ошибку 5 (доступ отклонен), если данные недоступны.

Ш Q192276 функция GetMailslotlnfo возвращает неверное значение параметра ipNextSize. Если вызвать эту API-функцию в Windows 95 OEM Service Release 2 (OSR2) или Windows 98 без установки компонента сетевого клиента, вы получите неправильное значение (обычно в миллионах) или отрицательное число для параметра IpNextSize Если вызвать функцию повторно, она обычно начинает возвращать правильное значение.

Q170581 — почтовый ящик, созданный в Windows 95, вмещает только 4093 байта. Вызов API-функции WriteFile для записи более 4093 байт в почтовый ящик, созданный на рабочей станции Windows 95, не завершается успешно.

ШQ131493 — функция CreateFile и почтовые ящики. В документации на API-функцию CreateFile неверно описаны возможные значения, возвращаемые ею при открытии клиентской части почтового ящика

Резюме

Сетевая технология почтовых ящиков предоставляет приложениям простой однонаправленный механизм межпроцессной связи с использованием перенаправителя Windows. Наиболее полезная возможность — широковещательная передача сообщений одному или нескольким компьютерам в сети. Впрочем, почтовые ящики не обеспечивают надежную передачу данных.

Если вы хотите надежно передавать данные с помощью перенаправителя Windows, используйте именованные каналы, которым посвящена следующая глава.