메모장
TCP/IP 소켓 프로그래밍 복습4 본문
참고문헌 - 윤성우 <윤성우의 열혈 TCP/IP 소켓 프로그래밍>
♨ 개인적 해석이 들어간 글임으로, 인지하지 못한 오류가 있을 수 있습니다 ♨
○ Windows 기반 Thread
▣ 커널 오브젝트(Kernel Object)
- 프로세스, 쓰레드, 파일, 세마포어, 뮤텍스 등은 운영체제가 만드는 리소스(Resource)다
-> 운영체제(Windows)가 생성해서 관리하는 리소스
- 이런 운영체제의 의해서 생서오디는 리소스들은 관리를 목적으로 정보를 기록하기 위해 내부적으로 데이터 블록을 생성(like 구조체 변수)
-> 리소스 마다 유지해야 하는 정보가 다르니, 데이터 블록의 형태는 리소스마다 차이가 있다
=> 이 데이터 블록이 '커널 오브젝트'
예) 윈도우 상에서 mydata.txt파일 생성
- 윈도우는 이 파일을 관리하기 위해 데이터 블록(커널 오브젝트) 생성
▣ 커널 오브젝트의 소유자는 운영체제
- 커널 오브젝트의 생성, 관리 그리고 소멸지점을 결정하는 것까지 모두 운영체제의 몫이다
▣ 프로세스와 쓰레드의 관계
- 프로그램이 시작될 때 main함수를 호출하는 것은 main 쓰레드이다
- 쓰레드를 별도로 생성하지 않는 프로그램 : 단일 쓰레드 모델의 프로그램
예) select 기반의 서버
- 쓰레드를 별도로 생성하는 프로그램 : 멀티 쓰레드 모델의 프로그램
-> main 함수를 호출하는 것은 쓰레드이고, 그 쓰레드를 담고 있는 것이 프로세스
▣ 윈도우에서의 쓰레드 생성방법
- CreateThread함수에 의해서 쓰레드가 생성되면 운영체제는 이 쓰레드의 관리를 위한 커널 오브젝트를 생성한다
-> 커널 오브젝트의 구분자(ID) 역할을 하는 정수로 표현되는 '핸들'이 반환된다(∴ CreateThread함수의 반환형은 HANDLE 타입)
#include <windows.h>
HANDLE CreateThread(-> 성공 시 쓰레드 핸들, 실패 시 NULL 반환
LPSECURITY_ATTRIBUTES lpThreadAttributes, ①
SIZE_T dwStackSize, ②
LPTHREAD_START_ROUTINE lpStartAddress, ③
LPVOID lpParameter, ④
DWORD dwCreationFlags, ⑤
LPDWORD lpThreadId ⑥
);
① lpThreadAttributes : 쓰레드의 보안관련 정보전달, 디폴트 보안설정을 위해서 NULL 전달
② dwStackSize : 쓰레드에게 할당할 스택의 크기를 전달, 0 전달하면 디폴트 크기의 스택 생성
③ lpStartAddress : 쓰레드의 main 함수정보 전달
④ lpParameter : 쓰레드의 main 함수호출 시 전달할 인자정보 전달
⑤ dwCreationFlags : 쓰레드 생성 이후의 행동을 결정, 0을 전달하면 생성과 동시에 실행 가능한 상태가 된다
⑥ lpThreadId : 쓰레드의 ID의 저장을 위한 변수의 주소 값 전달
-> 책에서 신경쓰고 있는 부분은 lpStartAddress와 lpParameter 두 가지 (나머지는 다 0 또는 null을 전달)
※ 윈도우 쓰레드의 소멸 시점
- 스레드에 의해서 처음 호출된, 쓰레드의 main 함수가 반환하는(종료되는) 시점
▣ 멀티 쓰레드 기반의 프로그램 작성을 위한 환경설정
- VC++상에서는 'C/C++ Runtime Library(이하 CRT)'라는 것을 지정해줘야 한다 -> C/C++ 표준함수의 호출에 필요한 라이브러리
[프로젝트] > [속성] > [C/C++] > [코드 생성(Code Generation)] > [런타임 라이브러리(Runtime Library)] > '다중 스레드 디버그 DLL(/MDd)' 설정
▣ 쓰레드에 안전한 C 표준함수의 호출을 위한 쓰레드 생성
- 앞서 나온 CreateThread를 통해 만들어진 쓰레드를 통해서 C/C++ 표준함수를 호출하면 안정적으로 동작하지 않는다
이를 대체하는(C/C++ 표준함수에 안정적으로 동작시킬 수 있는 쓰레드를 생성하려면) ' _beginthreadex '함수를 호출해야 한다
#include process.h
uintptr_t _beginthreadex(-> 성공 시 쓰레드 핸들, 실패 시 0 반환
void* security,
unsigned stack_size,
unsigned ( *start_address )( void* ),
void* arglist,
unsigned initflag,
unsigned* thrdaddr
);
-> CreateThread함수와 비교하면 각 매개변수가 지닌 의미와 순서가 동일하다
(이름과 자료형에 조금 차이난다)
※ _beginthreadex함수 이전에 정의된 _beginthread함수
- _beginthread함수는 쓰레드 생성시 반환되는 핸들을 무효화시켜 커널 오브젝트에 접근할 수 있는 방법을 막아버란다
-> 그 대신에 나온게 _beginthreadex함수
∴ ex 붙은 거로 쓰자
※※ _beginthreadex의 반환형인 uintptr_t는 64비트로 표현되는 unsigned 정수 자료형이다
예) thread_kun.c
#include <stdio.h>
#include <windows.h>
#include <process.h>// _beginthreadex, _endthreadex를 위해 추가
unsigned WINAPI ThreadFunc(void* arg);
int main(int argc, char* argv[])
{
HANDLE hThread;
unsigned threadID;
int param = 5;
hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (void*)¶m, 0, &threadID);
/* 쓰레드의 main함수로 ThreadFunc를, ThreadFunc의 인자로 param의 주소값을 전달하면서 쓰레드의 생성을 요구 */
if (hThread == 0)
{
puts("_beginthreadex() error");
return -1;
}
Sleep(3000);// 1/1000초(나노초) 단위로 블로킹 상태를 만든다 -> 3초 대기시간
puts("end of main");
return 0;
}
unsigned WINAPI ThreadFunc(void* arg)// WINAPI라는 윈도우 고유의 키워드 -> 매개변수의 전달바향, 할당된 스택의 반환방법 등을 포함하는 함수의 호출규약을 명시해 놓은 것
{// _beginthreadex 함수가 요구하는 호출규약을 지키기 위해 삽입한 것
int cnt = *((int*)arg);
for (int i = 0; i < cnt; i++)
{
Sleep(1000);
puts("running thread");
}
return 0;
}
▣ 커널 오브젝트의 상태, 그리고 상태의 확인
- 커널 오브젝트에 담겨진 해당 리소스에 대한 리로스 중 중요한 것 중 하나가 '상태(state)'
예) 쓰레드의 종료여부 : 종료된 상태 = 'signaled 상태' / 종료되지 않은 상태 = 'non-signaled 상태'
- 운영체제는 프로세스나 쓰레드가 종료되면 해당 커널 오브젝트를 'signaled 상태'로 변경한다 (∵ 프로세스와 쓰레드의 초기 상태는 'non-signaled 상태')
-> signaled / non-signaled는 boolean형 변수로 표현된다
signaled : true
non-signaled : false
- 커널 오브젝트의 상태값을 물어 main 쓰레드가 생성된 쓰레드의 main함수가 종료되기 전까지 기다리게 할 수 있다
-> WaitForSingleObject & WaitForMultileObjects
▣ WaitForSingleObject & WaitForMultileObjects
- 하나의 커널 오브젝트에 대해서 signaled 상태인지를 확인하기 위해서 호출하는 함수
#include <windows.h>
DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);-> 성공 시 이벤트 정보, 실패 시 WAIT_FAILED 반환
③ ① ②
① hHandle : 상태확인의 대상이 되는 커널 오브젝트의 핸들을 전달
② dwMilliseconds : 1/1000초 단위로 타임아웃을 지정,
인자로 INFINITE 전달 시, 커널 오브젝트가 signaled 상태가 되기 전에는 반환되지 않는다
③ DWORD(반환값) : signaled 상태로 인한 반환 시, WAIT_OBJECT_0 반환, 타임아웃으로 인한 반환 시 WAIT_TIMEOUT 반환
-> 이벤트 발생에 의해서(signaled 상태가 되어서) 반환되면, 해당 커널 오브젝트를 다시 non-signaled 상태로 되돌리기도 한다
=> 다시 non-signaled 상태가 되는 커널 오브젝트를 가르켜 'auto-reset 모드' 커널 오브젝트라 하고
자동으로 non-signaled 상태가 되지 않는 커널 오브젝트를 가르켜 'manual-reset 모드' 커널 오브젝트라 한다
- 둘 이상의 커널 오브젝트를 대상으로 상태를 확인하는 경우에 호출하는 함수
#include <windows.h>
DWORD WaitForMultipleObjects(
DWORD nCount, const HANDLE* lpHandles, BOOL bWaitAll, DWORD dwMilliseconds);
① ② ③ ④
① nCount : 상태확인의 대상이 되는 커널 오브젝트의 핸들을 전달
② lpHandles : 핸들정보를 담고 있는 배열의 주소 값 전달
③ bWaitAll : TRUE 전달 시, 모든 검사대상이 signaled 상태가 되어야 반환,
FALSE 전달 시, 검사대상 중 하나라도 signaled 상태가 되면 반환
④ dwMilliseconds : 1/1000초 단위로 타임아웃을 지정,
인자로 INFINITE 전달 시, 커널 오브젝트가 signaled상태가 되기 전까지 반환하지 않는다
예) WaitForSingleObject을 적용한 thread_kun.c
#include <stdio.h>
#include <windows.h>
#include <process.h>
unsigned WINAPI ThreadFunc(void* arg);
int main(int argc, char* argv[])
{
HANDLE hThread;
DWORD wr;
unsigned threadID;
int param = 5;
hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (void*)¶m, 0, &threadID);
if (hThread == 0)
{
puts("_beginthreadex() error");
return -1;
}
if ((wr = WaitForSingleObject(hThread, INFINITE)) == WAIT_FAILED)// WaitForSingleObject 함수호출을 통해서 쓰레드의 종료를 대기하고 있다
{
puts("thread wait error");
return -1;
}
printf("wait result : %s \n", (wr == WAIT_OBJECT_0) ? "signaled" : "time-out");// WaitForSingleObject 함수의 반환 값을 통해서 반환의 원일을 학인하고 있다
puts("end of main");
return 0;
}
unsigned WINAPI ThreadFunc(void* arg)
{
int cnt = *((int*)arg);
for (int i = 0; i < cnt; i++)
{
Sleep(1000);
puts("running thread");
}
return 0;
}
'TCP&IP 복습' 카테고리의 다른 글
TCP/IP 소켓 프로그래밍 복습3 (0) | 2020.10.29 |
---|---|
TCP/IP 소켓 프로그래밍 복습2 (0) | 2020.10.28 |
TCP/IP 소켓 프로그래밍 복습 1 (0) | 2020.10.26 |