Полный исходный текст на языке C++ для библиотеки (DLL) с моделями блоков, обменивающихся данными по сети, с дополнениями, призванными снизить нагрузку на сеть. Библиотека содержит три модели:
- Server – блок, включающий серверные функции в RDS (копия модели из §2.15.2);
- NetSend – блок, передающий вещественное число с возможностью ограничения минимального интервала передачи;
- NetReceive – блок, принимающий вещественное число (копия модели из §2.15.2);
- NetSendArray – блок, передающий массив вещественных чисел с возможностью ограничения минимального интервала передачи;
- NetReceiveArray – блок, принимающий массив вещественных чисел.
Уже описанные ранее модели Server и NetReceive включены сюда для того, чтобы все модели из рассматриваемого примера схемы находились в одной библиотеке.
Изменения относительно предыдущих версий моделей выделены цветом.
// Передача данных по сети #include <windows.h> #include <RdsDef.h> // Подготовка описаний сервисных функций #define RDS_SERV_FUNC_BODY GetInterfaceFunctions #include <RdsFunc.h> //========== Главная функция DLL ========== int WINAPI DllMain(HINSTANCE /*hinst*/, unsigned long reason, void* /*lpReserved*/) { if(reason==DLL_PROCESS_ATTACH) // Загрузка DLL { // Получение доступа к функциям RDS if(!GetInterfaceFunctions()) RDS_SERV_ERROR_MSGW // Сообщение: старая версия RDS } return 1; } //========= Конец главной функции ========= //========================================= // Модель блока, включающего сервер //========================================= extern "C" __declspec(dllexport) int RDSCALL Server(int CallMode, RDS_PBLOCKDATA BlockData, LPVOID ExtParam) { int *pConnId; switch(CallMode) { // Инициализация case RDS_BFM_INIT: // Отводим место под личную область данных размером в int BlockData->BlockData=pConnId=new int; // Запускаем сервер и соединяемся с ним *pConnId=rdsNetServer(-1, // Порт по умолчанию "ProgrammersGuide.Server", // Канал FALSE); // Не принимаем данные break; // Очистка case RDS_BFM_CLEANUP: // Личная область данных – целое число pConnId=(int*)(BlockData->BlockData); // Разрываем связь с сервером (он завершится сам) rdsNetCloseConnection(*pConnId); // Удаляем личную область delete pConnId; break; } return RDS_BFR_DONE; } //========================================= //========================================= // Личная область данных блоков приема // и передачи по сети //========================================= class TNetSendRcvData { public: int Mode; // Режим данного блока: прием или передача #define NETSRMODE_SENDER 0 // Передатчик #define NETSRMODE_RECEIVER 1 // Приемник char *ChannelName; // Имя канала int ConnId; // Идентификатор соединения // Переменные состояния блока-передатчика BOOL Connected; // Соединение установлено BOOL DataWaiting; // Передача данных отложенаRDS_TIMERID Timer; // Таймер для отсчета интервала BOOL WaitingForTimer;// Таймер запущен - ждем DWORD LastSendTime; // Время последней отправки// Функции класса void Connect(void); // Установить соединение void Disconnect(void); // Разорвать соединение void SendValue(double value); // Передать число в канал BOOL ReceiveValue(RDS_NETRECEIVEDDATA *rcv, // Реакция на double *pOut); // пришедшие данныеvoid SendArray(void *input); // Передать массив в канал BOOL ReceiveArray(RDS_NETRECEIVEDDATA *rcv, // Реакция на void *output); // пришедшие данныеvoid CreateTimer(void); // Создать таймер void DeleteTimer(void); // Удалить таймер BOOL CheckSendTimer(void); // Проверить, можно ли передавать, // и запустить таймер, если нельзяint Setup(char *title); // Функция настройки блока void SaveText(void); // Сохранить параметры void LoadText(char *text); // Загрузить параметры // Конструктор класса TNetSendRcvData(int mode) { ConnId=-1; // Нет соединения Connected=DataWaiting=FALSE;LimitSpeed=WaitingForTimer=FALSE;Timer=NULL;Delay=100;ChannelName=NULL; Mode=mode; // Режим передается в параметре конструктора }; // Деструктор класса ~TNetSendRcvData() { Disconnect(); // Разорвать соединениеDeleteTimer(); // Удалить таймерrdsFree(ChannelName); // Освободить строку имени канала }; }; //========================================= // Установка соединения void TNetSendRcvData::Connect(void) { char *PrefixedName; // Полное имя канала // Если имя канала пустое, соединение невозможно if(ChannelName==NULL || (*ChannelName)==0) return; // Добавляем префикс к имени канала PrefixedName=rdsDynStrCat("ProgrammersGuide.",ChannelName,FALSE); // Устанавливаем соединение с сервером ConnId=rdsNetConnect(NULL, // Сервер по умолчанию -1, // Порт по умолчанию PrefixedName,// Имя канала с префиксом Mode==NETSRMODE_RECEIVER); // Прием данных // Освобождаем динамически отведенную строку rdsFree(PrefixedName);// Создаем или уничтожаем таймер CreateTimer();} //========================================= // Разорвать соединение void TNetSendRcvData::Disconnect(void) { if(ConnId!=-1) // Соединение было создано rdsNetCloseConnection(ConnId); // Разорвать // Сбрасываем переменные состояния ConnId=-1; // Соединения больше нет Connected=FALSE; // Связи тоже больше нет } //=========================================// Создать таймер void TNetSendRcvData::CreateTimer(void) { if(Mode!=NETSRMODE_SENDER || // Приемнику таймер не нужен (!LimitSpeed) ) // Интервал передачи не ограничивается { DeleteTimer(); // Удаляем таймер, если он создан return; } if(Timer) // Таймер уже создан return; // Создаем таймер Timer=rdsSetBlockTimer( NULL, // Создается новый таймер 0, // Задержка задается при запуске RDS_TIMERM_STOP | RDS_TIMERS_TIMER, // Однократный FALSE); // Создается остановленным } //=========================================// Удалить таймер void TNetSendRcvData::DeleteTimer(void) { if(Timer) // Таймер есть { rdsDeleteBlockTimer(Timer); Timer=NULL; } WaitingForTimer=FALSE; // Сбрасываем флаг ожидания } //=========================================// Проверить, можно ли передать данные немедленно, и запустить // таймер, если нельзя BOOL TNetSendRcvData::CheckSendTimer(void) { DWORD interval; if(!Connected) // Нет связи с сервером return TRUE; // Разрешаем отправку, чтобы попытка отправки // была зафиксирована в функции SendValue if(!LimitSpeed) // Интервал не ограничивается return TRUE; if(WaitingForTimer) // Уже запустили таймер и ждем срабатывания return FALSE; // Интервал передачи ограничен, таймер сейчас выключен interval=GetTickCount()-LastSendTime; // Время с прошлой отправки if(interval>=Delay) // Прошло много времени – можно передавать return TRUE; // С прошлой отправки прошло менее Delay мс // Нужно подождать (Delay-interval) WaitingForTimer=TRUE; // Взводим флаг ожидания таймера rdsRestartBlockTimer(Timer,Delay-interval); // Запускаем таймер return FALSE; // Передавать сейчас нельзя – ждем таймера } //=========================================// Передать данные void TNetSendRcvData::SendValue(double value) { if(!Connected) // Нет связи с сервером { // Взводим флаг наличия данных, ожидающих передачи DataWaiting=TRUE; return; } // Связь есть – передаем всем блокам канала rdsNetBroadcastData(ConnId, // Соединение RDS_NETSEND_UPDATE|RDS_NETSEND_UDP, // Флаги 0,NULL, // Не передаем целое число и строку &value, // Указатель на данные sizeof(value)); // Размер данных // Сбрасываем флаг ожидания – мы только что передали данные DataWaiting=FALSE;// Запоминаем время последней передачи LastSendTime=GetTickCount();} //========================================= // Прием данных BOOL TNetSendRcvData::ReceiveValue( RDS_NETRECEIVEDDATA *rcv, // Указатель на структуру с данными double *pOut) // Указатель на выход блока { if(rcv==NULL || pOut==NULL) // Нет одного из указателей return FALSE; // Проверяем, есть ли среди принятых данных двоичные, // и равен ли размер этих данных размеру double if(rcv->Buffer==NULL || rcv->BufferSize!=sizeof(double)) return FALSE; // Нет данных или не совпал размер // Копируем принятое числов pOut memcpy(pOut,rcv->Buffer,sizeof(double)); return TRUE; // Приняты правильные данные } //=========================================// Передать массив void TNetSendRcvData::SendArray(void *input) { int N; if(!Connected) // Нет связи с сервером { // Взводим флаг наличия данных, ожидающих передачи DataWaiting=TRUE; return; } // Связь есть – определяем размер массива input N=RDS_ARRAYEXISTS(input)? (RDS_ARRAYROWS(input)*RDS_ARRAYCOLS(input)):0; if(N==0) // Массив пуст – передавать нечего return; // Передаем N чисел double всем блокам канала rdsNetBroadcastData( ConnId, // Соединение RDS_NETSEND_UPDATE|RDS_NETSEND_UDP, // Флаги N, // Целое число – размер массива NULL, // Строка не передается RDS_ARRAYDATA(input), // Начало данных массива N*sizeof(double)); // Размер массива в байтах // Сбрасываем флаг ожидания – мы только что передали данные DataWaiting=FALSE; // Запоминаем время передачи LastSendTime=GetTickCount(); } //=========================================// Прием массива BOOL TNetSendRcvData::ReceiveArray( RDS_NETRECEIVEDDATA *rcv, // Указатель на структуру с данными void *output) // Указатель на выход блока (массив) { int N; if(rcv==NULL||output==NULL) // Нет одного из указателей return FALSE; // Проверяем, есть ли среди принятых данных двоичные, // и кратен ли размер блока данных размеру double if(rcv->Buffer==NULL || // Нет буфера с данными rcv->BufferSize%sizeof(double)!=0) // Размер не кратен 8 return FALSE; // Вычисляем число элементов в принятом массиве N=rcv->BufferSize/sizeof(double); // Вычисленное число элементов должно совпасть с переданным if(N!=rcv->Id) return FALSE; // Принято N чисел double – отводим массив под них if(!rdsResizeVarArray(output,1,N,FALSE,NULL)) return FALSE; // Копируем принятые данные в отведенный массив выхода memcpy(RDS_ARRAYDATA(output),rcv->Buffer,rcv->BufferSize); return TRUE; } //=========================================// Сохранение параметров блока void TNetSendRcvData::SaveText(void) { RDS_HOBJECT ini; // Вспомогательный объект // Создаем вспомогательный объект ini=rdsINICreateTextHolder(TRUE); // Создаем в объекте секцию "[General]" rdsSetObjectStr(ini,RDS_HINI_CREATESECTION,0,"General"); // Записываем в эту секцию имя канала rdsINIWriteString(ini,"Channel",ChannelName);if(Mode==NETSRMODE_SENDER) // Передатчик { // Создаем новую секцию rdsSetObjectStr(ini,RDS_HINI_CREATESECTION,0,"Timer"); // Записываем параметры rdsINIWriteInt(ini,"On",LimitSpeed); rdsINIWriteInt(ini,"Delay",Delay); }// Сохраняем текст, сформированный объектом, как параметры блока rdsCommandObject(ini,RDS_HINI_SAVEBLOCKTEXT); // Удаляем вспомогательный объект rdsDeleteObject(ini); } //========================================= // Загрузка параметров блока void TNetSendRcvData::LoadText(char *text) { RDS_HOBJECT ini; // Вспомогательный объект char *str; // Создаем вспомогательный объект ini=rdsINICreateTextHolder(TRUE); // Записываем в объект полученный текст с параметрами блока rdsSetObjectStr(ini,RDS_HINI_SETTEXT,0,text); // Начинаем чтение секции "[General]", если она есть if(rdsINIOpenSection(ini,"General")) // Секция есть { // Освобождаем старое имя канала rdsFree(ChannelName); ChannelName=NULL; // Получаем у объекта указатель на строку с именем str=rdsINIReadString(ini,"Channel","",NULL); // Если такая строка есть в тексте, копируем ее в ChannelName if(str) ChannelName=rdsDynStrCopy(str); }if(Mode==NETSRMODE_SENDER && // Передатчик rdsINIOpenSection(ini,"Timer")) // Есть секция "[Timer]" { LimitSpeed=rdsINIReadInt(ini,"On",LimitSpeed)!=0; Delay=rdsINIReadInt(ini,"Delay",Delay); }// Удаляем вспомогательный объект rdsDeleteObject(ini); // Поскольку имя канала могло измениться, соединяемся с // сервером заново Disconnect(); // Разрываем старое соединение Connect(); // Создаем новое } //=========================================// Настройка параметров блока // ВАЖНО: Исходный текст программы должен быть записан в UTF8, // в противном случае необходимо использовать версии функций // с суффиксом "W" и символьные константы с префиксом "L" int TNetSendRcvData::Setup(char *title) { RDS_HOBJECT win; // Идентификатор вспомогательного объекта BOOL ok; // Пользователь нажал "OK" // Создание окна win=rdsFORMCreate(FALSE,-1,-1,title); // Поле ввода имени канала rdsFORMAddEdit(win,0,1,RDS_FORMCTRL_EDIT, "Имя канала:",200); rdsSetObjectStr(win,1,RDS_FORMVAL_VALUE,ChannelName); if(Mode==NETSRMODE_SENDER) { // Для передатчика – ввод интервала rdsFORMAddEdit(win,0,2, RDS_FORMCTRL_EDIT | RDS_FORMFLAG_CHECK, "Интервал передачи, мс:",80); // Значение интервала rdsSetObjectInt(win,2,RDS_FORMVAL_VALUE,Delay); // Разрешающий флаг поля rdsSetObjectInt(win,2,RDS_FORMVAL_CHECK,LimitSpeed); } // Открытие окна ok=rdsFORMShowModalEx(win,NULL); if(ok) { // Пользователь нажал OK char *NewName=rdsGetObjectStr(win,1,RDS_FORMVAL_VALUE); if(ChannelName==NULL || strcmp(NewName,ChannelName)!=0) { // Имя канала изменилось – запоминаем новое rdsFree(ChannelName); ChannelName=rdsDynStrCopy(NewName); } // Флаг ограничения интервала и сам интервал LimitSpeed=rdsGetObjectInt(win,2,RDS_FORMVAL_CHECK)!=0; Delay=rdsGetObjectInt(win,2,RDS_FORMVAL_VALUE); // Устанавливаем соединение с сервером заново и создаем // или удаляем таймер, если необходимо Disconnect(); Connect(); } // Уничтожаем вспомогательный объект-окно rdsDeleteObject(win); // Возвращаемое значение return ok?RDS_BFR_MODIFIED:RDS_BFR_DONE; } //=========================================//========================================= // Блок-передатчик числа //========================================= extern "C" __declspec(dllexport) int RDSCALL NetSend(int CallMode, RDS_PBLOCKDATA BlockData, LPVOID ExtParam) { // Указатель на личную область данных, приведенный к нужному типу TNetSendRcvData *data=(TNetSendRcvData*)(BlockData->BlockData); // Макроопределения для статических переменных #define pStart ((char *)(BlockData->VarTreeData)) #define Start (*((char *)(pStart))) #define Ready (*((char *)(pStart+RDS_VSZ_S))) #define x (*((double *)(pStart+2*RDS_VSZ_S))) switch(CallMode) { // Инициализация case RDS_BFM_INIT: // Создаем объект класса TNetSendRcvData с // Mode==NETSRMODE_SENDER (передатчик) BlockData->BlockData=new TNetSendRcvData(NETSRMODE_SENDER); break; // Очистка case RDS_BFM_CLEANUP: delete data; break; // Проверка типов переменных case RDS_BFM_VARCHECK: return strcmp((char*)ExtParam,"{SSD}")? RDS_BFR_BADVARSMSG:RDS_BFR_DONE; // Связь с сервером установлена case RDS_BFM_NETCONNECT: // Взводим флаг наличия связи data->Connected=TRUE; // Если были данные, ожидающие отправки, // посылаем значение входа блока if(data->DataWaiting) data->SendValue(x); break; // Связь с сервером разорвана case RDS_BFM_NETDISCONNECT: // Сбрасываем флаг наличия связи data->Connected=FALSE; break; // Запуск расчета case RDS_BFM_STARTCALC: // Если это – первый запуск после сброса, // передаем значение входа if(((RDS_PSTARTSTOPDATA)ExtParam)->FirstStart) data->SendValue(x); break;// Срабатывание таймера case RDS_BFM_TIMER: // Сбрасываем флаг ожидания таймера data->WaitingForTimer=FALSE; // Передаем значение входа блока data->SendValue(x); break;// Такт расчета case RDS_BFM_MODEL: if(data->CheckSendTimer()) // Можно передавать немедленно data->SendValue(x); // Передаем значение входа break; // Вызов настройки // ВАЖНО: Исходный текст программы должен быть записан в UTF8, // в противном случае необходимо использовать версии функций // с суффиксом "W" и символьные константы с префиксом "L" case RDS_BFM_SETUP: return data->Setup("Передача double"); // Сохранение параметров в текстовом виде case RDS_BFM_SAVETXT: data->SaveText(); break; // Загрузка параметров в текстовом виде case RDS_BFM_LOADTXT: data->LoadText((char*)ExtParam); break; } return RDS_BFR_DONE; // Отмена макроопределений #undef x #undef Ready #undef Start #undef pStart } //========================================= //========================================= // Блок-приемник числа //========================================= extern "C" __declspec(dllexport) int RDSCALL NetReceive(int CallMode, RDS_PBLOCKDATA BlockData, LPVOID ExtParam) { // Указатель на личную область данных, приведенный к нужному типу TNetSendRcvData *data=(TNetSendRcvData*)(BlockData->BlockData); // Макроопределения для статических переменных #define pStart ((char *)(BlockData->VarTreeData)) #define Start (*((char *)(pStart))) #define Ready (*((char *)(pStart+RDS_VSZ_S))) #define y (*((double *)(pStart+2*RDS_VSZ_S))) switch(CallMode) { // Инициализация case RDS_BFM_INIT: // Создаем объект класса TNetSendRcvData с // Mode==NETSRMODE_RECEIVER (приемник) BlockData->BlockData= new TNetSendRcvData(NETSRMODE_RECEIVER); break; // Очистка case RDS_BFM_CLEANUP: delete data; break; // Проверка типов переменных case RDS_BFM_VARCHECK: return strcmp((char*)ExtParam,"{SSD}")? RDS_BFR_BADVARSMSG:RDS_BFR_DONE; // По сети получены данные case RDS_BFM_NETDATARECEIVED: if(data->ReceiveValue((RDS_NETRECEIVEDDATA*)ExtParam,&y)) Ready=1; // Если данные верны, взводим флаг готовности // для передачи выхода по связям break; // Вызов настройки // ВАЖНО: Исходный текст программы должен быть записан в UTF8, // в противном случае необходимо использовать версии функций // с суффиксом "W" и символьные константы с префиксом "L" case RDS_BFM_SETUP: return data->Setup("Прием double"); // Сохранение параметров в текстовом виде case RDS_BFM_SAVETXT: data->SaveText(); break; // Загрузка параметров в текстовом виде case RDS_BFM_LOADTXT: data->LoadText((char*)ExtParam); break; } return RDS_BFR_DONE; // Отмена макроопределений #undef y #undef Ready #undef Start #undef pStart } //========================================= //========================================= // Блок-передатчик массива //========================================= extern "C" __declspec(dllexport) int RDSCALL NetSendArray(int CallMode, RDS_PBLOCKDATA BlockData, LPVOID ExtParam) { // Указатель на личную область данных, приведенный к нужному типу TNetSendRcvData *data=(TNetSendRcvData*)(BlockData->BlockData); // Макроопределения для статических переменных #define pStart ((char *)(BlockData->VarTreeData)) #define Start (*((char *)(pStart))) #define Ready (*((char *)(pStart+RDS_VSZ_S))) #define pX ((void **)(pStart+2*RDS_VSZ_S)) switch(CallMode) { // Инициализация case RDS_BFM_INIT: // Создаем объект класса TNetSendRcvData с // Mode==NETSRMODE_SENDER (передатчик) BlockData->BlockData=new TNetSendRcvData(NETSRMODE_SENDER); break; // Очистка case RDS_BFM_CLEANUP: delete data; break; // Проверка типов переменных case RDS_BFM_VARCHECK: return strcmp((char*)ExtParam,"{SSMD}")? RDS_BFR_BADVARSMSG:RDS_BFR_DONE; // Связь с сервером установлена case RDS_BFM_NETCONNECT: // Взводим флаг наличия связи data->Connected=TRUE; // Если были данные, ожидающие отправки, // посылаем значение входа блока if(data->DataWaiting) data->SendArray(pX); break; // Связь с сервером разорвана case RDS_BFM_NETDISCONNECT: // Сбрасываем флаг наличия связи data->Connected=FALSE; break; // Запуск расчета case RDS_BFM_STARTCALC: // Если это – первый запуск после сброса, // передаем значение входа if(((RDS_PSTARTSTOPDATA)ExtParam)->FirstStart) data->SendArray(pX); break; // Срабатывание таймера case RDS_BFM_TIMER: // Сбрасываем флаг ожидания таймера data->WaitingForTimer=FALSE; // Передаем значение входа блока data->SendArray(pX); break; // Такт расчета case RDS_BFM_MODEL: if(data->CheckSendTimer()) // Можно передавать немедленно data->SendArray(pX); // Передаем значение входа break; // Вызов настройки // ВАЖНО: Исходный текст программы должен быть записан в UTF8, // в противном случае необходимо использовать версии функций // с суффиксом "W" и символьные константы с префиксом "L" case RDS_BFM_SETUP: return data->Setup("Передача массива"); // Сохранение параметров в текстовом виде case RDS_BFM_SAVETXT: data->SaveText(); break; // Загрузка параметров в текстовом виде case RDS_BFM_LOADTXT: data->LoadText((char*)ExtParam); break; } return RDS_BFR_DONE; // Отмена макроопределений #undef pX #undef Ready #undef Start #undef pStart } //========================================= //========================================= // Блок-приемник массива //========================================= extern "C" __declspec(dllexport) int RDSCALL NetReceiveArray(int CallMode, RDS_PBLOCKDATA BlockData, LPVOID ExtParam) { // Указатель на личную область данных, приведенный к нужному типу TNetSendRcvData *data=(TNetSendRcvData*)(BlockData->BlockData); // Макроопределения для статических переменных #define pStart ((char *)(BlockData->VarTreeData)) #define Start (*((char *)(pStart))) #define Ready (*((char *)(pStart+RDS_VSZ_S))) #define pY ((void **)(pStart+2*RDS_VSZ_S)) switch(CallMode) { // Инициализация case RDS_BFM_INIT: // Создаем объект класса TNetSendRcvData с // Mode==NETSRMODE_RECEIVER (приемник) BlockData->BlockData= new TNetSendRcvData(NETSRMODE_RECEIVER); break; // Очистка case RDS_BFM_CLEANUP: delete data; break; // Проверка типов переменных case RDS_BFM_VARCHECK: return strcmp((char*)ExtParam,"{SSMD}")? RDS_BFR_BADVARSMSG:RDS_BFR_DONE; // По сети получены данные case RDS_BFM_NETDATARECEIVED: if(data->ReceiveArray((RDS_NETRECEIVEDDATA*)ExtParam,pY)) Ready=1; // Если данные верны, взводим флаг готовности // для передачи выхода по связям break; // Вызов настройки // ВАЖНО: Исходный текст программы должен быть записан в UTF8, // в противном случае необходимо использовать версии функций // с суффиксом "W" и символьные константы с префиксом "L" case RDS_BFM_SETUP: return data->Setup("Прием массива"); // Сохранение параметров в текстовом виде case RDS_BFM_SAVETXT: data->SaveText(); break; // Загрузка параметров в текстовом виде case RDS_BFM_LOADTXT: data->LoadText((char*)ExtParam); break; } return RDS_BFR_DONE; // Отмена макроопределений #undef pY #undef Ready #undef Start #undef pStart } //=========================================