반응형

※ 일반적인 쓰레드기반 다중 서버...

서버쪽 쓰레드 하나가 하나의 소켓을 전담한다.

서버쪽 쓰레드가 계속해서 일하는 것이기 아니기 때문에 소켓과 쓰레드가 1:1 대응되는 것은 서버에 부담이 된다. 

1. 리소스 부담... 

2. context switching이 빈번하게 발생하기 때문...


※ 소켓의 수는 줄일 수 없다.

하지만, 하나의 쓰레드가 3개의 소켓을 한꺼번에 담당한다면 리소스 부담 감소, context switching 감소...


22-1. Completion Port 입출력 기본 원리

IOCP란?

1. 기본적으로 비동기 입출력 모델이다. Overlapped IO

2. 제한된 쓰레드의 수를 통해서 여러 소켓의 입출력을 담당하게 한다.

3. 컨텍스트 스위칭에 소비되는 시간을 줄이는 모델이다.

4. Overlapped 입출력 모델의 특징과 비동기 Notification 입출력 모델의 특징을 동시에 지닌다.

원래 비동기 Notification 입출력 모델 : 먼저 확인 후 그 다음 입출력한다.

IOCP모델 : 먼저 입출력 한 후 확인을 나중에 한다.


IOCP 구현모델


Completion Port 내부에 큐가 생성된다. 

연결된 소켓이 입출력이 완료되면 Completion Packet을 만든다.

Completion Packet에는 입출력이 완료한 소켓 정보등에 대한 데이터 묶음이다.

이 packet을 Completion Queue에 집어 넣는다.

쓰레드는 큐에서 packet을 꺼내 내용을 처리한다.


IOCP에서 적절한 쓰레드의 수

Response Session : 서버가 클라이언트로 온 요청을 처리하고 전송하기 직전까지 준비과정.

Response Session이 짧은 경우(간단한 연산 결과를 돌려줌) : CPU의 개수를 초과하지 않는다.

Response Session이 긴 경우 : CPU의 개수를 적절히 초과하여 클라이언트의 평균 만족도를 높인다.

※ 실험적으로 판단하여 평가해야 한다.

일반적인 경우 : CPU의 개수를 초과하지 않는다.


22-2. Completion Port 입출력을 위한 기본 단계

1. Completion Port 오브젝트의 생성

HANDLE CreateIoCompletionPort(

HANDLE FileHandle, // 연결 시킬 소켓 핸들

HANDLE ExistingCompletionPort, // 연결시킬 completion port 핸들

ULONG_PTR CompletionKey, // completion key

DWORD NumberOfConcurrentThreads // 동시 실행 가능한 쓰레드의 수

);

실행예)

HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

※ NumberOfConcurrentThreads가 0이면 CPU의 수만큼 허용


2. Completion Port 오브젝트와 소켓의 연결

completionKey : 입출력이 완료되었을 때 Completion Packet에 저장하는 추가적인 데이터

실행예)

CreateIoCompletionPort(소켓핸들, hCompletionPort, (DWORD)PerHandleData, 0);


3. Completion Queue에 들어 있는 패킷 정보 확인

BOOL GetQueuedCompletionStatus(

HANDLE CompletionPort,  // completion port 핸들

LPDWORD lpNumberOfBytes, // 전송된 바이트수

PULONG_PTR lpCompletionKey, // file completion key

LPOVERLAPPED *lpOverlapped, // buffer

DWORD dwMilliseconds // 선택적인 timeout 값

);

2,3,4 인자를 통해 completion packet의 내용을 얻어온다.


22-3 Completion Port 입출력 기반의 서버구현


CreateCompletionPort의 키 : 완료된 소켓의 정보


Completion Port : 완료감지, completion packet 생성 : key, overlapped, 전송 바이트 수로 구성.

WSARecv의 6번째 인자인 LPWSAOVERLAPPED에서 hEvent를 이용한다. ->

수신된 데이터가 존재하는 버퍼 정보를 담음. overlapped 구조체 뿐만 아니라 다른 구조체도 추가 가능. 



반응형
반응형

21-1. Overlapped 입출력의 의미

비중첩 데이터 입출력 모델


중첩된 데이터 입출력 모델

하나의 쓰레드 내에서 여러 개의 입출력이 진행되는 것


21-2. Overlapped 입출력을 위한 기본 단계

1. Overlapped 소켓의 생성

SOCKET WSASocket(

  __in  int af,

  __in  int type,

  __in  int protocol,

  __in  LPWSAPROTOCOL_INFO lpProtocolInfo,

  __in  GROUP g,

  __in  DWORD dwFlags

);

af : address family

type : 소켓 형태

protocol : 사용된 프로토콜(여기까지 socket과 동일)

lpProtocolInfo : 생성될 소켓의 성경을 정의하는 wSAPROTOCOL_INFO 포인터

g : 예약됨.

dwFlags : 소켓 속성을 지정하는 플래그


WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);

WSA_FLAG_OVERLAPPED는 Overlapped 입출력이 가능하도록 하는 플래그.


2. 데이터 송수신

※ 다음 함수들은 버퍼에 존재하는 데이터를 모아서 한 번에 전송하고(Gather) 수신된 데이터를 여러 버퍼에 나누어 저장(Scatter)하는 Gather/Scatter 입출력을 한다.


int WSASend(

    SOCKET s,                  // 소켓 핸들

    LPWSABUF lpBuffers,    // WSABUF 구조체 배열의 포인터

    DWORD dwBufferCount, // lpBuffers가 가리키는 배열의 크기

    LPDWORD lpNumberOfBytesSent, // 전송된 바이트 수를 저장하기 위한 포인터

    DWORD dwFlags,

    LPWSAOVERLAPPED lpOverlapped,

    LPWSAOVERLAPPED_COMPLETION_ROUTING lpCompletionRoutine

);

리턴 : 성공시 0, 실패시 SOCKET_ERROR


typedef struct __WSABUF{

    u_long len;

    char FAR *buf;

}WSABUF, FAR* LPWSABUF;


int WSARecv(

    SOCKET s,                  // 소켓 핸들

    LPWSABUF lpBuffers,    // 수신 버퍼 정보를 지니는WSABUF 구조체 배열의 포인터

    DWORD dwBufferCount, // lpBuffers가 가리키는 배열의 크기

    LPDWORD lpNumberOfBytesRecvd, // 전송된 바이트 수를 저장하기 위한 포인터

    DWORD dwFlags,

    LPWSAOVERLAPPED lpOverlapped,

    LPWSAOVERLAPPED_COMPLETION_ROUTING lpCompletionRoutine

);

리턴 : 성공시 0, 실패시 SOCKET_ERROR

lpOverlapped : 중첩된 입출력을 하기 위해 사용.


3. 데이터 송수신 완료 확인

이벤트 커널 오브젝트 기반

CALLBACK 함수 기반


21-3. Event 커널 오브젝트 기반의 Overlapped I/O
이벤트 커널 오브젝트 : 데이터 송수신이 되었는지 확인하는 용도로 사용.

WSACreateEvent
typedef struct _WSAOVERLAPPED{
    DWORD Internal;
    DWORD InternalHigh;
    DWORD Offset;
    DWORD OffsetHigh;
    // 위의 4개 변수는 내부적으로 사용.
    WSAEVENT hEvent;    // 이벤트 핸들의 오브젝트를 저장한다.
}WSAOVERLAPPED, *LPWSAOVERLAPPED;

WSAWaitForMultipleEvents 함수는 이벤트가 시그널 되었는 지 확인한다.
WSAGetOverlappedResult(
    SOCKET s,
    LPWSAOVERLAPPED lpOverlapped, // WSASend 혹은 WSARecv 호출시 전달한 OVERLAPPED 구조체
    LPDWORD lpcbTransfer, // 전송된 바이트 수
    BOOL fWait,    // TRUE 전달 시 입출력 완료시까지 블로킹
    LPDWORD lpdwFlags
);
※ 송수신한 바이트수, 에러가 발생하였는가? 의 결과를 알려준다.

1. 소켓 이벤트와 WSAOVERLAPPED 구조체와 연결하는 방법
event = WSACreateEvent();
memset(&overlapped, 0, sizeof(overlapped));
overlapped.hEvent = event;
2. WSAWaitForMultipleEvents 함수 호출
WSAWaitForMultipleEvents(1, &event, TRUE, WSA_INFINITE, FALSE);
3. WSAGetOverlappedResult 함수 호출
WSAGetOverlappedResult(hSocket, &overlapped, &sendBytes, FALSE, NULL);

21-4. Completion Routines 기반의 Overlapped I/O
콜백 함수 : 특정 상황이 되면 시스템에서 의해 호출되는 함수.

LPWSAOVERLAPPED_COMPLETION_ROUTINE : 다음 콜백함수에 대한 포인터
void CALLBACK CompletionROUTINE(
    DWORD dwError,    // 오류 정보 전달 됨.
    DWORD cbTransferred,    // 전송된 바이트 수 전달됨
    LPWSAOVERLAPPED lpOverlapped,    // WSARecv 함수 호출시 전달한 구조체 변수
    DWORD dwFlags
);
콜백함수가 있을지라도 WSAOVERLAPPED 구조체는 전달해 주어야 한다.
그래서 이벤트 커널 오브젝트는 Dummy Object가 된다.

index = WSAWaitForMultipleEvents(1, &event, FALSE, WSA_INFINITE, TRUE);
index가 WAIT_IO_COMPLETION이면 중첩된 입출력이 완료되었다는 뜻.
다른 의미는 오류가 발생함.
반응형
반응형

1. 비동기 Notification의 의미

인터넷 계층

Link계층 : 물리 + 데이터링크

IP계층

TCP/UDP계층 : 송수신 버퍼가 생성된다.

응용프로그램 계층


동기화된 입력 및 출력 함수의 호출


동기화 : 함수의 호출과 행위의 시작, 함수의 리턴과 행위의 종료가 일치함.

데이터 전송 종료 :  Application -> TCP계층으로 데이터를 다 보낸 상황. send함수를 호출하면 출력버퍼로 보내고자 하는 데이터가 다 들어갔을 때의 상황. 

데이터 수신 종료 : TCP -> Application으로 데이터를 다 읽은 상황.


동기화된 데이터 입출력 과정상의 문제점


※ Host B의 버퍼가 꽉 찼을 경우에 (패킷 전송 일시 정지 요청에 의해) Host A는 패킷 전송을 하지 않고 blocking상태에 있게 된다.

※ send함수에서 패킷을 다 보냈을 경우 send함수가 리턴되어 종료된다.


비동기화 Notification

동기화 Notification

1. select 함수를 기반으로 한 서버의 구현

2. 핸들에 변화가 발생해야 리턴한다. select 함수의 리턴 시기와 핸들 변화 시기가 일치(동기)

비동기화 Notification

1. WSAEventSelect 함수를 기반으로 한 서버의 구현

2. 함수의 호출과 동시에 리턴한다. 따라서 핸들의 변화를 확인하기 위한 별도의 과정이 필요하다.


20.2 WSAEventSelect 모델 기반 서버 구현을 위한 API

구현순서


※ 소켓과 소켓을 위한 Event Object(소켓의 상태를 반영)가 쌍을 이룬다. 소켓의 변화가 다양하기 때문에 Event 객체가 따로 필요하다.

1. WSACreateEvent : Event를 편하게 이용할 수 있도록 돕는다.

2. WSAEventSelect

select함수 : 관심을 두고자 하는 핸들을 설정(설정) + 소켓의 변화(알림), 동기화 알림 함수.

WSAEventSelect : 관심을 두고자 하는 하나의 핸들을 설정만 할 수 있음.

3. WSAWaitForMultipleEvents : 소켓의 변화를 확인. nonsignaled에서 signaled로 변화할 때 리턴됨.

4. WSAEnumNetworkEvents : 소켓이 변화된 이유를 확인.


필요한 함수

WSAEVENT WSACreateEvent(void);

리턴 : manual-reset 모드, non-signaled 상태의 이벤트


int WSAEventSelect(

    SOCKET s,                       // 감시 대상이 되는 소켓

    WSAEVENT hEventObject, // 소켓의 변화를 확인하기 위한 이벤트 오브젝트의 핸들.

    long lNetowrkEvents         // 감시하고자 하는 이벤트 종류를 Bit-wise OR(|)로 묶어서 전달

);

리턴 : 성공시 0, 실패시 SOCKET_ERROR


감시하고자 하는 이벤트 종류

 이벤트 의미 
 FD_READ  수신된 데이터가 존재할 경우 
 FD_WRITE 블로킹되지 않고 데이터를 전송할 수 있는 경우 
 FD_OOB OOB 메세지 수신에 대한 이벤트
 FD_ACCEPT 연결 요청이 수신되었을 경우
 FD_CLOSE 연결 종료 요청이 수신되었을 경우


DWORD WSAWaitForMultipleEvents(

    DWORD cEvents,                           // 검사 대상의 핸들 수     

    const WSAEVENT FAR *lphEvents, // 검사 대상의 배열

    BOOL fWaitAll,                              // FALSE일 경우 하나의 배열만 변화가 있어도 리턴. TRUE면 모든 배열의 변화가 있어야 함.

    DWORD dwTimeout,                     // 타임아웃.

    BOOL fAlertable

);

여러 개의 이벤트 핸들(포인터로 배열 전달)이 signaled 상태가 되었는지 감시하는 함수.

리턴 : 실패시 WAIT_FAILED 리턴, 성공시 이벤트 발생 오브젝트 정보 리턴


int WSAEnumNetworkEvents(

    SOCKET s,                                                   // 소켓

    WSAEVENT hEventObject,                             // 이벤트가 발생한 소켓과 연결되어 있는 이벤트 오브젝트 핸들

    LPWSANETWORKEVENTS lpNetworkEvents    // 발생한 이벤트 정보와 오류 정보로 채워진 WSANETWORKEVENTS 구조체 포인터

);

이벤트 핸들이 발생한 이유를 알아내는 데 사용한다. signaled 상태의 이벤트 핸들을 nonsignaled상태로 바꿔주는 기능도 수행한다.

리턴 : 성공시 0, 실패시 SOCKET_ERROR 리턴


발생한 이벤트 정보와 오류 정보로 채워진 WSANETWORKEVENTS 구조체

typedef struct _WSANETWORKEVENTS{

    long lNetworkEvents;

    int    iErrorCode[FD_MAX_EVENTS];

} WSANETWORKEVENTS, *LPWSANETWORKEVENTS;


※ bind와 listen 사이의 소켓에 대해 이벤트를 만든다.

index = WSAWaitForMultipleEvents(sockTotal, hEventArray, FALSE, WSA_INFINITE, FALSE);

index = index - WSA_WAIT_EVENT_0;

여러 개의 소켓에서 변화가 일어났는 지 확인하고 소켓의 index값을 얻는다. 리턴된 index값은 가장 작은 소켓 인덱스 값이다. 

변화가 일어난 소켓이 여러개일 수 있기 때문에 각각을 for문을 통해 확인한다.


반응형
반응형

※ 커널 오브젝트가 생성될 때는 non-signaled 상태에 있다가 종료하는 경우에 signaled상태로 바뀐다.

※ nonsignaled상태이면 WaitForSingleObject가 멈추고 그렇지 않으면 통과(리턴)할 수 있다.


19-1. 쓰레드 동기화 기법의 분류

프로세스 실행의 두 가지 모드

사용자 모드(User Mode)와 커널 모드(Kernel Mode)


user mode : 시스템 리소스를 제외하고 원하는대로 사용가능. 

접근할 수 있는 메모리 공간이 제한되어 있음.

물리적 영역으로 직접 접근이 허용되지 않는다.

사용자 모드 동기화 기법 : CRITICAL_SECTION 오브젝트 사용

장점 : 프로그래밍하기 수월, 속도가 빠르다.

단점 : 커널 모드 동기화 기법에 비해 제한된 기능만 가진다.


kernel mode : 시스템 리소스를 접근할 수 있지만 시스템 콜을 통해서만 운영체제에서 직접 처리한다

시스템의 모든 메모리 영역으로 접근 가능.

시스템 리소스 : 파일, 쓰레드

시스템의 안전성을 보장하기 위해서 제공

커널 모드 동기화 기법 : Event, Semaphore, Mutex (다른 프로세스에서 접근 가능)

장점 : Deadlock 문제를 막을 수 있다. 둘 이상의 프로세스 내에 존재하는 쓰레드 간의 동기화 가능. 

단점 : 실행 속도의 저하가 발생.


19-2. CRITICAL_SECTION

동기화 관련 함수

※ lpCriticalSection : CRITICAL_SECTION 변수의 포인터

CS 초기화 : void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

CS 소유(임계영역에 들어갈 때) : void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

CS 반환(임계영역을 나올 때) : void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

CS 소멸 : void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);


커널 오브젝트의 상태

뮤텍스

signaled 상태 -> 뮤텍스 소유가 가능한 상태

non-signaled 상태 -> 뮤텍스가 이미 가지고 있는(소유되어진) 상태

세마포어

signaled 상태 -> 세마포어 카운트가 0이 아닌 경우.

non-signaled 상태 -> 세마포어 카운트가 0인 경우.


BOOL CloseHandle(HANDLE hObject);

커널 오브젝트 객체를 소멸한다. 참조카운트 1 감소.


19-3. Mutex(Mutual Exclusion)

HANDLE CreateMutex(

    LPSECURITY_ATTRIBUTES lpMutexAttributes,

    BOOL bInitialOwner,

    LPCTSTR lpName

);

뮤텍스 객체를 생성하고 참조카운트 1 증가.

리턴 : 성공시 생성된 Mutex 오브젝트 핸들, 실패시

lpMutexAttributes : 보안설정

bInitialOwner : false는 signal, true는 non-signal상태로

lpName : 객체 이름


※ WaitSingleObject함수는 뮤텍스를 가져오는(lock) 함수

BOOL ReleaseMutex(

    HANDLE hMutex

);

Mutex를 돌려주는(unlock)하는 함수.

리턴 : 성공시 TRUE 실패시 FALSE

non-signaled -> signaled


Mutex를 통한 쓰레드 동기화


19-4. Semaphore

세마포어가 0이 되어야 세마포어 오브젝트 non-signaled 상태가 된다.

HANDLE CreateSemaphore(

    LPSECURITY_ATTRIBTE lpSemaphoreAttributes,

    LONG lInitialCount,

    LONG lMaximumCount,

    LPCTSTR lpName

);

세마포어 객체를 생성하고 참조카운트 1 증가.

리턴 : 성공시 생성된 Mutex 오브젝트 핸들, 실패시

lpMutexAttributes : 보안설정

lInitialCount : 초기 세마포어값.

lMaximumCount : 세마포어의 최대값.

lpName : 객체 이름


※ WaitForSingleObject는 세마포어 값을 감소시킨다. signaled상태 -> nonsignaled상태로

BOOL ReleaseSemaphore(

    HANDLE hSemaphore,

    LONG lReleaseCount,

    LPLONG lpPreviousCount

);

리턴 : 성공시 TRUE, 실패시 FALSE

hSemaphore : 세마포어의 핸들.

lReleaseCount : 세마포어 값을 증가시킬 크기

lpPreviousCount : 이 함수를 호출하기 전에 카운트 값을 저장할 수 있다.


19-5. Event

Event 특징 : auto & manual-reset 모드의 커널 오브젝트의 생성이 가능하다.

※ 대기 중에 있는 쓰레드들을 한꺼번에 깨울 수 있다. (장점)

HANDLE CreateEvent(

    LPSECURITY_ATTRIBUTES lpEventAttributes,

    BOOL bManualReset,

    BOOL bInitialState,

    LPCTSTR lpName

);

이벤트를 생성한다.

리턴 : 성공시 생성된 Event 오브젝트 핸들. 실패시 NULL

lpEventAttributes : 보안설정

bManualReset : TRUE면 manual-reset 모드의 이벤트 생성. 

bInitialState : TRUE면 signaled 상태의 이벤트 생성, FALSE면 non-signaled 상태의 Evenet가 생성

lpName : Event에 이름을 준다.


manual-reset 모드 Event 동기화 원리

1. hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); -> 쓰레드 생성하면 대기상태로 생성.

2. SetEvent(hEvent);

3. NumberOfA, NumberOfOthers가 수행됨.

반응형
반응형

18-1 커널 오브젝트(Kernel Objects)

커널 오브젝트 

시스템 리소스의 정보를 담고 있는 데이터 블록(윈도우즈 기반에서만)

시스템(운영체제)이 관리한다.


18-2. 윈도우즈 기반의 쓰레드 생성


#include<process.h>

unsigned long _beginthreadex(

    void *security,

    unsigned stack_size,

    unsigned (*start_address)(void*),

    void *arglist,

    unsigned initflag,

    unsigned *thrdaddr

);


security : 보안에 관련된 설정

stack_size : 쓰레드 생성시 요구되는 스택의 크기. 0이면 default

start_address : 쓰레드에 의해 호출되는 함수의 포인터

arglist : start_address가 가리키는 함수 호출시, 전달할 인자를 지정해 준다.

initflag : 바로 실행상태(0)가 되느냐, 아니면 대기 상태로 들어가느냐 결정하는 요소. 

thrdaddr : 쓰레드 생성시 쓰레드 ID


쓰레드 생성을 위한 라이브러리 설정


※ 윈도우즈도 main thread가 먼저 끝나면 다른 thread들이 실행중이라도 프로그램이 종료가 된다.

※ 윈도우즈는 기본 실행 단위가 thread다. 프로그램을 만들면 main thread(main함수 실행하는 용도)가 생성된다.


커널 오브젝트, 리소스, 핸들과의 관계


※ 프로그램은 파일을 직접 조작하는 것이 아니라 커널 오브젝트에게 원하는 조작을 요청하고 시스템이 파일을 조작하게 된다.


18-3. Signaled & Non-Signaled 커널 오브젝트

커널 오브젝트의 상태

쓰레드 커널 오브젝트의 상태

생성시 : non-signaled 상태로 초기화.

종료시 : signaled 상태로 변경.


커널 오브젝트 상태 확인 함수

#include<windows.h>

DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);

리턴 : signaled 상태가 되어 리턴하는 경우 WAIT_OBJECT_0, 타임아웃에서 리턴될 경우 WAIT_TIMEOUT. 오류시 WAIT_FAILED.

hHandle : 상태를 확인할 커널 오브젝트의 핸들을 전달.

dwMilliseconds : 타임아웃을 1/1000초 단위로 설정한다. INFINITE를 전달할 경우 signaled 상태가 되기 전에 절대 리턴하지 않는다.

※ signaled 상태가 될 때까지 main thread가 기다려주면 된다. 리눅스의 JOIN과 비슷한 효과.


커널 오브젝트의 종류

auto-reset mode 커널 오브젝트 : 리턴이 되면서 커널 오브젝트가 non-signaled 상태가 됨.

manual-reset mode 커널 오브젝트 : 리턴이 되어도 커널 오브젝트는 자동으로 상태가 변경되지 않음.

반응형
반응형

17-1. 프로세스(Process)와 쓰레드(Thread)

쓰레드란 무엇인가?

경량화된 프로세스 

동시 실행 가능.


프로세스와 차이점

스택, PC를 포함한 레지스터 복사본, 쓰레드 지역 보관영역을 제외한 나머지 메모리 영역을 공유.

※ fork를 통해 프로세스 복사했다면 부모와 자식 프로세스는 서로 독립적으로 작동한다.

data : 전역변수가 저장되는 공간.

heap : 동적할당을 하는 메모리 공간.

stack : 지역변수를 위해 사용하는 공간.

보다 간단한 컨텍스트 스위칭.

일부 메모리를 공유하므로 쓰레드간 통신이 편리.

(프로그래밍 하는데 부담이 될 수 있음.)


17-2. 쓰레드 생성하기

쓰레드란 무엇인가?

프로세스(LINUX) : 실행되고 있는 일의 단위

쓰레드 : 프로세스 내에서 다시 나누어지는 일의 단위. 프로세스가 존재해야 쓰레드 생성이 가능.


#include<pthread.h>

int pthread_create(pthread_t * thread, pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);

쓰레드를 생성한다. nonblocking함수.

리턴 : 성공시 0, 실패시 이외 값.

thread : 생성된 쓰레드의 ID를 저장할 변수의 포인터.

attr : 생성하고자 하는 쓰레드의 특성. 일반적으로 NULL을 전달.

start_routine : 리턴타임이 void*이고 인자도 void*인 함수를 가리키는 포인터

arg : 쓰레드에 의해 호출되는 함수에(start_routine 포인터가 가리키는 함수) 전달하고자 하는 인자 값.


※ main쓰레드가 먼저 죽으면 자식 쓰레드는 끝까지 실행되지 않고 종료될 수 있다.


#include<pthread.h>

int pthread_join(pthread_t th, void **thread_return);

리턴 : 성공시 0, 실패시 이외 값.

th : 인자로 들어오는 ID의 쓰레드가 종료할 때까지 실행을 지연시킨다. blocking 함수.

thread_return : 쓰레드가 종료 시 반환하는 값에 접근할 수 있는 2차원 포인터.


임계영역과 쓰레드에 안전한 함수

임계영역 : 두 개 이상의 쓰레드에 의해서 동시에 실행되면 안 되는 영역.

쓰레드 안전한 함수(Thread safe function) : 임계영역에서 호출이 가능한 함수.

예) gethostbyname(쓰레드 불안전한 함수) vs gethostbyname_r(쓰레드 안전한 함수)


gcc -D_REENTRANT thread2.c -o thread2 -lpthread

-D_REENTRANT : #define _REENTRANT의 뜻. 쓰레드 불안전한 함수를 쓰레드 안전한 함수로 바꿔주는 매크로.

-lpthread : 기본적으로 쓰레드와 링크되어 있지 않기 때문에 링크시키기 위한 옵션.


17-3. 임계영역 & 쓰레드의 문제점
컴퓨터가 덧셈을 하는 기본 원리

※ 임시메모리 = accumulator

두 개의 쓰레드에 의한 덧셈 연산
int i = 10;    // 전역 변수
....
i+=10;

※ A Thread가 끝나기 전에 B Thread가 수행될 수 있다.

※ 3단계에서 i=30; 이 되어야 하지만 i=20;이 되어 버렸다.


17-4. 동기화(Synchronization)

임계영역 : 둘 이상의 쓰레드에 의해서 공유되는 메모리 공간에 접근하는 코드 영역.

동시에 접근 가능한 메모리(전역변수)에 접근해서 연산하는 코드 영역

동기화

1. 공유된 메모리에 둘 이상의 쓰레드가 동시 접근하는 것을 막는 행위.

2. 둘 이상의 쓰레드 실행순서를 컨트롤(control)하는 행위.

동기화 기법 : 뮤텍스, 세마포어


17-5. 뮤텍스(Mutex)

뮤텍스의 정의

Mutual Exclusion의 줄이말로 쓰레드 서로 간에 동시 접근을 허용하지 않겠다는 의미.

pthread_mutext_t 타입의 변수를 가리켜 뮤텍스라고 한다.

일종의 (화장실) 문고리에 해당.

뮤텍스의 기본 원리

임계영역에 들어갈 때 뮤텍스(문고리)를 잠그고 들어간다.

임계영역에서 나올 때 뮤텍스를 풀고 나간다.

뮤텍스를 조작하는 함수들

#include<pthread.h>

초기화 : pthread_mutex_init

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);

뮤텍스 소멸 : pthread_mutex_destroy

int pthread_mutex_destroy(pthread_mutex_t *mutex);

뮤텍스 잠금 : pthread_mutex_lock

int pthread_mutex_lock(pthread_mutex_t *mutex);

뮤텍스 풀기 : pthrea_mutex_unlock

int pthread_mutex_unlock(pthread_mutex_t *mutex);


뮤텍스 동기화 원리


17-6. 세마포어(Semaphore)

세마포어의 정의

sem_t 타입의 변수를 가리켜 흔히 세마포어라 표현한다.

세마포어의 기본 원리

세마포어는 정수를 지닌다. 정수의 값이 0이면 실행 불가를 의미한다.

세마포어가 1이상이 되면 실행 가능을 의미한다.

세마포어는 0미만이 될 수 없지만 1이상은 될 수 있다.

바이너리 세마포어 : 0과 1의 상태만을 지닌다.

세마포어를 조작하는 함수들

#include<semaphore.h>

세마포어 초기화 : sem_init

int sem_init(sem_t *sem, int pshared, unsigned int value);

value : 초기화하고자 하는 값.

세마포어 소멸 : sem_destroy

int sem_destroy(sem_t * sem);

세마포어 감소 : sem_wait

int sem_wait(sem_t * sem);

세마포어 증가 : sem_post

int sem_post(sem_t * sem);


세마포어 동기화 원리

Thread A는 새로운 데이터를 주고 Thread B는 그 데이터를 받는다.

a->b->a->b 순서가 반복된다.


17-7. 쓰레드 기반 서버 구현하기

채팅 서버의 임무 : 전체 클라이언트에게 메세지 내용을 다 보내야 되기 때문에 클라이언트 소켓을 갱신해야 한다.


pthread_mutex_lock(&mutx);

for(i=0;i<clnt_number;i++)

    write(clnt_socks[i], message, len);

pthread_mutex_unlock(&mutx);

위 소스에서 clnt_socks[i]에서 클라이언트 소켓 파일 디스크립터가 존재한다고 생각하고 write함수를 호출하지만 파일 디스크립터가 사라질 위험이 있기 때문에 mutex를 통해 잠가 놓는다.



반응형
반응형

16-1. 스트림의 분리

입출력 스트림의 분리 

입력을 위한 파일포인터와 출력을 위한 파일 포인터를 독립적으로 유지.

스트림을 분리하는 이유

프로그래밍을 단순하게 한다.

입출력 겸용 스트림의 경우 입출력 작업 전환시 버퍼를 비워야 한다.

일반적인 프로그래밍 방식.

주의할 사항

출력 스트림 종료시 EOF 전송이 된다.

문제점 : fclose함수를 통해 출력 스트림 뿐만 아니라 입력 스트림까지 완전 종료가 발생할 수 있다.

발생 상황 : 디스크립터가 하나고 그걸 통해 입출력(2개)용 FILE포인터를 만들 때 발생.


16-2. 파일 디스트립터의 복사와 스트림의 분리

파일 포인터(FILE*)를 이용한 소켓의 종료과정

fclose 함수의 호출은 파일 디스크립터의 종료로 이어지고, 이는 소켓의 완전종료를 의미한다.


※ 하나의 파일 디스크립터를 기반으로 입출력 파일 포인터 생성시 하나의 파일 포인터만 종료되어도 완전 종료된다.

※ 시스템 리소스(여기서는 소켓)는 가리키는 포인터(여기서는 파일 디스크립터)가 없을 때 종료된다.


하나의 파일 디스크립터를 기반으로 하나의 파일포인터만 생성

파일 디스크립터가 두 개일 경우에는 두 개의 파일 디스크립터가 종료되어야 완전 종료된다.


파일 디스크립터 복사

하나의 파일(혹은 소켓)에 접근할 수 있는 파일 디스크립터를 하나 더 만들어 내는 것.


#include<unistd.h>

int dup(int fildes);

int dup2(int fildes, int fildes2);

리턴 : 성공시 복사된 파일 디스크립터(번호), 실패시 -1

fildes : 복사하고자 원하는 파일 디스크립터

fildes2 : 복사되는 파일 디스크립터의 값을 명시적으로 지정하고 싶을 때 사용.


※ 쓰기 파일 디스크립터를 half close 함으로서 EOF가 전송됨.

fflush(sock);

shutdown(fileno(wstrm), SHUT_WR);    //  1차 종료, EOF 메세지 전송

fclose(wstrm);

반응형
반응형

15-1. 표준 입출력 함수의 장점

표준 입출력 함수의 장점

이식성이 좋아진다.

 - 모든 시스템은 ANSI 표준 C를 지원한다.

효율성을 높일 수 있다.

 - 표준 입출력 함수에 의한 버퍼를 제공받는다. (표준 입출력버퍼, 소켓 버퍼)

 - 소켓 생성 버퍼 목적 :  프로토콜의 완성을 위한 것.

 - 표준 입출력 버퍼 목적 : 성능 향상


파일 포인터와 파일 디스크립터

파일 포인터

 - 표준 입출력 함수 호출을 위해서 필요한 파일 포인터. = FILE 구조체의 포인터.

파일 디스크립터를 이용한 시스템 입출력

 - 시스템 입출력 함수 사용 시 필요

 - 정수값이다.

※ 소켓 기반의 표준 입출력 함수 사용을 위해 파일 디스크립터를 기반으로 파일 포인터를 생성.


15-2. 표준 입출력 함수의 사용

파일 디스크립터 -> 파일 포인터

#include<stdio.h>

FILE * fdopen(int files, const char *mode);

리턴 : 성공시 FILE 포인터, 실패시 NULL


fdopen 후 파일 디스크립터와 파일 포인터 상태


※ 표준 입출력 함수를 사용할 경우 : 파일 버퍼 -> 소켓 버퍼 -> 실제 전송 혹은 수신.


파일 포인터 -> 파일 디스크립터

#include<stdio.h>

int fileno(FILE *stream);

리턴 : 파일 포인터가 가리키는 파일의 디스크립터 리턴.

stream : 파일 포인터. 파일 디스크립터는 파일 모드와는 상관이 없다.


15-3. 소켓 기반의 표준 입출력 함수의 사용

파일에 버퍼가 필요한 이유

CPU가 직접 I/O를 하면 비효율적이기 때문에 버퍼에 그 내용을 저장했다가 파일로 전송한다.

flag를 두어 CPU가 여유 있을 때 system call을 통해 버퍼 내용을 파일로 보낸다.

system call을 이용하면 모아서 입출력 하는 것이 힘들다.

하지만 파일 버퍼를 이용하면 데이터를 모아서 입출력하는 데 좋아진다.

반응형
반응형

유선 : 고객에게 서비스를 제공. 멀티캐스트 방식(가입이 필요)

안테나 방송 : 서비스를 지정된 채널로 제공. 브로드캐스트 방식(가입 필요 없음)


14-1. 멀티캐스트(Multicast)

멀티캐스트 전송 방식.

UDP를 기반으로 하는 전송 방식. 

멀티캐스트 그룹(클래스 D에 속하는 IP주소)을 기반으로 멀티캐스트 패킷을 주고 받음.

하나의 멀티캐스트 패킷은 라우터를 통해서 다수의 호스트에 전송.

(한 번만 방송하면 가입자 모두 시청 가능.)

(정보를 단방향으로 제공할 때 주로 사용.)


라우팅(Routing)과 TTL(Time To Live)

라우팅 : 라우터에 의해서 패킷이 경로를 찾는 과정

TTL 

거쳐갈 수 있는 라우터의 수(홉)를 의미. 

이 값이 0이 되면 패킷은 더 이상 전달되지 못하고 소멸된다.


멀티캐스트 Sender와 Receiver

Sender : 멀티캐스트 그룹에 데이터를 전송하는 호스트

Receiver : 멀티캐스트 그룹으로부터 데이터를 수신하는 호스트

※ Sender는 서버(제공자), Receiver는 클라이언트로 구분 지을 수 있지만 요청하는 부분과는 관련이 적다.


멀티캐스트 Sender와 Receiver

 Sender Receiver 
 UDP 소켓 생성
 반드시 TTL 설정(소켓 옵션 설정)
 멀티캐스트 그룹으로 데이터 전송
UDP 소켓 생성
멀티캐스트 그룹 지정(ip_mreq)
멀티캐스트 그룹 가입(소켓 옵션 설정) 

Sender TTL 설정

state = setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*)&multi_TTL, sizeof(multi_TTL));


Receiver 멀티캐스트 그룹 지정.

struct ip_mreq

{

    struct in_addr imr_multiaddr;    // 가입하고자 원하는 D 클래스 ip주소

    struct in_addr imr_interface;     // 나의 ip 주소. htonl(IN_ADDR);을 써주면 된다.

};

※ 서로 포트는 일치 해야 한다.


14-2. 브로드캐스트(Broadcast)

브로드캐스트 전송방식

UDP를 기반으로 하는 전송 방식(=멀티캐스트와 같다.)

일반적으로 UDP 패킷과의 차이점은 전송 목적지 ip주소 뿐이다.

(멀티캐스트와의 차이점)

동일 네트워크에 속하는 모든 호스트에 동시 전송

인터넷 상에서 지역 네트워크 내에서만 브로드캐스를 허용한다. (네트워크 부하를 고려)


주소선택에 따른 브로드캐스트 방식 구분

지정된 브로드캐스트

예 : 192.12.31.255 -> 자신이 속한 IP주소만 설정 가능. (지역적 브로드캐스트를 더 많이 사용.)


지역적 브로드캐스트 

예 : 255.255.255.255 -> 알아서 현재 네트워크(LAN)에 브로드캐스트.


반응형
반응형

13-1. recv & send 입출력 함수

read & write 함수와 달리 데이터 입출력 방법에 있어서 옵션 부여.

#include<sys/types.h>

#include<sys/socket.h>


int recv(int sock, void *buf, int len, unsigned int flags);

int send(int sock, const void * buf, int len, unsigned int flags);


옵션의 종류와 의미

 - MSG_DONTROUTE (send 함수 호출시에만 사용하는 옵션)

    데이터 전송 시 라우팅 테이블(라우터에 있음)을 참조하지 않음.

    => Local network 내에서만 패킷을 전송하겠다는 뜻.

 - MSG_DONTWAIT (send, recv 함수 모두에 사용하는 옵션)

    not blocking 입출력 기능

 - MSG_OOB (send, recv 함수 모두에 사용하는 옵션)

    데이터 전송 시, 긴급 데이터(Out-of-band data) 전송

    ※ Out-of-band : 도로 상의 갓길을 생각하면 좋음.

 - MSG_PEEK (recv 함수 호출시에만 사용하는 옵션)

    버퍼에 데이터 유무 확인.


긴급한 데이터의 전송

전송 프로그램 예시

write(sock, "123", 3);

send(sock, "4", 1, MSG_OOB);

write(sock, "567", 3);

send(sock, "890", 3, MSG_OOB);


수신 프로그램 예시

① fcntl(send_sock, F_SETOWN, getpid());

파일 디스크립터를 제어하는 함수.

send_sock : 소켓의 파일 디스크립터

F_SETOWN 소유자를 설정.

getpid(); : 현재 프로세스의 ID

※ 뜻 : send_sock의 소유자를 현재 프로세스로 설정한다.
※ 사용 이유 : 어느 프로세스에게 시그널을 발생시킬지 정해야 한다. SIGURG를 받기 위해 반드시 소켓의 소유자를 지정해야 한다. 


② state = sigaction(SIGURG, &act, 0);

SIGURG : 긴급 데이터(out-of-band)를 받을 때 발생하는 시그널.


긴급 데이터 전송 시 생성되는 패킷

recv함수를 호출할 때 urgent point-1 위치부터 1바이트만 읽어들인다.

긴급 데이터 전송의 특징

- 버퍼 상황에 관계 없이 데이터 전송 ( 버퍼가 비어지길 기다리지 않고 바로 전송)

- Nagle 알고리즘 무시


입력버퍼 검사하기

sleep 함수 사용 이유 : 패킷을 온전히 다 받기 위해 사용.

MSG_PEEK : 메세지를 읽어도 소켓의 수신 버퍼에 데이터 내용을 지우지 않는다.

MSG_DONTWAIT : 버퍼가 비어 있지 않아도 바로 리턴 받기 위해서...

※ 두 개의 옵션을 같이 사용한다.


13-2. readv & writev 입출력 함수

데이터를 모아서 전송(writev) : 한 번 호출하여 한 번에 보낸다.

#include<sys/io.h>

int writev(int fd, const struct iovec *vector, int count);

리턴 : 성공시 전송한 바이트 수, 실패시 -1

데이터 여러 개(count개)를 모아서 전송한다.

fd : 데이터 전송의 목적지를 나타내는 소켓의 파일 디스크립터

vector : iovec 구조체 이름을 인자로 전달. 전송하고자 하는 데이터에 대한 정보가 담겨진다. 

count : iovec 구조체 변수의 개수.


데이터를 분산 수신(readv) : 한번 호출하여 나누어진 버퍼에 저장한다.

#include<sys/io.h>

int readv(int fd, const struct iovec *vector, int count);

리턴 : 성공시 수신한 바이트 수, 실패시 -1

fd : 데이터를 수신할 파일 디스크립터

vector : iovec 구조체 이름을 인자로 전달. 어디에 얼마칸큼 데이터를 수신할 것인지 정보를 전달.

count : iovec 구조체 변수의 개수.


배열을 정의하기 위한 구조체의 선언

struct iovec{

    ptr_t iov_base;    // 보내고자 하는 데이터의 시작주소

    size_t iov_len;    // 보내고자 하는 데이터의 크기

};


readv&writev 함수의 적절한 사용

1. 함수호출 회수가 적으면 효율적임.

2. nagle알고리즘을 적용하지 않는 경우 다음과 같은 장점을 가진다.

일반 write 3번 호출 : 3개의 패킷을 생성하여 전송

nagle알고리즘을 적용한 경우

writev 호출 : 1개의 패킷을 생성하여 전송.

3. 큰 배열을 생성하여 하드코딩해서 보내는 것보다 한번의 함수로 모아서 보내는 게 편리하다.


13-3. 윈도우즈 기반으로 구현하기

윈도우즈 기반 OOB 데이터 송수신

1. 윈도우즈 기반에서는 'signal 핸들링' 이라는 개념이 존재하지 않는다.

2. select 함수는 소켓을 통해서 예외 상황(OOB 데이터 수신)이 발생하였는지 확인 가능하다.

예시)

FD_ZERO(&except);

FD_SET(hSendSock,&except);

exceptCopy = except;

select(?, ?, ?, &exceptCopy, ?);


반응형

+ Recent posts