Tcp ip & io model

59
TCP/IP Review & IO Model NHN NEXT 남현욱

Transcript of Tcp ip & io model

TCP/IP Review

&IO Model

NHN NEXT남현욱

01

nagle algorithm“가능하면 조금씩 여러 번 보내지 말고 한 번에 많이 보내라”

01 nagle algorithm

•왜필요한가?패킷은 보낼 때마다 고정적인 비용(packet header)이 필요하다. 작은 크기의 패킷을 여러 번

보낼 경우 이 고정 비용때문에 네트워크 자원을 그만큼 더 많이 쓰게 된다. 이걸 버퍼에 모아두었

다가 되도록 묶어서 한 번에 보냄으로써 네트워크 자원을 효율적으로 쓰는 것이다.

•게임에서는?Nagle 알고리즘을 쓰면 네트워크 자원을 효율적으로 쓸 수 있긴 하지만 반대로 응답 속도가 안

좋아지게 된다. 그 때 그 때 보내는 게 아니라 모아서 보내기 때문. 따라서 반응 속도가 중요한 게

임의 경우 Nagle 알고리즘을 쓰지 않는게 좋을 때도 많다.

물론 항상 그렇듯이 case by case이기 때문에, 항상 Nagle을 쓰는게 좋고 항상 Nagle을 안 쓰

는게 좋고 이렇게 이야기할 수는 없다. 자신이 처한 상황에 따라 쓸 지 안 쓸 지 적절히 선택하는

게 좋다.

01 nagle algorithm

•동작원리Host A Host B Host A Host B

Nagle OFF Nagle ON

ACK

ACK

ACK

ACK

ACK

N

A

G

L

E

N

ACK

ACK

AGLE

01 nagle algorithm

•C++에서의구현

int opt = true;setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (const char*)&opt, sizeof(int));

Nagle 알고리즘은 기본적으로 ON되어 있다. 그래서 Nagle 알고리즘을 쓰지 않고 싶을 경우 위

코드를 이용해 Nagle 알고리즘의 사용을 해제할 수 있다.

- setsockopt : 소켓의 옵션을 변경하기 위해 사용되는 함수다. 인자로 소켓 / 소켓의 레벨 /

지정할 옵션 / 옵션 값을 지정하기 위한 포인터 / 옵션 값의 크기 를 받는다.

- IPPROTO_TCP: soket의 레벨 값으로는 IPPROTO_TCP와 SOL_SOCKET 두 가지가 있

다. 어떤 레벨이냐에 따라 지정가능한 옵션이 다른데 IPPROTO_TCP로 해야 Nagle 알고리즘 사

용여부 옵션을 바꿀 수 있다.

- TCP_NODELAY : 이름에서도 알 수 있듯이 이 옵션 값을 1로 하면(적용시켜주면) Nagle 알

고리즘이 꺼진다.

02

TCP States“단절을 감지하고, 우아하게 연결을 끊는 방법은?”

02 TCP States

•유령세션은왜생기나?Server Client

CLOSED CLOSED

02 TCP States

•유령세션은왜생기나?Server Client

CLOSED CLOSED

LISTEN

LISTEN

02 TCP States

•유령세션은왜생기나?Server Client

CLOSED CLOSED

LISTEN

LISTEN

SYNSENT

CONNECT/SYN3way-handshaking 과정을 통한 서버-클라이언트 연결 시작

02 TCP States

•유령세션은왜생기나?Server Client

CLOSED CLOSED

LISTEN

LISTEN

SYNSENT

CONNECT/SYN

SYNRECEIVED

SYN/SYN+ACK

02 TCP States

•유령세션은왜생기나?Server Client

CLOSED CLOSED

LISTEN

LISTEN

SYNSENT

CONNECT/SYN

SYNRECEIVED

SYN/SYN+ACK

ESTABLISHED

SYN+ACK/SYN

클라이언트 성공적으로연결 완료!

02 TCP States

•유령세션은왜생기나?Server Client

CLOSED CLOSED

LISTEN

LISTEN

SYNSENT

CONNECT/SYN

SYNRECEIVED

SYN/SYN+ACK

ESTABLISHED

SYN+ACK/SYN

ESTABLISHED

ACK

서버도 성공적으로연결 완료!

02 TCP States

•유령세션은왜생기나?Server Client

CLOSED CLOSED

LISTEN

LISTEN

SYNSENT

CONNECT/SYN

SYNRECEIVED

SYN/SYN+ACK

ESTABLISHED

SYN+ACK/SYN

ESTABLISHED

ACK이제 서로 데이터를 주고 받는다.

02 TCP States

•유령세션은왜생기나?Server Client

CLOSED CLOSED

LISTEN

LISTEN

SYNSENT

CONNECT/SYN

SYNRECEIVED

SYN/SYN+ACK

ESTABLISHED

SYN+ACK/SYN

ESTABLISHED

ACK

이 때 데이터를 주고 받다가 한 쪽에서 연결이 끊어지면?

비정상적인연결 끊김!

02 TCP States

•유령세션은왜생기나?Server Client

CLOSED CLOSED

LISTEN

LISTEN

SYNSENT

CONNECT/SYN

SYNRECEIVED

SYN/SYN+ACK

ESTABLISHED

SYN+ACK/SYN

ESTABLISHED

ACK

정상적 종료가 아니면 왜 데이터가 안 오는 지 알 수 없으므로 올 때까지 기다린다!

WAIT...

02 TCP States

•유령세션은왜생기나?아까 전 그림처럼 정상적으로 ‘연결이 종료되었음’이라는 메시지 없이 연결이 비정상적으로 끊

겼을 경우 무한히 대기하게 된다. 어떤 네트워크 사정때문에 데이터의 전송이 늦어지는 건지 아

니면 연결에 문제가 있는 건지 알 수 없기 때문이다. 이 때문에 유령 세션이 발생하게 된다.

•해결방법TCP 특성 상 깔끔하게 해결하기는 쉽지 않다.

heartbeat : 심장 박동처럼 일정 주기로 서버와 클라이언트가 패킷을 주고 받으면서 서로 살아

있는지 검사하는 방법이다.

keepalive : 소켓에서 자체적으로 heartbeat처럼 검사하는 방법이다. 일정 시간을 설정한 다음

그 시간을 넘으면 검사 패킷을 보내고 일정 이상 응답이 오지 않으면 연결을 종료시킨다.

02 TCP States

•TCP_LINGER옵션소켓의 연결을 종료할 때 미처 전송되지 못하고 버퍼에 남아있는 데이터를 어떻게 처리할 것인지

결정하기 위한 옵션이다. l_onoff와 l_linger 두 가지 옵션이 있다. l_onoff는 linger 옵션을 사

용할 건지 말 건지를 결정한다. 기본적으로는 linger 옵션 사용 안하고, close 호출 시 내부적으

로 정상적인 종료 과정을 진행한다.

l_onoff=1,l_linger=0

closesocket() 즉시 리턴. 버퍼에 남아 있는 데이터는 모두 파기한다.

l_onoff=1,l_linger>0

일단 정상적인 종료 과정(GracefulShutdown)을 진행하되, 지정한 시간(l_linger)이 지나도

제대로 완료되지 않으면 무시하고 위와 마찬가지로 즉시 연결 종료 후 남은 데이터는 파기시킨

다.

02 TCP States

•TCP_LINGER옵션

사용법코드예제

//Linger 옵션 지정용 구조체

LINGER ling = {0, };

ling.l_onoff = 1; //LINGER 옵션 사용

ling.l_linger = 0; //몇 초 기다릴 지 시간 설정

//LINGER 옵션 특정 소켓에 적용.

setsockopt(socket, SOL_SOCKET, SO_LINGER, (CHAR*)&ling, sizeof(ling));

// 해당 옵션이 적용된 socket 종료 과정 수행.

closesocket(socket);

02 TCP States

•GracefulShutdown데이터의 유실 없이 안전하게 소켓 간의 연결을 종료하기 위한 과정.

ServerClient

ESTABLISHED ESTABLISHED

02 TCP States

•GracefulShutdown데이터의 유실 없이 안전하게 소켓 간의 연결을 종료하기 위한 과정.

ServerClient

ESTABLISHED ESTABLISHEDFIN

FIN_WAIT_1 CLOSE_WAIT연결 종료를 시작한다는 의미로 Client에서 FIN을 보낸다.

02 TCP States

•GracefulShutdown데이터의 유실 없이 안전하게 소켓 간의 연결을 종료하기 위한 과정.

ServerClient

ESTABLISHED ESTABLISHEDFIN

FIN_WAIT_1 CLOSE_WAITACK

FIN_WAIT_2 Server는 그에 대한 응답을 보내고 연결을 종료하기 위한 준비를 한다.

02 TCP States

•GracefulShutdown데이터의 유실 없이 안전하게 소켓 간의 연결을 종료하기 위한 과정.

ServerClient

ESTABLISHED ESTABLISHEDFIN

FIN_WAIT_1 CLOSE_WAITACK

FIN_WAIT_2 LAST_ACKFIN

TIME_WAIT 준비가 끝나면 client에 FIN을 보내 연결을 종료할 것임을 알린다.

02 TCP States

•GracefulShutdown데이터의 유실 없이 안전하게 소켓 간의 연결을 종료하기 위한 과정.

ServerClient

ESTABLISHED ESTABLISHEDFIN

FIN_WAIT_1 CLOSE_WAITACK

FIN_WAIT_2 LAST_ACKFIN

TIME_WAIT

CLOSED

FIN을 받아도 Client는 종료되지 않고 한동안 TIME_WAIT가 된다. 네트워크 문제로 FIN보다 늦게 도착하는 패킷이 존재할 수 있기 때문.

ACK

02 TCP States

•GracefulShutdown데이터의 유실 없이 안전하게 소켓 간의 연결을 종료하기 위한 과정.

ServerClient

ESTABLISHED ESTABLISHEDFIN

FIN_WAIT_1 CLOSE_WAITACK

FIN_WAIT_2 LAST_ACKFIN

TIME_WAIT

CLOSED

ACK

CLOSED 이렇게 일정 시간이 지나면 종료시킴.

02 TCP States

•GracefulShutdownGraceful Shutdown에서 주의해야할 점은 다음과 같다.

1.TIME_WAIT는먼저종료를시작한쪽에서생긴다.

Graceful Shutdown과정에서 TIME_WAIT가 생기는 건 불가피한 일이다. 하지만 TIME_

WAIT단계가 서버에서 발생한다면 서버 부하의 원인이 될 수 있으므로 꼭 클라이언트에서 종료

를 시작하게 만들자.

2.Server에서TIME_WAIT가안생기게하는법

• 서버에서 클라이언트에 종료하라는 커맨드를 보낸다

• 수신한 클라이언트는 종료하겠다 전송 후 closesocket() 수행

• 클라이언트의 종료하겠다는 메시지 받은 서버는 해당 소켓에 대해 closesocket() 호출. 이

때 linger 옵션을 이용해 혹시 모를 TIME_OUT을 막음(어차피 서버에서 클라이언트로 보낼

데이터는 없을게 확실하기 때문에 linger 옵션을 사용)

03

IO Model“다양한 네트워크 IO 모델의 장단점을 알아보자”

03 IO Model

•SynchronousVsAsynchronous

Synchronous(동기)작업을 요청한 후 해당 작업의 결과가 나올 때까지 기다린 후 처리하는 것 .

IO 작업에 대한 readiness를 기다린다. 특정 IO 작업을 하기 위한 준비가 되었는 지에 집중. 그

런 이벤트의 발생을 기다렸다가 그 이벤트가 발생하면 그에 따른 적합한 처리를 한다.

Asynchronous(비동기)작업을 요청해놓고 딴 일을 하다가 해당 작업이 완료되면 그 때 완료되었음을 통지 받고 그에 따

른 처리를 하는 것이다.

IO 작업의 completion을 기다린다. 운영체제 단계의 비동기 API를 통해 이루어지며 IO 작업이

completion되면 그에 적합한 Handler를 이용해 처리를 한다.

03 IO Model

•BlockingVsNon-blockingI/O 작업에서 Blocking으로 동작할 경우 해당 I/O가 끝날 때까지 대기해야하고(I/O가 끝나기

전에는 함수가 반환되지 않는다), Non-Blocking으로 동작할 경우 작업을 완료할 수 있다면 완

료하고 그렇지 못한 상황이라면 대기하지 않고 리턴해버린다.

Socket에서의Non-Blocking설정

ULONG isNonblocking = 1;

ioctlsocket(socket, FIONBIO, &isNonBlocking);

ioctlsocket: 특정 socket의 I/O 상태를 바꿀 때 사용하는 함수다.

FIONBIO : 3번째 인자로 넘어온 속성 값이 0이면 Blocking, 0이 아니면 non-blocking으로

설정한다.

&isNonBlocking : 속성 값을 지정한 변수의 주소를 넘긴다.

03 IO Model

•BlockingVsNon-blockingNon-BlockingSocket의IO non-blocking socket에서 데이터를 전부 받으려면 아래처럼 해야한다

for (;;){ if (recvLen = recv(socket, str + length, strLen, 0) < 0) { if (WSAGetLastError() == WSAEWOULDBLOCK) Sleep(1000); // 기다림 } else { length += recvLen; if (length >= strLen) break; }}

recvLen은 이번에 받은 양, strLen은 받고자 하는 길이, length는 받은 양의 누적 합이다.

non-blocking은 recv를 호출해도 바로 리턴되기 때문에 데이터를 끝까지 읽지 못했거나 하나

도 못 읽은 경우가 발생할 수 있다. 따라서 recv 호출 후에 얼만큼 읽었는지, 현재 socket의 상태

가 어떤 지 확인하고 그에 따른 처리를 해 줘야 한다.

03 IO Model

•IO이벤트통지모델구세대네트워크I/O방식

Server

Thread1

Thread2

Thread3

Thread4

Client1

Client2

Client3

Client4

접속한 각각의 클라이언

트 별로 Thread를 하나씩

두고 따로 처리하게 만든

다. 이용자 수가 많을 수록

Thread간의 컨텍스트 스

위칭 비용이 커진다.

I/O이벤트통지모델이필요한이유

이벤트 통지 모델을 이용하면 Thread 개수를 훨씬 줄일 수 있다. 동시 접속자 수가 많을 수록 이

벤트 통지 모델을 이용하는 편이 성능이 좋을 것이다.

03 IO Model

•SELECTSELECT는 이벤트 별로 감시할 소켓들을 등록하고(fd_set), 등록된 소켓에 뭔가 이벤트가 발생

했을 경우 그걸 확인하는 방식으로 동작한다.

int select(int nfds, fd_set* readfds, fd_set* writefds,

fd_set* errorfds, struct timeval* timeout);

첫 번째 인자로 검사할 fd(file descriptor)의 개수를 설정한다. 0~fd-1까지를 검사한다.

readfds, writefds, errorfds는 각각의 이벤트에 대해 검사할 소켓의 목록을 저장한 비트필드

의 포인터이다(nullptr이면 해당 이벤트는 체크 안함). fd_set을 만들기 위해 다음과 같은 함수

들을 이용한다.

voidFD_ZERO(fd_set*fdset) : 해당 fd_set의 값을 모두 0으로 초기화한다.

voidFD_CLR(intfd,fd_set*fdset): 해당 fd_set에서 fd에 해당하는 비트를 0으로 만든다.

voidFD_SET(intfd,fd_set*fdset): 해당 fd_set에서 fd에 해당하는 비트를 1로 만든다.

voidFD_ISSET(intfd,fd_set*fdset) : fd_set에서 fd에 해당하는 비트가 1인지 검사한다.

03 IO Model

•SELECT예제코드

FD_ZERO(&readsets);while (true){ FD_SET(socket, &readsets); n = select(max_fd + 1, &readsets, nullptr, nullptr, nullptr); if (n < 0) //error 처리 return -1; for (int i = 0; i < max_fd; i++) { if (FD_ISSET(i, &readsets)) { // ... recv, accept 등 처리 } }}

03 IO Model

•SELECT장점 지원하는 OS가 많아 이식성이 좋다(POSIX 표준).

단점 검사할 수 있는 fd 개수에 제한이 있다(최대 1024개).

매번 검사할 때마다 루프로 전체 fd를 다 검사해야하기 때문에 속도가 느리다.

Async/Sync,Blocking/Non-blocking? OS 레벨에서 비동기로 IO 작업을 하며 IO가 끝났음을 통지 받아서 처리하는 것이 아니라 자

체적인 event loop를 통해 매번 소켓의 상태가 변했는지 확인한 뒤 그에 따른 처리를 하므로

Synchronous.

timeout을 nullptr로 설정하면 뭔가 이벤트가 발생할 때까지 진행하지 않는다. 따라서 이 경우

는 Blocking이다. timeout를 설정할 경우 해당 시간이 지나면 아무 이벤트가 발생하지 않아도

넘어가므로 Non-blocking이 된다.

03 IO Model

•EPOLLselect의 단점을 개선한 것이다. select가 매번 모든 fd를 다 검사해야하는 반면 epoll은 검사할

fd만 등록해 놓으면 커널이 그 fd들을 관리하면서 이벤트 발생 여부를 알려주기 때문에 더 효율

이 뛰어나다.

epoll_create(intsize)

epoll을 위한 인스턴스를 생성한 뒤 그 fd를 돌려준다.

epoll_ctl(intepfd,intop,intfd,structepoll_event*event)

epoll에 이벤트 등록 등의 일을 담당하는 함수다. epfd: epoll_create로 받은 epoll 인스턴스

의 fd. op : 어떤 동작을 할 지 결정. EPOLL_CTL_ADD는 epoll 인스턴스에 감시할 fd 추가,

EPOLL_CTL_MOD는 등록된 fd와 연관된 event 변경, EPOLL_CTL_DEL은 등록된 fd의 삭

제. 마지막 인자는 어떤 이벤트를 통지 받을 지 구조체를 정해 넘긴다.

epoll_wait(intepfd,structepoll_event*events,intmaxevents,inttimeout)

epoll_create로 만든 인스턴스의 번호를 넘기면 이벤트가 발생할 때까지 지정한 시간 만큼 기

다렸다가 이벤트가 발생한 fd들을 반환한다.

03 IO Model

•EPOLL예제코드

epollfd = epoll_create(10); //epoll 인스턴스 생성if (epollfd == -1) return -1;ev.envents = EPOLLIN; //읽을 수 있는 지(accept)ev.data.fd = listenSock;if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listenSock, &ev) == -1) return -1;while (true){ if ((nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1)) == -1) return -1; for (n = 0; n < nfds; ++n){ if (events[n].data.fd == listenSock){ connSock = accept(listen_sock, (struct sockaddr *) &local, &addrlen); if (connSock == -1) return -1; setnonblocking(connSock); // 논블로킹 설정 ev.events = EPOLLIN | EPOLLET; ev.data.fd = connSock; //accept된 socket도 epoll에 추가(recv 이벤트 통지). if (epoll_ctl(epollfd, EPOLL_CTL_ADD, connSock, &ev) == -1) return -1; } else do_use_fd(events[n].data.fd); //읽은 데이터 처리 }}

03 IO Model

•EPOLL장점 select와는 다르게 관리할 수 있는 fd 개수의 제한이 없다.

관리할 fd를 한 번만 등록하면 커널에서 관리하며 이벤트가 발생한 것만 넘겨주므로 루프 돌 때

마다 매 번 검사해야하는 select보다 훨씬 효율적이다.

단점 리눅스 커널 2.6 이상에서만 지원하므로 이식성이 떨어진다.

Async/Sync,Blocking/Non-blocking? Select와 마찬가지 이유로 Synchronous.

Select와 마찬가지로 timeout을 nullptr로 설정하면 뭔가 이벤트가 발생할 때까지 진행하지

않는다. 따라서 이 경우는 Blocking이고, timeout를 설정할 경우 Non-Blocking이 된다.

03 IO Model

•WSAAsyncSelect특정 소켓에 대해 통지 받을 이벤트와 통지 받을 때 사용할 메시지를 지정해주면 해당 이벤트가

생길 때 메시지 큐를 이용해 윈도우 메시지의 형태로 통지해주는 방식이다.

WSAAsyncSelect(SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent)

s : 이벤트를 통지받을 소켓.

hWnd : 메시지를 보낼 윈도우의 핸들.

wMsg : 해당 이벤트를 통지할 때 사용할 윈도우 메시지 값이다.

lEvent : 통지 받을 이벤트. 주로 사용되는 값으로 FD_ACCEPT, FD_READ, FD_WRITE,

FD_CONNECT, FD_CLOSE등이 있다.

03 IO Model

•WSAAsyncSelect예제 코드

int main(){ ... WSAAsyncSelect(socket, hWnd, WM_SOCKET, FD_READ | FD_WRITE | FD_CLOSE); ... }

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam){ switch(wMsg) { case WM_SOCKET: switch(WSAGETSELECTEVENT(lParam)) { case FD_READ: ... break; case FD_WRITE: ... break; } break; } }

03 IO Model

•WSAAsyncSelect장점 소켓 이벤트를 윈도우 메시지 형태로 전달 받을 수 있다. 다른 메시지 처리하듯이 일관성 있게 처

리 가능하므로 편리하다.

단점 윈도우 메시지 큐 기반이므로 사용하지 않더라도 윈도우를 반드시 띄워야 한다.

하나의 윈도우에서 윈도우 메시지와 소켓 메시지를 동시에 처리해야하므로 성능이 저하될 수 있

다.

Async/Sync,Blocking/Non-blocking? 특정 IO 작업이 완료되면 운영체제가 그때 그때 메시지를 보내주기 때문에 Asyncronous이다.

WSAAsyncSelect 함수를 호출하면 해당 socket이 자동으로 non-blocking으로 바뀐다. 따

라서 이 socket에 대한 IO 작업도 non-blocking으로 이루어지므로 non-blocking이다.

03 IO Model

•WSAEventSelectWSAAsyncSelect가 메시지 형태로 처리하는 것과는 다르게 특정 네트워크 이벤트가 발생했을

때 이와 연동시킬 이벤트 오브젝트를 등록하는 방식으로 처리된다.

WSAEVENTWSACreateEvent()

이벤트 객체를 생성해서 돌려준다.

intWSAEventSelect(SOCKETs,WSAEVENThEventObject,longlNetworkEvents)

소켓과 이벤트 객체를 짝짓는다. s는 이벤트를 감지할 소켓, hEventObject는 해당 결과를 통지

받기 위한 이벤트 객체의 핸들, lNetworkEvents는 감지할 이벤트의 종류다.

WSAAsyncSelect와 동일하게 FD_READ, FD_WRITE, FD_ACCEPT, FD_CLOSE 등등의 값

을 이용한다.

DWORDWSAWaitForMultipleEvents(DWORDcEvents,constWSAEVENT*

lphEvents,BOOLfWaitAll,DWORDdwTimeout,BOOLfAlertable)

이벤트가 발생할 때까지 기다린다.이벤트 개수, 이벤트들의 배열, 전체 이벤트 배열의 signal이

바뀔 때까지 기다릴지 여부, 타임 아웃 시간, alertable state 설정 여부 등을 지정한다.

03 IO Model

•WSAEventSelect예제코드

//event 생성 및 설정eventArray[0] = WSACreateEvent();...

WSAEventSelect(sockets[0], eventArray[0], FD_READ | FD_CLOSE);while(true) { index = WSAWaitForMultipleEvents(totalEvent, eventArray, false, WSA_INFINITE, false); index = index - WSA_WAIT_EVENT_0; WSAEnumNetworkEvents(sockets[index], eventArray[index], &netEvents);

//각 이벤트에 맞는 처리. if(netEvents.lNetworkEvents & FD_READ) { ... }

if(netEvents.lNetworkEvents & FD_CLOSE) { ... }

}

03 IO Model

•WSAEventSelect장점 무조건 윈도우를 만들어줘야 하는 WSAAsyncSelect와는 다르게 윈도우를 만들어 주지 않아

도 사용 가능하다.

단점 WaitForMultipleObject에서 처리가능한 최대 이벤트 개수가 64개다. 그 이상 처리를 위해서

는 스레드를 늘려야한다.

WaitForMultipleObject가 한 번에 여러 개 이벤트가 왔을 경우 배열에서 가장 앞 인덱스를 돌

려준다. 이것만 처리하면 뒤쪽 이벤트는 잘 처리가 안 될 수 있으므로 배열의 다른 인덱스들도 확

인을 해 주어야 한다.

Async/Sync,Blocking/Non-blocking? Select와 비슷하게 폴링하는 방식이므로 Synchronous이다.

WSAEventSelect도 WSAAsyncSelect와 마찬가지로 소켓을 자동으로 non-blocking으로

바꾼다. 따라서 non-blocking이다.

03 IO Model

•OverlappedI/OCallbackWSASend, WSARecv를 할 때 Overlapped 구조체 설정을 통해 I/O를 중첩시킬 수 있다. 완

료 루틴 함수까지 인자로 넘겨주면 해당 I/O 작업이 끝나고 난 뒤 완료 루틴 함수를 호출해준다.

WSASend(SOCKETs,LPWSABUFlpBuffers,DWORDdwBufferCount,LPDWORD

lpNumberOfBytesSent,DWORDdwFlags,LPWSAOVERLAPPEDlpOverlapped,

LPWSAOVERLAPPED_COMPLETION_ROUTINElpCompletionRoutine)

여기서 처음 5개 인자는 일반적인 recv/ send 함수와 별반 다를 바 없다.

중요한 건 lpOverlapped 인자와 lpCompletionRoutine 인자이다. lpOverlapped 인자로

WSAOVERLAPPED 구조체의 주소값을 넘기는데, 이 구조체에 I/O 작업이 끝났음을 통지하기

위한 이벤트를 등록해서 넘긴다. lpCompletionRoutine 인자로는 I/O 작업이 끝났을 때 수행

할 함수의 주소를 넘긴다. 그러면 I/O 작업이 끝난 다음 APC 큐에 들어가서 하나씩 완료 루틴 함

수를 수행하게 된다.

03 IO Model

•OverlappedI/OCallback예제코드

//완료 루틴 함수void WINAPI IOCompletionRoutine(DWORD dwError, DWORD cbTransferred, LPOVERLAPPED lpOverlapped, DWORD dwFlags);

WSAOVERLAPPED overlapped;WSABUF dataBuf;char buffer[DATA_BUFSIZE] = { 0, };

overlapped.hEvent = WSACreateEvent();dataBuf.len = DATA_BUFSIZE;dataBuf.buf = buffer;

WSASend(socket, &dataBuf, &sendBytes, 0, &overlapped, IOCompletionRoutine);

//다른 작업 수행...

SleepEx(INFINITE, true);

03 IO Model

•OverlappedI/OCallback장점 사용자가 지정한 버퍼로 바로 복사가 일어나기 때문에 데이터 복사 비용이 줄어든다.

단점 멀티 스레드에 특화된 방식이 아니다.

Async/Sync,Blocking/Non-blocking? 운영체제가 내부적으로 비동기 IO 작업을 한 뒤 IO 작업이 끝나면 그 걸 알려주는 방식으로 동작

하기 때문에 Asynchronous 방식이다.

또 I/O 작업이 완료되지 않아도 함수는 바로 리턴되고 다음 명령어로 넘어가 다른 작업을 수행

할 수 있기 때문에 non-blocking 방식이다.

Bonus

Coroutine

Bonus Coroutine

•Coroutine함수 내에서 호출한 쪽을 다시 호출할 수 있고, 다시 다른 routine에서 함수의 중간 지점을 호출

할 수 있는 것.

Routine A Coroutine Routine B

Bonus Coroutine

•Coroutine함수 내에서 호출한 쪽을 다시 호출할 수 있고, 다시 다른 routine에서 함수의 중간 지점을 호출

할 수 있는 것.

Coroutine Routine BRoutine A

Routine A 작업 진행

Bonus Coroutine

•Coroutine함수 내에서 호출한 쪽을 다시 호출할 수 있고, 다시 다른 routine에서 함수의 중간 지점을 호출

할 수 있는 것.

Coroutine Routine BRoutine A

이 시점에서 Coroutine 호출하여 Coroutine의 일부 수행

Bonus Coroutine

•Coroutine함수 내에서 호출한 쪽을 다시 호출할 수 있고, 다시 다른 routine에서 함수의 중간 지점을 호출

할 수 있는 것.

Coroutine Routine BRoutine A

다시 원래 함수 호출하여 진행하던 부분부터 다음 부분 진행

Bonus Coroutine

•Coroutine함수 내에서 호출한 쪽을 다시 호출할 수 있고, 다시 다른 routine에서 함수의 중간 지점을 호출

할 수 있는 것.

Coroutine Routine BRoutine A

이 때 다른 루틴 B에서 자기 작업 수행

Bonus Coroutine

•Coroutine함수 내에서 호출한 쪽을 다시 호출할 수 있고, 다시 다른 routine에서 함수의 중간 지점을 호출

할 수 있는 것.

Coroutine Routine BRoutine A 이 시점에서 Coroutine 호출. 이전 호출 지점에서부터 이어서 진행됨.

Bonus Coroutine

•Coroutine함수 내에서 호출한 쪽을 다시 호출할 수 있고, 다시 다른 routine에서 함수의 중간 지점을 호출

할 수 있는 것.

Coroutine Routine BRoutine A

다시 원래 함수 호출해서 하던 작업 끝까지 마무리

Bonus Coroutine

•Coroutine함수 내에서 호출한 쪽을 다시 호출할 수 있고, 다시 다른 routine에서 함수의 중간 지점을 호출

할 수 있는 것.

Coroutine Routine BRoutine A

이 때 루틴 A에서 다시 Coroutine 호출하면 이전 부분부터 이어서 진행

Bonus Coroutine

•Coroutine함수 내에서 호출한 쪽을 다시 호출할 수 있고, 다시 다른 routine에서 함수의 중간 지점을 호출

할 수 있는 것.

Coroutine Routine BRoutine A

다시루틴 A호출하여나머지 수행

Bonus Coroutine

•Coroutine함수 내에서 호출한 쪽을 다시 호출할 수 있고, 다시 다른 routine에서 함수의 중간 지점을 호출

할 수 있는 것.

이러한 특성 덕분에 비동기적인로직처리에 굉장히 유용하게 사용될 수 있다. AI의 FSM 구현

등을 쉽게 만드는 것 등에도 이용할 수 있다고 함.

C++에서의사용 C++ 언어에서 정식으로 지원하지는 않고(MS에서 C++ 17 표준에 __resumable / __await

라는 코루틴 관련 기능을 제출했다고는 함) boost 라이브러리를 이용하면 코루틴을 이용할 수

있긴 하다.

Bonus Coroutine

•Coroutine예제코드

template<typename T>using coro_t = boost::coroutines::coroutine<T>;

void test(coro_t<void ()>::caller_type& caller){ printf(“first test\n”); caller(); printf(“second test\n”); caller(); }

int main(){ printf(“first main\n”); coro_t<void ()> coro(test); printf(“second main\n”); coro(); if(!coro) { printf(“exit\n”); } return 0; }

실행결과

first mainfirst testsecond mainsecond testexit

End

Thank You!

End Thank You!

•참고자료• TCPState관련http://kuaaan.tistory.com/118http://en.wikipedia.org/wiki/Nagle’s_algorithm

• IOMultiplexing,Sync/AsyncIO관련http://www.terabit.com.au/docs/Comparison.htmhttp://icourse.cuc.edu.cn/networkprogramming/lectures/Unit9_WinSock_Multiplex.pdf

• Coroutine관련http://www.gamedevforever.com/200http://gamedevforever.com/209http://www.gamedevforever.com/289