IP-телефония — практические подходы к реализации схемы PC—PC

С.В. Захаров

В настоящее время интерес к IP-телефонии очень высок, что связано с возможностью существенной экономии денег на телефонных переговорах. Начало данной технологии было положено в 1995 году, когда была реализована схема соединения двух компьютеров (PC—-PC), что означает возможность звонить с одного компьютера на другой. Номером телефона является ваш IP-адрес. Затраты на поддержание такого соединения равняются нулю, поскольку не требуется никаких дополнительных аппаратных средств, за исключением имеющихся в компьютере звуковой платы и микрофона.

Рассказывать о развитии IPT можно очень долго, но эта информация уже неоднократно публиковалась, так что не имеет смысла останавливаться на этой теме. Цель этой статьи — показать читателям один из способов реализации схемы PC—PC на программном уровне, используя для этого пакет Microsoft Visual C++ 6.0. Думаю, вы найдете здесь для себя много интересного, поскольку мы будем рассматривать следующие аспекты:

  • чтение/запись в реестре;
  • передача данных в сети;
  • быстрый опрос сети;
  • управление звуковыми устройствами.

Предлагаемый вашему вниманию курс рассчитан на тех, кто уже имеет определенный опыт работы в Visual C++ Platform SDK Windows Multimedia и Windows Socket.

Все модули были протестированы на ОС Windows 98 и Windows NT 4.0. Сеть — витая пара 10 Mбит.

Модем не поддерживается! Сетевая версия!

Компьютер: Pentium-133/32Мбайт/4Мбайт/10\100Мбит

Рассмотрим схему и функции, которые были реализованы в программе .

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

I. Передача сообщения/документа:

  1. Выбор сервера на соединение (опрос сети);
  2. Подготовка сообщения/документа;
  3. Соединение и передача сообщения/документа с сопутствующей информацией об отправителе;
  4. Завершение сеанса подключения.

II. Соединение для разговора:

  1. Определение вашего IP-адреса;
  2. Выбор сервера на соединение (опрос сети);
  3. Соединение вашей клиентской части с удаленным Сервером и передача информации о вас (ваш IP-адрес);
  4. Возвратная реакция удаленного сервера на ваш запрос. Удаленный Клиент производит дополнительное соединение с вашим Сервером;
  5. Инициализация звуковой платы на In и Out (Сервер управляет Out, Клиент — In);
  6. Считывание и передача данных Клиентом, параллельно производятся прием и воспроизведение звуковых данных Сервером;
  7. Закрытие устройств;
  8. Завершение сеанса подключения.

III. Работа с параметрами.

Не буду полностью расписывать, как программно реализована эта схема, рассмотрим только самое главное.

Итак, приступим...

Занятие 1. Чтение/запись в реестр

Как узнать вашу операционную систему, IP-адрес, имя компьютера, имя пользователя?

Большую часть информации о системе можно обнаружить в системном реестре ОС . (Для просмотра реестра вы можете использовать программу, входящую в комплект ОС, regedit.exe.) Считать ее можно, выполнив следующую последовательность команд:

// ---------- Описание переменных
HKEY hKey;
DWORD dwType, dwCount;
CString strValue;
//----------- Определить имя OC
RegOpenKeyEx(HKEY_LOCAL_MACHINE,
"Software\\Microsoft\\Windows\\CurrentVersion",
0,KEY_READ, &hKey);
RegQueryValueEx (hKey,"ProductName", NULL,
&dwType, NULL, &dwCount);
RegQueryValueEx (hKey,"ProductName", NULL,
&dwType, (LPBYTE)strValue.GetBuffer(dwCount/
sizeof(TCHAR)), &dwCount);
strValue.ReleaseBuffer();
RegCloseKey(hKey);

Функция RegOpenKeyEx позволяет открыть папку реестра, в которой расположена интересующая вас переменная. Параметры задают путь и маску доступа. Обратите внимание, что переход между папками осуществляется "\\", а не "\" — это важно. Если операция выполнена корректно, то ключ-ссылка на папку записана в переменную hKey.

Функция RegQueryValueEx позволит считать значение интересующей вас переменной, в данном случае это ProductName. В первый раз, выполняя функцию RegQueryValueEx, мы определим тип и размер буфера данных. Второй раз — считываем значение в буфер переменной strValue. Следующий шаг — освободить буфер и ключ-ссылку.

После выполнения этого программного кода в переменной strValue должно храниться имя операционной системы, установленной на ваш компьютер. Здесь необходимо оговориться: это справедливо, если вы используете ОС Windows 98. В противном случае значение будет пустым — это связано с тем, что реестры OC Windows 98 и Windows NT не совпадают. Но для нас этого вполне достаточно: теперь мы уже ориентируемся, в какой ОС находимся, и в дальнейшем просто разделить модули работы с реестром Windows 98 и Windows NT. Если вы хотите использовать данный программный код для своих целей, то достаточно будет изменить путь к переменной. Однако при этом следует помнить, что таким образом вы сможете считать только текстовую информацию, но не битовую (ее мы рассмотрим чуть позже).

Имя компьютера и имя пользователя определяются по сходной схеме: необходимо только заменить путь в функции RegOpenKeyEx и имя переменной в функции RegQueryValueEx.

 

Определение имени компьютера
98 RegOpenKeyEx "SYSTEM\\CurrentControlSet\\Control\\ComputerName\\ComputerName"
RegQueryValueEx "ComputerName"
NT RegOpenKeyEx "SYSTEM\\ControlSet001\\Control\\ComputerName\\ActiveComputerName"
RegQueryValueEx "Computer Name"
Определение имени пользователя
98 RegOpenKeyEx "SYSTEM\\CurrentControlSet\\Control"
RegQueryValueEx "Current User"
NT RegOpenKeyEx "Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon"
RegQueryValueEx "DefaultUserName"

 

С определением IP-адреса дело обстоит несколько сложнее, поскольку приходится учитывать не только то, в какой ОС вы находитесь, но и то, по какой схеме работаете (с прямым IP-адресом или используя сервер DHCP), а также сколько у вас установлено сетевых протоколов (TCP/IP, IPX/SPX, NetBEUI...).

Если у вас — ОС Windows 98 и установлен прямой IP-адрес, то информацию о нем можно получить в одной из папок реестра (0000, 0001, 0002...). В какой именно — зависит от привязок.

Нам будет необходимо просмотреть их последовательно, один за другим, пока мы не отыщем там интересующую нас переменную. Ниже представлен один из методов определения вашего прямого IP-адреса.

CString Temp="SYSTEM\\CurrentControlSet\\Services\\
Class\\NetTrans\\000";
CString IPAddress;

for (i=0;i<10;i++)
{
bufTemp.Format("%d",i);
RegOpenKeyEx(HKEY_LOCAL_MACHINE,
mTemp+bufTemp,0,KEY_READ,&hKey);
RegQueryValueEx(hKey,"IPAddress", NULL, &dwType,
NULL, &dwCount);
RegQueryValueEx(hKey,"IPAddress", NULL, &dwType,
(LPBYTE)strValue.GetBuffer(dwCount/
sizeof(TCHAR)), &dwCount);
strValue.ReleaseBuffer();
RegCloseKey(hKey);
if ((strValue!="0.0.0.0")&&(strValue!=""))
{
IPAddress=strValue;
break;
}
}

Переменной Temp присваивается строка, в которой содержится часть пути к переменной реестра IPAddress. Далее организован цикл поиска по десяти папкам — от '0000' до '0009' (этого вполне достаточно). В первых трех строчках цикла производится корректировка пути к переменной реестра. За ними следует уже знакомая вам последовательность команд чтения. Если поиск в папке прошел успешно, то мы производим выход из цикла, в противном случае — переходим к следующей папке. По окончании поиска мы будем иметь в переменной strValue значение нашего IP-адреса.

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

bufTemp="";
CString strDhcp="";
CString mTemp="System\\CurrentControlSet\\Services\\VxD\\
DHCP\\DhcpInfo0";
for (i=0;i<10;i++)
{
bufTemp.Format("%d",i);
RegOpenKeyEx(HKEY_LOCAL_MACHINE,
mTemp+bufTemp, 0,KEY_READ,&hKey);
RegQueryValueEx(hKey,"DhcpIPAddress", NULL, &dwType,
NULL, &dwCount);
RegQueryValueEx(hKey,"DhcpIPAddress", NULL, &dwType,
(LPBYTE)strDhcp.GetBuffer(dwCount/sizeof(TCHAR)),
&dwCount);
strDhcp.ReleaseBuffer();
RegCloseKey(hKey);
if (strDhcp!="")
{
strValue.Format("%d%s%d%s%d%s%d",strDhcp[0],".",
strDhcp[1],".",strDhcp[2],".",strDhcp[3]);
IPAddress=strValue;
break;
}
}

Если вы используете Windows NT 4.0 и прямой адрес IP, то вам понадобится предварительно определить имя сетевой платы. В том случае, если у вас не одна сетевая карта, нужно определить, какую вы будете использовать. Для этого откорректируйте концовку пути RegOpenKeyEx:

CString NameNetCard;

RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SOFTWARE\\
Microsoft\\Windows NT\\CurrentVersion\\
NetworkCards\\1", 0,KEY_READ,&hKey);
RegQueryValueEx(hKey,"ServiceName", NULL, &dwType,
NULL, &dwCount);
RegQueryValueEx(hKey,"ServiceName", NULL, &dwType, (LPBYTE)NameNetCard.GetBuffer(dwCount/sizeof(TCHAR)),
&dwCount);
NameNetCard.ReleaseBuffer();
RegCloseKey(hKey);

В переменой NameNetCard хранится имя вашей сетевой карты. Используйте его для установки пути к IP-адресу вашей сетевой карты:

CString TempVar="SYSTEM\\ControlSet001\\Services\\"+
NameNetCard+"\\Parameters\\Tcpip";
RegOpenKeyEx(HKEY_LOCAL_MACHINE,TempVar,0,
KEY_READ, hKey);
RegQueryValueEx(hKey, "IPAddress", NULL, &dwType,
NULL, dwCount);
pBytes = dwCount;
ppData = new BYTE[dwCount];
RegQueryValueEx(hKey, "IPAddress", NULL, &dwType, ppData, &dwCount);
RegCloseKey(hKey);
IPAddress=ppData;
delete [] ppData;
ppData = NULL;

Обратите внимание — процедура несколько видоизменена. Это связано с тем, что нам приходится читать информацию, представленную в битовом формате. После получения 16-ричных кодов неявно преобразуем их к текстовому формату.

Чтобы определить IP-адрес, назначенный вам DHCP-сервером, выполните следующую процедуру:

CString TempVar="SYSTEM\\ControlSet001\\Services\\"+
NameNetCard+"\\Parameters\\Tcpip";

RegOpenKeyEx(HKEY_LOCAL_MACHINE,TempVar,0
KEY_READ,&hKey);
RegQueryValueEx(hKey,"DhcpIPAddress", NULL, &dwType,
NULL, &dwCount);
RegQueryValueEx(hKey,"DhcpIPAddress", NULL, &dwType,
(LPBYTE)strValue.GetBuffer(dwCount/sizeof(TCHAR)),
&dwCount);
strValue.ReleaseBuffer();
RegCloseKey(hKey);

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

SetRegistryKey(_T("Имя Вашего проекта"));

В реестре появится папка Имя Вашего проекта, место положения которой:

HKEY_CURRENT_USER\Software\Имя проекта\\

Работа с папкой наглядно продемонстрирована ниже:

if (GetProfileInt("Sound", "Reg_mMHz",-1)==-1)
WriteProfileInt("Sound", "Reg_mMHz", 22050);

if (GetProfileString("NameFile", "f_in","")=="")
WriteProfileString("NameFile", "f_in", res\\Ringin.wav");

Функцией GetProfileInt мы производим чтение из подкаталога Sound каталога Имя Вашего проекта целочисленной переменной Reg_mMHz. Если переменная не была найдена, то будет возвращено значение, указанное в параметре функции (-1), и функция WriteProfileInt создаст необходимый нам подкаталог и переменную и присвоит ей значение по умолчанию (22050). Аналогично можно производить чтение/запись строковых переменных.

Теперь приступайте к практике. Только будьте осторожны — не повредите имеющуюся в реестре информацию. Последствия могут быть непредсказуемы, вплоть до переустановки операционной системы.

Продолжение следует

КомпьютерПресс 2'2001