CNetworkCommunication - UDP v MFC

Petr Wiedemann, 14. březen 2009

CNetworkCommunication je třída odvozená z CAsyncSocket, napsaná pro příjem a vysílání paketů protokolem UDP. Je závislá na funkcích MFC. Před jejím použitím je třeba zavolat funkci AfxSocketInit(), například při spuštění aplikace ve funkci InitInstance().

K použití jsou funkce:

bool InitializeInterface(UINT port);
int ReceiveData(BYTE *p_buffer, DWORD bufferSize, DWORD *p_readed);
int TransmitData(BYTE *p_buffer, DWORD n_count, CString address, UINT port, DWORD *p_wrote);

V souboru NetworkCommunication.h jsou definovány hodnoty pro maximální čas (ms), který čeká funkce ReceiveData na příchozí data a jejich návratové hodnoty.

#define NW_MAX_RESPONSE_TIME 500
#define NW_ALL_OK 0x00
#define NW_NO_DATA_IN_QUEUE 0x01
#define NW_IO_ERROR 0x03

Pro příjem a posílání dat je nejprve nutné zavolat funkci InitializeInterface, která inicializuje sokety pro příjem a vysílání. Parametr port určuje, ze kterého portu budou čtena data.

bool CNetworkCommunication::InitializeInterface(UINT port)
{
  // vytvoreni soketu this (prijem)
  if(!Create(port, SOCK_DGRAM, FD_READ))
    return false;
  BOOL bMultipleApps = TRUE;
  SetSockOpt(SO_REUSEADDR, (void*)&bMultipleApps, sizeof(BOOL), SOL_SOCKET);

  // nastaveni velikosti prichoziho bufferu
  int iBufferSize = 131072;
  BOOL bRet = SetSockOpt(SO_RCVBUF, (void*)&iBufferSize, sizeof(int), SOL_SOCKET);
  ASSERT(bRet);

  // vytvoreni soketu pro vysilani
  if(!send.Create(0, SOCK_DGRAM, 0))
  {
    return false;
  }

  return true;
}

Příjem nových dat

Pro získání nových dat slouží funkce ReceiveData se 3 parametry: BYTE *p_buffer, DWORD bufferSize, DWORD *p_readed.

  • BYTE *p_buffer - buffer, kam se uloží nová data
  • DWORD bufferSize - velikost p_buffer
  • DWORD *p_readed - počet dat, uložených v p_buffer

Výpis funkce:

int CNetworkCommunication::ReceiveData(BYTE *p_buffer, DWORD bufferSize, DWORD *p_readed)
{
  DWORD dwLen;
  DWORD dwTick1 = GetTickCount();

l_read_again:
#ifdef LOGGING
  FormatLog(fLogStatus, _T("CNetworkCommunication::ReceiveData - l_read_again label"),
    LOG_NEWLINE | LOG_LOGTIME);
#endif

  if (!IOCtl(FIONREAD, &dwLen))
    return NW_IO_ERROR;

  // test, jestli buffer obsahuje data
  // pokud nejsou data k dispozici, zkousi nacitat max. cas NW_MAX_RESPONSE_TIME
  if (dwLen < 1)
  {
    if ((GetTickCount() - dwTick1) > NW_MAX_RESPONSE_TIME)
      return NW_NO_DATA_IN_QUEUE;
    else
    {
      Sleep(1);
      goto l_read_again;
    }
  }

  // nacteni dat
  int error = ReceiveFrom (p_buffer, bufferSize, senderip, senderport);

#ifdef LOGGING
  // logovani nactenych dat
  DWORD log_counter;
  CString str, str_temp;
  if (error > 0)
  {
    str = _T("CNetworkCommunication::ReceiveData data in ->");

    for (log_counter = 0; log_counter < (DWORD)error; log_counter++)
    {
      str_temp.Format(_T(" 0x%02X"), p_buffer[log_counter]);
      str += str_temp;

      if (iswprint((wint_t)p_buffer[log_counter]))
      {
        str_temp.Format(_T(" (%c)"), p_buffer[log_counter]);
        str += str_temp;
      }
    }

    FormatLog(fLogStatus, str, LOG_NEWLINE | LOG_LOGTIME);
  }
  else
  {
    FormatLog(fLogStatus, _T("CNetworkCommunication::ReceiveData data in -> no data"),
      LOG_NEWLINE | LOG_LOGTIME);
  }
#endif

  if(error == SOCKET_ERROR)
  {
    return NW_IO_ERROR;
  }
  else
  {
    // pocet nactenych dat
    *p_readed = error;
    return NW_ALL_OK;
  }
}

Odesílání dat

Pro odeslání dat je k dispozici funkce TransmitData s 5 parametry: BYTE *p_buffer, DWORD n_count, CString address, UINT port, DWORD *p_wrote.

  • BYTE *p_buffer - buffer, kde jsou data k vyslání
  • DWORD n_count - počet dat v p_buffer
  • CString address - adresa počítače, na který mají být data vyslána
  • UINT port - port, na kterém cílový počítač naslouchá
  • DWORD *p_wrote - počet skutečně vyslaných dat

Výpis funkce:

int CNetworkCommunication::TransmitData(BYTE *p_buffer, DWORD n_count,
  CString address, UINT port, DWORD *p_wrote)
{
  // vyslani dat
  int error = send.SendTo((BYTE*)p_buffer, n_count, port, address);
  if (error == SOCKET_ERROR)
    return NW_IO_ERROR;

#ifdef LOGGING
  // logovani vyslanych dat
  DWORD log_counter;
  CString str, str_temp;
  str = _T("CNetworkCommunication::TransmitData data out ->");
  for (log_counter = 0; log_counter < n_count; log_counter++)
  {
    str_temp.Format(_T(" 0x%02X"), p_buffer[log_counter]);
    str += str_temp;
  }
  FormatLog(fLogStatus, str.GetBuffer(1024), LOG_NEWLINE | LOG_LOGTIME);
  str.ReleaseBuffer();
#endif

  // pocet odeslanych dat
  *p_wrote = error;

  return NW_ALL_OK;
}

Třídu jsem použil v aplikaci, kde se o síťovou komunikaci staralo samostatné vlákno, takže případné zpoždění ve funkci ReceiveData tolik nevadilo. Hlavní vlákno aplikace potom bylo informováno o dostupnosti nových dat pomocí zpráv (SendMessage). Třída samotná asi moc využití nenajde, snad ale poslouží jako příklad pro posílání a příjem dat přes UDP.