[데이터 전송 구성도]
서버(server_v2.cpp) ↔ 클라이언트(client_v4.cpp) → 다른 프로젝트/프로세스(BasicThread_v0.cpp)
□ 서버와 클라이언트 : TCP/IP 송수신
□ 클라이언트와 다른 프로젝트/프로세스 : 공유 메모리(Shared Memory)
꼭 읽으면 좋은 글
본 포스팅이 어렵다면, 아래 링크의 포스팅을 공부하면 쉽게 이해할 수 있습니다.
1. TCP/IP 기초: https://easycode.tistory.com/19
3. TCP/IP 중급: https://easycode.tistory.com/20
4. TCP/IP 고급: https://easycode.tistory.com/21
5. 공유 메모리 : https://easycode.tistory.com/23
Let's go
본 포스팅은 공유 메모리와 Windows 세마포어를 사용하여 C++에서 두 스레드를 동기화하는 IPC(프로세스 간 통신)를 구현하는 방법을 살펴보겠습니다. 데이터가 서버에서 클라이언트로 전송된 다음 공유 메모리를 사용하여 클라이언트에서 다른 프로세스로 전달되는 서버-클라이언트 시나리오를 시연합니다. 클라이언트는 SetTimer(1000ms)를 사용하고 다른 프로세스는 세마포어로 구성된 다중 스레드 코드를 사용합니다.
1단계: 서버에서 클라이언트로 데이터 전송
서버는 클라이언트와 통신하기 위해 TCP/IP 소켓을 설정합니다. 카운터 및 부울 값과 같은 다양한 값을 포함하여 전송할 데이터를 저장하는 구조를 초기화합니다. 그런 다음 서버는 클라이언트의 IP 주소와 포트 번호를 사용하여 클라이언트에 연결하고 설정된 연결을 통해 데이터 구조를 보냅니다.
2단계: 클라이언트 측에서 데이터 수신
클라이언트는 서버와 통신하기 위해 유사한 TCP/IP 소켓을 초기화합니다. 서버의 IP 주소와 포트 번호를 사용하여 서버에 접속한 다음 서버에서 보낸 데이터를 수신합니다. 클라이언트는 수신된 데이터를 처리하고 구조에 저장합니다.
3단계: 공유 메모리를 사용하여 클라이언트에서 다른 프로세스로 데이터 전송
클라이언트는 다른 프로세스에서 액세스할 수 있는 고유 식별자로 공유 메모리 세그먼트를 만듭니다. 수신된 데이터를 이러한 공유 메모리 세그먼트에 매핑하여 다른 프로세스가 공유 메모리에서 직접 데이터를 읽을 수 있도록 합니다.
4단계: 클라이언트에서 SetTimer(1000ms) 메커니즘 구현
클라이언트는 SetTimer 함수를 사용하여 1000ms마다 실행되는 콜백 함수로 타이머를 설정합니다. 이를 통해 클라이언트는 주기적으로 서버와 데이터를 송수신하고 공유 메모리를 최신 데이터로 업데이트할 수 있습니다.
5단계: 다른 프로세스에서 세마포어를 사용하여 다중 스레드 코드 구현
다른 프로세스는 수신된 데이터를 처리하기 위해 두 개의 스레드를 만듭니다. 첫 번째 스레드는 공유 메모리 세그먼트에서 데이터를 읽고 처리합니다. 두 번째 스레드는 작업을 수행하기 전에 첫 번째 스레드에서 세마포어가 해제될 때까지 기다립니다. 세마포어를 사용하면 첫 번째 스레드가 작업을 완료했을 때만 두 번째 스레드가 작업을 실행하므로 두 스레드 간의 동기화가 유지됩니다.
이를 통해, 우리는 C++에서 두 스레드를 동기화하기 위해 공유 메모리와 Windows 세마포를 사용하여 IPC를 구현하는 방법을 구현했습니다. 이 접근 방식을 따르면 서로 다른 프로세스 간의 통신을 효율적으로 관리하고 애플리케이션에서 원활한 데이터 흐름을 보장할 수 있습니다.
결과는 아래와 같습니다.
예시코드
[server_v2.cpp]
/* Code Explain
This code is a C++ implementation of a simple TCP server using the Winsock API.
The server listens on a specified port for incoming connections and then exchanges data with the connected client using predefined packet structures.
*/
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<winsock2.h>
#include<iostream>
#include<string>
#include<sstream>
#include<fstream>
#include<windows.h>
#pragma comment (lib, "Ws2_32.lib")
#define PORT 8000
#define PACKET_SIZE 1000
#define SERVER_IP "xxx.xxx.xxx.xxx" // 서버 아이피
#define CAMERA_BUFFER_Value2 100
#define MAX_RETRIES 5
#define RETRY_INTERVAL 1000
using namespace std;
typedef struct _EXAMPLE_SEND_PACKET
{
int32_t Counter;
char Value1[16];
int32_t Value2;
int32_t Value3;
int32_t Value4;
}_EXAMPLE_SEND_PACKET;
typedef struct _EXAMPLE_RECV_PACKET {
int32_t Counter;
bool Value5;
bool Value6;
bool Value7;
bool Value8;
}_EXAMPLE_RECV_PACKET;
struct _EXAMPLE_SEND_PACKET clientRecv;
struct _EXAMPLE_RECV_PACKET ServerRequest;
int main(int argc, char* argv[], char* envp[])
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
SOCKET hServerSocket;
hServerSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
SOCKADDR_IN tAddr = {};
tAddr.sin_family = AF_INET;
tAddr.sin_port = htons(PORT);
tAddr.sin_addr.s_addr = htonl(INADDR_ANY);
int bind_result = bind(hServerSocket, (SOCKADDR*)&tAddr, sizeof(tAddr));
if (bind_result == SOCKET_ERROR) {
// Handle error
cout << "Error binding socket" << endl;
closesocket(hServerSocket);
return 1;
}
int listen_result = listen(hServerSocket, 5);
if (listen_result == SOCKET_ERROR) {
// Handle error
cout << "Error listening on socket" << endl;
closesocket(hServerSocket);
return 1;
}
while (true) {
SOCKET hClientSocket;
SOCKADDR_IN clientAddr;
int clientAddrSize = sizeof(clientAddr);
hClientSocket = accept(hServerSocket, (SOCKADDR*)&clientAddr, &clientAddrSize);
if (hClientSocket == INVALID_SOCKET) {
// Handle error
cout << "Error accepting connection" << endl;
closesocket(hServerSocket);
return 1;
}
int recv_result = recv(hClientSocket, (char*)&ServerRequest, PACKET_SIZE, 0);
if (recv_result == SOCKET_ERROR) {
// Handle error
cout << "Error receiving data from client" << endl;
closesocket(hClientSocket);
continue;
}
printf("----------------------------------------------------------------\n");
printf("-------------------------- Server Part -------------------------\n");
printf("----------------------------------------------------------------\n");
printf("Server_RecvData Counter : %d\n", ServerRequest.Counter);
printf("Server_RecvData Value5 : %d\n", ServerRequest.Value5);
printf("Server_RecvData Value6 : %d\n", ServerRequest.Value6);
printf("Server_RecvData Value7 : %d\n", ServerRequest.Value7);
printf("Server_RecvData Value8 : %d\n", ServerRequest.Value8);
clientRecv.Counter = clientRecv.Counter + 1;
strcpy(clientRecv.Value1, "TEST GOOD");
clientRecv.Value2 = 123456;
clientRecv.Value3 = 654321;
clientRecv.Value4 = 1;
int send_result = send(hClientSocket, (char*)&clientRecv, sizeof(struct _EXAMPLE_SEND_PACKET), 0);
if (send_result == SOCKET_ERROR) {
// Handle error
cout << "Error sending data to client" << endl;
closesocket(hClientSocket);
continue;
}
closesocket(hClientSocket);
}
closesocket(hServerSocket);
WSACleanup();
return 0;
}
[코드 설명]
이 코드는 Winsock API를 사용하는 간단한 TCP 서버의 C++ 구현입니다. 서버는 지정된 포트에서 들어오는 연결을 수신한 다음 미리 정의된 패킷 구조를 사용하여 연결된 클라이언트와 데이터를 교환합니다.
▶ Defining constants and packet structures:
코드는 표준 입력/출력, Winsock API 및 기타 필수 기능에 필요한 헤더를 가져오기 위해 일련의 #include 지시문으로 시작합니다. #pragma 주석(lib, "Ws2_32.lib") 줄은 Winsock 라이브러리를 연결합니다.
▶ Initializing Winsock:
PORT, PACKET_SIZE, SERVER_IP, CAMERA_BUFFER_Value2, MAX_RETRIES 및 RETRY_INTERVAL과 같은 여러 상수가 정의됩니다. _EXAMPLE_SEND_PACKET 및 _EXAMPLE_RECV_PACKET의 두 가지 구조가 정의됩니다. 이러한 구조는 클라이언트와 서버 간에 데이터를 교환하는 데 사용됩니다.
▶ Winsock 초기화:
WSAStartup() 함수는 Winsock 라이브러리를 초기화하기 위해 호출됩니다.
▶ Creating a server socket:
TCP 서버 소켓은 PF_INET 프로토콜 계열, SOCK_STREAM 소켓 유형 및 IPPROTO_TCP 프로토콜과 함께 socket() 함수를 사용하여 생성됩니다.
▶ Binding the server socket:
서버 소켓은 bind() 함수를 사용하여 지정된 포트 및 로컬 IP 주소(INADDR_ANY)에 바인딩됩니다. 바인딩에 실패하면 오류 메시지가 표시되고 서버 소켓이 닫힙니다.
▶ Listening for incoming connections:
서버 소켓은 listen() 함수를 사용하여 들어오는 연결을 듣기 시작합니다. 수신에 실패하면 오류 메시지가 표시되고 서버 소켓이 닫힙니다.
▶ Main server loop:
서버는 들어오는 클라이언트 연결을 계속 기다리는 무한 루프에 들어갑니다.
a. Accepting client connections:
accept() 함수는 들어오는 연결을 수락하고 클라이언트와 통신하기 위한 새 소켓(hClientSocket)을 만드는 데 사용됩니다. 연결이 유효하지 않으면 오류 메시지가 표시되고 서버 소켓이 닫힙니다.
b. Receiving data from the client:
recv() 함수는 연결된 클라이언트로부터 데이터를 수신하기 위해 호출됩니다. 수신된 데이터는 ServerRequest 구조에 저장됩니다. 데이터 수신 중 오류가 발생하면 오류 메시지가 표시되고 클라이언트 소켓이 닫힙니다.
c. Displaying received data:
서버는 Counter, Value5, Value6, Value7 및 Value8 필드를 포함하여 클라이언트로부터 받은 데이터를 표시합니다.
d. Preparing data to send to the client:
서버는 clientRecv 구조를 사용하여 클라이언트로 보낼 데이터를 준비합니다. Counter 필드를 증가시키고 Value1 필드를 "TEST GOOD"로 설정하고 다른 필드를 미리 정의된 값으로 설정합니다.
e. Sending data to the client:
서버는 send() 함수를 사용하여 clientRecv 구조를 연결된 클라이언트로 보냅니다. 데이터 전송 중 오류가 발생하면 오류 메시지가 표시되고 클라이언트 소켓이 닫힙니다.
f. Closing the client socket:
데이터 교환이 완료되면 서버는 closesocket() 함수를 사용하여 클라이언트 소켓을 닫고 다시 다음 클라이언트 연결을 대기합니다.
▶ Cleanup:
서버 소켓이 닫히고 프로그램이 종료되기 전에 WSACleanup() 함수를 사용하여 Winsock 라이브러리가 정리됩니다.
[결과]
[BasicThread_v0.cpp]
멀티-스레트(multi-thread)에 관한 내용은 추후 포스팅을 개시할 예정입니다. 우선 가장 간단한 구조이니 이번 포스팅에서는 "이런게 있구나" 참고만 해주시면 감사하겠습니다.
★★★ updated 2023.03.20, https://easycode.tistory.com/25 ★★★
/* Code Explain
This code demonstrates the use of inter-process communication (IPC) using shared memory and Windows semaphores to synchronize two threads in C++.
*/
#include <iostream>
#include <thread>
#include <Windows.h>
#include <stdlib.h>
#include <stdint.h>
#include <ctime>
#include <vector>
#include <stdio.h>
// namespace
using namespace std;
HANDLE sem1;
const std::string currentDateTime() {
time_t now = time(0); //현재 시간을 time_t 타입으로 저장
struct tm tstruct;
char Buffer[80];
tstruct = *localtime(&now);
strftime(Buffer, sizeof(Buffer), "%Y-%m-%d_%H:%M:%S", &tstruct); // YYYY-MM-DD.HH:mm:ss 형태의 스트링
return Buffer;
}
void thread_1() {
// local variable
bool start;
string user_input;
clock_t start_time;
clock_t end_time;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 0. Shared Memory
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
cout << "Please read it before running the code. " << endl;
cout << "The order of project execution is as follows. " << endl;
cout << "Run in the order of server_v2.cpp for the first, BasicThread_v0.cpp for the second, and client_v4.cpp for the third. " << endl;
cout << "If you ready, enter 'start' in the cmd: ";
cin >> user_input;
if (user_input == "start") {
start = true;
}
else
{
cout << "Invalid input. Please enter 'start' to run the other threads." << std::endl;
}
while (start)
{
start_time = clock();
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//// 0. 공유메모리 선언 및 local variable 선언
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////
HANDLE HanValue1;
HanValue1 = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(char) * 16, "IPCValue1");
if (HanValue1 == NULL) { std::cout << currentDateTime() << " -- " << "Could not read HanCoilID : " << GetLastError() << endl; exit(0); }
HANDLE HanValue2;
HanValue2 = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(int32_t), "IPCValue2");
if (HanValue2 == NULL) { std::cout << currentDateTime() << " -- " << "Could not read HanLength : " << GetLastError() << endl; exit(0); }
HANDLE HanValue3;
HanValue3 = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(int32_t), "IPCValue3");
if (HanValue3 == NULL) { std::cout << currentDateTime() << " -- " << "Could not read HanCoilStatus : " << GetLastError() << endl; exit(0); }
HANDLE HanValue4;
HanValue4 = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(int32_t), "IPCValue4");
if (HanValue4 == NULL) { std::cout << currentDateTime() << " -- " << "Could not read HanCoilSpeed : " << GetLastError() << endl; exit(0); }
char* recvValue1 = (char*)MapViewOfFile(HanValue1, FILE_MAP_WRITE, 0, 0, 0);
int32_t* recvValue2 = (int32_t*)MapViewOfFile(HanValue2, FILE_MAP_WRITE, 0, 0, 0);
int32_t* recvValue3 = (int32_t*)MapViewOfFile(HanValue3, FILE_MAP_WRITE, 0, 0, 0);
int32_t* recvValue4 = (int32_t*)MapViewOfFile(HanValue4, FILE_MAP_WRITE, 0, 0, 0);
printf("recvValue1 : %s\n", recvValue1);
printf("recvValue2 : %d\n", *recvValue2);
printf("recvValue3 : %d\n", *recvValue3);
printf("recvValue4 : %d\n", *recvValue4);
end_time = clock();
printf("Thread_1 is worked......... Processed Time : %d......... \n", end_time - start_time);
ReleaseSemaphore(sem1, 1, NULL);
}
}
void thread_2() {
clock_t start_time;
clock_t end_time;
while (TRUE) {
WaitForSingleObject(sem1, INFINITE);
start_time = clock();
/*
기능 추가
*/
end_time = clock();
printf("Thread_2 is worked......... Processed Time : %d......... \n", end_time - start_time);
}
}
int main() {
// Initialize the semaphores
sem1 = CreateSemaphore(NULL, 0, 1, NULL);
// Create the threads
thread t1(thread_1);
thread t2(thread_2);
t1.join();
t2.join();
CloseHandle(sem1);
return 0;
}
[코드 설명]
▶ 헤더 파일: 코드에는 입출력을 위한 <iostream>, 멀티스레딩을 위한 <thread>, Windows API 함수를 위한 <Windows.h>, 시간 관련 함수를 위한 <ctime> 등 프로그램에 필요한 헤더 파일이 포함되어 있습니다. .
▶ 네임스페이스: using 네임스페이스 std; 문은 표준 라이브러리 함수 및 개체를 포함하는 std 네임스페이스의 사용을 허용합니다.
▶ 전역 변수: 코드는 두 개의 스레드를 동기화하는 데 사용되는 두 개의 전역 세마포 sem1 및 sem2를 선언합니다.
▶ currentDateTime() 함수: 이 함수는 현재 날짜와 시간을 형식화된 문자열로 반환합니다.
▶ thread_1() 함수: 이 스레드는 다음 작업을 수행합니다.
a. 사용자 입력이 시작되기를 기다립니다.
b. 4개의 다른 값(HanValue1에서 HanValue4까지)에 대한 공유 메모리를 생성합니다.
c. 공유 메모리를 지역 변수에 매핑합니다(recvValue1에서 recvValue4로).
d. 공유 메모리에서 읽은 값을 인쇄합니다.
e. thread_2()와의 동기화를 위해 세마포어 sem1을 해제합니다.
▶ thread_2() 함수: 이 스레드는 코드를 실행하기 전에 세마포어 sem1을 기다립니다. 이 스레드의 실제 기능은 추가 개발을 위한 자리 표시자로 남겨둡니다.
▶ main() 함수: main 함수는 세마포어를 초기화하고 thread_1() 및 thread_2() 함수에 대한 두 개의 스레드(t1 및 t2)를 생성합니다. 그런 다음 스레드를 조인하고 반환하기 전에 세마포어 핸들을 닫습니다.
이 코드는 스레드 간의 통신 및 동기화를 위해 공유 메모리 및 세마포어를 사용하는 다중 스레드 프로그램을 작성하기 위한 기반으로 사용할 수 있습니다. 학생들은 코드를 수정하여 thread_2() 함수에 기능을 추가하고 필요에 따라 다른 IPC 메커니즘을 실험할 수 있습니다.
[결과]
[client_v4]
/* Code Explain
This code is a C++ implementation of a simple TCP client using the Winsock API.
The client connects to a server, exchanges data using predefined packet structures, and updates shared memory with the received data.
*/
#include<stdio.h>
#include<stdlib.h>
#include<winsock2.h>
#include<iostream>
#include<string>
#include<sstream>
#include<fstream>
#include<windows.h>
#pragma comment (lib, "Ws2_32.lib")
#define PORT 8000
#define PACKET_SIZE 1000
#define SERVER_IP "xxx.xxx.xxx.xxx" // 서버 아이피
#define CAMERA_BUFFER_Value2 100
#define TIMER_ID 1
using namespace std;
typedef struct _EXAMPLE_SEND_PACKET
{
int32_t Counter;
char Value1[16];
int32_t Value2;
int32_t Value3;
int32_t Value4;
}_EXAMPLE_SEND_PACKET;
typedef struct _EXAMPLE_RECV_PACKET
{
int32_t Counter;
bool Value5;
bool Value6;
bool Value7;
bool Value8;
}_EXAMPLE_RECV_PACKET;
struct _EXAMPLE_SEND_PACKET clientRecv;
struct _EXAMPLE_RECV_PACKET ServerRequest;
const std::string currentDateTime() {
time_t now = time(0); //현재 시간을 time_t 타입으로 저장
struct tm tstruct;
char Buffer[80];
tstruct = *localtime(&now);
strftime(Buffer, sizeof(Buffer), "%Y-%m-%d_%H:%M:%S", &tstruct); // YYYY-MM-DD.HH:mm:ss 형태의 스트링
return Buffer;
}
void TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//// 0. 공유메모리 선언 및 local variable 선언
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////
HANDLE HanValue1;
HanValue1 = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "IPCValue1");
if (HanValue1 == NULL) { std::cout << currentDateTime() << " -- " << "Could not read HanValue1 : " << GetLastError() << endl; exit(0); }
HANDLE HanValue2;
HanValue2 = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "IPCValue2");
if (HanValue2 == NULL) { std::cout << currentDateTime() << " -- " << "Could not read HanValue2 : " << GetLastError() << endl; exit(0); }
HANDLE HanValue3;
HanValue3 = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "IPCValue3");
if (HanValue3 == NULL) { std::cout << currentDateTime() << " -- " << "Could not read HanCoilValue3 : " << GetLastError() << endl; exit(0); }
HANDLE HanValue4;
HanValue4 = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "IPCValue4");
if (HanValue4 == NULL) { std::cout << currentDateTime() << " -- " << "Could not read HanCoilValue4 : " << GetLastError() << endl; exit(0); }
char* chkValue1 = (char*)MapViewOfFile(HanValue1, FILE_MAP_WRITE, 0, 0, 0);
int32_t* chkValue2 = (int32_t*)MapViewOfFile(HanValue2, FILE_MAP_WRITE, 0, 0, 0);
int32_t* chkValue3 = (int32_t*)MapViewOfFile(HanValue3, FILE_MAP_WRITE, 0, 0, 0);
int32_t* chkValue4 = (int32_t*)MapViewOfFile(HanValue4, FILE_MAP_WRITE, 0, 0, 0);
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//// 1. TCP/IP Socket 통신 선언
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
SOCKET hSocket;
hSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
SOCKADDR_IN tAddr = {};
tAddr.sin_family = AF_INET;
tAddr.sin_port = htons(PORT);
tAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
ServerRequest.Counter = ServerRequest.Counter + 1;
ServerRequest.Value5 = TRUE;
ServerRequest.Value6 = TRUE;
connect(hSocket, (SOCKADDR*)&tAddr, sizeof(tAddr));
send(hSocket, (char*)&ServerRequest, sizeof(struct _EXAMPLE_RECV_PACKET), 0);
recv(hSocket, (char*)&clientRecv, PACKET_SIZE, 0);
printf("----------------------------------------------------------------\n");
printf("--------------------- Server to Client -------------------------\n");
printf("----------------------------------------------------------------\n");
printf("Client_RecvData Counter : %d\n", clientRecv.Counter);
printf("Client_RecvData Value1 : %s\n", clientRecv.Value1);
printf("Client_RecvData Value2 : %d\n", clientRecv.Value2);
printf("Client_RecvData Value3 : %d\n", clientRecv.Value3);
printf("Client_RecvData Value4 : %d\n", clientRecv.Value4);
/////////////////// 공유 메모리에 저장 ///////////////////
memcpy(chkValue1, clientRecv.Value1, sizeof(char) * 16);
memcpy(chkValue3, &clientRecv.Value3, sizeof(int32_t));
memcpy(chkValue2, &clientRecv.Value2, sizeof(int32_t));
memcpy(chkValue4, &clientRecv.Value4, sizeof(int32_t));
printf("----------------------------------------------------------------\n");
printf("---------------------- Client to Shared Mem --------------------\n");
printf("----------------------------------------------------------------\n");
printf("chkValue1 : %s\n", chkValue1);
printf("chkValue2 : %d\n", *chkValue2);
printf("chkValue3 : %d\n", *chkValue3);
printf("chkValue4 : %d\n", *chkValue4);
}
int main(int argc, char* argv[], char* envp[])
{
ServerRequest.Counter = 0;
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
SOCKET hSocket;
hSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
SOCKADDR_IN tAddr = {};
tAddr.sin_family = AF_INET;
tAddr.sin_port = htons(PORT);
tAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
SetTimer(NULL, TIMER_ID, 1000, (TIMERPROC)TimerProc); //SetTimer 지정 : 초당 1번
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
closesocket;
}
[코드 설명]
이 코드는 Winsock API를 사용하는 간단한 TCP 클라이언트의 C++ 구현입니다. 클라이언트는 서버에 연결하여 미리 정의된 패킷 구조를 사용하여 데이터를 교환하고 수신된 데이터로 공유 메모리를 업데이트합니다.
▶ Including necessary headers and libraries:
코드는 표준 입력/출력, Winsock API 및 기타 필수 기능에 필요한 헤더를 가져오기 위해 일련의 #include 지시문으로 시작합니다. #pragma 주석(lib, "Ws2_32.lib") 줄은 Winsock 라이브러리를 연결합니다.
▶ Defining constants and packet structures:
PORT, PACKET_SIZE, SERVER_IP, CAMERA_BUFFER_Value2 및 TIMER_ID와 같은 여러 상수가 정의됩니다. _EXAMPLE_SEND_PACKET 및 _EXAMPLE_RECV_PACKET의 두 가지 구조가 정의됩니다. 이러한 구조는 클라이언트와 서버 간에 데이터를 교환하는 데 사용됩니다.
▶ Defining a helper function:
currentDateTime() 함수는 현재 날짜와 시간을 형식화된 문자열로 반환합니다.
▶ TimerProc function:
TimerProc 함수는 main() 함수에 설정된 타이머를 기준으로 주기적으로 실행되는 콜백 함수입니다. 여기에는 클라이언트 프로그램의 기본 논리가 포함됩니다.
a. Opening shared memory:
클라이언트는 4개의 공유 메모리 핸들 HanValue1, HanValue2, HanValue3 및 HanValue4를 열고 로컬 변수 chkValue1, chkValue2, chkValue3 및 chkValue4에 매핑합니다.
b. Initializing Winsock:
WSAStartup() 함수는 Winsock 라이브러리를 초기화하기 위해 호출됩니다.
c. Creating a client socket:
TCP 클라이언트 소켓은 PF_INET 프로토콜 계열, SOCK_STREAM 소켓 유형 및 IPPROTO_TCP 프로토콜과 함께 socket() 함수를 사용하여 생성됩니다.
d. Preparing server address:
서버 주소 구조 tAddr은 AF_INET 주소 계열, PORT 및 SERVER_IP로 초기화됩니다.
e. Updating and sending data to the server:
클라이언트는 카운터 필드를 증가시키고 ServerRequest 구조의 Value5 및 Value6 필드를 설정합니다. connect() 함수를 사용하여 서버에 연결하고, send() 함수를 사용하여 ServerRequest 구조를 보내고, recv() 함수를 사용하여 서버에서 clientRecv 구조로 데이터를 수신합니다.
f. Displaying received data:
클라이언트는 카운터, 값1, 값2, 값3 및 값4 필드를 포함하여 서버에서 받은 데이터를 표시합니다.
g. Updating shared memory:
클라이언트는 clientRecv 구조의 값을 공유 메모리에 매핑된 로컬 변수에 복사하여 수신된 데이터로 공유 메모리를 업데이트합니다.
▶ Main function:
주요 기능은 Winsock 라이브러리를 초기화하고, 클라이언트 소켓을 생성하고, 서버 주소를 준비하고, 1000ms 간격(1초)으로 타이머를 설정합니다. TimerProc 함수는 이 타이머를 기반으로 주기적으로 실행됩니다. 그런 다음 클라이언트는 타이머 이벤트를 처리하기 위해 메시지 루프에 들어갑니다.
이 클라이언트 프로그램은 서버에 연결하고, 미리 정의된 패킷 구조를 사용하여 데이터를 교환하고, 수신된 데이터로 공유 메모리를 업데이트하는 TCP 클라이언트 생성의 기본 사항을 보여줍니다.
[결과]
댓글