2025년 2월 22일 토요일

c++ thread.h

 c++에서 쓰레드 돌릴려면 thread.h 헤더를 쓰면 되는데

이 친구는 쓰레드가 아직 실행 중인지, 아니면 강제 종료하거나 하는

함수가 없어서 조금 아쉬운 애다.



std::thread 는 로컬 변수로 선언하든 new 동적 할당을 하든

start 함수가 없어서 선언되자마자 실행된다



그리고 저번에 서버 프로그램 만들 때 쓰면서 알게 된 사실인데

스레드는 완전하게 독립된 상태가 아니라

스레드 코드가 끝나기 전에 선언했던 본문이 먼저 끝나면




이와 같이 abort 오류가 발생함

그래서 detach 함수로 독립시켜주거나 join을 호출해야 한다




스레드를 실행했다면 종료시키는 방법은

이제 스레드 코드가 알아서 끝날때 까지 기다리면 된다

면... 무한반복문의 경우 어떻게 할 수 없기 때문에 뭔가 해줘야 함



그래도 외부 변수로 반복하는 스레드의 경우

합법적으로 종료 시킬 수 있음


외부 변수를 false로 대입하고

join 하면 해당 스레드가 종료될 때까지 기다린다


그런데 출력에 이상이 생긴 걸 알 수 있음

cout 에서 버퍼링?에 사용하는 자원에 동시 접근해서 발생한것으로 추측



그래서 자바처럼 같은 자원에 접근 할 때 생기는 문제를 해결하기 위해

동기화 synchronized 와 비슷한 mutex가 있다.




얘는 변수로 선언해서 써야 하는데

이런식으로 출력에 생긴 문제를 해결하는 모습을 볼 수 있다.


lock 함수로 공유 자원에 대한 접근을 대기 시키고

unlock 으로 대기 해제 시킨다


이후 스레드를 끝내고 join해서 코드가 완전히 종료될 때 까지 기다림



그럼 스레드가 끝났는지 확인하는 법은?

이것도 외부변수로 체크함...


thread에 joinable 함수가 있긴 하지만 

join이나 detach 했는지를 확인하는 함수라 

스레드가 아직 실행 중인지 확인하는 좋은 방법은 아닌듯 하다


그렇다고 thread를 강제 종료시킬 수도 없다...

프로세스가 끝나면 어차피 다 강제 종료된다


2025년 2월 15일 토요일

C++ winsock2

c++ 소켓은 winsock2로 구현할 수 있는데

자바의 소켓이랑은 많이 달랐음



클라이언트에서 데이터 보내기 및 받기 - Win32 apps | Microsoft Learn

예시 코드로는 ms 설명서에 제대로 나와있기는 한데



일단 winsock.h 과 winsock2.h 두 가지 버전으로 나뉨

전자는 옛날버전이고 후자는 기능 추가 한걸로 알고 있음



winsock.h는 windows.h 헤더에 이미 포함되어 있어

winsock2.h와 windows.h 헤더를 같이 쓰면 재정의 오류가 뜸




이를 해결하려면 #define WIN32_LEAN_AND_MEAN 를 추가하여

winsock.h 및 여러 불필요한 헤더들을 windows.h로 부터 제외 가능

이렇게 같이 사용할 수 있음


하지만 간접 헤더 포함될 때도 재정의 오류가 뜸...

그래서 모든 헤더에 #define 하기 그래서 함수 포인터를 씀

거기에 소켓 관련 함수들을 싹다 넣고 호출 



서버 코드

#include <winsock2.h>
#include <ws2tcpip.h>
#include <string>
#include <thread>
#include <vector>
#include <wchar.h>
#include <iostream>

#pragma comment(lib, "Ws2_32.lib")

#define SERVER_PORT 8001
#define BUFFER_SIZE 256

using namespace std;

struct ClientData
{
    SOCKET clientSocket = {};
    SOCKADDR_IN clientAddr = {};
    wchar_t clientName[10] = { L"player" };
    bool online;
};

vector<ClientData*> clients;

void SendToAll(char* typeMsg, wchar_t* chatMsg)
{
    int result = 0;
    int clsSize = clients.size();

    for (int i = 0; i < clsSize; i++)
    {
        if (!clients[i]->online)
        {
            char type3 = 0x03;
            wchar_t exitMsg[BUFFER_SIZE] = {};

            wcscat_s(exitMsg, clients[i]->clientName);
            wcscat_s(exitMsg, L" - ");
            wcscat_s(exitMsg, L"님이 퇴장했습니다.\n");
            SendToAll(&type3, exitMsg);

            delete clients[i];
            clients.erase(clients.begin() + i);
        }
    }

    for (int i = 0; i < clsSize; i++)
    {
        result = send(clients[i]->clientSocket, typeMsg, sizeof(char), 0);
        if (result == SOCKET_ERROR)
        {
            closesocket(clients[i]->clientSocket);
            continue;
        }

        result = send(clients[i]->clientSocket, (char*)chatMsg, BUFFER_SIZE * sizeof(wchar_t), 0);
        if (result == SOCKET_ERROR)
        {
            closesocket(clients[i]->clientSocket);
            continue;
        }
    }
}

int main() 
{
    int result;

    // Winsock 초기화
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) 
    {
        std::cerr << "WSAStartup failed!" << std::endl;
        return 0;
    }

    // 소켓 생성
    SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (serverSocket == INVALID_SOCKET) {
        std::cerr << "Socket creation failed! Error: " << WSAGetLastError() << std::endl;
        WSACleanup();
        return 0;
    }

    // 서버 주소 설정
    SOCKADDR_IN serverAddr = {};
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(SERVER_PORT);

    bind(serverSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr));
    listen(serverSocket, SOMAXCONN);

    wcout.imbue(locale("kor"));

    ////논 블로킹으로 설정
    //u_long argp = 1;
    //ioctlsocket(clientSocket, FIONBIO, &argp);

    int repeatCount = 0;

    //새로운 클라이언트를 계속해서 받는 반복문
    while (true)
    {
		ClientData* newClientData = new ClientData;
		int clientAddrSize = sizeof(SOCKADDR_IN);

		newClientData->clientSocket = accept(serverSocket, (SOCKADDR*)&newClientData->clientAddr, &clientAddrSize);
		if (newClientData->clientSocket == INVALID_SOCKET) {
			std::cerr << "Accept failed! Error: " << WSAGetLastError() << std::endl;
			closesocket(serverSocket);
			WSACleanup();

			break;
		}

		wchar_t convNum[4] = {};

		_itow_s(repeatCount, convNum, _countof(convNum), 10);//카운트를 문자로
		wcscat_s(newClientData->clientName, convNum);//클라이언트 이름에 숫자 추가
		newClientData->online = true;//연결중임을 의미
		clients.push_back(newClientData);//클라이언트 배열에 추가

        char type3 = 0x03;
        wchar_t joinMsg[BUFFER_SIZE] = {};

        wcscat_s(joinMsg, newClientData->clientName);
        wcscat_s(joinMsg, L" - ");
        wcscat_s(joinMsg, L"님이 접속했습니다.\n");
        SendToAll(&type3, joinMsg);

        wcout << L"New Player Entered : " << newClientData->clientName << endl;

		//지속적으로 클라이언트로부터 메시지를 받아 다른 클라로 전달해주는 반복문
		thread recvThread
		(
			[](ClientData* cls)
			{
				while (true)
				{  
                    int tresult;
					char typeBuffer;
                    wchar_t nameAndBar[BUFFER_SIZE] = {};
					wchar_t strBuffer[BUFFER_SIZE] = {};

                    tresult = recv(cls->clientSocket, &typeBuffer, sizeof(char), 0);
					if (tresult == SOCKET_ERROR)
					{
						closesocket(cls->clientSocket);
                        cerr << WSAGetLastError() << endl;
                        cls->online = false;

                        for (int i = 0; i < clients.size(); i++)
                        {
                            if (!clients[i]->online)
                            {
                                char type3 = 0x03;
                                wchar_t exitMsg[BUFFER_SIZE] = {};

                                wcscat_s(exitMsg, clients[i]->clientName);
                                wcscat_s(exitMsg, L" - ");
                                wcscat_s(exitMsg, L"님이 퇴장했습니다.\n");

                                delete clients[i];
                                clients.erase(clients.begin() + i);

                                SendToAll(&type3, exitMsg);
                            }
                        }

						break;
					}

                    tresult = recv(cls->clientSocket, (char*)strBuffer, BUFFER_SIZE * sizeof(wchar_t), 0);
					if (tresult == SOCKET_ERROR)
					{
						closesocket(cls->clientSocket);
                        cerr << WSAGetLastError() << endl;
                        cls->online = false;
						break;
					}

                    wcscat_s(nameAndBar, cls->clientName);
                    wcscat_s(nameAndBar, L" - ");
                    wcscat_s(strBuffer, L"\n");

                    SendToAll(&typeBuffer, nameAndBar);
					SendToAll(&typeBuffer, strBuffer);
				}
			}, newClientData
		);

		recvThread.detach();

		repeatCount++;//카운트 증가
    }
 
    std::cout << "Server ShutDown" << std::endl;

    // 소켓 닫기
    closesocket(serverSocket);
    WSACleanup();

    return 0;
}

메시지 받은걸 모든 접속자에게 돌려주는 프로그램

을.. 실행하고, 채팅 주고 받을 수 있는 클라로 접속하면 된다.



소켓 통신에서 데이터를 주고 받을 땐 send와 recv 함수가 있는데

기본적으로 blocking 이라는 데이터가 제대로 전달될 때 까지

기다리는 기능이 있는데 이것 때문에 메인 스레드는 못써먹게 됨



따라서 스레드 새로 생성해서 그쪽에서 돌려야 함



blocking을 없애는 것도 가능하다 //주석처리로 해둠

ioctlsocket 함수(winsock.h) - Win32 apps | Microsoft Learn

 

시연 이미지


같은 공유기를 쓰거나 내부 사설망의 경우 간단하게 접속 가능

아니라면 포트포워드


클라이언트 소스코드

GitHub - h117562/Tutorial_Socket

서버 프로그램은 data 폴더에 exe로 뒀고 포트는 8001 고정이다





c++ thread.h

 c++에서 쓰레드 돌릴려면 thread.h 헤더를 쓰면 되는데 이 친구는 쓰레드가 아직 실행 중인지, 아니면 강제 종료하거나 하는 함수가 없어서 조금 아쉬운 애다. std::thread 는 로컬 변수로 선언하든 new 동적 할당을 하든 start 함...