객체지향 언어에서 빠질 수 없는 동적바인딩 기법에 대해 알아보자.
예시는 C++ 언어로 아주 예전에 ModbusTCP Protocol 을 개발했던 프로젝트로 할 것 이다.
git log 를 보니 정확히 금일 날짜 기준 593일 전에 git push 되었다.
소스는 아래경로에 올려 두었다.
필자가 예전에 직접 짠 코드라 프리하게 오픈 가능해서 편하다.
https://github.com/Anthony8062/ModbusTcp
GitHub - Anthony8062/ModbusTcp: 프로젝트
프로젝트. Contribute to Anthony8062/ModbusTcp development by creating an account on GitHub.
github.com
정적 바인딩
정적 바인딩은 컴피일 시점에 그 심볼 주소 값이 확정이 되는 것이다.
일반적으로 변수에 값이 할당될 때 라고 볼 수 있다.
장점으로는 동적 바인딩 대비 당연히 속도가 빠르다.
단점은 유연성이 없다.
동적 바인딩
동적 바인딩은 컴파일 이후 실제 실행 될 당시에 해당 심볼 주소 값이 확정 되는 것이다.
장점으로는 유연성이 있으며, 객체지향 답게 프로그램 구조 설계시 매우 유용하다.
단점으로는 속도가 정적 바인딩 대비 비교적 느리다.
동적 바인딩 과정에 대하여 결론을 설명 하자면,
아래와 같이 virtual 키워드를 사용하면, 해당 심볼은
Virtual Pointer Table 에 속하게 되고, 특정 주소값을 할당 받게 된다.
그 이후 Server class 에서 virtial 심볼을 실제로 구현한 후
Server class 를 객체화 하는 순간 Virtual Pointer Table 에 정해져 있던
주소값에서 새로운 주소값인 Server 의 ? 주소 값으로 할당 받게 된다.
즉, 실제 객체화가 되는 순간을 실제 실행으로 간주하게 되어 동적 바인딩이 이루어 지는 것이다.
구조는 Client 와 Server 로 구성된다.
우선 아래 main 함수에서 CModbusTCP class 를 객체화 함으로써 동적바인딩은 이루어 진다.
그러면 어떻게 이루어 지는지를 알아보도록 하자.
#pragma once
#include <vector>
using namespace std;
class CTcpIpInfo;
class CServerTcp;
class CModbusTCP
{
public:
CServerTcp *m_pServer;
vector<CTcpIpInfo*> m_pTcpInfo;
public:
void Run__Socket();
public:
CModbusTCP(void);
~CModbusTCP(void);
};
int main(void)
{
CModbusTCP* m_pModbusTCP = new CModbusTCP;
m_pModbusTCP->Run__Socket();
delete m_pModbusTCP;
m_pModbusTCP = nullptr;
getchar();
return 0;
}
실제로 아래 디버깅의 조사식을 통해 TcpInfo 부모 class 객체화 시
vfptr 가 만들어 지는데 이 녀석이 Virtual Pointer Table 이며,
실제 virtual 키워드를 통해 만들었던 심볼들의 주소값이 할당된다.
아래의 경우는 CModbusTCP 생성자 부분으로 인해 부모 class에 자식 class 주소가 쓰여진다.
즉, 동적 바인딩도 한참 전에 완료되고, TcpIpInfo 로 Server 인 자식 class 에 접근하게 된다.
그러므로, 이 글에서는 그 과정을 보여주기 위한 단계로
동적 바인딩 동작을 보기위해 소스를 조금 수정해 보겠다.
생성자 부분을 아래 사진과 같이 수정 후 빌드하여 디버깅 조사식으로 다시 비교 해 볼 것이다.
그러면 아래와 같은 조사식의 주소값을 얻을 수 있다.
조금 더 자세히 보면, 객체화 된 m_TcpInfo class 의 vfptr 에는 TcpInfo 의 주소 들이 할당 되어있다.
즉, virtual 상태의 주소로 되어있는 것이다.
그러나, 아래 m_Server class 의 vfptr 을 보면 위의 virtual 심볼들이
다른 주소값으로 동적 바인딩 되어있는 것을 확인 할 수 있다.
실제 Server 클래스의 심볼로 변경 된 것을 확인 할 수 있다.
이렇게 유연성 있는 동적바인딩을 통해 새로운 기법들의 코딩 방식을 적용 할 수 있다.
아래 부분은 해당 프로젝트의 소스로 virtual 부분을 동적바인딩 하여 구현한 소스 구현부 이다.
필요 시 참고하면 될 것 같다.
우선 부모 Class 로 아래와 같이 virtual 로 구현을 한다.
#pragma once
class CTcpIpInfo
{
public:
virtual void InitInfo();
virtual bool SocketCleanUp();
virtual bool Run_Socket();
virtual bool ReadOrWrite();
virtual unsigned short ReverseByteOrder(unsigned short value);
virtual unsigned short CreateCRC16(void* buff, size_t len);
public:
CTcpIpInfo();
virtual ~CTcpIpInfo();
};
위 class 를 상속받은 server class 의 .h 부분이다.
#pragma once
#include "define.h"
#include "TcpIpInfo.h"
class CServerTcp : public CTcpIpInfo
{
private:
WSADATA wsa;
SOCKET local_sock;
SOCKET client_sock;
struct sockaddr_in sock_addr;
struct sockaddr_in client_addr;
int client_addr_size;
unsigned short reg_map[150];
unsigned char recv_buff[8];
unsigned char send_buff[255];
unsigned short m_sAddress;
unsigned short m_sValue;
unsigned short m_sStatus;
unsigned short m_sLength;
unsigned short m_sCrc;
unsigned short m_rValue2; // To Master Send
int m_nConnectCount;
public:
void InitInfo();
bool SocketCleanUp();
bool Run_Socket();
bool ReadOrWrite();
bool ReadHoldingRegister();
bool ReadInputRegister();
bool WriteSingleRegister();
bool WriteMultipleRegister();
unsigned short ReverseByteOrder(unsigned short value);
void PrintHexa(unsigned char* buff, size_t len);
unsigned short CreateCRC16(unsigned char* buff, size_t len);
void SetCennectCount(int nVal) { m_nConnectCount = nVal; }
int GetCennectCount() { return m_nConnectCount; }
//------------------------------------------------------------
// Singleton
//------------------------------------------------------------
private:
static CServerTcp* m_pInstance;
public:
static CServerTcp* GetInstance();
static void DelInstance();
//------------------------------------------------------------
//------------------------------------------------------------
public:
CServerTcp(void);
~CServerTcp(void);
};
아래는 위 server class 의 .c 부분으로 virtual 선언 부분을 실제로 구현하였다.
#include "ServerTcp.h"
#include "ClientTcpDeviceRead.h"
#include "ClientTcpDeviceWrite.h"
//------------------------------------------------------------
// Singleton
//------------------------------------------------------------
CServerTcp* CServerTcp::m_pInstance = nullptr;
CServerTcp* CServerTcp::GetInstance()
{
return m_pInstance ? m_pInstance : (m_pInstance = new CServerTcp);
}
void CServerTcp::DelInstance()
{
if (m_pInstance)
{
delete m_pInstance;
m_pInstance = nullptr;
}
}
//------------------------------------------------------------
//------------------------------------------------------------
CServerTcp::CServerTcp(void)
{
InitInfo();
}
CServerTcp::~CServerTcp(void)
{
SocketCleanUp();
}
void CServerTcp::InitInfo()
{
memset(reg_map, 0x00, sizeof(short) * 150);
memset(&sock_addr, 0, sizeof(sock_addr));
client_addr_size = sizeof(client_addr);
memset(&client_addr, 0, client_addr_size);
reg_map[6] = 0xFE;
// 소켓 정보 - IP, port 정보를 기입한다.
sock_addr.sin_family = AF_INET;
sock_addr.sin_addr.s_addr = htonl(INADDR_ANY);
sock_addr.sin_port = htons(SERVER_PORT);
m_nConnectCount = 1; // Default = 1
}
bool CServerTcp::SocketCleanUp()
{
closesocket(local_sock);
closesocket(client_sock);
local_sock = INVALID_SOCKET;
client_sock = INVALID_SOCKET;
WSACleanup();
return false;
}
bool CServerTcp::Run_Socket()
{
if (GetCennectCount() < 1)
{
printf("Insufficient Connect Count");
return false;
}
int nConnectCount = GetCennectCount();
//-----------------------------------------------------------------------
// WSAStartup함수는 프로세스에서 Winsock DLL의 사용을 초기화한다.
//-----------------------------------------------------------------------
if (WSAStartup(MAKEWORD(2, 2), &wsa) == SOCKET_ERROR)
{
printf("Server::WSAStartup() Error\n");
return SocketCleanUp();
}
//-----------------------------------------------------------------------
// 소켓을 생성한다.
//-----------------------------------------------------------------------
if ((local_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
{
printf("Server::socket() Error\n");
return SocketCleanUp();
}
//-----------------------------------------------------------------------
// 통신 관련 정보를 서버 소켓에 등록한다.
//-----------------------------------------------------------------------
if (bind(local_sock, (struct sockaddr*) & sock_addr, sizeof(sock_addr)) == SOCKET_ERROR)
{
printf("Server::bind() Error\n");
return SocketCleanUp();
}
//-----------------------------------------------------------------------
// 접속을 기다린다. Device Read Holding, Read Input, Device Write 등을
// 연결하기 위하여 접속 개수 n개로 설정한다.
//-----------------------------------------------------------------------
if (listen(local_sock, nConnectCount) == SOCKET_ERROR)
{
printf("Server::listen() Error\n");
return SocketCleanUp();
}
int i = 0;
//for (i = 0; i < nConnectCount; i++)
for(;;)
{
printf("-----------------------------------------------------------------------\n");
printf("Modbus TCP for Server is working!\n");
printf("Waiting Client Connection!\n");
//-----------------------------------------------------------------------
// Thread 를 돌려야 될거같고... 계속 대기중... 싱글턴을 해볼까??
// fail -> Thread 가 하나라서... 결론은 Thread로 하자..
//-----------------------------------------------------------------------
if ((client_sock = accept(local_sock, (struct sockaddr*) & client_addr, &client_addr_size)) == SOCKET_ERROR)
{
printf("Server::accept() Error\n");
return SocketCleanUp();
}
printf("Connected Client Count::%d\n", ++i);
//-----------------------------------------------------------------------
// Client로 부터 Accept 성공시 recv를 실행한다.
//-----------------------------------------------------------------------
if (recv(client_sock, (char*)recv_buff, 8, 0) == SOCKET_ERROR)
{
printf("Server::recv() Error\n");
return SocketCleanUp();
}
//-----------------------------------------------------------------------
// Client로 부터 recv 성공시 Data Parsing 을 실행하여 Response 한다.
//-----------------------------------------------------------------------
if (ReadOrWrite() != true)
{
printf("Server::ReadOrWrite() Error\n");
return SocketCleanUp();
}
memset(recv_buff, 0, 8);
closesocket(client_sock);
client_sock = INVALID_SOCKET;
}
SocketCleanUp();
system("pause");
return true;
}
bool CServerTcp::ReadOrWrite()
{
if ((recv_buff[0] == reg_map[6]) || (recv_buff[0] == 0xFF))
{
ReadHoldingRegister();
}
else if ((recv_buff[0] == reg_map[6]) || (recv_buff[0] == 0xFD))
{
ReadInputRegister();
}
else if ((recv_buff[0] == reg_map[6]) || (recv_buff[0] == 0x02))
{
WriteSingleRegister();
}
else if ((recv_buff[0] == reg_map[6]) || (recv_buff[0] == 0x03))
{
WriteMultipleRegister();
}
else
{
printf("Server to Device Write::ID Check Please!\n");
return false;
}
return true;
}
bool CServerTcp::ReadHoldingRegister()
{
// 0x03
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
// recv_buff[0] -> ID (0xFF) or 0xFE // send_buff[0] -> ID (0xFF) or 0xFE
// recv_buff[1] -> FunctionCode // send_buff[1] -> FunctionCode
// recv_buff[2] -> StartingAddress addr // send_buff[2] -> Length 2byte, 1 -> 2
// recv_buff[3] -> StartingAddress addr // send_buff[3] -> Output Data 2byte
// recv_buff[4] -> Length // send_buff[4] ->
// recv_buff[5] -> Length // send_buff[5] -> CRC
// recv_buff[6] -> CRC // send_buff[6] ->
// recv_buff[7] -> CRC // send_buff[7] ->
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
if (recv_buff[1] == READ_HOLDING_REGISTERS) // 0x03 read Function Code
{
printf("Server - Recv::");
PrintHexa(recv_buff, 8);
memcpy(&m_sAddress, &recv_buff[2], 2); // [2][3] -> Starting Address
m_sAddress = ReverseByteOrder(m_sAddress); // LSB -> MSB
memcpy(&m_sLength, &recv_buff[4], 2); // [4][5] -> Length
m_sLength = ReverseByteOrder(m_sLength); // LSB -> MSB
//send_buff = (unsigned char*)malloc(3 + m_sLength * 2 + 2); // malloc 7 .. ID , r/w function code, length, register no to read, CRC2bytes
send_buff[0] = recv_buff[0];
send_buff[1] = READ_HOLDING_REGISTERS;
send_buff[2] = (unsigned char)m_sLength * 2; //2
unsigned short i;
for (i = 0; i < m_sLength; i++)
{
m_sValue = reg_map[m_sAddress + i]; // Start Address ~ to Length
m_sValue = ReverseByteOrder(m_sValue); // MSB -> LSB 보낼때는 LSB로 바꿔서
memcpy(&send_buff[3 + i * 2], &m_sValue, 2); // Copy to Send Buffer
}
m_sCrc = CreateCRC16(send_buff, 3 + m_sLength * 2); // 7 Checksum
memcpy(&send_buff[3 + m_sLength * 2], &m_sCrc, 2);
if (send(client_sock, (char*)send_buff, 3 + m_sLength * 2 + 2, 0) == SOCKET_ERROR) // Response Sending
{
printf("Server to Device Read::send() Error\n");
return SocketCleanUp();
}
printf("Server - Send::");
PrintHexa(send_buff, 8);
//free(send_buff);
//send_buff = NULL;
}
return true;
}
bool CServerTcp::ReadInputRegister()
{
//0x04
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
// recv_buff[0] -> ID (0xFD) or 0xFE // send_buff[0] -> ID (0xFD) or 0xFE
// recv_buff[1] -> FunctionCode // send_buff[1] -> FunctionCode
// recv_buff[2] -> StartingAddress addr // send_buff[2] -> Length 2byte
// recv_buff[3] -> StartingAddress addr // send_buff[3] -> Output Data 2byte
// recv_buff[4] -> Port Status // send_buff[4] ->
// recv_buff[5] -> Port Status // send_buff[5] -> CRC
// recv_buff[6] -> CRC // send_buff[6] ->
// recv_buff[7] -> CRC // send_buff[7] ->
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
if (recv_buff[1] == READ_INPUT_REGISTER)
{
printf("Server - Recv::");
PrintHexa(recv_buff, 8);
memcpy(&m_sAddress, &recv_buff[2], 2); // [2][3] -> Starting Address
m_sAddress = ReverseByteOrder(m_sAddress); // LSB -> MSB
memcpy(&m_sStatus, &recv_buff[4], 2); // [4][5] -> Port Status
m_sStatus = ReverseByteOrder(m_sStatus); // LSB -> MSB
//send_buff = (unsigned char*)malloc(3 + m_sLength * 2 + 3); // malloc 8
send_buff[0] = recv_buff[0];
send_buff[1] = READ_INPUT_REGISTER;
send_buff[2] = (unsigned char)m_sLength * 2; //2
unsigned short i;
for (i = 0; i < m_sLength; i++)
{
m_sStatus = reg_map[m_sAddress + i]; // Start Address ~ to Length = Set Port
m_sStatus = ReverseByteOrder(m_sStatus); // MSB -> LSB 보낼때는 LSB로 바꿔서
memcpy(&send_buff[3 + i * 2], &m_sStatus, 2); // Copy to Send Buffer, Register Value, element 3 5 7 9
}
m_sCrc = CreateCRC16(send_buff, 3 + m_sLength * 2); // 7 Checksum
memcpy(&send_buff[3 + m_sLength * 2], &m_sCrc, 2);
if (send(client_sock, (char*)send_buff, 3 + m_sLength * 2 + 2, 0) == SOCKET_ERROR) // Response Sending
{
printf("Server to Device Read Input::send() Error\n");
return SocketCleanUp();
}
printf("Server - Send::");
PrintHexa(send_buff, 8);
//free(send_buff);
//send_buff = NULL;
}
return true;
}
bool CServerTcp::WriteSingleRegister()
{
//0x06
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
// recv_buff[0] -> ID (0x02) or 0xFE // send_buff[0] -> ID (0x02) or 0xFE
// recv_buff[1] -> FunctionCode , 0x06 ,Write Single Register // send_buff[1] -> FunctionCode
// recv_buff[2] -> StartingAddress addr, 0x06 // send_buff[2] -> Length
// recv_buff[3] -> StartingAddress addr // send_buff[3] -> Register Value
// recv_buff[4] -> Write Data -> 0x0001 Data // send_buff[4] ->
// recv_buff[5] -> Write Data // send_buff[5] -> CRC
// recv_buff[6] -> CRC // send_buff[6] ->
// recv_buff[7] -> CRC // send_buff[7] ->
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
if (recv_buff[1] == WRITE_SINGLE_REGISTER)
{
printf("Server - Recv::");
PrintHexa(recv_buff, 8);
memcpy(&m_sAddress, &recv_buff[2], 2); // [2][3] -> Starting Address
m_sAddress = ReverseByteOrder(m_sAddress); // LSB -> MSB
memcpy(&m_sValue, &recv_buff[4], 2); // [4][5] -> Write Data
m_sValue = ReverseByteOrder(m_sValue); // LSB -> MSB
//send_buff = (unsigned char*)malloc(3 + m_sLength * 2 + 3); // malloc 8
send_buff[0] = recv_buff[0];
send_buff[1] = WRITE_SINGLE_REGISTER;
send_buff[2] = (unsigned char)m_sLength * 2; //2
unsigned short i;
for (i = 0; i < m_sLength; i++)
{
reg_map[m_sAddress + i] = m_sValue; // Start Address ~ to Length = Set Port
m_rValue2 = ReverseByteOrder(reg_map[m_sAddress + i]); // MSB -> LSB 보낼때는 LSB로 바꿔서
memcpy(&send_buff[3 + i * 2], &m_rValue2, 2); // Copy to Send Buffer, Register Value
}
m_sCrc = CreateCRC16(send_buff, 3 + m_sLength * 2); // 7 Checksum
memcpy(&send_buff[3 + m_sLength * 2], &m_sCrc, 2);
if (send(client_sock, (char*)send_buff, 3 + m_sLength * 2 + 3, 0) == SOCKET_ERROR) // Response Sending
{
printf("Server to Device Write::send() Error\n");
return SocketCleanUp();
}
printf("Server - Send::");
PrintHexa(send_buff, 8);
//free(send_buff);
//send_buff = NULL;
}
return true;
}
bool CServerTcp::WriteMultipleRegister()
{
//0x10
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
// recv_buff[0] -> ID (0x02) or 0xFE // send_buff[0] -> ID (0x02) or 0xFE
// recv_buff[1] -> FunctionCode , 0x10 ,Write Multi Register // send_buff[1] -> FunctionCode
// recv_buff[2] -> StartingAddress addr, 0x06 // send_buff[2] -> Length
// recv_buff[3] -> StartingAddress addr // send_buff[3] -> Register Value -> 0x0400
// recv_buff[4] -> Write Data -> 0x0004 Data // send_buff[4] ->
// recv_buff[5] -> Write Data // send_buff[5] -> CRC
// recv_buff[6] -> CRC // send_buff[6] ->
// recv_buff[7] -> CRC // send_buff[7] ->
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
if (recv_buff[1] == WRITE_MULTIPLE_REGISTER)
{
printf("Server - Recv::");
PrintHexa(recv_buff, 8);
memcpy(&m_sAddress, &recv_buff[2], 2); // [2][3] -> Starting Address
m_sAddress = ReverseByteOrder(m_sAddress); // LSB -> MSB
memcpy(&m_sValue, &recv_buff[4], 2); // [4][5] -> Write Data
m_sValue = ReverseByteOrder(m_sValue); // LSB -> MSB
//send_buff = (unsigned char*)malloc(3 + m_sLength * 2 + 3); // malloc 8
send_buff[0] = recv_buff[0];
send_buff[1] = WRITE_MULTIPLE_REGISTER;
send_buff[2] = (unsigned char)m_sLength * 2; //2
unsigned short i;
for (i = 0; i < m_sLength; i++)
{
reg_map[m_sAddress + i] = m_sValue; // Start Address ~ to Length = Set Port
m_rValue2 = ReverseByteOrder(reg_map[m_sAddress + i]); // MSB -> LSB 보낼때는 LSB로 바꿔서
memcpy(&send_buff[3 + i * 2], &m_rValue2, 2); // Copy to Send Buffer, Register Value
}
m_sCrc = CreateCRC16(send_buff, 3 + m_sLength * 2); // 7 Checksum
memcpy(&send_buff[3 + m_sLength * 2], &m_sCrc, 2);
if (send(client_sock, (char*)send_buff, 3 + m_sLength * 2 + 3, 0) == SOCKET_ERROR) // Response Sending
{
printf("Server to Device Write Multiple::send() Error\n");
return SocketCleanUp();
}
printf("Server - Send::");
PrintHexa(send_buff, 8);
//free(send_buff);
//send_buff = NULL;
}
return true;
}
unsigned short CServerTcp::ReverseByteOrder(unsigned short value) //big endian <-> little endian 으로 바꾸고
{
unsigned short ret = 0;
((char*)&ret)[0] = ((char*)&value)[1];
((char*)&ret)[1] = ((char*)&value)[0];
return ret;
}
void CServerTcp::PrintHexa(unsigned char* buff, size_t len)
{
size_t i;
for (i = 0; i < len; i++)
{
printf("%02X ", (unsigned char)buff[i]);
}
printf("\n");
}
unsigned short CServerTcp::CreateCRC16(unsigned char* buff, size_t len)
{
unsigned short crc16 = 0xFFFF;
int i = 0;
while (len--)
{
crc16 ^= *buff++;
for (i = 0; i < 8; i++)
{
if (crc16 & 1)
{
crc16 >>= 1;
crc16 ^= 0xA001;
}
else
{
crc16 >>= 1;
}
}
}
return crc16;
}
조금 더 나아가 이를 활용한 코딩 기법에 대해 간략히 설명하자면,
아래와 같이 구현을 할 수 있다.
Client 부분으로 각 TcpInfo class에 상속된 class 들을 부모 class 로 접근 할 수 있다.
'C++' 카테고리의 다른 글
[C++] pair, multimap, adjacent_find 구현 (0) | 2022.02.20 |
---|---|
[C++] count, count_if, equal을 위한 template 함수, 함수 포인터, 객체 컨테이너 구현 (0) | 2022.02.20 |
[C++] Compare 컨테이너 구현(vector 활용) (0) | 2022.02.20 |
[C++] MFC 기반의 유저 PC 접속 응용프로그램 만들기 (0) | 2021.03.20 |
ModbusTcp (2) | 2020.04.18 |