공부

20200904 공부

글로벌디노 2020. 9. 4. 13:45

1. 문제풀이

2. 스레드 동기화

3. UDP 서버 - 클라이언트

4. 브로드캐스팅

 

 

1. 문제풀이

 

1. DDB와 DIB의 차이에 대해 서술하시오. BMP파일은 어떤 쪽에 해당하며, 우리가 WinAPI를 통해 그림을 찍으려면 BMP파일을 어떻게 해야 되는지 DIB와 DDB관점에서 설명하시오.

 

DDB (Device Dependent Bitmap)

GDI에서 DC와 연결되는 Bitmap을 DDC라 한다

출력 장치에 종속적

이미지의 크기, 색상 등 기본적인 정보와 이미지 데이터로 구성

 

DIB (Device Independent Bitmap)

출력 장치에 독립적

DDB에 비해 색상 테이블, 해상도 정보 등의 추가 정보를 가지므로 장치에 종속되지 않음

BMP파일은 DIB에 해당한다

 

출력은 .. 다른점을 잘 모르겠다

어쨌든 DC로 출력 해야한다

출력DC로 복사하려면 BitBlt, StretchBlt, StretchDIBits 등의 함수를 사용한다

 

추가)

이미지를 파일로 저장하기 위해선 독립적인 DIB로 저장되어야 하며, 이를 윈도우에서 GDI를 통해 화면에 출력 하려면 DDB로 변환을 해야한다

 

참고

비트맵의 종류(DDB, DIB)

비트맵 함수 (MSDN)

 

 

2. 레퍼런스와 포인터의 차이는 무엇이고, 어셈블리어 입장에서는 어떻게 차이가 나게되는가?

 

int n = 10;
int* p = &n;
int& r = n;

*p = 20;
r = 30;
        int n = 10;
00181D44  mov         dword ptr [ebp-24h],0Ah  
        int* p = &n;
00181D4B  lea         eax,[ebp-24h]  
00181D4E  mov         dword ptr [ebp-30h],eax  
        int& r = n;
00181D51  lea         eax,[ebp-24h]  
00181D54  mov         dword ptr [ebp-3Ch],eax  

        *p = 20;
00181D57  mov         eax,dword ptr [ebp-30h]  
00181D5A  mov         dword ptr [eax],14h  
        r = 30;
00181D60  mov         eax,dword ptr [ebp-3Ch]  
00181D63  mov         dword ptr [eax],1Eh

디스어셈블리 창으로 확인하면 동작방식은 똑같다

둘 다 n 의 주소 를 저장하고 주소를 참조해서 값을 변경한다

마지막에 n을 출력하면 30이 나온다

 

그럼 뭐가 다를까?

 

 

다음의 경우를 보자

int n = 10;
int* p = &n;
int& r = n;

*p = 11;
r = 12;

int m = 20;
p = &m;
r = m;

*p = 21;
r = 22;

마지막에 n과 m을 출력하면 어떤 결과가 있을지 예상해보자

....

 

 

결과는 n = 22, m = 21 이다

포인터변수는 저장된 주소와 주소의 값을 변경할 수 있다 (const 없을 경우)

레퍼런스는 저장된 주소의 값을 변경할 수 있지만 주소를 변경할 수 없다

int n = 10;

int* const ref = n;

위 처럼 주소값에 const를 붙인 포인터처럼 생각해도 될까?

 

추가)

레퍼런스는 처음에 무조건 초기화를 해주고 나중에 바꿀 수 없다

 

 

3. 다음 코드의 실행 결과를 예측해보시오.

int main()
{
	int Data = 10;		//	주소 : 0x00affa64
	int Data2 = 11;		//	주소 : 0x00affa5c
	int* Pointer = &Data;
	int& Reference = Data;
	Reference = Data2;

	printf("Data2 = %d , Data = %d , Reference = %d \n", Data2, Data, Reference);
	printf("&Referece = 0x%p \n", &Reference);
	return 0;
}

Data2 = 11, Data = 11, Reference = 11

&Reference = 0x00AFFA64

 

 

4. 아래 코드의 "여기"부분을, 형태에 알맞게 정의하시오.

void Test("여기")
{
	int x = 0;

	callback(x);
}

void CallBack_1(int Value)
{
	printf("%d \n", Value);
}

int main()
{
	Test(CallBack_1);
	return 0;
}

void(*callback)(int)

형태 : 리턴(*ptr)(인자);

 

 

5. 아래 코드는 무슨 의도로 작성된 코드인가 설명하시오.

fseek(pFile, 0, SEEK_END); 
Temp= ftell(pFile);

 

파일의 크기를 알아낸다

 

 

 

2. 스레드 동기화

 

동기화 기법

 

임계영역 (critical section)

공유 자원에 대해 오직 한 스레드의 접근만 허용한다. (한 프로세스에 속한 스레드 간에만 사용 가능)

 

뮤텍스 (mutex)

공유 자원에 대해 오직 한 스레드의 접근만 허용한다. (서로 다른 프로세스에 속한 스레드 간에도 사용 가능)

 

이벤트 (event)

사건 발생을 알려 대기 중인 스레드를 깨운다

 

세마포어 (semaphore)

한정된 개수의 자원에 여러 스레드가 접근할 때, 자원을 사용할 수 있는 스레드 개수를 제한한다

 

대기 기능 타이머 (waitable timer)

정해진 시간이 되면 대기 중인 스레드를 깨운다

 

 

임계영역 예제

 

 

이벤트

 

이벤트를 사용하는 전형적인 절차

1. 이벤트를 비신호 상태로 생성한다

2. 한 스레드가 작업을 진행하고, 나머지 스레드는 이벤트에 대해 Wait*() 함수를 호출해 이벤트가 신호 상태가 될 때까지 대기한다(sleep)

3. 스레드가 작업을 완료하면 이벤트를 신호 상태로 바꾼다.

4. 기다리고 있던 스레드 중 하나 혹은 전부가 깨어난다(wakeup)

 

상태 변경 함수

BOOL SetEvent( HANDLE hEvent );   // 비신호 상태 -> 신호 상태
BOOL ResetEvent( HANDLE hEvent ); // 신호 상태 -> 비신호 상태

 

자동 리셋(auto-reset) 이벤트 : 이벤트를 신호 상태로 바꾸면, 기다리는 스레드 중 하나만 깨운 후 자동으로 비신호 상태가 된다. 따라서 자동 리셋 이벤트에 대해서는 ResetEvent() 함수를 사용할 필요가 없다

 

수동 리셋(manual-reset) 이벤트 : 이벤트를 신호 상태로 바꾸면, 기다리는 스레드를 모두 깨운 후 계속 신호 상태를 유지한다. 자동 리셋 이벤트와 달리 비신호 상태로 바꾸려면 명시적으로 ResetEvent() 함수를 호출해야 한다

 

이벤트 생성 함수

HANDLE CreateEventW(
  LPSECURITY_ATTRIBUTES lpEventAttributes,
  BOOL                  bManualReset,
  BOOL                  bInitialState,
  LPCWSTR               lpName
);

성공 : 이벤트 핸들
실패 : NULL

 

이벤트 연습

#include <Windows.h>
#include <stdio.h>

#define BUFSIZE 10

HANDLE hReadEvent;
HANDLE hWriteEvent;
int buf[BUFSIZE];

DWORD WINAPI WriteThread(LPVOID arg)
{
	DWORD retVal;

	for (int k = 1; k <= 500; k++)
	{
		// 읽기 완료 대기
		retVal = WaitForSingleObject(hReadEvent, INFINITE);
		if (retVal != WAIT_OBJECT_0)
			break;

		// 공유 버퍼에 데이터 저장
		for (int i = 0; i < BUFSIZE; i++)
			buf[i] = k;

		// 쓰기 완료 알림
		SetEvent(hWriteEvent);
	}

	return 0;
}

DWORD WINAPI ReadThread(LPVOID arg)
{
	DWORD retVal;

	while (1)
	{
		// 쓰기 완료 대기
		retVal = WaitForSingleObject(hWriteEvent, INFINITE);
		if (retVal != WAIT_OBJECT_0)
			break;

		// 읽은 데이터 출력
		printf("Thread %6d  ", GetCurrentThreadId());
		for (int i = 0; i < BUFSIZE; i++)
			printf("%3d ", buf[i]);
		printf("\n");

		// 버퍼 초기화
		ZeroMemory(buf, sizeof(buf));

		// 읽기 완료 알림
		SetEvent(hReadEvent);
	}

	return 0;
}

int main()
{
	// 자동 리셋 이벤트 두 개 생성 (각각 비신호, 신호 상태)
	hWriteEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
	if (hWriteEvent == NULL) return 1;
	hReadEvent = CreateEventW(NULL, FALSE, TRUE, NULL);
	if (hReadEvent == NULL) return 1;

	// 스레드 세 개 생성
	HANDLE hThread[3];
	hThread[0] = CreateThread(NULL, 0, WriteThread, NULL, 0, NULL);
	hThread[1] = CreateThread(NULL, 0, ReadThread, NULL, 0, NULL);
	hThread[2] = CreateThread(NULL, 0, ReadThread, NULL, 0, NULL);

	// 스레드 세개 종료 대기
	WaitForMultipleObjects(3, hThread, TRUE, INFINITE);

	// 이벤트 제거
	CloseHandle(hWriteEvent);
	CloseHandle(hReadEvent);

	return 0;
}

 

 

 

3. UDP 서버 - 클라이언트

 

UDP 서버

1. socket() 함수로 소켓을 생성함으로써 사용할 프로토콜을 결정한다

2. bind() 함수로 지역 IP 주소와 지역 포트 번호를 결정한다

3. 클라이언트가 보낸 데이터를 recvfrom() 함수로 받는다. 이때 원격 IP 주소와 원격 포트 번호, 즉 클라이언트의 주소를 알 수 있다.

4. 받은 데이터를 처리한 결과를 sendto() 함수로 보낸다

5. 모든 작업을 마치면 closesocket() 함수로 소켓을 닫는다

 

UDP 클라이언트

1. socket() 함수로 소켓을 생성함으로써 사용할 프로토콜을 결정한다

2. sendto() 함수로 서버에 데이터를 보낸다. 이때 원격 IP 주소와 원격 포트 번호는 물론, 지역 IP 주소와 지역 포트 번호도 결정된다

3. 서버가 처리해 보낸 데이터를 recvfrom() 함수로 받는다

4. 모든 작업을 마치면 closesocket() 함수로 소켓을 닫는다

 

 

서버코드

#pragma comment(lib, "Ws2_32.lib")
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <stdio.h>

#define SERVERPORT 9000
#define BUFSIZE 512

// 소켓 함수 오류 출력
void err_display(const char* msg)
{
	LPVOID msgBuf;
	FormatMessageA(
		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
		NULL, GetLastError(),
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPSTR)&msgBuf, 0, NULL);
	MessageBoxA(NULL, (LPCSTR)msgBuf, msg, MB_ICONERROR);
	LocalFree(msgBuf);
}

// 소켓 함수 오류 출력 후 종료
void err_quit(const char* msg)
{
	err_display(msg);
	exit(1);
}

int main()
{
	int retVal;

	// 윈속 초기화
	WSADATA wsa;
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
		return 1;

	// socket()
	SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock == INVALID_SOCKET)
		err_quit("socket()");

	// bind()
	SOCKADDR_IN serverAddr;
	ZeroMemory(&serverAddr, sizeof(serverAddr));
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons(SERVERPORT);
	serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	retVal = bind(sock, (SOCKADDR*)&serverAddr, sizeof(serverAddr));

	// 데이터 통신에 사용할 변수
	SOCKADDR_IN clientAddr;
	int addrLen;
	char buf[BUFSIZE + 1];

	// 클라이언트와 데이터 통신
	while (1)
	{
		// 데이터 받기
		addrLen = sizeof(clientAddr);
		retVal = recvfrom(sock, buf, BUFSIZE, 0, (SOCKADDR*)&clientAddr, &addrLen);
		if (retVal == SOCKET_ERROR)
		{
			err_display("recvfrom()"); 
			continue;
		}

		// 받은 데이터 출력
		char bufAddr[64];
		InetNtopA(clientAddr.sin_family, &clientAddr.sin_addr, bufAddr, sizeof(bufAddr));
		buf[retVal] = '\0';
		printf_s("[UDP%s:%d] %s\n", bufAddr, ntohs(clientAddr.sin_port), buf);

		// 데이터 보내기
		retVal = sendto(sock, buf, retVal, 0, (SOCKADDR*)&clientAddr, sizeof(clientAddr));
		if (retVal == SOCKET_ERROR)
		{
			err_display("sendto()");
			continue;
		}
	}

	closesocket(sock);
	WSACleanup();

	return 0;
}

 

클라이언트코드

#pragma comment(lib, "Ws2_32.lib")
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <stdio.h>

#define SERVERIP "127.0.0.1"
#define SERVERPORT 9000
#define BUFSIZE 512

// 소켓 함수 오류 출력
void err_display(const char* msg)
{
	LPVOID msgBuf;
	FormatMessageA(
		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
		NULL, GetLastError(),
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPSTR)&msgBuf, 0, NULL);
	MessageBoxA(NULL, (LPCSTR)msgBuf, msg, MB_ICONERROR);
	LocalFree(msgBuf);
}

// 소켓 함수 오류 출력 후 종료
void err_quit(const char* msg)
{
	err_display(msg);
	exit(1);
}

int main()
{
	int retVal;

	// 윈속 초기화
	WSADATA wsa;
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
		return 1;

	// socket()
	SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock == INVALID_SOCKET)
		err_quit("socket()");

	// 서버 소켓주소 초기화
	SOCKADDR_IN serverAddr;
	ZeroMemory(&serverAddr, sizeof(serverAddr));
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons(SERVERPORT);
	InetPtonA(AF_INET, SERVERIP, &serverAddr.sin_addr);

	// 데이터 통신에 사용할 변수
	SOCKADDR_IN peerAddr;
	int addrLen;
	char buf[BUFSIZE + 1];
	int len;

	// 서버와 데이터 통신
	while (1)
	{
		// 데이터 입력
		printf_s("\n[보낼데이터] ");
		if (fgets(buf, BUFSIZE + 1, stdin) == NULL)
			break;

		// '\n' 문자 제거
		len = strlen(buf);
		if (buf[len - 1] == '\n')
			buf[len - 1] = '\0';
		if (strlen(buf) == 0)
			break;

		// 데이터 보내기
		retVal = sendto(sock, buf, strlen(buf), 0, (SOCKADDR*)&serverAddr, sizeof(serverAddr));
		if (retVal == SOCKET_ERROR)
		{
			err_display("sendto()");
			continue;
		}

		printf_s("[UDP 클라이언트] %d바이트를 보냈습니다\n", retVal);

		// 데이터 받기
		addrLen = sizeof(peerAddr);
		retVal = recvfrom(sock, buf, BUFSIZE, 0, (SOCKADDR*)&peerAddr, &addrLen);
		if (retVal == SOCKET_ERROR)
		{
			err_display("recvfrom()");
			continue;
		}

		// 송신자의 IP 주소 체크
		if (memcmp(&peerAddr, &serverAddr, sizeof(peerAddr)))
		{
			printf_s("잘못된 데이터 입니다\n");
			continue;
		}

		// 받은 데이터 출력
		buf[retVal] = '\0';
		printf_s("[UDP 클라이언트] %d바이트를 받았습니다\n", retVal);
		printf_s("[받은데이터] %s \n", buf);
	}

	closesocket(sock);
	WSACleanup();
	return 0;
}

 

실행결과

 

 

UDP 브로드캐스팅 연습

 

BroadcastSender (송신자)

#pragma comment(lib, "Ws2_32.lib")
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <stdio.h>

#define REMOTEIP "255.255.255.255"
#define REMOTEPORT 9000
#define BUFSIZE 512

// 소켓 함수 오류 출력
void err_display(const char* msg)
{
	LPVOID msgBuf;
	FormatMessageA(
		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
		NULL, GetLastError(),
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPSTR)&msgBuf, 0, NULL);
	MessageBoxA(NULL, (LPCSTR)msgBuf, msg, MB_ICONERROR);
	LocalFree(msgBuf);
}

// 소켓 함수 오류 출력 후 종료
void err_quit(const char* msg)
{
	err_display(msg);
	exit(1);
}

int main()
{
	int retVal;

	// 윈속 초기화
	WSADATA wsa;
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
		return 1;

	// socket()
	SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock == INVALID_SOCKET)
		err_quit("socket()");

	// 브로드캐스팅 활성화
	BOOL bEnable = TRUE;
	retVal = setsockopt(sock, SOL_SOCKET, SO_BROADCAST,
		(char*)&bEnable, sizeof(bEnable));

	// 소켓 주소 구조체 초기화
	SOCKADDR_IN remoteAddr;
	ZeroMemory(&remoteAddr, sizeof(remoteAddr));
	remoteAddr.sin_family = AF_INET;
	remoteAddr.sin_port = htons(REMOTEPORT);
	InetPtonA(AF_INET, REMOTEIP, &remoteAddr.sin_addr);

	// 데이터 통신에 사용할 변수
	char buf[BUFSIZE + 1];
	int len;

	// 브로드캐스트 데이터 보내기
	while (1)
	{
		// 데이터 입력
		printf_s("\n[보낼데이터] ");
		if (fgets(buf, BUFSIZE + 1, stdin) == NULL)
			break;

		// '\n' 문자 제거
		len = strlen(buf);
		if (buf[len - 1] == '\n')
			buf[len - 1] = '\0';
		if (strlen(buf) == 0)
			break;

		// 데이터 보내기
		retVal = sendto(sock, buf, strlen(buf), 0,
			(SOCKADDR*)&remoteAddr, sizeof(remoteAddr));
		if (retVal == SOCKET_ERROR)
		{
			err_display("sendto()");
			continue;
		}

		printf_s("[UDP 클라이언트] %d바이트를 보냈습니다\n", retVal);
	}

	closesocket(sock);
	WSACleanup();
	return 0;
}

 

BroadcastReceiver (수신자)

#pragma comment(lib, "Ws2_32.lib")
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <stdio.h>

#define LOCALPORT 9000
#define BUFSIZE 512

// 소켓 함수 오류 출력
void err_display(const char* msg)
{
	LPVOID msgBuf;
	FormatMessageA(
		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
		NULL, GetLastError(),
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPSTR)&msgBuf, 0, NULL);
	MessageBoxA(NULL, (LPCSTR)msgBuf, msg, MB_ICONERROR);
	LocalFree(msgBuf);
}

// 소켓 함수 오류 출력 후 종료
void err_quit(const char* msg)
{
	err_display(msg);
	exit(1);
}

int main()
{
	int retVal;

	// 윈속 초기화
	WSADATA wsa;
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
		return 1;

	// socket()
	SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock == INVALID_SOCKET)
		err_quit("socket()");

	// bind()
	SOCKADDR_IN localAddr;
	ZeroMemory(&localAddr, sizeof(localAddr));
	localAddr.sin_family = AF_INET;
	localAddr.sin_port = htons(LOCALPORT);
	localAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	retVal = bind(sock, (SOCKADDR*)&localAddr, sizeof(localAddr));
	if (retVal == SOCKET_ERROR)
		err_quit("bind()");

	// 데이터 통신에 사용할 변수
	SOCKADDR_IN peerAddr;
	int addrLen;
	char buf[BUFSIZE + 1];

	// 브로드캐스트 데이터 받기
	while (1)
	{
		// 데이터 받기
		addrLen = sizeof(peerAddr);
		retVal = recvfrom(sock, buf, BUFSIZE, 0, (SOCKADDR*)&peerAddr, &addrLen);
		if (retVal == SOCKET_ERROR)
		{
			err_display("recvfrom()");
			continue;
		}

		// 받은 데이터 출력
		buf[retVal] = '\0';
		char addrBuf[50];
		InetNtopA(peerAddr.sin_family, 
			&peerAddr.sin_addr, 
			addrBuf, sizeof(addrBuf));
		printf_s("[UDP/%s:%d] %s \n", addrBuf,
			ntohs(peerAddr.sin_port), buf);
	}

	closesocket(sock);
	WSACleanup();
	return 0;
}

 

실행결과

'공부' 카테고리의 다른 글

BMP 파일 이미지 데이터 읽기  (0) 2020.09.13
20200905 공부  (0) 2020.09.05
20200903 공부  (0) 2020.09.03
20200902 공부  (0) 2020.09.02
20200901 공부  (0) 2020.09.02