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 고정이다





2025년 1월 9일 목요일

불 쉐이더

 이펙트 구현하기 위해 돌아다니다가

간단한 불 이펙트를 설명하는 사이트를 찾아서

참고하여 HLSL로 불 느낌나게 만듦


https://realtimevfx.com/t/simple-fire-shader-breakdown/11213




사용하는 텍스처가 딱 한개라 아주 좋아용



원리는 두 텍스처를 서로 교차되는 방향으로 이동시켜 

교차되는 부분을 알파 테스트로 통과하여 불 색을 입히고

발광 수치랑 기타 등등 을 적용하는 내용인데


불 느낌만 내면 상관없기에

불 필요한 계산은 제외하고 내 맘대로 했음  


Texture2D fireTexture : register(t0);
Texture2D noiseTexture : register(t1);
Texture2D alphaTexture : register(t2);
SamplerState SampleTypeWrap : register(s0);

cbuffer NoiseBuffer : register(b0)
{
    float3 padding;
	float frameTime;
}

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD;
};

float4 main(PixelInputType input) : SV_TARGET
{
    float4 finalColor;
    float noiseColor;
    float noiseColor2;
    float finalNoise;
    float2 noiseCoord;
    float2 noiseCoord2;


    noiseCoord = input.tex;
    noiseCoord.xy *= float2(0.8f, 0.7f);
    noiseCoord.xy += float2(0.1f, 0.5f) * frameTime;

    noiseCoord2 = input.tex;
    noiseCoord2.xy *= float2(1.0f, 0.5f);
    noiseCoord2.xy += float2(-0.2f, 0.3f) * frameTime;

    noiseColor = noiseTexture.Sample(SampleTypeWrap, noiseCoord).r;
    noiseColor2 = noiseTexture.Sample(SampleTypeWrap, noiseCoord2).r;

    finalNoise = noiseColor * noiseColor2;
    //finalNoise = saturate(finalNoise);

    finalColor = fireTexture.Sample(SampleTypeWrap, finalNoise);

    finalColor.a = finalNoise + input.tex.y - 0.3f;

    return finalColor;
}


시간 계수만 받아와 노이즈 텍스처 두개 각각 좌표를 타일링 하고,

속도만큼 이동시켜 샘플링, 어차피 rgb 셋다 동일한 값을 갖는

텍스처라 빨강 값만 가져와 사용,



불 색상은 그라데이션으로 적용하기 위해

노이즈 값을 uv좌표로 활용,

색이 진할 수록 노랑값을 갖고

연할 수록 빨강 값을 갖도록 하였음

알파 테스트는 건너뜀




다만 이거는 빌보드나 X 모양으로 겹쳐서 그려야

그나마 이펙트 기능을 할듯한데 파티클 없이 단독으로 불 효과를 내는건

의미 없어 보임.



c++ thread.h

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