회의 내용
패킷 구조 논의
1) JSON만 주고 받기
2) 길이 + JSON
3) 길이 + BinaryHeader + BinaryBdoy
4) BinaryHeader + JSON + BinaryImage
> 여러가지 논의가 있었지만 결정된 건 아래 구조임.
Header : MSG(1) + BodyLen(4) + ImgID(4)
Body : IMG or RESULT
로 결정함.
공통 프로토콜(MFC 클라이언트용)
<CommonProtocol.h>
#pragma once
#include <cstdint>
// ======================
// 공통 프로토콜 정의
// ======================
// 메시지 타입(1Byte)
enum class MsgType : uint8_t
{
IMG_REQ = 1, // 이미지 전송 요청
IMG_RES = 2, // 이미지 전송 응답
RESULT_REQ = 3, // 결과 요청
RESULT_RES = 4, // 결과 응답
HEARTBEAT = 9 // 연결 유지용
};
// 고정값
constexpr int MAX_JSON_SIZE = 4096; // 메타 정보 최대 크기
constexpr int MAX_IMAGE_SIZE = 1 * 1024 * 1024; // 최대 1MB 이미지
constexpr int HEADER_SIZE = 9; // 1 + 4 + 4 = 9바이트
#pragma pack(push, 1)
struct PacketHeader
{
uint8_t msgType; // 1Byte
uint32_t bodyLen; // 4Byte
uint32_t imgId; // 4Byte
};
#pragma pack(pop)
<Pakcet.h>
#pragma once // 헤더 중복방지
#include "CommonProtocol.h"
#include <string>
#include <vector>
#include <atomic>
#ifdef _WIN32
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#else // 리눅스용
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
typedef int SOCKET;
#endif
// ======================
// 패킷 클래스
// ======================
class Packet
{
public:
Packet();
~Packet();
// 연결
bool Connect(const std::string& ip, uint16_t port);
void Disconnect();
// 송/수신
bool Send(MsgType type, const std::vector<uint8_t>& body);
bool Receive(MsgType& outType, uint32_t& outImgId, std::vector<uint8_t>& outBody);
private:
SOCKET sock; // 소켓
std::atomic<uint32_t> nextImgId; // 이미지 ID(자동 증가)
// 송/수신 루프
bool sendAll(const void* data, size_t size);
bool recvAll(void* data, size_t size);
};
<Packet.cpp>
#include "pch.h"
#include "Packet.h"
#include <iostream>
// 생성자, 소멸자
Packet::Packet() : sock(INVALID_SOCKET), nextImgId(1) {}
Packet::~Packet() { Disconnect(); }
// 연결 메서드
bool Packet::Connect(const std::string& ip, uint16_t port)
{
#ifdef _WIN32 // 윈도우 전용 초기화
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
#endif
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) {
std::cerr << "[ERROR] 소켓 생성 실패됨\n";
return false;
}
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
inet_pton(AF_INET, ip.c_str(), &addr.sin_addr);
if (connect(sock, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
std::cerr << "[ERROR] 연결 실패됨\n";
Disconnect();
return false;
}
std::cout << "[INFO] 연결됨 => " << ip << ":" << port << "\n";
return true;
}
// 종료 메서드
void Packet::Disconnect()
{
if (sock != INVALID_SOCKET) {
#ifdef _WIN32
closesocket(sock);
WSACleanup();
#else
close(sock);
#endif
sock = INVALID_SOCKET;
std::cout << "[INFO] 연결해제됨\n";
}
}
// 데이터 수/송신 루프(모든 데이터 받기)
bool Packet::sendAll(const void* data, size_t size)
{
const char* ptr = reinterpret_cast<const char*>(data);
size_t total = 0;
while (total < size)
{
int sent = send(sock, ptr + total, static_cast<int>(size - total), 0);
if (sent <= 0)
return false;
total += sent;
}
return true;
}
bool Packet::recvAll(void* data, size_t size)
{
char* ptr = reinterpret_cast<char*>(data);
size_t total = 0;
while (total < size)
{
int r = recv(sock, ptr + total, static_cast<int>(size - total), 0);
if (r <= 0)
return false;
total += r;
}
return true;
}
// 데이터 전송 메서드
bool Packet::Send(MsgType type, const std::vector<uint8_t>& body)
{
if (sock == INVALID_SOCKET)
return false;
PacketHeader header{};
header.msgType = static_cast<uint8_t>(type);
header.bodyLen = static_cast<uint32_t>(body.size());
header.imgId = nextImgId.fetch_add(1); // 자동 1씩 증가
// ---- Step 1: Header(9Byte)
if (!sendAll(&header, sizeof(header)))
return false;
// ---- Step 2: Body(이미지 or 결과)
if (header.bodyLen > 0)
if (!sendAll(body.data(), body.size()))
return false;
std::cout << "[SEND] Header: "
<< "MsgType=" << (int)header.msgType
<< ", BodyLen=" << header.bodyLen
<< ", ImgId=" << header.imgId << "\n";
return true;
}
bool Packet::Receive(MsgType& outType, uint32_t& outImgId, std::vector<uint8_t>& outBody)
{
outBody.clear();
// ---- Step 1: Header
PacketHeader header{};
if (!recvAll(&header, sizeof(header)))
return false;
outType = static_cast<MsgType>(header.msgType);
outImgId = header.imgId;
// ---- Step 2: Body
if (header.bodyLen > 0)
{
outBody.resize(header.bodyLen);
if (!recvAll(outBody.data(), header.bodyLen))
return false;
}
std::cout
<< "[RECV] MsgType=" << (int)header.msgType
<< ", BodyLen=" << header.bodyLen
<< ", ImgId=" << header.imgId << "\n";
return true;
}
MFC 학습 내용
1. MFC(Microsoft Foundation Class)
> Win32 API 기반
> 메시지 루프 기반 : 이벤트 발생 -> "메시지 맵" -> 함수
> App : 프로그램 전체 관리 / Dialog : 창(UI)
2. 프로그램 전체 흐름
Windows OS
↓
WinMain()
↓
AfxWinMain() : MFC 내부 진입점
↓
CMFCClientApp::InitInstance()
↓
CMFCClientDlg dlg;
dlg.DoModal(); : 메인 대화상자 실행
↓
대화상자 종료 시 → InitInstance() 반환(FALSE)
↓
프로그램 종료
3. 주요 클래스
1) CMFCClientApp
> 프로그램 진입점, 초기화 및 종료
> InitInstance() : main() 과 유사함.
> return FALSE; : 대화상자 닫히면 프로그램 종료
2) CMFClientDlg
> 인터페이스(UI) 담당, 창, 버튼/이벤트
> 메시지 맵 : BEGIN_MESSAGE_MAP() ~ END_MESSAGE_MAP() 을 통한 이벤트 -> 함수
4. 메시지 맵
BEGIN_MESSAGE_MAP(CMFCClientDlg, CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BTN_CONNECT, &CMFCClientDlg::OnBnClickedBtnConnect)
END_MESSAGE_MAP()
> ON_WM_SYSCOMMAND() : 시스템 메뉴 처리
> ON_WM_PAINT() : 창을 다시 그림
> ON_WM_QUERYDRAGICON() : 최소화 상태에서 아이콘 요청
> ON_BN_CLICKED() : 버튼 클릭 -> 함수 연결
5. 주요 함수(생성시 기본)
> InitInstance() : 프로그램 시작시 최초 실행
> OnInitDialog() : 창이 처음 표시될 때 한 번만 실행
> OnSysCommand() : 정보(About) 클릭시 About 창 표시
> OnPaint() : 창 다시 그릴 때(최소화/복원)
> OnQueryDragIcon() : 최소화된 창 드래그 시 표시할 아이콘 지정
'LMS 7 > 개발일지' 카테고리의 다른 글
| 25.10.29 개발일지 / C++ MFC 프로젝트 4일차 (0) | 2025.11.14 |
|---|---|
| 25.10.28 개발일지 MFC 프로젝트 3일차(Pylon, openCV) (0) | 2025.11.13 |
| 25.10.26 개발일지 / Git 병합(pull/merge) (0) | 2025.11.13 |
| 25.10.24 개발일지 / MFC 프로젝트 1일차 (0) | 2025.11.13 |
| 25.10.22 개발일지 / (주)그림 (0) | 2025.11.13 |