Передача указателей в объектах классов и структур по сети

Все исходники / Язык программирования C++ / OS Windows / Desktop / Сетевые приложения / Передача указателей в объектах классов и структур по сети
Оглавление:
  1. Передача различных данных по сети
  2. Сложности передачи динамических переменных
  3. Способ передачи указателей по сети
  4. Исходник приложения отправки указателей по сети
  5. Класс указателей CPointers
  6. Отправка в сеть
  7. Извлечение данных из сетевого буфера
  8. Исходник примера передачи указателей по сети

Передача различных данных по сети

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

Вместе с тем, расшифровая это обобщение, надо сказать, что для передачи по сети различных типов переменных используются разные алгоритмы. Классы имеющие только примитивные типы: int, char, bool, float, double и объекты классов, состоящие из них, занимают фиксированный объем памяти на любой машине. В передающем методе указывается адрес отправляемого объекта и размер в байтах измеренный sizeof(). При получении, аналогично, в метод Receive(...) передаётся адрес и указывается размер в байтах объекта-приёмника. Извлекающим методом чётко распределяются полученные байты в памяти занимаемым объектом класса. Но передать по сети таким способом классы и структуры содержащие переменные (строки, указатели с данными, массивы) распределяемые в динамической памяти и не получится.

Сложности передачи динамических переменных

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

Если в составе класса переменные фиксированного размера сетевой обмен строится просто. Функция sizeof() даёт точное количество байтов занимаемое в памяти объектом класса. Сложности возникают при передаче переменных с изменяемыми размерами. Указатели на объекты, на массивы, содержащие только адреса на реальные данные отправлять в сеть в составе класса не имеет смысла. Сам указатель это всего лишь переменная хранящая адрес и стандартное измерение её размера не учитывает связанные с ней данные. Если отправить в сеть в составе класса указатель на существующие данные, то эти данные неизбежно будут потеряны. Объекту отправляемого класса принадлежит только переменная-указатель, но не данные, на которые ссылается этот указатель.

Указатель содержит адрес на ячейку памяти с какими-либо объектами. Каждая машина имеет своё уникальное адресное пространство. Адрес в памяти имеет актуальность только на отправляющей машине, на принимающем устройстве, извлеченный указатель будет ссылаться на случайные данные. При приёме, да и при отправке, ненулевого указателя неизбежно возникнет исключение.

Способ передачи указателей по сети

Передача указателей вместе с "их данными" в составе класса заключается в передаче по очереди данных, с которыми они ассоциируются и на точке-приёма создание копии объекта класса на основе полученных байтов. Если в составе класса есть объект CString, перед отправкой необходимо получить указатель LPCTSTR на строку и размер в байтах применяя программный код:

int len = m_String.GetLength(); int sizeBytes = len * sizeof(TCHAR) + sizeof(TCHAR);

Байты строки обязательно должны замыкаться нулевым символом. Информация связанная с указателями на массивы передаётся аналогичным способом.

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

Листинг вспомогательного класса:
class CSendInfo
{
public:
    // Все размеры указываются в байтах.

    // Размер строки
    int m_SizeString = 0;

    // Размер массива символов
    int m_SizeArrayTCHAR = 0;

    // Размер массива целых чисел
    int m_SizeArrayInt = 0;

    // Общий размер всех данных
    int m_TotalDataSize = 0;

    // Вспомогательный метод сброса 
    // всех размеров в ноль.
    void Reset();
};

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

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

Программа передачи указателей по сетиК статье прикреплён исходник примера передачи по сети указателей в составе класса. Исходник представляет собой сетевое приложение, которое может работать в двух режимах: в серверном или клиентском. Пара сервер-клиент обмениваются по локальной сети классом в составе которого указатели на объекты размещенные в динамической памяти.

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

Класс указателей CPointers

CPointers содержит несколько указателей для различных типов и вспомогательные переменные и методы для вычисления размера отправляемых данных. В качестве строк произвольной длины используются класс CString и массив символов TCHAR.

Небольшое отступление от темы статьи. TCHAR это не тип, а макрос определяющий тип символов в зависимости от настройки конфигурации приложения. TCHAR может определяться как двухбайтовый WCHAR при установке наборе символов Unicode или как однобайтовый char при настройке на многобайтовую кодировку. В нашем случае приложение настроено на использование символов Unicode.

Листинг класса CPointers:
// --- Объявление класса, файл CPointers.h ---

class CPointers
{
public:
    CPointers();
    ~CPointers();

public:
    int* m_pInt;
    double* m_pDouble;
    CString m_String;
    TCHAR* m_pTCHAR;
    int* m_pArrayInt;

private:
    int m_LenArrayTCHAR;
    int m_LenArrayInt;

public:
    LPCTSTR GetDataString(int &sizeBytes);
    int GetSizeArrayTCHAR();
    void SetArrayTCHAR(CString s);
    void SetArrayInt(int* array, int num);
    int GetSizeArrayInts();
};


//  --- Определение класса, файл CPointers.cpp ---
 ...
CPointers::~CPointers()
{
    // --- Освобождение динамической памяти ---

    // При уничтожении объекта класса, автоматически очистится память 
    // занимаемая данными, адреса которых записаны в указателях.
    // Объект m_String освобождает память самостоятельно.

    delete[] m_pTCHAR;
    m_pTCHAR = NULL;

    delete m_pInt;
    m_pInt = NULL;

    delete m_pDouble;
    m_pDouble = NULL;

    delete[] m_pArrayInt;
    m_pArrayInt = NULL;
} 
 ...

Отправка в сеть

Значения создающиеся в динамической памяти, размер которых заранее неизвестен, уверенно одним пакетом отправить не получится. В таком случае предпочтительней выбрать стратегию отправки данных порциями. Лучше создать общий буфер и в порядке очереди скопировать туда данные указателей. Благодаря стремительному языку С++, процесс копирования происходит мгновенно. Далее буфер отправляем пакетами, для рациональности, равными размеру буфера отправки.

Программный код отправки данных:
if (pSockSend != NULL)
{
    // Ставим флаг процесса отправки.
    // Пока партия данных не отправлена, следующую отправлять нельзя.
    m_FlagSent = FALSE;

    // Размер буфера отправки. 
    // Для наибольшего КПД, размер пакета должен быть равен размеру буфера отправки.
    int sizeBuffer = 0;
    int len = 4;
    if (pSockSend->GetSockOpt(SO_RCVBUF, &sizeBuffer, &len) == FALSE)
    {
        CUtil::BeepError();
    }

    // Объект, данные которого передаются по сети.
    // Данный объект может создаваться в любом месте 
    // программного кода приложения. 
    // Здесь он создаётся только для примера. 
    CPointers pointers;

    // Создаём в динамической памяти целое число и
    // сразу заполняем его значением из окна CEdit.
    pointers.m_pInt = new int;
    *pointers.m_pInt = m_valueInt;

    // Значение double в динамической памяти.
    pointers.m_pDouble = new double;
    *pointers.m_pDouble = m_valueDouble;


    // На самом деле размер массива может быть любым.
    // Но наглядность нам обеспечивают только 4 окна CEdit.
    // При желании можно добавить ещё окна и код будет работать
    // без проблем.
    int temp[4] = { m_valueArrayInt0, m_valueArrayInt1, m_valueArrayInt2, m_valueArrayInt3 };
    pointers.SetArrayInt(temp, 4);
    int sizeArrayInt = pointers.GetSizeArrayInts();


    // При получении указателя строки LPCTSTR, к размеру 
    // обязательно добавляем ещё и размер нулевого символа.
    // Это происходит в методе CPointers::GetDataString(sizeString)
    pointers.m_String = m_valueString;
    int sizeString = 0;
    LPCTSTR pString = pointers.GetDataString(sizeString);

    //
    pointers.SetArrayTCHAR(m_valueArrayChar);
    int tcharSize = pointers.GetSizeArrayTCHAR();

    // Информационный класс для сведений о размерах данных.
    CSendInfo cSendInfo;
    cSendInfo.m_SizeString = sizeString;
    cSendInfo.m_SizeArrayTCHAR = tcharSize;
    cSendInfo.m_SizeArrayInt = sizeArrayInt;

    // Итоговый размер всех данных.
    int totalDataSize = sizeof(CSendInfo) + sizeof(int) + 
        sizeof(double) + cSendInfo.m_SizeArrayInt + 
            cSendInfo.m_SizeString + cSendInfo.m_SizeArrayTCHAR;
    cSendInfo.m_TotalDataSize = totalDataSize;
		
		
    // Общий буфер для отправляемых данных
    BYTE* totalBuffer = new BYTE[totalDataSize];
    ZeroMemory(totalBuffer, totalDataSize);

    // Сдвиг адреса для добавления новых данных в общий буфер.
    int offset = 0;

    // Копирование байтов CSendInfo
    memcpy(totalBuffer + offset, &cSendInfo, sizeof(CSendInfo));

    // Копирование байтов pointers.m_pInt
    offset += sizeof(CSendInfo);
    memcpy(totalBuffer + offset, pointers.m_pInt, sizeof(int));

    // Копирование байтов pointers.m_pDouble
    offset += sizeof(int);
    memcpy(totalBuffer + offset, pointers.m_pDouble, sizeof(double));


    // Bytes of pointers.m_pArrayInt
    offset += sizeof(double);
    memcpy(totalBuffer + offset, pointers.m_pArrayInt, sizeArrayInt);

    // pString
    offset += sizeArrayInt;
    memcpy(totalBuffer + offset, pString, sizeString);

    // Массив символов 
    offset += sizeString;
    memcpy(totalBuffer + offset, pointers.m_pTCHAR, tcharSize);
		
    // Реально отправленное количество
    int totalActualSend = 0;
		
    // Отправка данных пакетами.
    while (totalActualSend < totalDataSize)
    {
        // Если размер буфера позволит, отправим всё сразу.
        int sizeCompute = (totalDataSize - totalActualSend);

        // Но если размер отправляемых данных больше буфера,
        // размер пакета устанавливаем равным буферу. 
        // Если меньше размер пакета остаётся равен отправляемому остатку.
        if ((totalDataSize - totalActualSend) > sizeBuffer)
        {
            sizeCompute = sizeBuffer;
        }
		
        // Подсчитываем реально отправляемые данные.
        int sent = pSockSend->Send(totalBuffer + totalActualSend, sizeCompute);
        totalActualSend += sent;
    }

    // Сброс данных для отправки следующей партии.
    if (totalActualSend == totalDataSize)
    {
        delete [] totalBuffer;
        totalBuffer = NULL;

        // Данные отправлены. Снимаем флаг. 
        // Разрешаем отправку следующей партии.
        m_FlagSent = TRUE;

        CUtil::BeepOk();
    }

    // Вывод статуса отправки.
    m_Status = _T(" Отправлено: ") + 
        CUtil::IntToStr(totalActualSend) + _T("(")  + 
             CUtil::IntToStr(totalDataSize) + _T(") байт");

    UpdateData(FALSE);
}

Извлечение данных из сетевого буфера

В нашем приложении приём данных из сети, также как и отправка, осуществляется пакетами. При каждой доставке пакета в приёмный буфер генерируется событие OnReceive(...). Перед извлечением данных измеряется их размер и затем извлекается точно такое же количество. Первоначально определяется буфер равный первому пакету, из первых полученных байтов извлекается объект CSendInfo с информацией о размерах отправленных данных. Как только успешно будет получена информация о размерах, создаётся главный буфер равный величине всех данных. Затем полученные и извлекаемые байты последовательно помещаются в него.

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

Программный код метода извлечения байтов из сетевого буфера:
ParseReceiveData(CMySocket* recvSocket)
{
    DWORD recvSize = 0;
    if (recvSocket->IOCtl(FIONREAD, &recvSize) == TRUE)
    {
        // Данный объект, после создания полной копии
        // отправленного объекта, можно передать далее
        // какой-либо метод. Здесь же он просто в конце
        // этого метода разрушается.
        CPointers pointersReceive;

        if (m_InfoOk == FALSE)
        {
            // Размер CSendInfo не более 20 байт. Первый пакет, с высокой вероятностью, 
            // размером будет больше чем объект CSendInfo.
            // Но для коммерческих версий здесь необходим 
            // код обработки ситуации когда recvSize < sizeof(CSendInfo).
            // В последующих статьях я добавлю этот код.
            //if (recvSize >= sizeof(CSendInfo))
            //{
                   BYTE* buffer = new BYTE[recvSize];
                   int check = recvSocket->Receive(buffer, recvSize);

                   m_CounterRecv += check;
				
                   // Копируем только размер объекта CSendInfo.
                   // Остальные байты будут получать другие переменные.
                   memcpy_s(&m_sendInfo, sizeof(CSendInfo), buffer, sizeof(CSendInfo));

                   // Основной буфер
                   m_DataBuffer = new BYTE[m_sendInfo.m_TotalDataSize];
                   memcpy_s(m_DataBuffer, recvSize, buffer, recvSize);

                   m_InfoOk = TRUE;
                   delete[] buffer;
                   buffer = NULL;

           //}
        }
        else
        {
            int check = recvSocket->Receive(m_DataBuffer + m_CounterRecv, recvSize);

            m_CounterRecv += check;
        }

        // Проверка окончания приёма данных.
        if (m_sendInfo.m_TotalDataSize > 0 && m_CounterRecv >= m_sendInfo.m_TotalDataSize)
        {
            // Возможная ошибка.
            if (m_CounterRecv > m_sendInfo.m_TotalDataSize)
            {
                CUtil::BeepError();
            }
			
            // Смещение точки отсчёта для копирования следующих данных.
            int offset = sizeof(CSendInfo);

            // --- Получаем значение m_pInt класса CPointers ---

            int* i = new int;
            memcpy_s(i, sizeof(int), m_DataBuffer + offset, sizeof(int));

            //  Восстановление данных указателя
            pointersReceive.m_pInt = i;
            // Показ значения в окне интерфейса
            m_valueInt = *pointersReceive.m_pInt;

			
            // --- Получаем значение m_pDouble класса CPointers ---

            offset += sizeof(int);
            double* d = new double;
            memcpy_s(d, sizeof(double), m_DataBuffer + offset, sizeof(double));

            pointersReceive.m_pDouble = d;
            m_valueDouble = *pointersReceive.m_pDouble;

            // --- Получаем значение массива целочисленных значений CPointers::m_pArrayInt --- 

            offset += sizeof(double);
            int* pArrayInt = new int[m_sendInfo.m_SizeArrayInt];
            memcpy_s(pArrayInt, m_sendInfo.m_SizeArrayInt, 
                m_DataBuffer + offset, m_sendInfo.m_SizeArrayInt);

            // Восстановление указателя на массив
            pointersReceive.m_pArrayInt = pArrayInt;

            // Показ значений в окнах интерфейса
            m_valueArrayInt0 = pointersReceive.m_pArrayInt[0];
            m_valueArrayInt1 = pointersReceive.m_pArrayInt[1];
            m_valueArrayInt2 = pointersReceive.m_pArrayInt[2];
            m_valueArrayInt3 = pointersReceive.m_pArrayInt[3];


            // --- Получаем данные строки CString m_String класса CPointers --- 

            offset += m_sendInfo.m_SizeArrayInt;
            BYTE* pString = new BYTE[m_sendInfo.m_SizeString];
            memcpy_s(pString, m_sendInfo.m_SizeString, 
                m_DataBuffer + offset, m_sendInfo.m_SizeString);

            pointersReceive.m_String = (LPCTSTR)pString; //(TCHAR*)pString;
            m_valueString = pointersReceive.m_String; 

            // Байты строки pString полностью копируются в объект pointersReceive.m_String,
            // поэтому память занятую pString необходимо освобождать.
            delete[] pString;
            pString = NULL;

            // --- Поучаем байты массива символов CPointers::m_pTCHAR ---

            offset += m_sendInfo.m_SizeString;
            TCHAR* pArrayChar = new TCHAR[m_sendInfo.m_SizeArrayTCHAR];
            memcpy_s(pArrayChar, m_sendInfo.m_SizeArrayTCHAR, 
                m_DataBuffer + offset, m_sendInfo.m_SizeArrayTCHAR);
			 
            pointersReceive.m_pTCHAR = pArrayChar;
            m_valueArrayChar = pointersReceive.m_pTCHAR;

            // Освобождаем память занятую главным буфером.
            delete[] m_DataBuffer;
            m_DataBuffer = NULL;

            // Получена точная копия объекта класса CPointers, данные которого 
            // были отправлены в сеть.
            // pointersReceive

            // Индикация статуса приёма данных.			
            m_Status = L" Получено:" + CUtil::IntToStr(m_CounterRecv) + L" байт";

            // Сброс глобальных переменных-членов для следующей партии данных.
            m_CounterRecv = 0;
            m_sendInfo.Reset();
            m_InfoOk = FALSE;
        }

		
    }
    else
    {
        CUtil::BeepError();
    }

    UpdateData(FALSE);

    return FALSE;
}

Исходник примера передачи указателей по сети

Что было описано выше реализовано в исходнике приложения прикрепленного к данной статье. Исходник создан в интегрированной среде программирования MS Visual Studio 2019. Для наглядного графического интерфейса исходника С++ приложение построено на диалоговых окнах MFC (Microsoft Foundation Classes). Библиотека MFC в разы повышает скорость разработки на языке программирования C++.

Скачать исходник

Тема: «Передача указателей в объектах классов и структур по сети» Язык программирования С++ MFCPointerNetwork-vs16.zip Размер:3853 КбайтЗагрузки:321