본문 바로가기

개발/C&C++

[C/C++]Packet Parser 클래스(Similar. String...)

728x90
반응형

십수년 전에.. 개발자로 취업을 해서 초기에 델파이, PHP, C/C++ 을 실무에서 사용하다가 저를 가르쳐주신 사수 차장님의 반강제(?)적인 권유로 인해 만들어 본 PacketParser Class 입니다.

 

보시기에 부족한 부분이 많겠지만 너그럽게 봐주시기 바랍니다 (__)

( 너무 오래 전에 만들어서 기억도 못 했는데 최근에 프로젝트를 진행했던 소스들을 보다가 문득 발견하게 되어서 찾은 기념으로 한번 올려봅니다...)

 

클래스의 기능은 사용자가 지정한 구분자(Delimeter)를 기준으로 문자열을 Parsing  하여 문자, 숫자 등의 데이터로 각 데이터값을 순차적 또는 인덱스를 지정하여 값을 확인할 수 있습니다. 

어찌 보면 살짝(?) 부족한 문자열 클래스라고 볼수도 있겠네요. 다행히 문자열 클래스보다 메모리는 덜 잡아 먹겠네요. 하..

 

클래스 명은 CPacketParser 입니다.

 

CPakcetParser.h(헤더 파일) 

 

헤더 파일 소스 입니다.

 

#pragma once

#include <iostream>

#define BUFSIZE         8192
#define BASE_DELIMETER	','

class CPacketParser
{
private :
	int     m_nIndex;                   // current Index
	int     m_nMaxSize;                 // After Parsing Data Size 
	char    m_chDel;                    // Delimeter
	char    m_chEnd;                    // String End(NULL)
	char    m_sData[BUFSIZE];           // Base Data
	std::vector<char *> m_arrDatas;     // After Parsing Data

public :
	CPacketParser(char chDelimeter = BASE_DELIMETER);
	virtual ~CPacketParser();

	int     GetSize();
	bool    IsEmpty();
	void    Clear();
	int     Find(LPSTR sValue);
	byte    GetByteValue();
	short   GetShortValue();
	int     GetIntValue();
	long    GetLongValue();
	float   GetFloatValue();
	double  GetDoubleValue();
	LPSTR   GetStringValue();
	char    GetCharValue();

	void    SetString(LPCSTR sData, UINT nSize = 0);
	void    SetString(LPCWSTR sData);
	void    SetDelimeter(char chDel);
	void    SetEndChar(char chEnd);
	void    Trim(LPSTR sData);

	char    GetDelimeter();
	LPSTR   GetString();
	LPSTR   GetString(int nIndex);
	int     GetCurrentIndex();
};

 

심플하죠..?

버퍼 사이즈는 그때 통신 데이터 사이즈를 감안하여 최대 8MB 로 지정하였고 기본 구분자는 ',' 로 설정하였고 객체 생성 시에 구분자를 지정할 수도 있습니다.

 

멤버 변수에 대한 설명은 주석과 같구요, 각 함수에 대한 설명은 함수의 기능이 구현된 소스파일에서 설명 드리겠습니다.

 

 

 

CPacketParser.cpp(소스 파일)

 

소스파일 전체 소스 입니다.

 

#include "PacketParser.h"

#define TRIM_RIGHT(x) { int i ; for (i = strlen(x) - 1; (i >= 0)&&(x[i]==' '); i--); x[i + 1] = 0; };
#define TRIM_LEFT(x) { _tcsrev(x); TRIM_RIGHT(x); _tcsrev(x); };
#define TRIM(x) { TRIM_RIGHT(x); TRIM_LEFT(x); };

CPacketParser::CPacketParser(char chDelimeter)
{
	m_nIndex = 0;
	m_nMaxSize = 0;
	m_chDel = chDelimeter;
	m_chEnd = 0;
	memset(m_sData, 0, sizeof(m_sData));
	m_arrDatas.clear();
}

CPacketParser::~CPacketParser()
{
	m_nIndex = 0;
	m_nMaxSize = 0;
	m_chDel = 0;
	memset(m_sData, 0, sizeof(m_sData));
	if (!m_arrDatas.empty())
	{
		for (size_t i = 0; i < m_arrDatas.size(); i++)
			delete [] m_arrDatas[i];

		m_arrDatas.clear();
	}
}

int CPacketParser::GetSize()
{
	return m_nMaxSize;
}

bool CPacketParser::IsEmpty()
{
	return (m_nMaxSize > 0) ? FALSE : TRUE;
}

void CPacketParser::Clear()
{
	m_nIndex = 0;
	m_nMaxSize = 0;
	memset(m_sData, 0, sizeof(m_sData));
	if (!m_arrDatas.empty())
	{
		for (size_t i = 0; i < m_arrDatas.size(); i++)
			delete [] m_arrDatas[i];
		m_arrDatas.clear();
	}
}

int CPacketParser::Find( LPSTR sValue )
{
	for (int i = 0; i < m_nMaxSize; i++)
	{
		if (strcmp(m_arrDatas[i], sValue) == 0)
			return i;
	}

	return -1;
}

byte CPacketParser::GetByteValue()
{
	return (byte)GetLongValue();
}

short CPacketParser::GetShortValue()
{
	return (short)GetLongValue();
}

int CPacketParser::GetIntValue()
{
	return (int)GetLongValue();
}

long CPacketParser::GetLongValue()
{
	if(m_nMaxSize <= m_nIndex || m_nIndex < 0)
		return -1;

	long ntemp = atol(m_arrDatas[m_nIndex++]);
	return ntemp;
}

float CPacketParser::GetFloatValue()
{
	return (float)GetDoubleValue();
}

double CPacketParser::GetDoubleValue()
{
	if (m_nMaxSize <= m_nIndex || m_nIndex < 0)
		return -1;

	double ntemp = atof(m_arrDatas[m_nIndex++]);
	return ntemp;
}

LPSTR CPacketParser::GetStringValue()
{
	if (m_nMaxSize <= m_nIndex || m_nIndex < 0)
		return "";

	return m_arrDatas[m_nIndex++];
}

char CPacketParser::GetCharValue()
{
    if (m_nMaxSize <= m_nIndex || m_nIndex < 0)
        return -1;

    //SetTrim(m_arrDatas[m_nIndex]);
    return m_arrDatas[m_nIndex++][0];
}

void CPacketParser::SetString( LPCSTR sData, UINT nSize /*= 0*/ )
{
	if (sData == NULL)
		return ;

	char temp[BUFSIZE + 1]; // Declare Buffer
	memset(temp, 0, sizeof(temp)); // Init
	if (nSize > 0)
	{
		memcpy(temp, sData, nSize); // Const Char Data Copy
	}
	else
	{
		nSize = strlen(sData);
		memcpy(temp, sData, strlen(sData)); // Const Char Data Copy
	}

	char * ptok = static_cast<char *>(memchr(temp, m_chEnd, sizeof(temp))); // Check NULL Data Position
	if (NULL == ptok) return ;

	int templen = (int)ptok - (int)temp; // Start Data Position - NULL Data Position
	temp[templen] = m_chDel; // Last Data Next Input Delimeter

	if (!m_arrDatas.empty()) // Data Array Init
		m_arrDatas.clear();

	int nOffset = 0;
	ptok = static_cast<char *>(memchr(temp, m_chDel, sizeof(temp))); // Find Next Delimeter
	while(NULL != ptok && ((UINT)nOffset <= nSize) ) // Exist Delimeter
	{
		int nCurrentSize = (int)ptok - (int)temp - nOffset; // Current Data Size
		if (nCurrentSize <= 0)
		{
			nOffset += 1;
			continue;
		}

		char * stemp = NULL;
		stemp = new char[nCurrentSize + 1]; // Create Current Data Buffer
		memset(stemp, 0, nCurrentSize + 1); // Init
		memcpy(stemp, temp + nOffset, nCurrentSize); // Copy Current Data
        Trim(stemp);
		m_arrDatas.push_back(stemp); // Append Current Data To Data Array
		
		nOffset += nCurrentSize + 1; // Current position Set
		if (nOffset + 1 >= sizeof(temp)) // Check Total Size & Current Position
			break;

		ptok = static_cast<char *>(memchr(temp + nOffset, m_chDel, sizeof(temp) - nOffset)); // Find Next Delimeter
	}

	m_nMaxSize = m_arrDatas.size(); // Total Count  
	memset(m_sData, 0, sizeof(m_sData));
	memcpy(m_sData, sData, nSize);
	memset(temp, 0, sizeof(temp));
}

void CPacketParser::SetString(LPCWSTR sData)
{
	int nLen = wcslen(sData);
	char * stemp = new char[nLen * 2];
	memset(stemp, 0, nLen * 2);
	WideCharToMultiByte(CP_ACP, 0, sData, -1, stemp, nLen * 2, NULL, NULL);
	SetString(stemp);
	delete [] stemp;
}

void CPacketParser::SetDelimeter(char chDel)
{
	m_chDel = chDel;
}

void CPacketParser::SetEndChar(char chEnd)
{
	m_chEnd = chEnd;
}

void CPacketParser::Trim( LPSTR sData )
{
    if (sData == NULL)
        return ;

    int nSize = strlen(sData);
    int nRightCount = 0;
    for (int i = nSize; i >= 0; i--)
    {
        if (sData[i] != ' ' && sData[i] != 0 && sData[i] != '\t')
        {
            nRightCount = i + 1;
            break;
        }
    }

    int nLeftCount = 0;
    for (int i = 0; i < nSize; i++)
    {
        if (sData[i] != ' ' && sData[i] != 0 && sData[i] != '\t')
        {
            nLeftCount = i;
            break;
        }
    }

    char * strValue = new char[nSize];
    memset(strValue, 0, nSize);
    memcpy(strValue, sData + nLeftCount, nSize - nLeftCount - (nSize - nRightCount));

    memset(sData, 0, nSize);
    memcpy(sData, strValue, nSize);
    if (strValue != NULL)
    {
        delete [] strValue;
        strValue = NULL;
    }    
}

char CPacketParser::GetDelimeter()
{
	return m_chDel;
}

LPSTR CPacketParser::GetString()
{
	return m_sData;
}

LPSTR CPacketParser::GetString(int nIndex)
{
	if (nIndex > m_nMaxSize || nIndex < 0)
		return NULL;

	return m_arrDatas[nIndex];
}

int CPacketParser::GetCurrentIndex()
{
	return m_nIndex;
}

 

 

소스파일에 대한 기능은 함수별로 설명 드리겠습니다.(설명이 조금이라도 필요한 함수들만...)

 

CPacketParser(char chDelimeter) : 생성자는 멤버변수를 초기화 하고 구분자를 파라미터로 받아서 설정할 수 있습니다.

 
CPacketParser::CPacketParser(char chDelimeter)
{
	m_nIndex = 0;
	m_nMaxSize = 0;
	m_chDel = chDelimeter;
	m_chEnd = 0;
	memset(m_sData, 0, sizeof(m_sData));
	m_arrDatas.clear();
}

 

 

~CPacketParser() : 소멸자는 멤버변수의 값을 삭제합니다.
 
CPacketParser::~CPacketParser()
{
	m_nIndex = 0;
	m_nMaxSize = 0;
	m_chDel = 0;
	memset(m_sData, 0, sizeof(m_sData));
	if (!m_arrDatas.empty())
	{
		for (size_t i = 0; i < m_arrDatas.size(); i++)
			delete [] m_arrDatas[i];

		m_arrDatas.clear();
	}
}

 

 

GetSize() : 파싱된 데이터의 전체 개수를 반환합니다.
 
int CPacketParser::GetSize()
{
	return m_nMaxSize;
}

 

 

IsEmpty() : 파싱된 데이터가 존재하는지 확인 합니다.
 
bool CPacketParser::IsEmpty()
{
	return (m_nMaxSize > 0) ? FALSE : TRUE;
}

 

 

Clear() : 모든 멤버변수를 초기화 합니다.
 
void CPacketParser::Clear()
{
	m_nIndex = 0;
	m_nMaxSize = 0;
	memset(m_sData, 0, sizeof(m_sData));
	if (!m_arrDatas.empty())
	{
		for (size_t i = 0; i < m_arrDatas.size(); i++)
			delete [] m_arrDatas[i];
		m_arrDatas.clear();
	}
}

 

 

Find( LPSTR sValue ) : sValue 와 동일한 문자열이 있는 배열 데이터의 인덱스를 반환합니다. 없으면 -1을 반환합니다.
 
int CPacketParser::Find( LPSTR sValue )
{
	for (int i = 0; i < m_nMaxSize; i++)
	{
		if (strcmp(m_arrDatas[i], sValue) == 0)
			return i;
	}

	return -1;
}

 

 

GetByteValue(), GetShortValue(), GetIntValue(), GetLongValue() : 정수형 데이터를 반환합니다.

 

byte CPacketParser::GetByteValue()
{
	return (byte)GetLongValue();
}

short CPacketParser::GetShortValue()
{
	return (short)GetLongValue();
}

int CPacketParser::GetIntValue()
{
	return (int)GetLongValue();
}

long CPacketParser::GetLongValue()
{
	if(m_nMaxSize <= m_nIndex || m_nIndex < 0)
		return -1;

	long ntemp = atol(m_arrDatas[m_nIndex++]);
	return ntemp;
}

 

 

GetFloatValue(), GetStringValue() : 실수형 데이터를 반환합니다.

 

float CPacketParser::GetFloatValue()
{
	return (float)GetDoubleValue();
}

double CPacketParser::GetDoubleValue()
{
	if (m_nMaxSize <= m_nIndex || m_nIndex < 0)
		return -1;

	double ntemp = atof(m_arrDatas[m_nIndex++]);
	return ntemp;
}
 

 

GetStringValue(), GetString(int nIndex) : 문자열 데이터를 반환합니다. 흐름에 맞게 반환이 되고 필요 시 인덱스를 지정해서 반환 가능합니다.

 

LPSTR CPacketParser::GetStringValue()
{
	if (m_nMaxSize <= m_nIndex || m_nIndex < 0)
		return "";

	return m_arrDatas[m_nIndex++];
}

LPSTR CPacketParser::GetString(int nIndex)
{
	if (nIndex > m_nMaxSize || nIndex < 0)
		return NULL;

	return m_arrDatas[nIndex];
}

 

 

SetString( LPCSTR sData, UINT nSize /*= 0*/ ) : 데이터를 파싱합니다.(멀티바이트)
 
void CPacketParser::SetString( LPCSTR sData, UINT nSize /*= 0*/ )
{
	if (sData == NULL)
		return ;

	char temp[BUFSIZE + 1]; // Declare Buffer
	memset(temp, 0, sizeof(temp)); // Init
	if (nSize > 0)
	{
		memcpy(temp, sData, nSize); // Const Char Data Copy
	}
	else
	{
		nSize = strlen(sData);
		memcpy(temp, sData, strlen(sData)); // Const Char Data Copy
	}

	char * ptok = static_cast<char *>(memchr(temp, m_chEnd, sizeof(temp))); // Check NULL Data Position
	if (NULL == ptok) return ;

	int templen = (int)ptok - (int)temp; // Start Data Position - NULL Data Position
	temp[templen] = m_chDel; // Last Data Next Input Delimeter

	if (!m_arrDatas.empty()) // Data Array Init
		m_arrDatas.clear();

	int nOffset = 0;
	ptok = static_cast<char *>(memchr(temp, m_chDel, sizeof(temp))); // Find Next Delimeter
	while(NULL != ptok && ((UINT)nOffset <= nSize) ) // Exist Delimeter
	{
		int nCurrentSize = (int)ptok - (int)temp - nOffset; // Current Data Size
		if (nCurrentSize <= 0)
		{
			nOffset += 1;
			continue;
		}

		char * stemp = NULL;
		stemp = new char[nCurrentSize + 1]; // Create Current Data Buffer
		memset(stemp, 0, nCurrentSize + 1); // Init
		memcpy(stemp, temp + nOffset, nCurrentSize); // Copy Current Data
        Trim(stemp);
		m_arrDatas.push_back(stemp); // Append Current Data To Data Array
		
		nOffset += nCurrentSize + 1; // Current position Set
		if (nOffset + 1 >= sizeof(temp)) // Check Total Size & Current Position
			break;

		ptok = static_cast<char *>(memchr(temp + nOffset, m_chDel, sizeof(temp) - nOffset)); // Find Next Delimeter
	}

	m_nMaxSize = m_arrDatas.size(); // Total Count  
	memset(m_sData, 0, sizeof(m_sData));
	memcpy(m_sData, sData, nSize);
	memset(temp, 0, sizeof(temp));
}

 

먼저 파싱할 데이터가 있는지 확인하고 없으면 파싱을 중지합니다.

파싱할 데이터가 있을 경우 데이터를 임시 버퍼에 복사합니다.( Size 가 지정되지 않았을 경우 사이즈 확인 후 복사)

문자열의 끝을 찾습니다.(null(문자열의 끝)) 문자열이 아닐 경우 파싱을 중지합니다.

문자열일 경우 원활한 파싱 진행을 위해 문자열의 끝을 구분자로 교체합니다.

제일 처음 나오는 구분자를 찾습니다.

구분자가 존재하고 그 위치가 전체 문자열의 크기를 넘지 않는지 확인하고 조건에 충족할 경우 파싱된 데이터의 사이즈를 확인 후 파싱된 문자열 데이터를 가공하여 파싱 데이터를 저장할 배열에 넣습니다.

다음 데이터 검색을 위해 temp 데이터의 메모리 시작 위치를 offset 만큼 이동시켜서 계속 데이터 검색을 진행합니다.

모든 데이터를 파싱 한 후 파싱을 진행했던 문자열 원본을 m_sData 에 복사하고 파싱된 데이터 크기를 저장한 후 임시 버퍼인 temp 의 데이터를 삭제(초기화) 합니다.

 

 

SetString(LPCWSTR sData) : 데이터를 파싱합니다.(유니코드 변환 후 파싱)
 
void CPacketParser::SetString(LPCWSTR sData)
{
	int nLen = wcslen(sData);
	char * stemp = new char[nLen * 2];
	memset(stemp, 0, nLen * 2);
	WideCharToMultiByte(CP_ACP, 0, sData, -1, stemp, nLen * 2, NULL, NULL);
	SetString(stemp);
	delete [] stemp;
}

 

 

SetDelimeter(char chDel) : 구분자를 설정합니다. 구분자는 char 타입입니다.
 
void CPacketParser::SetDelimeter(char chDel)
{
	m_chDel = chDel;
}

 

 

Trim( LPSTR sData ) : 문자열의 앞뒤 공백을 제거합니다.(space or null or tab)
 
void CPacketParser::Trim( LPSTR sData )
{
    if (sData == NULL)
        return ;

    int nSize = strlen(sData);
    int nRightCount = 0;
    for (int i = nSize; i >= 0; i--)
    {
        if (sData[i] != ' ' && sData[i] != 0 && sData[i] != '\t')
        {
            nRightCount = i + 1;
            break;
        }
    }

    int nLeftCount = 0;
    for (int i = 0; i < nSize; i++)
    {
        if (sData[i] != ' ' && sData[i] != 0 && sData[i] != '\t')
        {
            nLeftCount = i;
            break;
        }
    }

    char * strValue = new char[nSize];
    memset(strValue, 0, nSize);
    memcpy(strValue, sData + nLeftCount, nSize - nLeftCount - (nSize - nRightCount));

    memset(sData, 0, nSize);
    memcpy(sData, strValue, nSize);
    if (strValue != NULL)
    {
        delete [] strValue;
        strValue = NULL;
    }    
}

 

 

 

클래스 내부 C/C++ 함수 레퍼런스 및 설명

 

CPacketParser 클래스 제작 시에 사용한 C/C++ 함수의 종류와 기능에 대해 드리겠습니다.

 

 

void memset(void *s, int c, size_t n);

- s : 채울 대상의 메모리

- c : 채울 값, int 형이지만 1바이트(char) 로 인식.

- n : 채울 개수

>> s 가 포인트하는 메모리를 값 c 로 n 개를 채웁니다.

 

 

int atol(const chat * s);

- s : 정수로 변환될 문자열

>> s 문자열을 long 형 정수로 변환하여 반환합니다.

     문자열은 숫자 및 부호로 이루어져 있어야 하며 숫자의 앞부분의 Tab 이나 공백은 무시하고 변환합니다. 

     문자열이 정수로 변환이 불가능할 경우 0 을 반환합니다.

 

 

double atof(const char * s);

- s: 실수로 변환될 문자열

>> s 문자열을 double 형 부동 소수점으로 변환하여 반환합니다.

     문자열은 숫자 및 부호, 소수점으로 이루어져 있어야 하며 숫자의 앞부분의 Tab 이나 공백은 무시하고 변환합니다. 

     문자열이 실수로 변환이 불가능할 경우 0 을 반환합니다.

 

 

void * memcpy(void * dest, void * src, size_t n);

- dest : 복사 대상 메모리(target)

- src : 복사원 메모리(source)

- n : 복사할 길이

>> src 번지에 있는 데이터를 dest 가 지정하는 번지로 n 바이트만큼 복사를 합니다.

     복사에 성공하였을 경우 dest 포인터를 반환하고, 에러 발생 시 NULL 을 반환합니다.

 

 

size_t strlen(const char * s);

- s : 길이를 계산할 문자열

>> 문자열 s 의 길이를 계산합니다. 

     참고로 문자열의 길이란 문자열의 선두에서부터 NULL 문자 사이의 문자 개수를 말합니다.

 

 

void * memchr(const void * s, char c, size_t n);

- s: 시작 포인트 메모리 번지

- c : 찾아야 되는 문자

- n : 찾을 수 있는 최대 개수

>> s 의 메모리 번지부터 n 바이트 내의 메모리까지 중에 문자 c 를 찾아서 위치를 반환합니다.

     발견 시 최초로 발견한 문자 c 의 위치를 반환하고 없을 경우 NULL 을 반환합니다.

 

 

int strcmp(const char * s1, const char * s2);

- s1 : 크기를 비교할 첫번째 문자열

- s2 : 크기를 비교할 두번째 문자열

>> 두개의 문자열의 대소를 비교합니다.(대문자와 소문자 구분)

     s1 > s2 : 양수 반환

     s1 = s2 : 0 을 반환

     s1 < s2 : 음수 반환

 

 

int WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, int cchWideChar,

                                         LPSTR lpMultiByteStr, int cbMultiByte, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar);

- CodePage : 변환 방법(타켓 인코딩)

- dwFlags : 0

- lpWideCharStr : 원본 문자열(source)

- cchWideChar : 원본 문자열 길이(-1 을 넣으면 자동할당)

- lpMultiByteStr : 변환된 문자열(target)

- cbMultiByte : 변환된 문자열 크기(유니코드 문자열의 2배를 할당)

- lpDefaultChar, lpUserDefaultChar : 실패 시 사용(기본적으로 NULL 을 할당)

>> 유니코드 문자열을 멀티바이트 문자열로 변환하여 할당합니다.

     반대 기능의 함수도 있지만 요즘은 사용하지 않을 함수 이므로 여기서 중략...

 

 

여기까지 임미다.

728x90