
2015년 1월 20일 화요일
TCP/IP 소켓 프로그래밍 24강
24강은 지금까지 배운 것을 토대로 HTTP 서버를 제작하는 강이다.
그야말로 마무으리!!
24-1: HTTP(Hypertext Transfer Protocol)의 개요
-웹(Web) 서버의 이해-
"HTTP을 기반으로 웹페이지에 해당하는 파일을 클라이언트에게 전송하는 역할의 서버"
-HTTP-
1. 클라이언트가 서버에 데이터 요청
2. 서버가 데이터 응답
3. 서버가 연결 종료
HTTP는 클라이언트의 상태정보를 유지하지 않는다.
그래서 HTTP를 상태가 존재하지 않는 Stateless 프로토콜이라 한다.
-요청 메세지(Request Message)의 구성-
요청라인, 메시지 헤더, 공백라인, 메시지 몸체로 구성되어 있다.
요청라인 - 요청방식에 대한 정보가 삽입(GET/POST)
메시지 헤더 - 요청에 사용된(응답 받을) 브라우저 정보, 사용자 인증 정보 등
공백라인 - 몸체와 메시지의 헤더 사이를 구분하기 위해 삽입
메시지 몸체 - 클라이언트가 웹서버에게 전송할 데이터(POST 방식에서만 삽입)
-응답 메세지(Response Message)의 구성-
상태 라인, 메시지 헤더, 공백라인, 메시지 몸체로 구성되어 있다.
상태라인 - 클라이언트의 요청에 대한 결과 정보
메시지 헤더 - 전송되는 데이터의 타입 및 길이정보 등
공백라인 - 몸체와 메시지 헤더 사이를 구분하기 위해 삽입
메시지 몸체 - 클라이언트가 요청한 파일의 데이터
24-2: 매우 간단한 웹 서버의 구현
이 부분은 그냥 예제다. 직접 해보는 것이 가장 좋다 ㅎㅎ;
역시 소스 코드를 다운 받아 해보자.
소스 코드 다운 받기
휴~~ 이로써 TCP/IP 소켓 프로그래밍이 끝났다.
얇은 책(551페이지)이라 금방 끝날줄 알았는데....
역시 처음해보는 분야는 습득 시간이 긴거 같다.
24강이면 하루에 1강씩 해서 한달이면 끝날줄 알았는데 ㅎㅎ;;
다음에는 네트워크 프로그래밍 관련해서 몇 가지 도서 추천을 할려고 한다.
(윤성우 열혈 TCP/IP 소켓 프로그래밍 마지막 강에서도 도서 추천을 한다)
막연했던 소켓 프로그래밍의 맛보기라도 하게 해준 이 책에게 감사하다.
소켓 프로그래밍은 학교에서도 수강을 안했었기에 전혀 개념이 없었다
(수강 신청할 껄 ㅡㅜ)
소켓 프로그래밍을 처음 공부하는 분들은
윤성우 열혈 TCP/IP 소켓 프로그래밍 책을 적극 추천한다.
책 사러 가기
그럼 이만~
그야말로 마무으리!!
24-1: HTTP(Hypertext Transfer Protocol)의 개요
-웹(Web) 서버의 이해-
"HTTP을 기반으로 웹페이지에 해당하는 파일을 클라이언트에게 전송하는 역할의 서버"
-HTTP-
1. 클라이언트가 서버에 데이터 요청
2. 서버가 데이터 응답
3. 서버가 연결 종료
HTTP는 클라이언트의 상태정보를 유지하지 않는다.
그래서 HTTP를 상태가 존재하지 않는 Stateless 프로토콜이라 한다.
-요청 메세지(Request Message)의 구성-
요청라인, 메시지 헤더, 공백라인, 메시지 몸체로 구성되어 있다.
요청라인 - 요청방식에 대한 정보가 삽입(GET/POST)
메시지 헤더 - 요청에 사용된(응답 받을) 브라우저 정보, 사용자 인증 정보 등
공백라인 - 몸체와 메시지의 헤더 사이를 구분하기 위해 삽입
메시지 몸체 - 클라이언트가 웹서버에게 전송할 데이터(POST 방식에서만 삽입)
-응답 메세지(Response Message)의 구성-
상태 라인, 메시지 헤더, 공백라인, 메시지 몸체로 구성되어 있다.
상태라인 - 클라이언트의 요청에 대한 결과 정보
메시지 헤더 - 전송되는 데이터의 타입 및 길이정보 등
공백라인 - 몸체와 메시지 헤더 사이를 구분하기 위해 삽입
메시지 몸체 - 클라이언트가 요청한 파일의 데이터
24-2: 매우 간단한 웹 서버의 구현
이 부분은 그냥 예제다. 직접 해보는 것이 가장 좋다 ㅎㅎ;
역시 소스 코드를 다운 받아 해보자.
소스 코드 다운 받기
휴~~ 이로써 TCP/IP 소켓 프로그래밍이 끝났다.
얇은 책(551페이지)이라 금방 끝날줄 알았는데....
역시 처음해보는 분야는 습득 시간이 긴거 같다.
24강이면 하루에 1강씩 해서 한달이면 끝날줄 알았는데 ㅎㅎ;;
다음에는 네트워크 프로그래밍 관련해서 몇 가지 도서 추천을 할려고 한다.
(윤성우 열혈 TCP/IP 소켓 프로그래밍 마지막 강에서도 도서 추천을 한다)
막연했던 소켓 프로그래밍의 맛보기라도 하게 해준 이 책에게 감사하다.
소켓 프로그래밍은 학교에서도 수강을 안했었기에 전혀 개념이 없었다
(수강 신청할 껄 ㅡㅜ)
소켓 프로그래밍을 처음 공부하는 분들은
윤성우 열혈 TCP/IP 소켓 프로그래밍 책을 적극 추천한다.
책 사러 가기
그럼 이만~
TCP/IP 소켓 프로그래밍 23강
휴~ 드디어 TCP/IP 공부를 마쳤다. (그래봐야 이제 첫 걸음 땐 것 ㅎㅎ;)
IT 계열은 평생 공부해야할 분야인 것 같다.
이제야 조금 네트워크 프로그래밍을 시작한 단계다.
21,22 강이 바로 이 23강을 이해하기 위한 기본이였다.
23강은 IOCP(Input Output Completion Port) 이다.
윈도우에서는 다음과 같이 넌-블로킹 소켓의 속성을 변경한다.
SOCKET hLisnSock;
int mode = 1;
......................
hLisnSock = WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0,
WSA_FLAG_OVERLAPPED);
ioctlsocket(hLisnSock, FIONBIO, &mode); //넌-블로킹 소켓
핸들 hLisnSock 이 참조하는 소켓의 입출력모드(FIONBIO)를 변수 mode에 저장된 값의 형태로 변경한다.
속성이 넌-블로킹 모드로 변경되면 다음과 같은 특징을 지니게 된다
-클라이언트의 연결 요청이 존재하지 않는 상태에서 accept 함수가 호출되면 INVALID_SOCKET 이 곧바로 반환된다.
-그리고 이어서 WSAGetLastError 함수를 호출하면 WSAEWOULDBLOCK 가 반환된다.
-accept 함수호출을 통해서 새로 생성되는 소켓 역시 넌-블로킹 속성을 지닌다.
IOCP 모델의 서버 구현을 위해서는 두가지 일을 진행해야 한다.
-Completion Port 오브젝트의 생성
-Completion Port 오브젝트와 소켓의 연결
이때 소켓은 반드시 Overlapped 속성이 부여된 소켓이어야 한다.
CP 오브젝트의 생성 함수다.
#include <windows.h>
HANDLE CreateIoCompletionPort (
HANDLE FileHandle, HANDLE ExistingCompletionPort,
ULONG_PTR CompletionKey, DWORD NumberOfConcurrentThreads);
-> 성공시 CP 오브젝트의 핸들, 실패시 NULL 반환
-FileHandle : CP 오브젝트 생성시에는 INVALID_HANDLE_VALUE 를 전달
-ExistingCompletionPort : CP 오브젝트 생성시에는 NULL 전달.
-CompletionKey : CP 오브젝트 생성시에는 0 전달.
-NumberOfConcurrentThreads : 완료된 IO를 처리할 쓰레드의 수를 전달
위 함수를 CP 오브젝트의 생성 목적으로 호출할 때는 마지막 매개변수만이 의미를 갖는다.
만약 CP 오브젝트에 할당되어 IO를 처리할 쓰레드의 수를 2개로 지정할 때
다음과 같이 구성하면 된다.
HANDLE hCpObject;
......................
hCpObject = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 2);
CP 오브젝트가 생성되었다면, 소켓과 연결시켜야 한다.
그래야 완료된 소켓의 IO 정보가 CP오브젝트에 등록된다.
여기서 위에 쓰인 함수가 또 쓰인다(매개변수에 들어가는 값만 바뀐다.)
#include <windows.h>
HANDLE CreateIoCompletionPort (
HANDLE FileHandle, HANDLE ExistingCompletionPort,
ULONG_PTR CompletionKey, DWORD NumberOfConcurrentThreads);
-> 성공시 CP 오브젝트의 핸들, 실패시 NULL 반환
-FileHandle : CP 오브젝트에 연결할 소켓의 핸들 전달
-ExistingCompletionPort : 소켓과 연결할 CP 오브젝트의 핸들 전달
-CompletionKey : 완료된 IO 관련 정보의 전달을 위한 매개변수
-NumberOfConcurrentThreads : 어떠한 값을 전달해도 두 번째 매개변수가 NULL이 아니면 무시된다.
CP에 등록되는 완료된 IO를 확인하는 함수다.
#include <windows.h>
BOOL GetQueuedCompletionStatus ( HANDLE CompletionPort,
LPDWORD lpNumberOfBytes, PULONG_PTR lpCompletionKey,
LPOVERLAPPED* lpOverlapped, DWORD dwMilliseconds);
-> 성공시 TRUE, 실패시 FALSE 반환
-CompletionPort : 완료된 IO 정보가 등록되어 있는 CP 오브젝트의 핸들 전달
-lpNumberOfBytes : 입출력 과정에서 송수신 된 데이터의 크기 정보를 저장할 변수의 주소 값 전달
-lpCompletionKey : CreateloCompletionPort 함수의 세번째 인자로 전달된 값의 저장을 위한 변수의 주소 값 전달
-lpOverlapped : WSASend, WSARecv 함수호출 시 전달하는 OVERLAPPED 구조체 변수의 주소 값이 저장될, 변수의 주소 값 전달
-dwMilliseconds : 타임아웃 정보전달, 지정한 시간이 완료되면 FALSE를 반환하면서 함수를 빠져나가며, INFINITE를 전달하면 완료된 IO가 CP오브젝트에 등록될 때까지 블로킹 상태에 있게 된다.
이제 위 함수를 이용한 소스를 보자.
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <winsock2.h>
#include <windows.h>
#define BUF_SIZE 100
#define READ 3
#define WRITE 5
//클라이언트와 연결된 소켓 정보를 담기 위한 구조체
typedef struct
{
SOCKET hClntSock;
SOCKADDR_IN clntAdr;
} PER_HANDLE_DATA, *LPPER_HANDLE_DATA;
//OVERLAPPED 구조체 변수를 담아서 구조체를 정의
typedef struct // buffer info
{
OVERLAPPED overlapped;
WSABUF wsaBuf;
char buffer[BUF_SIZE];
int rwMode; // READ or WRITE
} PER_IO_DATA, *LPPER_IO_DATA;
DWORD WINAPI EchoThreadMain(LPVOID CompletionPortIO);
void ErrorHandling(char *message);
int main(int argc, char* argv[])
{
WSADATA wsaData;
HANDLE hComPort;
SYSTEM_INFO sysInfo;
LPPER_IO_DATA ioInfo;
LPPER_HANDLE_DATA handleInfo;
SOCKET hServSock;
SOCKADDR_IN servAdr;
int recvBytes, i, flags=0;
if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error!");
//CP 오브젝트 생성
//마지막 인자가 0이니, 코어의 수만큼 쓰레드가 CP 오브젝트에 할당 가능
hComPort=CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
//현재 실행중인 시스템 정보를 얻기 위해서 GetSystemInfo 함수를 호출
GetSystemInfo(&sysInfo);
//CPU의 수 만큼 반복해서 쓰레드를 생성하고
//CP 오브젝트에 핸들을 전달한다.
for(i=0; i<sysInfo.dwNumberOfProcessors; i++)
_beginthreadex(NULL, 0, EchoThreadMain, (LPVOID)hComPort, 0, NULL);
hServSock=WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
memset(&servAdr, 0, sizeof(servAdr));
servAdr.sin_family=AF_INET;
servAdr.sin_addr.s_addr=htonl(INADDR_ANY);
servAdr.sin_port=htons(atoi(argv[1]));
bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr));
listen(hServSock, 5);
while(1)
{
SOCKET hClntSock;
SOCKADDR_IN clntAdr;
int addrLen=sizeof(clntAdr);
hClntSock=accept(hServSock, (SOCKADDR*)&clntAdr, &addrLen);
//LPPER_HANDLE_DATA 구조체 변수를 동적 할당
//클라이언트와 연결된 소켓, 그리고 클리라언트의 주소정보를 담고 있다.
handleInfo=(LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA));
handleInfo->hClntSock=hClntSock;
memcpy(&(handleInfo->clntAdr), &clntAdr, addrLen);
//CP 오브젝트와 생성된 소켓을 연결하고 있다.
CreateIoCompletionPort((HANDLE)hClntSock, hComPort, (DWORD)handleInfo, 0);
//LPPER_IO_DATA 구조체 변수를 동적 할당하였다.
//따라서 WSARecv 함수호출에 필요한 OVERLAPPED 구조체 변수와
//WSABUF 구조체 변수, 그리고 버퍼까지 한번에 마련되었다.
ioInfo=(LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
ioInfo->wsaBuf.len=BUF_SIZE;
ioInfo->wsaBuf.buf=ioInfo->buffer;
//IOCP는 기본적으로 입력의 완료와 출력의 완료를 구분 지어주지 않는다.
//입력이건 출력이건 완료되었다는 사실만 인식시켜준다.
//그래서 입력을 진행한 것인지, 출력을 진행한 것인지 정보를 별도로 기록해줘야 한다.
ioInfo->rwMode=READ;
WSARecv(handleInfo->hClntSock, &(ioInfo->wsaBuf),
1, &recvBytes, &flags, &(ioInfo->overlapped), NULL);
}
return 0;
}
//쓰레드에 의해 실행되는 함수다.
DWORD WINAPI EchoThreadMain(LPVOID pComPort)
{
HANDLE hComPort=(HANDLE)pComPort;
SOCKET sock;
DWORD bytesTrans;
LPPER_HANDLE_DATA handleInfo;
LPPER_IO_DATA ioInfo;
DWORD flags=0;
while(1)
{
//IO가 완료되고, 이에 대한 정보가 등록되었을 때 반환한다.
GetQueuedCompletionStatus(hComPort, &bytesTrans,
(LPDWORD)&handleInfo, (LPOVERLAPPED*)&ioInfo, INFINITE);
sock=handleInfo->hClntSock;
//포인터 ioInfo에 저장된 값은 OVERLAPPED 구조체 변수의 주소 값이지만,
//PER_IO_DATA 구조체 변수의 주소 값이기도 하다.
//멤버 rwMode에 저장된 값의 확인을 통해서 입력의 완료인지 출력의 완료인지 확인한다.
if(ioInfo->rwMode==READ)
{
puts("message received!");
if(bytesTrans==0) // EOF 전송시
{
closesocket(sock);
free(handleInfo); free(ioInfo);
continue;
}
//서버가 수신한 메시지를 클라이언트에게 재전송하는 과정을 보이고 있다.
memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
ioInfo->wsaBuf.len=bytesTrans;
ioInfo->rwMode=WRITE;
WSASend(sock, &(ioInfo->wsaBuf),
1, NULL, 0, &(ioInfo->overlapped), NULL);
//메시지 재전송 이후에 클라이언트가 전송하는 메시지의 수신과정을 보이고 있다.
ioInfo=(LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
ioInfo->wsaBuf.len=BUF_SIZE;
ioInfo->wsaBuf.buf=ioInfo->buffer;
ioInfo->rwMode=READ;
WSARecv(sock, &(ioInfo->wsaBuf),
1, NULL, &flags, &(ioInfo->overlapped), NULL);
}
//완료된 IO가 출력한 경우에 실행되는 else 영역이다.
else
{
puts("message sent!");
free(ioInfo);
}
}
return 0;
}
void ErrorHandling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
원래 내용이 훨씬 많지만 정말 간단하게 요약해서 정리한 것이다.
역시 소스 코드를 다운 받아 해보는 것이 가장 좋다.
소스 코드 다운 받기
IT 계열은 평생 공부해야할 분야인 것 같다.
이제야 조금 네트워크 프로그래밍을 시작한 단계다.
21,22 강이 바로 이 23강을 이해하기 위한 기본이였다.
23강은 IOCP(Input Output Completion Port) 이다.
23-1: Overlapped IO를 기반으로 IOCP 이해하기
-넌-블로킹 모드의 소켓 구성하기-
윈도우에서는 다음과 같이 넌-블로킹 소켓의 속성을 변경한다.
SOCKET hLisnSock;
int mode = 1;
......................
hLisnSock = WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0,
WSA_FLAG_OVERLAPPED);
ioctlsocket(hLisnSock, FIONBIO, &mode); //넌-블로킹 소켓
핸들 hLisnSock 이 참조하는 소켓의 입출력모드(FIONBIO)를 변수 mode에 저장된 값의 형태로 변경한다.
속성이 넌-블로킹 모드로 변경되면 다음과 같은 특징을 지니게 된다
-클라이언트의 연결 요청이 존재하지 않는 상태에서 accept 함수가 호출되면 INVALID_SOCKET 이 곧바로 반환된다.
-그리고 이어서 WSAGetLastError 함수를 호출하면 WSAEWOULDBLOCK 가 반환된다.
-accept 함수호출을 통해서 새로 생성되는 소켓 역시 넌-블로킹 속성을 지닌다.
23-2 : IOCP의 단계적 구현
-Completion Port 의 생성-
IOCP 모델의 서버 구현을 위해서는 두가지 일을 진행해야 한다.
-Completion Port 오브젝트의 생성
-Completion Port 오브젝트와 소켓의 연결
이때 소켓은 반드시 Overlapped 속성이 부여된 소켓이어야 한다.
CP 오브젝트의 생성 함수다.
#include <windows.h>
HANDLE CreateIoCompletionPort (
HANDLE FileHandle, HANDLE ExistingCompletionPort,
ULONG_PTR CompletionKey, DWORD NumberOfConcurrentThreads);
-> 성공시 CP 오브젝트의 핸들, 실패시 NULL 반환
-FileHandle : CP 오브젝트 생성시에는 INVALID_HANDLE_VALUE 를 전달
-ExistingCompletionPort : CP 오브젝트 생성시에는 NULL 전달.
-CompletionKey : CP 오브젝트 생성시에는 0 전달.
-NumberOfConcurrentThreads : 완료된 IO를 처리할 쓰레드의 수를 전달
위 함수를 CP 오브젝트의 생성 목적으로 호출할 때는 마지막 매개변수만이 의미를 갖는다.
만약 CP 오브젝트에 할당되어 IO를 처리할 쓰레드의 수를 2개로 지정할 때
다음과 같이 구성하면 된다.
HANDLE hCpObject;
......................
hCpObject = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 2);
-Completion Port 오브젝트와 소켓의 연결-
CP 오브젝트가 생성되었다면, 소켓과 연결시켜야 한다.
그래야 완료된 소켓의 IO 정보가 CP오브젝트에 등록된다.
여기서 위에 쓰인 함수가 또 쓰인다(매개변수에 들어가는 값만 바뀐다.)
#include <windows.h>
HANDLE CreateIoCompletionPort (
HANDLE FileHandle, HANDLE ExistingCompletionPort,
ULONG_PTR CompletionKey, DWORD NumberOfConcurrentThreads);
-> 성공시 CP 오브젝트의 핸들, 실패시 NULL 반환
-FileHandle : CP 오브젝트에 연결할 소켓의 핸들 전달
-ExistingCompletionPort : 소켓과 연결할 CP 오브젝트의 핸들 전달
-CompletionKey : 완료된 IO 관련 정보의 전달을 위한 매개변수
-NumberOfConcurrentThreads : 어떠한 값을 전달해도 두 번째 매개변수가 NULL이 아니면 무시된다.
-Completion Port 의 완료된 IO 확인과 쓰레드의 IO 처리-
CP에 등록되는 완료된 IO를 확인하는 함수다.
#include <windows.h>
BOOL GetQueuedCompletionStatus ( HANDLE CompletionPort,
LPDWORD lpNumberOfBytes, PULONG_PTR lpCompletionKey,
LPOVERLAPPED* lpOverlapped, DWORD dwMilliseconds);
-> 성공시 TRUE, 실패시 FALSE 반환
-CompletionPort : 완료된 IO 정보가 등록되어 있는 CP 오브젝트의 핸들 전달
-lpNumberOfBytes : 입출력 과정에서 송수신 된 데이터의 크기 정보를 저장할 변수의 주소 값 전달
-lpCompletionKey : CreateloCompletionPort 함수의 세번째 인자로 전달된 값의 저장을 위한 변수의 주소 값 전달
-lpOverlapped : WSASend, WSARecv 함수호출 시 전달하는 OVERLAPPED 구조체 변수의 주소 값이 저장될, 변수의 주소 값 전달
-dwMilliseconds : 타임아웃 정보전달, 지정한 시간이 완료되면 FALSE를 반환하면서 함수를 빠져나가며, INFINITE를 전달하면 완료된 IO가 CP오브젝트에 등록될 때까지 블로킹 상태에 있게 된다.
이제 위 함수를 이용한 소스를 보자.
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <winsock2.h>
#include <windows.h>
#define BUF_SIZE 100
#define READ 3
#define WRITE 5
//클라이언트와 연결된 소켓 정보를 담기 위한 구조체
typedef struct
{
SOCKET hClntSock;
SOCKADDR_IN clntAdr;
} PER_HANDLE_DATA, *LPPER_HANDLE_DATA;
//OVERLAPPED 구조체 변수를 담아서 구조체를 정의
typedef struct // buffer info
{
OVERLAPPED overlapped;
WSABUF wsaBuf;
char buffer[BUF_SIZE];
int rwMode; // READ or WRITE
} PER_IO_DATA, *LPPER_IO_DATA;
DWORD WINAPI EchoThreadMain(LPVOID CompletionPortIO);
void ErrorHandling(char *message);
int main(int argc, char* argv[])
{
WSADATA wsaData;
HANDLE hComPort;
SYSTEM_INFO sysInfo;
LPPER_IO_DATA ioInfo;
LPPER_HANDLE_DATA handleInfo;
SOCKET hServSock;
SOCKADDR_IN servAdr;
int recvBytes, i, flags=0;
if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error!");
//CP 오브젝트 생성
//마지막 인자가 0이니, 코어의 수만큼 쓰레드가 CP 오브젝트에 할당 가능
hComPort=CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
//현재 실행중인 시스템 정보를 얻기 위해서 GetSystemInfo 함수를 호출
GetSystemInfo(&sysInfo);
//CPU의 수 만큼 반복해서 쓰레드를 생성하고
//CP 오브젝트에 핸들을 전달한다.
for(i=0; i<sysInfo.dwNumberOfProcessors; i++)
_beginthreadex(NULL, 0, EchoThreadMain, (LPVOID)hComPort, 0, NULL);
hServSock=WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
memset(&servAdr, 0, sizeof(servAdr));
servAdr.sin_family=AF_INET;
servAdr.sin_addr.s_addr=htonl(INADDR_ANY);
servAdr.sin_port=htons(atoi(argv[1]));
bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr));
listen(hServSock, 5);
while(1)
{
SOCKET hClntSock;
SOCKADDR_IN clntAdr;
int addrLen=sizeof(clntAdr);
hClntSock=accept(hServSock, (SOCKADDR*)&clntAdr, &addrLen);
//LPPER_HANDLE_DATA 구조체 변수를 동적 할당
//클라이언트와 연결된 소켓, 그리고 클리라언트의 주소정보를 담고 있다.
handleInfo=(LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA));
handleInfo->hClntSock=hClntSock;
memcpy(&(handleInfo->clntAdr), &clntAdr, addrLen);
//CP 오브젝트와 생성된 소켓을 연결하고 있다.
CreateIoCompletionPort((HANDLE)hClntSock, hComPort, (DWORD)handleInfo, 0);
//LPPER_IO_DATA 구조체 변수를 동적 할당하였다.
//따라서 WSARecv 함수호출에 필요한 OVERLAPPED 구조체 변수와
//WSABUF 구조체 변수, 그리고 버퍼까지 한번에 마련되었다.
ioInfo=(LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
ioInfo->wsaBuf.len=BUF_SIZE;
ioInfo->wsaBuf.buf=ioInfo->buffer;
//IOCP는 기본적으로 입력의 완료와 출력의 완료를 구분 지어주지 않는다.
//입력이건 출력이건 완료되었다는 사실만 인식시켜준다.
//그래서 입력을 진행한 것인지, 출력을 진행한 것인지 정보를 별도로 기록해줘야 한다.
ioInfo->rwMode=READ;
WSARecv(handleInfo->hClntSock, &(ioInfo->wsaBuf),
1, &recvBytes, &flags, &(ioInfo->overlapped), NULL);
}
return 0;
}
//쓰레드에 의해 실행되는 함수다.
DWORD WINAPI EchoThreadMain(LPVOID pComPort)
{
HANDLE hComPort=(HANDLE)pComPort;
SOCKET sock;
DWORD bytesTrans;
LPPER_HANDLE_DATA handleInfo;
LPPER_IO_DATA ioInfo;
DWORD flags=0;
while(1)
{
//IO가 완료되고, 이에 대한 정보가 등록되었을 때 반환한다.
GetQueuedCompletionStatus(hComPort, &bytesTrans,
(LPDWORD)&handleInfo, (LPOVERLAPPED*)&ioInfo, INFINITE);
sock=handleInfo->hClntSock;
//포인터 ioInfo에 저장된 값은 OVERLAPPED 구조체 변수의 주소 값이지만,
//PER_IO_DATA 구조체 변수의 주소 값이기도 하다.
//멤버 rwMode에 저장된 값의 확인을 통해서 입력의 완료인지 출력의 완료인지 확인한다.
if(ioInfo->rwMode==READ)
{
puts("message received!");
if(bytesTrans==0) // EOF 전송시
{
closesocket(sock);
free(handleInfo); free(ioInfo);
continue;
}
//서버가 수신한 메시지를 클라이언트에게 재전송하는 과정을 보이고 있다.
memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
ioInfo->wsaBuf.len=bytesTrans;
ioInfo->rwMode=WRITE;
WSASend(sock, &(ioInfo->wsaBuf),
1, NULL, 0, &(ioInfo->overlapped), NULL);
//메시지 재전송 이후에 클라이언트가 전송하는 메시지의 수신과정을 보이고 있다.
ioInfo=(LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
ioInfo->wsaBuf.len=BUF_SIZE;
ioInfo->wsaBuf.buf=ioInfo->buffer;
ioInfo->rwMode=READ;
WSARecv(sock, &(ioInfo->wsaBuf),
1, NULL, &flags, &(ioInfo->overlapped), NULL);
}
//완료된 IO가 출력한 경우에 실행되는 else 영역이다.
else
{
puts("message sent!");
free(ioInfo);
}
}
return 0;
}
void ErrorHandling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
원래 내용이 훨씬 많지만 정말 간단하게 요약해서 정리한 것이다.
역시 소스 코드를 다운 받아 해보는 것이 가장 좋다.
소스 코드 다운 받기
2015년 1월 16일 금요일
TCP/IP 소켓 프로그래밍 22강
21강에서 알아본 비동기는 "알림"이었다.
전송이 완료되면 완료 되었다고 알려주는 것이였다.
22강에서는 IO를 비동기로 처리하는 방법을 설명한다.
22-1 : Overlapped IO 모델의 이해
-IO(입출력)의 중첩이란?-
하나의 쓰레드 내에서 동시에 둘 이상의영역으로 데이터를 전송(또는 수신)함으로 인해서,
입출력이 중첩되는 상황을 가리켜 "IO의 중첩"이라 한다.
이러한 일이 가능하려면 호출된 입출력 함수가 바로 반환을 해야한다.
비동기 IO가 가능하려면 호출되는 입출력함수는 넌-블로킹 모드로 동작해야 한다.
윈도우에서 말하는 Overlapped IO는 입출력만을 뜻하는 것이 아니고
입출력의 완료를 확인하는 방법까지 포함한 것이다.
-Overlapped IO 소켓의 생성-
#include <winsock2.h>
SOCKET WSASocket ( int af, int type, int protocol,
LPWSAPROTOCOL_INFO lpProtocolInfo, GROUP g, DWORD dwFlags);
->성공시 소켓의 핸들, 실패시 INVALID_SOCKET 반환
- af : 프로토콜 체계 정보 전달
- type : 소켓의 데이터 전송방식에 대한 정보 전달
-protocol : 두 소켓 사이에 사용되는 프로토콜 정보 전달
-lpProtocolInfo : 생성되는 소켓의 특성정보를 담고 있는 WSAPROTOCOL_INFO 구조체
변수의 주소 값 전달, 필요 없는 경우 NULL 전달
- g : 함수의 확장을 위해서 예약되어 있는 매개변수, 따라서 0 전달
- dwFlags : 소켓의 속성정보 전달
-Overlapped IO를 진행하는 WSASend 함수-
데이터의 입출력의 사용되는 함수는 달리해야 한다.
데이터 출력 함수를 보자
#include <winsock2.h>
int WSASend (SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent, DWORD dwFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
-> 성공시 0, 실패시 SOCKET_ERROR 반환
- s : 소켓의 핸들 전달, Overlapped IO 속성이 부여된 소켓의 핸들 전달시
Overlapped IO 모델로 출력 진행
- lpBuffers : 전송할 데이터 정보를 지니는 WSABUF 구조체 변수들로
이뤄진 배열의 주소 값 전달
- dwBufferCount : 두 번째 인자로 전달된 배열의 길이정보 전달
- lpNumberOfBytesSent : 전송된 바이트 수가 저장될 변수의 주소 값 전달
-dwFlags : 함수의 데이터 전송특성을 변경하는 경우에 사용
- lpOverlapped : WSAOVERLAPPED 구조체 변수의 주소 갑 전달,
이벤트 오브젝트를 사용해서
데이터 전송의 완료를 확인하는 경우에 사용되는 매개변수
- lpCompletionRoutine : Completion Routine 이라는 함수의 주소 값 전달,
이를 통해서도 데이터 전송의 완료를 확인할 수 있다.
주의할 점이 있다. Overlapped IO를 진행하려면 WSASend 함수의 매개변수 lpOverlapped에는 항상 NULL이 아닌, 유효한 구조체 변수의 주소 값을 전달해야 한다.
만약에 lpOverlapped에 NULL 이 전달되면, WSASend 함수의 첫 번째 인자로 전달된 핸들의 소켓은 블로킹 모드로 동작하는 일반적인 소켓으로 간주된다.
WSASend 함수는 SOCKET_ERROR를 반환하고, WSAGetLastError 함수 호출을 통해서 확인 가능한 오류코드로는 WSA_IO_PENDING이 등록된다. 그리고 이 경우에는 다음 함수호출을 통해서 실제 전송된 데이터의 크기를 확인해야 한다.
#include <winsock2.h>
BOOL WSAGetOverlappedResult (SOCKET s, LPWSAOVERLAPPED lpOverlapped,
LPDWORD lpcbTransfer, BOOL fWait, LPDWORD lpdwFlags);
-> 성공시 TRUE, 실패시 FALSE 반환
-s : Overlapped IO가 진행된 소켓의 핸들
-lpOverlapped : Overlapped IO 진행시 전달한
WSAOVERLAPPED 구조체 변수의 주소값 전달
-lpcbTransfer : 실제 송수신된 바이트 크기를 저장할 변수의 주소 값 전달
-fWait : 여전히 IO가 진행중인 상황의 경우,
TRUE 전달시 IO가 완료될 때까지 대기를 하게 되고,
FALSE 전달 시 FALSE를 반환하면서 함수를 빠져 나온다.
-lpdwFlags : WSARecv 함수가 호출된 경우, 부수적인 정보를 얻기 위해 사용된다.
불필요하면 NULL 를 전달한다.
-Overlapped IO를 진행하는 WSARecv 함수-
#include <winsock2.h>
int WSARecv ( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,
LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine );
-> 성공 시 0, 실패시 SOCKET_ERROR 반환
- s : Overlapped IO 속성이 부여된 소켓의 핸들 전달
- lpBuffers : 수신된 데이터 정보가 저장될 버퍼의 정보를 지니는
WSABUF 구조체 배열의 주소 값 전달
- dwBufferCount : 두 번째 인자로 전달된 배열의 길이정보 전달
- lpNumberOfBytesRecvd : 수신된 데이터의 크기정보가 저장될 변수의 주소 값 전달
- lpFlags : 전송특성과 관련된 정보를 지정하거나 수신하는 경우에 사용된다.
- lpOverlapped : WSAOBERLAPPED 구조체 변수의 주소 값 전달
- lpCompletionRoutine : Completion Routine 이라는 함수의 주소 값 전달
22-1 : Overlapped IO에서의 입출력 완료의 확인
입출력의 완료/결과를 확인하는 방법에는 두 가지가 있다.
(1)WSASend, WSARecv 함수의 6번째 매개 변수 활용방법, 이벤트 오브젝트 기반
(2)WSASend, WSARecv 함수의 7번째 매개 변수 활용방법, Completion Routine기반
-이벤트 오브젝트 사용하기-
-IO가 완료되면 WSAOVERLAPPED 구조체 변수가 참조하는 이벤트 오브젝트가 signaled 상태가 된다.
-IO의 완료 및 결과를 확인하려면 WSAGetOverlappedResult 함수를 사용한다.
-Completion Routine 사용하기-
역시 소스를 여기에 기재해야하는데 공간이 부족하니 다운 받아서 확인해보는 것이 좋다.
그리고 윈도우는 뭔 이름이 이리 긴지 모르겠다 ㅡㅡ
소스 코드 다운 받기
전송이 완료되면 완료 되었다고 알려주는 것이였다.
22강에서는 IO를 비동기로 처리하는 방법을 설명한다.
22-1 : Overlapped IO 모델의 이해
-IO(입출력)의 중첩이란?-
하나의 쓰레드 내에서 동시에 둘 이상의영역으로 데이터를 전송(또는 수신)함으로 인해서,
입출력이 중첩되는 상황을 가리켜 "IO의 중첩"이라 한다.
이러한 일이 가능하려면 호출된 입출력 함수가 바로 반환을 해야한다.
비동기 IO가 가능하려면 호출되는 입출력함수는 넌-블로킹 모드로 동작해야 한다.
윈도우에서 말하는 Overlapped IO는 입출력만을 뜻하는 것이 아니고
입출력의 완료를 확인하는 방법까지 포함한 것이다.
-Overlapped IO 소켓의 생성-
#include <winsock2.h>
SOCKET WSASocket ( int af, int type, int protocol,
LPWSAPROTOCOL_INFO lpProtocolInfo, GROUP g, DWORD dwFlags);
->성공시 소켓의 핸들, 실패시 INVALID_SOCKET 반환
- af : 프로토콜 체계 정보 전달
- type : 소켓의 데이터 전송방식에 대한 정보 전달
-protocol : 두 소켓 사이에 사용되는 프로토콜 정보 전달
-lpProtocolInfo : 생성되는 소켓의 특성정보를 담고 있는 WSAPROTOCOL_INFO 구조체
변수의 주소 값 전달, 필요 없는 경우 NULL 전달
- g : 함수의 확장을 위해서 예약되어 있는 매개변수, 따라서 0 전달
- dwFlags : 소켓의 속성정보 전달
-Overlapped IO를 진행하는 WSASend 함수-
데이터의 입출력의 사용되는 함수는 달리해야 한다.
데이터 출력 함수를 보자
#include <winsock2.h>
int WSASend (SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent, DWORD dwFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
-> 성공시 0, 실패시 SOCKET_ERROR 반환
- s : 소켓의 핸들 전달, Overlapped IO 속성이 부여된 소켓의 핸들 전달시
Overlapped IO 모델로 출력 진행
- lpBuffers : 전송할 데이터 정보를 지니는 WSABUF 구조체 변수들로
이뤄진 배열의 주소 값 전달
- dwBufferCount : 두 번째 인자로 전달된 배열의 길이정보 전달
- lpNumberOfBytesSent : 전송된 바이트 수가 저장될 변수의 주소 값 전달
-dwFlags : 함수의 데이터 전송특성을 변경하는 경우에 사용
- lpOverlapped : WSAOVERLAPPED 구조체 변수의 주소 갑 전달,
이벤트 오브젝트를 사용해서
데이터 전송의 완료를 확인하는 경우에 사용되는 매개변수
- lpCompletionRoutine : Completion Routine 이라는 함수의 주소 값 전달,
이를 통해서도 데이터 전송의 완료를 확인할 수 있다.
주의할 점이 있다. Overlapped IO를 진행하려면 WSASend 함수의 매개변수 lpOverlapped에는 항상 NULL이 아닌, 유효한 구조체 변수의 주소 값을 전달해야 한다.
만약에 lpOverlapped에 NULL 이 전달되면, WSASend 함수의 첫 번째 인자로 전달된 핸들의 소켓은 블로킹 모드로 동작하는 일반적인 소켓으로 간주된다.
WSASend 함수는 SOCKET_ERROR를 반환하고, WSAGetLastError 함수 호출을 통해서 확인 가능한 오류코드로는 WSA_IO_PENDING이 등록된다. 그리고 이 경우에는 다음 함수호출을 통해서 실제 전송된 데이터의 크기를 확인해야 한다.
#include <winsock2.h>
BOOL WSAGetOverlappedResult (SOCKET s, LPWSAOVERLAPPED lpOverlapped,
LPDWORD lpcbTransfer, BOOL fWait, LPDWORD lpdwFlags);
-> 성공시 TRUE, 실패시 FALSE 반환
-s : Overlapped IO가 진행된 소켓의 핸들
-lpOverlapped : Overlapped IO 진행시 전달한
WSAOVERLAPPED 구조체 변수의 주소값 전달
-lpcbTransfer : 실제 송수신된 바이트 크기를 저장할 변수의 주소 값 전달
-fWait : 여전히 IO가 진행중인 상황의 경우,
TRUE 전달시 IO가 완료될 때까지 대기를 하게 되고,
FALSE 전달 시 FALSE를 반환하면서 함수를 빠져 나온다.
-lpdwFlags : WSARecv 함수가 호출된 경우, 부수적인 정보를 얻기 위해 사용된다.
불필요하면 NULL 를 전달한다.
-Overlapped IO를 진행하는 WSARecv 함수-
#include <winsock2.h>
int WSARecv ( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,
LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine );
-> 성공 시 0, 실패시 SOCKET_ERROR 반환
- s : Overlapped IO 속성이 부여된 소켓의 핸들 전달
- lpBuffers : 수신된 데이터 정보가 저장될 버퍼의 정보를 지니는
WSABUF 구조체 배열의 주소 값 전달
- dwBufferCount : 두 번째 인자로 전달된 배열의 길이정보 전달
- lpNumberOfBytesRecvd : 수신된 데이터의 크기정보가 저장될 변수의 주소 값 전달
- lpFlags : 전송특성과 관련된 정보를 지정하거나 수신하는 경우에 사용된다.
- lpOverlapped : WSAOBERLAPPED 구조체 변수의 주소 값 전달
- lpCompletionRoutine : Completion Routine 이라는 함수의 주소 값 전달
22-1 : Overlapped IO에서의 입출력 완료의 확인
입출력의 완료/결과를 확인하는 방법에는 두 가지가 있다.
(1)WSASend, WSARecv 함수의 6번째 매개 변수 활용방법, 이벤트 오브젝트 기반
(2)WSASend, WSARecv 함수의 7번째 매개 변수 활용방법, Completion Routine기반
-이벤트 오브젝트 사용하기-
-IO가 완료되면 WSAOVERLAPPED 구조체 변수가 참조하는 이벤트 오브젝트가 signaled 상태가 된다.
-IO의 완료 및 결과를 확인하려면 WSAGetOverlappedResult 함수를 사용한다.
-Completion Routine 사용하기-
역시 소스를 여기에 기재해야하는데 공간이 부족하니 다운 받아서 확인해보는 것이 좋다.
그리고 윈도우는 뭔 이름이 이리 긴지 모르겠다 ㅡㅡ
소스 코드 다운 받기
2015년 1월 13일 화요일
TCP/IP 소켓 프로그래밍 21강
21강은 한동안 이해가 안가서 멘붕이었다 ㅡㅡ;
하지만 2번 3번 보니 이해가 가더라....포기하지 않는 것이 제일 중요한 것 같다.
send 함수가 호출되면 데이터의 전송이 완료된 후에야 반환이 이루어졌다.
함수의 반환이 이루어져야 전송의 완료를 확인하는 것이다.
이런식이다 보니 반환을 기둘리는 CPU 입장에서는 쓸데없이 노는 시간(?)이 생긴 것이다.
이것을 극복하고자 나온 것이 비동기 방식이다.
한마디로 CPU가 쉬는 시간을 주지 않겠다는 것이다.(노동착취 ㅡ_ㅡ)
비동기를 대략적으로 설명하자면 이렇다.
1. send를 호출해서 데이터 전송을 시작한다.
2. 전송이 완료가 되던 말던 CPU는 딴 일을 한다.
3. 전송을 "관찰"하는 함수를 호출한다.
4. 전송 완료가 되면 "관찰하던 함수"가 "send 함수"에게 완료되었다고 알려준다.
5. 관찰하던 함수 종료, send 함수 종료
IO의 상태변화를 알리는 것을 약간 고급스럽게(?) Notification 이라고 한다.
소켓을 대상으로 이벤트 발생여부의 관찰을 명령할 때 사용하는 함수다.
#include <winsock2.h>
int WSAEventSelect(SOCKET s, WSAEVENT hEventObject, long lNetworkEvents);
-> 성공시 0 , 실패시 SOCKET_ERROR 반환
-s : 관찰 대상인 소켓의 핸들 전달
-hEventObject : 이벤트 발생유무의 확인을 위한 이벤트 오브젝트 핸들 전달
-lNetworkEvents : 감시하고자 하는 이벤트의 유형 정보 전달
WSAEventSelect 함수는 매개변수 s에 전달된 핸들의 소켓에서,
세 번째 인자에 전달된 이벤트 중 하나가 발생하면,
두 번째 인자에 전달된 핸들의 커널 오브젝트를 signaled 상태로 바꾸는 함수다.
세 번째 인자에 들어가는 이벤트의 종류를 살펴보자.
FD_READ : 수신할 데이터가 존재하는가?
FD_WRITE : 블로킹 없이 데이터 전송이 가능한가?
FD_OOB : Out-of-band 데이터가 수신되었는가?
FD_ACCEPT : 연결요청이 있었는가?
FD_CLOSE : 연결의 종료가 요청되었는가?
참고로 WSAEventSelect 함수 호출을 통해서 전달된 소켓의 정보는 OS에 등록되고,
이렇게 등록된 소켓에 대해서는 WSAEventSelect 함수의 재호출이 불필요하다.
이전에는 CreateEvent 함수를 이용해서 이벤트 오브젝트를 생성했다.
CreateEvent는 이벤트 오브젝트를 생성할 때,
auto-reset 모드와 manual-reset 모드를 선택해서 생성할 수 있는 함수였다.
그러나 여기서 필요한 것은 "manual-reset 모드이면서, non-signaled 상태"인
이벤트 오브젝트다. 이 때 필요한 함수를 보자.
#include <winsock2.h>
WSAEVENT WSACreateEvent(void);
-> 성공시 이벤트 오브젝트 핸들, 실패시 WSA_INVALID_EVENT 반환
위의 함수로 생성된 이벤트 오브젝트 "종료"를 위한 함수는 다음과 같다
#include <winsock2.h>
WSAEVENT WSACloseEvent(WSAEVENT hEvent);
-> 성공시 true, 실패시 false 반환
WSAEventSelect 함수호출 "이후"를 고민해야한다. "이벤트 발생유무의 확인"을 위해서는
이벤트 오브젝트를 확인해야 한다. 이때 사용하는 함수다.
#include <winsock2.h>
DWORD WSAWaitForMultipleEvents (
DWORD cEvents, const WSAEVENT* lphEvents,
BOOL fWaitAll, DWORD dwTimeout, BOOL fAlertable);
-> 성공시 이벤트 발생 오브젝트 관련정보, 실패시 WSA_INVALID_EVENT 반환
-cEvents : signaled 상태로의 전이여부를 확인할 Event 오브젝트의 개수 정보 전달
-lphEvents : Event 오브젝트의 핸들을 저장하고 있는 배열의 주소 값 전달
-fWaitAll : true 전달시 모든 이벤트 오브젝트가 signaled 상태일 때 반환, false 전달시 하나만 signaled 상태가 되어도 반환
-dwTimeout : 1/1000 초 단위로 타임아웃 지정, WSA_INFINITE 전달시 signaled 상태가 될 때까지 반환하지 않는다.
-fAlertable : true 전달시 alertable wait 상태로의 진입
-반환값 : 반환된 정수 값에서 상수 값 WSA_WAIT_EVENT_0을 빼면,
두 번째 매개변수로 전달된 배열을 기준으로,
signaled 상태가 된 이벤트 오브젝트의 핸들이 저장된 인덱스가 계산된다.
만약에 둘 이상의 이벤트 오브젝트가 signaled 상태로 전이되었다면,
그 중 작은 인덱스 값이 계산된다.
그리고 타임아웃이 발생하면 WAIT_TIMEOUT 이 반환된다.
위 함수는 소켓의 이벤트 발생에 의해서 이벤트 오브젝트가 signaled 상태가 되어야 반환하는 함수이므로 소켓의 이벤트 발생여부를 확인하기에 좋은 함수다.
WSAWaitForMultipleEvents 함수를 통해서 signaled 상태로 전이 된 이벤트 오브젝트까지 알아낼 수 있게 되었으니 이 상태가 된 원인을 확인해야 한다.
#include <winsock2.h>
int WSAEnumNetworkEvents (
SOCKET s, WSAEVENT hEventObject,
LPWSANETWORKEVENTS lpNetworkEvents);
-> 성공시 0, 실패시 SOCKET_ERROR 반환
- s : 이벤트가 발생한 소켓의 핸들 전달
- hEventObject : 소켓과 연결된 signaled 상태인 이벤트 오브젝트의 핸들 전달
- lpNetworkEvents : 발생한 이벤트의 유형정보와 오류 정보로 채워질
WSANETWORKEVENTS 구조체 변수의 주소 전달
위의 함수를 사용한 소스를 봐야 하는데.....
좀 길어서 여기에 기재하기가 힘들다 ㅡㅜ
역시 소스를 다운 받아 해보는 것이 좋겠다.
소스 코드 다운 받기
하지만 2번 3번 보니 이해가 가더라....포기하지 않는 것이 제일 중요한 것 같다.
21-1 : 비동기 Notification IO 모델의 이해
-동기(Synchronous)와 비동기(Asynchronous)에 대한 이해-
send 함수가 호출되면 데이터의 전송이 완료된 후에야 반환이 이루어졌다.
함수의 반환이 이루어져야 전송의 완료를 확인하는 것이다.
이런식이다 보니 반환을 기둘리는 CPU 입장에서는 쓸데없이 노는 시간(?)이 생긴 것이다.
이것을 극복하고자 나온 것이 비동기 방식이다.
한마디로 CPU가 쉬는 시간을 주지 않겠다는 것이다.(노동착취 ㅡ_ㅡ)
비동기를 대략적으로 설명하자면 이렇다.
1. send를 호출해서 데이터 전송을 시작한다.
2. 전송이 완료가 되던 말던 CPU는 딴 일을 한다.
3. 전송을 "관찰"하는 함수를 호출한다.
4. 전송 완료가 되면 "관찰하던 함수"가 "send 함수"에게 완료되었다고 알려준다.
5. 관찰하던 함수 종료, send 함수 종료
IO의 상태변화를 알리는 것을 약간 고급스럽게(?) Notification 이라고 한다.
21-2 : 비동기 Notification IO 모델의 이해와 구현
소켓을 대상으로 이벤트 발생여부의 관찰을 명령할 때 사용하는 함수다.
#include <winsock2.h>
int WSAEventSelect(SOCKET s, WSAEVENT hEventObject, long lNetworkEvents);
-> 성공시 0 , 실패시 SOCKET_ERROR 반환
-s : 관찰 대상인 소켓의 핸들 전달
-hEventObject : 이벤트 발생유무의 확인을 위한 이벤트 오브젝트 핸들 전달
-lNetworkEvents : 감시하고자 하는 이벤트의 유형 정보 전달
WSAEventSelect 함수는 매개변수 s에 전달된 핸들의 소켓에서,
세 번째 인자에 전달된 이벤트 중 하나가 발생하면,
두 번째 인자에 전달된 핸들의 커널 오브젝트를 signaled 상태로 바꾸는 함수다.
세 번째 인자에 들어가는 이벤트의 종류를 살펴보자.
FD_READ : 수신할 데이터가 존재하는가?
FD_WRITE : 블로킹 없이 데이터 전송이 가능한가?
FD_OOB : Out-of-band 데이터가 수신되었는가?
FD_ACCEPT : 연결요청이 있었는가?
FD_CLOSE : 연결의 종료가 요청되었는가?
참고로 WSAEventSelect 함수 호출을 통해서 전달된 소켓의 정보는 OS에 등록되고,
이렇게 등록된 소켓에 대해서는 WSAEventSelect 함수의 재호출이 불필요하다.
-manual-reset 모드 Event 오브젝트의 또 다른 생성방법-
이전에는 CreateEvent 함수를 이용해서 이벤트 오브젝트를 생성했다.
CreateEvent는 이벤트 오브젝트를 생성할 때,
auto-reset 모드와 manual-reset 모드를 선택해서 생성할 수 있는 함수였다.
그러나 여기서 필요한 것은 "manual-reset 모드이면서, non-signaled 상태"인
이벤트 오브젝트다. 이 때 필요한 함수를 보자.
#include <winsock2.h>
WSAEVENT WSACreateEvent(void);
-> 성공시 이벤트 오브젝트 핸들, 실패시 WSA_INVALID_EVENT 반환
위의 함수로 생성된 이벤트 오브젝트 "종료"를 위한 함수는 다음과 같다
#include <winsock2.h>
WSAEVENT WSACloseEvent(WSAEVENT hEvent);
-> 성공시 true, 실패시 false 반환
-이벤트 발생유무의 확인-
WSAEventSelect 함수호출 "이후"를 고민해야한다. "이벤트 발생유무의 확인"을 위해서는
이벤트 오브젝트를 확인해야 한다. 이때 사용하는 함수다.
#include <winsock2.h>
DWORD WSAWaitForMultipleEvents (
DWORD cEvents, const WSAEVENT* lphEvents,
BOOL fWaitAll, DWORD dwTimeout, BOOL fAlertable);
-> 성공시 이벤트 발생 오브젝트 관련정보, 실패시 WSA_INVALID_EVENT 반환
-cEvents : signaled 상태로의 전이여부를 확인할 Event 오브젝트의 개수 정보 전달
-lphEvents : Event 오브젝트의 핸들을 저장하고 있는 배열의 주소 값 전달
-fWaitAll : true 전달시 모든 이벤트 오브젝트가 signaled 상태일 때 반환, false 전달시 하나만 signaled 상태가 되어도 반환
-dwTimeout : 1/1000 초 단위로 타임아웃 지정, WSA_INFINITE 전달시 signaled 상태가 될 때까지 반환하지 않는다.
-fAlertable : true 전달시 alertable wait 상태로의 진입
-반환값 : 반환된 정수 값에서 상수 값 WSA_WAIT_EVENT_0을 빼면,
두 번째 매개변수로 전달된 배열을 기준으로,
signaled 상태가 된 이벤트 오브젝트의 핸들이 저장된 인덱스가 계산된다.
만약에 둘 이상의 이벤트 오브젝트가 signaled 상태로 전이되었다면,
그 중 작은 인덱스 값이 계산된다.
그리고 타임아웃이 발생하면 WAIT_TIMEOUT 이 반환된다.
위 함수는 소켓의 이벤트 발생에 의해서 이벤트 오브젝트가 signaled 상태가 되어야 반환하는 함수이므로 소켓의 이벤트 발생여부를 확인하기에 좋은 함수다.
-이벤트 종류의 구분-
WSAWaitForMultipleEvents 함수를 통해서 signaled 상태로 전이 된 이벤트 오브젝트까지 알아낼 수 있게 되었으니 이 상태가 된 원인을 확인해야 한다.
#include <winsock2.h>
int WSAEnumNetworkEvents (
SOCKET s, WSAEVENT hEventObject,
LPWSANETWORKEVENTS lpNetworkEvents);
-> 성공시 0, 실패시 SOCKET_ERROR 반환
- s : 이벤트가 발생한 소켓의 핸들 전달
- hEventObject : 소켓과 연결된 signaled 상태인 이벤트 오브젝트의 핸들 전달
- lpNetworkEvents : 발생한 이벤트의 유형정보와 오류 정보로 채워질
WSANETWORKEVENTS 구조체 변수의 주소 전달
위의 함수를 사용한 소스를 봐야 하는데.....
좀 길어서 여기에 기재하기가 힘들다 ㅡㅜ
역시 소스를 다운 받아 해보는 것이 좋겠다.
소스 코드 다운 받기
2015년 1월 9일 금요일
TCP/IP 소켓 프로그래밍 20강
20강은 윈도우에서의 쓰레드 동기화를 알아볼려고 한다.
이번 강은 함수가 많이 나와서 함수 소개를 주로 하겠다 ㄷㄷ
윈도우에는 2가지 연산 방식 프로그램 실행방식이 있는데. 유저모드, 커널모드가 있다.
-유저모드 : 응용 프로그램이 실행되는 기본모드, 물리적인 영역으로의 접근이 허용되지 않으며 접근할 수 있는 메모리의 영역에도 제한이 따른다.
-커널모드 : 운영체제가 실행될 때의 모드로, 메모리 뿐만 아니라, 하드웨어의 접근에도 제한이 따르지 않는다.
하지만 유저모드에서 커널모드로 전환해야 하는 경우도 있다.
바로 쓰레드를 커널에게 요청할 때다.
문제는 모드를 자주 바꾸면 성능이 떨어진다는 것이다.
그래서 유저모드에서 할 수 있는 쓰레드 동기화라면
모드를 바꾸지 않고 하는 것이 더 이득이다.
먼저 유저모드의 동기화를 알아보자. 바로 "critical_section 동기화"다 .
critical_section를 줄여서 cs 오브젝트라고 표현하겠다.(절대 치기 귀찮아서 이러는게 아니다)
cs오브젝트의 초기화 및 소멸에 쓰이는 함수를 보자.
#include <windows.h>
void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection); (초기화)
void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection); (소멸)
- lpCriticalSection :Inin.... 함수에서는 cs 오브젝트의 주소 값 전달,
Del.. 함수에서는 해제할 cs오브젝트 주소값 전달
DeleteCriticalSection 함수는 cs 오브젝트를 소멸하는 함수가 아니다.
cs 오브젝트가 사용하던 리소스를 소멸시키는 함수다.
다음은 cs오브젝트 열쇠의 획득 및 반납에 대한 함수를 보자.
#include <windows.h>
void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection); (획득)
void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection); (반납)
- lpCriticalSection : 획득(소유) 및 반납할 cs오브젝트의 주소 값 전달
위 함수를 이용한 소스를 보자.
#include <stdio.h>
#include <windows.h>
#include <process.h>
#define NUM_THREAD 50
unsigned WINAPI threadInc(void * arg);
unsigned WINAPI threadDes(void * arg);
long long num=0;
//cs 오브젝트 변수 설정
CRITICAL_SECTION cs;
int main(int argc, char *argv[])
{
HANDLE tHandles[NUM_THREAD];
int i;
// cs 오브젝트 초기화
InitializeCriticalSection(&cs);
//쓰레드 생성후 핸들값에 넣기
for(i=0; i<NUM_THREAD; i++)
{
if(i%2)
tHandles[i]=(HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);
else
tHandles[i]=(HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);
}
//둘 이상의 커널 오브젝트를 대상으로 상태 확인
WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);
//리소스 해제
DeleteCriticalSection(&cs);
printf("result: %lld \n", num);
//윈도우는 메인함수가 끝나면 쓰레드도 같이 종료된다.
return 0;
}
unsigned WINAPI threadInc(void * arg)
{
int i;
//임계영역 설정
EnterCriticalSection(&cs);
for(i=0; i<50000000; i++)
num+=1;
LeaveCriticalSection(&cs);
return 0;
}
unsigned WINAPI threadDes(void * arg)
{
int i;
//임계영역 설정
EnterCriticalSection(&cs);
for(i=0; i<50000000; i++)
num-=1;
LeaveCriticalSection(&cs);
return 0;
}
커널 모드는 유저 모드 보다 많은 동기화 기법을 지원한다.
대표적인 뮤텍스, 세마포어, 이벤트 기법을 알아보자.
뮤텍스 오브젝트 생성 함수
#include <windows.h>
HANDLE CreatMutex (LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner, LPCTSTR lpName);
-> 성공시 생성된 뮤텍스 오브젝트의 핸들, 실패시 NULL 반환
-lpMutexAttributes : 보안관련 특성 정보의 전달, 디폴트 보안 설정을 위해서 NULL 전달
-bInitialOwner : true 전달시, 생성되는 뮤텍스 오브젝트는 이함수를 호출한 쓰레드의 소유가 되면서 non-signaled 상태가 된다. false 전달시 생성되는 뮤텍스 오브젝트는 소유자가 존재하지 않으며, signaled 상태로 생성된다.
-ipName : 뮤텍스 오브젝트에 이름을 부여할 때 사용된다. NULL을 전달하면 이름 없는 뮤텍스 오브젝트가 생성된다.
뮤텍스 소멸 함수
#include <windows.h>
BOOL CloseHandle(HANDLE hObject);
-> 성공시 true, 실패시 false 반환
-hObject : 소멸하고자 하는 커널 오브젝트의 핸들 전달
뮤택스 반납 함수
#include <windows.h>
BOOL ReleaseMutex(HANDLE hMutex);
-> 성공시 true, 실패시 false 반환
- hMutex : 반납할, 다시 말해서 소유를 해제할 뮤텍스 오브젝트의 핸들 전달
세마포어 오브젝트 생성 함수
#include <windows.h>
HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
LONG linitialCount, LONG lMaximumCount, LPCTSTR lpName);
-> 성공시 생성된 세마포어 오브젝트 핸들, 실패시, NULL 반환
-lpSemaphoreAttributes :보안관련 정보의 전달, 디폴트 보안설정을 위해서 NULL 전달
-linitialCount : 세마포어의 초기값 설정, 매개변수 lMaximumCount에 전달된 값보다 크면 안되고 , 0 이상이여야 함
-linitialCount : 최대 세마포어 값을 지정한다. 1을 지정하면 세마포어 값이 0, 또는 1이 되는 바이너리 세마포어가 구성된다.
-lpName : 세마포어 오브젝트에 이름을 부여할 때 사용한다. NULL을 전달하면 이름없는 세마포어 오브젝트가 된다.
세마포어 오브젝트 반납 함수
#include <windows.h>
Bool ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount,
LPLONG lpPreviousCount);
-> 성공시 TRUE, 실패시 FALSE 반환
-hSemaphore : 반납할 세마포어 오브젝트 핸들 전달
-lReleaseCount : 반납은 세마포어 값의 증가를 의미하는데, 이 매개변수를 통해서 증가되는 값의 크기를 지정할 수 있다. 그리고 이로 인해서 세마포어의 최대 값을 넘어서게 되면, 값은 증가하지 않고 fales 가 반환된다.
-lpPreviousCount : 변경 이전의 세마포어 값 저장을 위한 변수의 주소 값 전달, 불필요하다면 NULL 전달
이벤트 오브젝트 생성에 사용되는 함수
#include<windows.h>
HANDLE CreateEvent (LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName);
-> 성공시 생성된 이벤트 오브젝트의 핸들, 실패시 NULL 반환
-lpEventAttributes : 보안 관련 정보의 전달, 디폴트 보안설정을 위해서 NULL 전달
-bManualReset : true 전달시 manual-reset 모드 이벤트, false 전달시 auto-reset 모드 이벤트 오브젝트 생성
- bInitialState : true 전달시 singnaled 상태의 이벤트 오브젝트 생성, false 전달시 non-signaled 상태의 이벤트 오브젝트 생성
-lpName : 이벤트 오브젝트에 이름을 부여할 때 사용된다. NULL을 전달하면 이름없는 이벤트 오브젝트가 생성된다.
명시적으로 오브젝트 상대를 변경하는 함수
#include <windows.h>
BOOL ResetEvent(HANDLE hEvent); //non-signaled
BOOL setEvent(HANDLE hEvent); //signaled
-> 성공시 true, 실패시 false 반환
아오 몸이 안좋으니깐 이거 치는 것도 꽤 힘들다 ㅠㅠ
윈도우 함수는 왜이리 대소문자로 왔다갔다해야하는지,........
이름이 너무 긴게 많아!!
원래는 여기 소스도 올려서 확인해봐야 하는데
내 몸상태가 영 나쁘니....
소스 코드를 직접 다운 받아 해보자 /엉엉/
소스 코드 다운 받기
이번 강은 함수가 많이 나와서 함수 소개를 주로 하겠다 ㄷㄷ
20-1 : 동기화 기법의 분류와 critical_section 동기화
윈도우에는 2가지 연산 방식 프로그램 실행방식이 있는데. 유저모드, 커널모드가 있다.
-유저모드 : 응용 프로그램이 실행되는 기본모드, 물리적인 영역으로의 접근이 허용되지 않으며 접근할 수 있는 메모리의 영역에도 제한이 따른다.
-커널모드 : 운영체제가 실행될 때의 모드로, 메모리 뿐만 아니라, 하드웨어의 접근에도 제한이 따르지 않는다.
하지만 유저모드에서 커널모드로 전환해야 하는 경우도 있다.
바로 쓰레드를 커널에게 요청할 때다.
문제는 모드를 자주 바꾸면 성능이 떨어진다는 것이다.
그래서 유저모드에서 할 수 있는 쓰레드 동기화라면
모드를 바꾸지 않고 하는 것이 더 이득이다.
먼저 유저모드의 동기화를 알아보자. 바로 "critical_section 동기화"다 .
critical_section를 줄여서 cs 오브젝트라고 표현하겠다.(절대 치기 귀찮아서 이러는게 아니다)
cs오브젝트의 초기화 및 소멸에 쓰이는 함수를 보자.
#include <windows.h>
void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection); (초기화)
void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection); (소멸)
- lpCriticalSection :Inin.... 함수에서는 cs 오브젝트의 주소 값 전달,
Del.. 함수에서는 해제할 cs오브젝트 주소값 전달
DeleteCriticalSection 함수는 cs 오브젝트를 소멸하는 함수가 아니다.
cs 오브젝트가 사용하던 리소스를 소멸시키는 함수다.
다음은 cs오브젝트 열쇠의 획득 및 반납에 대한 함수를 보자.
#include <windows.h>
void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection); (획득)
void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection); (반납)
- lpCriticalSection : 획득(소유) 및 반납할 cs오브젝트의 주소 값 전달
위 함수를 이용한 소스를 보자.
#include <stdio.h>
#include <windows.h>
#include <process.h>
#define NUM_THREAD 50
unsigned WINAPI threadInc(void * arg);
unsigned WINAPI threadDes(void * arg);
long long num=0;
//cs 오브젝트 변수 설정
CRITICAL_SECTION cs;
int main(int argc, char *argv[])
{
HANDLE tHandles[NUM_THREAD];
int i;
// cs 오브젝트 초기화
InitializeCriticalSection(&cs);
//쓰레드 생성후 핸들값에 넣기
for(i=0; i<NUM_THREAD; i++)
{
if(i%2)
tHandles[i]=(HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);
else
tHandles[i]=(HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);
}
//둘 이상의 커널 오브젝트를 대상으로 상태 확인
WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);
//리소스 해제
DeleteCriticalSection(&cs);
printf("result: %lld \n", num);
//윈도우는 메인함수가 끝나면 쓰레드도 같이 종료된다.
return 0;
}
unsigned WINAPI threadInc(void * arg)
{
int i;
//임계영역 설정
EnterCriticalSection(&cs);
for(i=0; i<50000000; i++)
num+=1;
LeaveCriticalSection(&cs);
return 0;
}
unsigned WINAPI threadDes(void * arg)
{
int i;
//임계영역 설정
EnterCriticalSection(&cs);
for(i=0; i<50000000; i++)
num-=1;
LeaveCriticalSection(&cs);
return 0;
}
20 - 2: 커널모드 동기화 기법
커널 모드는 유저 모드 보다 많은 동기화 기법을 지원한다.
대표적인 뮤텍스, 세마포어, 이벤트 기법을 알아보자.
1. 뮤텍스(Mutex) 오브젝트 기반 동기화
뮤텍스 오브젝트 생성 함수
#include <windows.h>
HANDLE CreatMutex (LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner, LPCTSTR lpName);
-> 성공시 생성된 뮤텍스 오브젝트의 핸들, 실패시 NULL 반환
-lpMutexAttributes : 보안관련 특성 정보의 전달, 디폴트 보안 설정을 위해서 NULL 전달
-bInitialOwner : true 전달시, 생성되는 뮤텍스 오브젝트는 이함수를 호출한 쓰레드의 소유가 되면서 non-signaled 상태가 된다. false 전달시 생성되는 뮤텍스 오브젝트는 소유자가 존재하지 않으며, signaled 상태로 생성된다.
-ipName : 뮤텍스 오브젝트에 이름을 부여할 때 사용된다. NULL을 전달하면 이름 없는 뮤텍스 오브젝트가 생성된다.
뮤텍스 소멸 함수
#include <windows.h>
BOOL CloseHandle(HANDLE hObject);
-> 성공시 true, 실패시 false 반환
-hObject : 소멸하고자 하는 커널 오브젝트의 핸들 전달
뮤택스 반납 함수
#include <windows.h>
BOOL ReleaseMutex(HANDLE hMutex);
-> 성공시 true, 실패시 false 반환
- hMutex : 반납할, 다시 말해서 소유를 해제할 뮤텍스 오브젝트의 핸들 전달
2. 세마포어 오브젝트 기반 동기화
세마포어 오브젝트 생성 함수
#include <windows.h>
HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
LONG linitialCount, LONG lMaximumCount, LPCTSTR lpName);
-> 성공시 생성된 세마포어 오브젝트 핸들, 실패시, NULL 반환
-lpSemaphoreAttributes :보안관련 정보의 전달, 디폴트 보안설정을 위해서 NULL 전달
-linitialCount : 세마포어의 초기값 설정, 매개변수 lMaximumCount에 전달된 값보다 크면 안되고 , 0 이상이여야 함
-linitialCount : 최대 세마포어 값을 지정한다. 1을 지정하면 세마포어 값이 0, 또는 1이 되는 바이너리 세마포어가 구성된다.
-lpName : 세마포어 오브젝트에 이름을 부여할 때 사용한다. NULL을 전달하면 이름없는 세마포어 오브젝트가 된다.
세마포어 오브젝트 반납 함수
#include <windows.h>
Bool ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount,
LPLONG lpPreviousCount);
-> 성공시 TRUE, 실패시 FALSE 반환
-hSemaphore : 반납할 세마포어 오브젝트 핸들 전달
-lReleaseCount : 반납은 세마포어 값의 증가를 의미하는데, 이 매개변수를 통해서 증가되는 값의 크기를 지정할 수 있다. 그리고 이로 인해서 세마포어의 최대 값을 넘어서게 되면, 값은 증가하지 않고 fales 가 반환된다.
-lpPreviousCount : 변경 이전의 세마포어 값 저장을 위한 변수의 주소 값 전달, 불필요하다면 NULL 전달
3. 이벤트 오브젝트 기반 동기화
이벤트 오브젝트 생성에 사용되는 함수
#include<windows.h>
HANDLE CreateEvent (LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName);
-> 성공시 생성된 이벤트 오브젝트의 핸들, 실패시 NULL 반환
-lpEventAttributes : 보안 관련 정보의 전달, 디폴트 보안설정을 위해서 NULL 전달
-bManualReset : true 전달시 manual-reset 모드 이벤트, false 전달시 auto-reset 모드 이벤트 오브젝트 생성
- bInitialState : true 전달시 singnaled 상태의 이벤트 오브젝트 생성, false 전달시 non-signaled 상태의 이벤트 오브젝트 생성
-lpName : 이벤트 오브젝트에 이름을 부여할 때 사용된다. NULL을 전달하면 이름없는 이벤트 오브젝트가 생성된다.
명시적으로 오브젝트 상대를 변경하는 함수
#include <windows.h>
BOOL ResetEvent(HANDLE hEvent); //non-signaled
BOOL setEvent(HANDLE hEvent); //signaled
-> 성공시 true, 실패시 false 반환
아오 몸이 안좋으니깐 이거 치는 것도 꽤 힘들다 ㅠㅠ
윈도우 함수는 왜이리 대소문자로 왔다갔다해야하는지,........
이름이 너무 긴게 많아!!
원래는 여기 소스도 올려서 확인해봐야 하는데
내 몸상태가 영 나쁘니....
소스 코드를 직접 다운 받아 해보자 /엉엉/
소스 코드 다운 받기
2015년 1월 8일 목요일
TCP/IP 소켓 프로그래밍 19강
어느덧 파트3까지 왔다. 파트 3부터는 윈도우 기반 프로그래밍이다.
19강은 윈도우에서의 쓰레드 사용이다.
커널 오브젝트란???
운영체제가 만드는 리소스의 종류는 다양하다.
이런 리소스를 관리하는 방식도 다양하다.
리소스마다 유지해야 하는 정보가 다르니,
이 데이터 블록 형태는 리소스마다 차이가 있다.
이 데이터 블록을 커널 오브젝트라고 한다.
커널 오브젝트의 소유자는 운영체제다.
커널의 오브젝트의 생성, 관리 그리고 소멸 시점을 결정하는 것까지
모두 운영체제의 몫이다.
프로세스와 쓰레드의 관계
main 함수의 호출주체는 쓰레드다.
각종 운영체제들도 운영체제 레벨에서 쓰레드를 지원한다.
따라서 쓰레드를 별도로 생성하지 않는 프로그램은 "단일 쓰레드 모델의 프로그램"
그리고 쓰레드를 별도로 생성하는 방식의 프로그램은 "멀티 쓰레드 모델의 프로그램"
이라고 한다.
윈도우에서 쓰레드 생성방법
1. 이 함수가 호출되면
2. 쓰레드가 생성되고
3. os는 관리를 위해 커널 오브젝트를 생성한다
4. 이 커널 오브젝트의 구분자 역할을 하는, 정수로 표현되는 "핸들"을 반환한다.
#include <windows.h>
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
-> 성공시 쓰레드 핸들, 실패시 NULL 반환
- lpThreadAttributes : 쓰레드의 보안관련 정보전달, 디폴트 보안설정을 위해서 NULL 전달
- dwStackSize : 쓰레드에게 할당할 스택의 크기를 전달, 0 전달하면 디폴트 크기의 스택 생성
- lpStartAddress : 쓰레드 main 함수 정보 전달
- lpParameter : 쓰레드의 main 함수호출 시 전달할 인자정보 전달.
- dwCreationFlags : 쓰레드 생성 이후의 행동을 결정, 0을 전달하면 생성과 동시에 실행 가능한 상태가 된다.
- lpThreadId : 쓰레드 ID의 저장을 위한 변수의 주소 값 전달.
뭔가 굉장히 복잡해 보이는데 lpStartAddress , lpParameter 만 신경 쓰고,
나머지는 0 또는 NULL을 전달하면 된다.
C/C++ 표준함수를 호출하려면 CreateThread 함수호출을 사용하면 안된다.
왜냐하면 안정적으로 동작하지 않기 때문이다.
쓰레드에 안전한 C 표준함수의 호출을 위한 쓰레드 생성 함수를 소개한다.
#include <process.h>
uintptr_t _beginthreadex (
void *security,
unsigned stack_size,
unsigned (*start_address)(void*),
void *arglist,
unsigned initflag,
unsigned *thrdaddr
);
-> 성공시 쓰레드 핸들, 실패시 0반환
CreateThread 함수와 비교해 보면, 거의 비슷하다.
매개변수의 수도 같고, 의미와 순서도 같다. 단지 이름과 자료형만 조금 다를 뿐이다.
참고로 uintptr_t 는 64비트로 표현되는 unsigned 정수 자료형이다.
소스를 보자.
#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;
//쓰레드의 main 함수로 ThreadFunc를, 그리고 ThreadFunc에 변수 param의 주소값이 전달 하면서 쓰레드의 생성을 요구하고 있다.
hThread=(HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (void*)¶m, 0, &threadID);
if(hThread==0)
{
puts("_beginthreadex() error");
return -1;
}
//3초간의 대기시간을 갖는다.
Sleep(3000);
puts("end of main");
return 0;
}
//WINAPI 는 _beginthreadex 함수가 요구하는 호출규약을 지키기 위해 삽입한 것이다.
unsigned WINAPI ThreadFunc(void *arg)
{
int i;
int cnt=*((int*)arg);
for(i=0; i<cnt; i++)
{
Sleep(1000); puts("running thread");
}
return 0;
}
main 함수의 반환으로 인해 프로세스가 종료되면, 그 안에 담겨 있는 모든 쓰레드들도 함께 종료된다.
커널 오브젝트의 상태, 그리고 상태의 확인을 운영체제가 하는데
커널 오브젝트의 signaled, non-signaled 상태는 boolean 형 변수 하나로 표현한다.
이 때 쓰는 함수를 보자.
#include <windows.h>
DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);
-> 성공시 이벤트 정보, 실패 시 WAIT_FAILED 반환
-hHandle : 상태 확인의 대상이 되는 커널 오브젝트의 핸들을 전달.
-dwMilliseconds : 1/1000초 단위로 타임아웃을 지정, 인자로 INFINITE 전달시, 커널 오브젝트가 signaled 상태가 되기 전에는 반환하지 않는다.
-반환값 : signaled 상태로 인한 반환시, WAIT_OBJECT_0 반환, 타임아웃으로 인한 반환시 WAIT_TIMEOUT 반환.
위 함수는 이벤트 발생에 의해서 반환되면,
해당 커널 오브젝트를 다시 non-signaled 상태로 되돌리기도 한다.
그리고 이렇게 다시 non-signaled 상태가 되는
커널 오브젝트를 가리켜 "auto-reset 모드" 커널 오브젝트라 하고,
자동으로 non-signaled 상태가 되지 않는 커널 오브젝트를 가리켜
"manual-reset 모드"커널 오브젝트라 한다.
다음 함수는 둘 이상의 커널 오브젝트를 대상으로 상태를 확인하는 경우에 필요한 함수다.
#include <windows.h>
DWORD WaitForMultipleObjects(
DWORD nCount, const HANDLE* lpHandles, BOOL bWaitAll,
DWORD dwMilliseconds);
-> 성공시 이벤트 정보, 실패시 WAIT_FAILED 반환
-nCount : 검사할 커널 오브젝트의 수 전달
-lpHandles : 핸들정보를 담고 있는 배열의 주소 값 전달
-bWaitAll : true 전달시, 모든 검사 대상이 signaled 상태가 되어야 반환,
false 전달시, 검사대상 중 하나라도 signaled 상태가 되면 반환
-dwMilliseconds : 1/1000 초 단위로 타임아웃 지정, 인자로 INFINITE 전달시, 커널 오브젝트가 signaled 상태가 되기 전에는 반환하지 않는다.
새벽 3시 넘어서 글을 쓰다 보니 졸려서 비몽사몽이다 ㅡㅡ;;
윈도우 함수들은 왜이리 이상한지 모르겠다.
역시 예제를 통해서 확인해보는 것이 좋다.
소스 다운 받기
19강은 윈도우에서의 쓰레드 사용이다.
19-1 : 커널 오브젝트
커널 오브젝트란???
운영체제가 만드는 리소스의 종류는 다양하다.
이런 리소스를 관리하는 방식도 다양하다.
리소스마다 유지해야 하는 정보가 다르니,
이 데이터 블록 형태는 리소스마다 차이가 있다.
이 데이터 블록을 커널 오브젝트라고 한다.
커널 오브젝트의 소유자는 운영체제다.
커널의 오브젝트의 생성, 관리 그리고 소멸 시점을 결정하는 것까지
모두 운영체제의 몫이다.
19-2 : 윈도우 기반의 쓰레드 생성
프로세스와 쓰레드의 관계
main 함수의 호출주체는 쓰레드다.
각종 운영체제들도 운영체제 레벨에서 쓰레드를 지원한다.
따라서 쓰레드를 별도로 생성하지 않는 프로그램은 "단일 쓰레드 모델의 프로그램"
그리고 쓰레드를 별도로 생성하는 방식의 프로그램은 "멀티 쓰레드 모델의 프로그램"
이라고 한다.
윈도우에서 쓰레드 생성방법
1. 이 함수가 호출되면
2. 쓰레드가 생성되고
3. os는 관리를 위해 커널 오브젝트를 생성한다
4. 이 커널 오브젝트의 구분자 역할을 하는, 정수로 표현되는 "핸들"을 반환한다.
#include <windows.h>
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
-> 성공시 쓰레드 핸들, 실패시 NULL 반환
- lpThreadAttributes : 쓰레드의 보안관련 정보전달, 디폴트 보안설정을 위해서 NULL 전달
- dwStackSize : 쓰레드에게 할당할 스택의 크기를 전달, 0 전달하면 디폴트 크기의 스택 생성
- lpStartAddress : 쓰레드 main 함수 정보 전달
- lpParameter : 쓰레드의 main 함수호출 시 전달할 인자정보 전달.
- dwCreationFlags : 쓰레드 생성 이후의 행동을 결정, 0을 전달하면 생성과 동시에 실행 가능한 상태가 된다.
- lpThreadId : 쓰레드 ID의 저장을 위한 변수의 주소 값 전달.
뭔가 굉장히 복잡해 보이는데 lpStartAddress , lpParameter 만 신경 쓰고,
나머지는 0 또는 NULL을 전달하면 된다.
C/C++ 표준함수를 호출하려면 CreateThread 함수호출을 사용하면 안된다.
왜냐하면 안정적으로 동작하지 않기 때문이다.
쓰레드에 안전한 C 표준함수의 호출을 위한 쓰레드 생성 함수를 소개한다.
#include <process.h>
uintptr_t _beginthreadex (
void *security,
unsigned stack_size,
unsigned (*start_address)(void*),
void *arglist,
unsigned initflag,
unsigned *thrdaddr
);
-> 성공시 쓰레드 핸들, 실패시 0반환
CreateThread 함수와 비교해 보면, 거의 비슷하다.
매개변수의 수도 같고, 의미와 순서도 같다. 단지 이름과 자료형만 조금 다를 뿐이다.
참고로 uintptr_t 는 64비트로 표현되는 unsigned 정수 자료형이다.
소스를 보자.
#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;
//쓰레드의 main 함수로 ThreadFunc를, 그리고 ThreadFunc에 변수 param의 주소값이 전달 하면서 쓰레드의 생성을 요구하고 있다.
hThread=(HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (void*)¶m, 0, &threadID);
if(hThread==0)
{
puts("_beginthreadex() error");
return -1;
}
//3초간의 대기시간을 갖는다.
Sleep(3000);
puts("end of main");
return 0;
}
//WINAPI 는 _beginthreadex 함수가 요구하는 호출규약을 지키기 위해 삽입한 것이다.
unsigned WINAPI ThreadFunc(void *arg)
{
int i;
int cnt=*((int*)arg);
for(i=0; i<cnt; i++)
{
Sleep(1000); puts("running thread");
}
return 0;
}
main 함수의 반환으로 인해 프로세스가 종료되면, 그 안에 담겨 있는 모든 쓰레드들도 함께 종료된다.
19-3 : 커널 오브젝트의 두 가지 상태
커널 오브젝트의 상태, 그리고 상태의 확인을 운영체제가 하는데
커널 오브젝트의 signaled, non-signaled 상태는 boolean 형 변수 하나로 표현한다.
이 때 쓰는 함수를 보자.
#include <windows.h>
DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);
-> 성공시 이벤트 정보, 실패 시 WAIT_FAILED 반환
-hHandle : 상태 확인의 대상이 되는 커널 오브젝트의 핸들을 전달.
-dwMilliseconds : 1/1000초 단위로 타임아웃을 지정, 인자로 INFINITE 전달시, 커널 오브젝트가 signaled 상태가 되기 전에는 반환하지 않는다.
-반환값 : signaled 상태로 인한 반환시, WAIT_OBJECT_0 반환, 타임아웃으로 인한 반환시 WAIT_TIMEOUT 반환.
위 함수는 이벤트 발생에 의해서 반환되면,
해당 커널 오브젝트를 다시 non-signaled 상태로 되돌리기도 한다.
그리고 이렇게 다시 non-signaled 상태가 되는
커널 오브젝트를 가리켜 "auto-reset 모드" 커널 오브젝트라 하고,
자동으로 non-signaled 상태가 되지 않는 커널 오브젝트를 가리켜
"manual-reset 모드"커널 오브젝트라 한다.
다음 함수는 둘 이상의 커널 오브젝트를 대상으로 상태를 확인하는 경우에 필요한 함수다.
#include <windows.h>
DWORD WaitForMultipleObjects(
DWORD nCount, const HANDLE* lpHandles, BOOL bWaitAll,
DWORD dwMilliseconds);
-> 성공시 이벤트 정보, 실패시 WAIT_FAILED 반환
-nCount : 검사할 커널 오브젝트의 수 전달
-lpHandles : 핸들정보를 담고 있는 배열의 주소 값 전달
-bWaitAll : true 전달시, 모든 검사 대상이 signaled 상태가 되어야 반환,
false 전달시, 검사대상 중 하나라도 signaled 상태가 되면 반환
-dwMilliseconds : 1/1000 초 단위로 타임아웃 지정, 인자로 INFINITE 전달시, 커널 오브젝트가 signaled 상태가 되기 전에는 반환하지 않는다.
새벽 3시 넘어서 글을 쓰다 보니 졸려서 비몽사몽이다 ㅡㅡ;;
윈도우 함수들은 왜이리 이상한지 모르겠다.
역시 예제를 통해서 확인해보는 것이 좋다.
소스 다운 받기
피드 구독하기:
덧글 (Atom)



