MailSlot и все-все-все

MailSlot и все-все-все

Сетевые технологии все глубже проникают в наше повседневное существование. Однако иногда они принимают довольно интересные формы. Хочется поведать вам об одной из них, называемой MailSlot.

Наверняка многие о ней слышали, но как-то пропускали все мимо ушей. Хотя, на мой взгляд, технология довольно-таки интересна. Суть ее заключается в следующем. Программа может создать на компьютере некий объект, собственно и называемый Mailslot'ом, доступ к которому может получить уже не только она сама, но и другие программы — причем необязательно выполняющиеся на том же компьютере. Это значит, что доступ к mailslots можно получить в том числе из сети. Вот это и представляет наибольший интерес. И вот почему. Большинство файрволлов при настройках по умолчанию (ну или с незначительными изменениями, не затрагивающими тонкие механизмы их функционирования) свободно пропускают трафик, если он идет через mailslots. А это, согласитесь, открывает весьма широкие возможности для творчества. Технология эта создавалась для обмена данными между программами.

А теперь давайте попробуем посмотреть на этого "зверя" поближе. Какие средства для этого имеются? Используем широко известную MFC и Visual C++. Достоинством Windows является то, что при определенном уровне абстракции все, с чем может взаимодействовать программа, можно считать устройством. Это файлы, последовательные и параллельные порты и пр. Большинство устройств открывается при помощи функции CreateFile, как ни странно это смотрится. Но для начала создаем mailslot:
HANDLE hMailslot = Create Mailslot(MailslotName, 0, MAIL SLOT_WAIT_FOREVER, NULL);
где hMailslot — хэндл на наш мэйлслот, а MailslotName — имя слота вида \\.\mailslot\ mailslotname,. Теперь, когда mailslot создан, можно открыть это "устройство" функцией CreateFile. Пример:
HANDLE hMailslot = Create File(MailslotName, GENERIC_ READ | GENERIC_WRITE, FILE_ SHARE_READ | FILE_SHARE_ WRITE, NULL, OPEN_EXISTING, 0, NULL);
где соответственно Mailslot-Name — имя слота в виде \\servername\mailslot\mailslotname. Флаги на открытие означают следующее (второй параметр):
0 — чтение и запись в устройство не предполагается. Используется для изменения параметров.

GENERIC_READ — только чтение.
GENERIC_WRITE — только на запись. Это значение не предполагает автоматической доступности устройства для чтения.
GENERIC_READ | GENERIC_WRITE — устройство доступно и для чтения, и для записи.
Третий параметр определяет режим совместного использования устройства:
0 — монопольный доступ.
FILE_SHARE_READ — устройство доступно только для чтения. Вызовет ошибку, если устройство уже открыто.
FILE_SHARE_WRITE — устройство доступно для записи. Вызовет ошибку, если устройство уже открыто для чтения.
FILE_SHARE_READ | FILE_SHARE_WRITE — устройство доступно и для чтения, и для записи.

Четвертый параметр отвечает за безопасность (разграничение доступа). Для настроек по умолчанию — NULL. Вместо OPEN_EXISTING доступны следующие значения:
CREATE_NEW — создание нового. Если уже существует, функция выдаст ошибку.
CREATE_ALWAYS — создание нового, даже если "устройство" существует. (Т.е. замена на новый).
OPEN_EXISTING — открытие уже существующего. Если таковой отсутствует, произойдет ошибка.
OPEN_ALWAYS — открыть существующий, если нет — создать новый.
TRUNCATE_EXISTING — открывает существующий файл, делая его длину равной 0. Если файл отсутствует, даст ошибку. Возможно использование только с параметром GENERIC_WRITE.

Остальные параметры по отношению к мэйлслотам рассматривать не будем.
Как вы, наверное, уже догадались, как только мэйлслот будет открыт, в него можно писать информацию и считывать оную. Но сначала еще одна функция:
// Размер сообщения.
DWORD Messages;
// Количество сообщений в канале Mailslot.
DWORD Number;
BOOL Return = GetMailslot Info(Mailslot, NULL, &Messa-ges, &Number, NULL);
где Mailslot — хэндл мэйлслота.

Я думаю, назначение и использование понятно — получение информации о наличии сообщений. Писать и считывать данные из слота можно при помощи ReadFile() и WriteFile. А каким образом можно посмотреть открытые у вас на компьютере mailslots? Стандартными (системными) способами, насколько я знаю, никак (если я не прав — поправьте меня). Тогда остаются нестандартные. Напишем для этого небольшую программку. Открываем наш любимый Visual C++ и при помощи MFC AppWizard создаем диалоговое приложение примерно с таким интерфейсом:



Назовем его out.exe. Дополнительно, кроме стандартно предлагаемой кнопки Cancel (которую мы успешно можем переделать в EXIT), нам понадобится еще одна кнопка и Edit-бокс, ID кнопки IDC_BUTTON1:-), обработчик — функция void COutDlg::OnButton1(), ID Edit-бокса IDC_EDIT1, переменная Cstring m_out. Изменять тело функции COutDlg:: OnButton1() нужно следующим образом:

void COutDlg::OnButton1()
{
CFileFind finder;//Создаем объект CFileFind
finder.FindFile("\\\\.\\mailslot\\*");// и ищем все мэйлслоты
BOOL bCountinue = TRUE;
while (bCountinue)
{

bCountinue = finder.Find NextFile();
CString str = finder. GetFileName();
//Выводим информацию
m_out+=str;
m_out+="\r\n";
}
//Цикл завершается, когда все слоты найдены
finder.Close();
UpdateData(FALSE);
}

Видите, поиск мэйлслотов ничем не отличается от поиска файлов. По сути дела, я взял простейшую программу для поиска файлов по маске и прописал в качестве параметров только специфический путь.
Откомпилируйте и запустите программу. Информация вас удивит, можете мне поверить. Но просмотреть мэйлслоты маловато. Ниже хочу привести пример "клиента" и "сервера", работающих через mailslot.

Создаем сервер. При помощи MFC AppWizard создаем диалоговое приложение Server примерно с таким интерфейсом:



ID элементов и обработчики нажатий кнопок приведены в таблице. Кнопка ОК оставлена без изменений:-). Нам понадобятся еще две переменные, принадлежащие классу Cser-verDlg. Первая из них — Mail типа HANDLE — в ней мы будем хранить указатель на созданный мэйлслот и flag типа int — для определения, создавали мы уже этот слот или нет. Необходимо также добавить в класс CServerDlg еще одну функцию (щелкнув правой кнопкой по CserverDlg и выбрав из меню AddMemberFunction). Вот она: void CServer Dlg::Create(LPSTR NameMailslot). Она будет создавать mailslot при запуске сервера.



Итак, нам еще необходимо связать с нашим приложением таймер, что можно сделать при помощи ClassWizavd. Приложение будет по таймеру обращаться к мэйлслоту и проверять наличие нового сообщения. Тела функций следует изменить следующим образом. Обработчик кнопки Start:

void CServerDlg::Onstart()
{
//Запускаем таймер
SetTimer( 1, 1000, NULL );
}
Таймер будет срабатывать каждую секунду. В принципе, этого достаточно, но можно и меньше — это по желанию.
Обработчик кнопки Stop:
void CServerDlg::Onstop()
{
//Закрываем мэйлслот и останавливам таймер
CloseHandle(Mail);
KillTimer( 1 );
}
Функция создания мэйлслота (добавленная нами вручную):
//В нее передается имя создаваемого канала NameMail-slot
void CServerDlg::Create (LPSTR NameMailslot)
{
char temp[10];
m_mess+="Mailslot demo\r\n";
UpdateData(FALSE);
// Создаем канал Mailslot, имеющий имя Name Mailslot, в результате в Mail сохраняется хэндл на созданный канал
Mail = CreateMailslot(Name Mailslot, 0, MAILSLOT_ WAIT_ FOREVER, NULL);
// Если возникла ошибка, выводим ее код и выходим из функции
if(Mail == INVALID_HANDLE_VALUE)
{
m_mess+="CreateMailslot: Error";
itoa(GetLastError(), temp, 10 );
m_mess+=(CString)&temp[0];
m_mess += "\r\n";
UpdateData(FALSE);
return;
}
// Выводим сообщение о создании канала и обновляем данные
m_mess+="Mailslot OK\r\n";
//Устанавливаем флаг успешного создания канала
flag=1;
UpdateData(FALSE);
return ;
}
Эта функция выполняется каждый раз при срабатывании таймера. Но благодаря наличию флага создание канала происходит только один раз.
//Функция получения сообщений
void CServerDlg::OnTimer (UINT nIDEvent)
{
// Код возврата из функций
BOOL Code;
// Размер сообщения.
DWORD Msg;
// Количество сообщений в канале Mailslot
DWORD Number;
char szBuf[512];
char temp[10];
LPSTR MailslotName = "\\\\.\\mailslot\\$Box_1$";
// Количество байт данных, принятых через канал
DWORD cbRead;
//Создаем мэйлслот, если это первый вызов функции
if(flag!=1)
{
CServerDlg::Create(MailslotName);
};
// Определяем состояние канала Mailslot
Code = GetMailslotInfo(Mail, NULL, &Msg, &Number, NULL);
if(!Code)
{
m_mess+="GetMAilslot Error: Error";
itoa(GetLastError(), temp, 10 );
m_mess+=(CString)&temp[0];
m_mess += "\r\n";
UpdateData(FALSE);
return;
}
// Если в канале есть Mailslot сообщения, читаем его и выводим
if(Number != 0)
{
if(ReadFile(Mail, szBuf, 512, &cbRead, NULL))
{
//Выводим принятую строку
m_mess+=(CString)& szBuf[0];
m_mess += "\r\n";
UpdateData(FALSE);
}
else
{
m_mess+="ReadFile: Error";
itoa(GetLastError(),temp,10);
m_mess+=(CString)& temp[0];
m_mess += "\r\n";
UpdateData(FALSE);
return;
}
}
CDialog::OnTimer(nIDEvent);
}

Программа работает следующим образом. После инициализации приложения при нажатии на кнопку Start выполняется CServerDlg::Onstart() и запускается таймер с периодичностью раз в секунду. При первом срабатывании таймера вызывается CServerDlg::OnTimer(). А т.к. flag не равен 1, то из тела CServerDlg::OnTimer() выполняется CServerDlg::Create(), которая, получив в качестве параметра имя создаваемого мэйлслота MailslotName = \\\\.\\ mailslot\\$Box_1$, собственно, его и создает. Далее при следующих срабатываниях таймера просто получаем информацию о новых сообщениях Code = GetMailslot Info(Mail, NULL, &Msg, &Num-ber, NULL); и, если таковые есть, выводим их пользователю. При нажатии на кнопку Stop вызываем CServer Dlg::Onstop(), в которой выключаем таймер и закрываем хэндл на слот.

Все, с сервером разобрались, теперь давайте посмотрим, как нам создать клиента, который, собственно, и будет отсылать информацию на сервер. Как и в предыдущем случае, создаем диалоговое приложение с помощью MFC AppWizard с таким вот (или похожим) интерфейсом и именем Client:



В таблице приведены ID и имена функций-обработчиков:



Предлагаемую стандартную кнопку ОК оставим без изменений. Только желательно выделенной по умолчанию сделать кнопочку Send, чтобы при нажатии на Enter информация отправлялась на сервер, а приложение не закрывалось (сделаем что-то наподобие одностороннего чата:-). И, опять же, как и в предыдущем случае, нам понадобится переменная Mail типа HANDLE в классе CclientDlg, flag типа int, а также функция void CClientDlg::OnConnect(char *Ser-verName), которая будет открывать слот, созданный сервером. Ниже преведены тела функций, которые следует изменить. Начнем с void CClientDlg::OnSend():

void CClientDlg::OnSend()
{
UpdateData(TRUE);
// Буфер для имени канала Mailslot
char ServerName[256];
// Буфер для передачи данных через канал
char *szBuf;
CString tm;
// Количество байт, переданных через канал
DWORD cbWritten;
strcpy(ServerName,"\\\\. \\mailslot\\$Box_1$");
//Подключаемся к серверу, если это первый вызов функции, и сообщаем об этом радостном известии пользователю
if(flag!=1)
{
MessageBox("Connecting...");
//Вызываем функцию подключения
CClientDlg::OnConnect (&ServerName[0]);
};
//Преобразуем введенные данные
szBuf=m_mess.GetBuffer(m_mess.GetLength());
//Отсылаем введенную строку на сервер
WriteFile(Mail, szBuf, strlen (szBuf) + 1,&cbWritten, NULL);
Очищаем окно ввода, чтобы не передавать лишнего при следующем вызове
m_mess="";
UpdateData(FALSE);
return ;
}

Я думаю, в особых пояснениях данная функция не нуждается. Единственное, если кому-то захочется подключаться не к локальному серверу (т.е. если программа Server запущена не на той же машине, что и Client), следует предусмотреть введение пользователем имени сервера. Тогда строка Server Name будет выглядеть следующим образом: \\servername\mailslot\ mailslotname, естественно, учитывая дополнительные "\", чтобы компилятор не ругался:-). Переходим к void CClientDlg: :OnConnect(char *ServerName):

void CClientDlg::OnConnect (char *ServerName)
{
char temp[10],name[256];
CString tm;
strcpy(name,ServerName);
//Подключаемся к серверу
// Создаем канал
Mail = CreateFile(name, GENE-RIC_READ | GENERIC_WRITE, FILE_ SHARE_READ | FILE_SHARE_ WRITE, NULL, OPEN_EXISTING, 0, NULL);
// Если возникла ошибка, выводим ее код и выходим из функции
if(Mail == INVALID_HANDLE_VALUE)
{
m_mess+="CreateMailslot: Error";
itoa(GetLastError(),temp,10);
m_mess+=(CString)&temp[0];
m_mess += "\r\n";
UpdateData(FALSE);
return;
}
flag=1;
return;
}

Вот и все. Теперь вы знаете, что такое mailslot, и с чем этого зверя есть. Использование этой технологии может быть самым разнообразным. Ведь именно ее использует IChat в своей работе, а учитывая, что настроить файрволл на вылавливание трафика по mailslots довольно сложно, открываются заманчивые перспективы. А теперь до свидания, уважаемые любители сами знаете чего!

Спичеков Александр aka MentalzavR,
Zavr6@mail.ru



Компьютерная газета. Статья была опубликована в номере 07 за 2004 год в рубрике программирование :: разное

©1997-2024 Компьютерная газета