레이블이 TCP-IP인 게시물을 표시합니다. 모든 게시물 표시
레이블이 TCP-IP인 게시물을 표시합니다. 모든 게시물 표시

2010년 10월 22일 금요일

TCP/IP 프로토콜 제작 / 파싱 및 Auditing 강좌 - 5

이번 강좌에서 배울 내용은 프로토콜을 만들려면 어떻게 해야 하는가에 대한 간단한 튜토리얼이라고 생각 해 주시기 바랍니다. 실제 프로그램을 만들고 하나하나 따라하기 식으로 한참동안 글을 작성했지만, 글을 쓰고 보니 이건 초보자를 위한 글도 아니요, 중급자를 위한 글도 아닌 애매한 글이 되어버리더군요. 조금 딱딱하지만 간단하게 정리하도록 하겠습니다.

 

 

프로토콜을 한 번 만들어 볼까요?  메신저 프로그램을 만든다고 가정하겠습니다.

구성도는 다음과 같습니다.

 

메신저 서버                --- 클라이언트

                                --- 클라이언트

                                --- 클라이언트

 

1.     원하는 기능 리스트 만들기

대표적인 기능 몇 가지만을 추려보도록 합니다.

(S Server, C Client.. B Broadcast를 의미합니다)

 

-       채팅 기능 (1:1 채팅 , N:N 채팅) (C-> S-> C)

-       개인 설정내용 전송기능 (S-> C)

-       파일 전송하기 (C-> S-> C)

-       접속 알림 기능 (C-> S-> B)

 

2.     프로토콜 만들기

프로토콜을 만들 경우 패킷의 형태를 다음과 같이 만들도록 합니다. 대부분의 프로토콜은 다음의 형태를 가지고 있지만 프로그램에 따라서 전혀 다른 프로토콜 형식을 가지는 프로그램도 많습니다.

 

BUFFER (1450 byte max)

Buffer Header

Buffer Data

 

Buffer Header 항목에는 보통 다음에 올 Buffer Data가 어떠한 형태의 데이터인지를 구분해 줄 수 있는 항목이 들어오게 되며 그 외에 기록해야 할 항목들을 기록하게 됩니다.

 

프로토콜을 만들 때에는 다음을 유념 해 주셔야 합니다.

 

    전체 기능을 포함할 수 있는 내용을 작성하도록 한다.

, 정해진 형태의 프로토콜만을 보고도 이것이 무슨 역할을 하는지를 알아낼 수 있어야 합니다.

    특정 기능을 빼고는 Fixed Size로 작성하도록 하고 되도록 Fixed Size 프로토콜은 별도로 보내도록 한다.

한 번에 여러 개의 내용을 담아서 쏘게 되면 받아들이는 쪽에서 한 번에 그만큼 더 많은 작업을 한번에 해야 합니다. 하긴 이러한 부분은 사소한 부분일 테고 받아들이는 쪽에서 조금 더 안전한, 안정적인 작업을 할 수 있게 하는 경험상의 노하우라고 해 두죠.

물론 한 번에 많은 양의 정보를 보내게 되면 패킷의 전송율이 줄어드는 장점도 있겠지만 실제 패킷의 양이 늘어난다고 해서 네트워크 시스템에 부하가 걸릴 정도의 부담은 없습니다.

    보내야 하는 내용이 ON/OFF 정도의 내용일 때는 BIT로 보내도록 한다.

Char의 경우에는 8가지 정보를, Short int의 경우에는 총 16가지의 정보를 표시하는 것이 가능합니다. 조합한다면 더 많은 정보도 가능하겠지요. 1바이트에서 2바이트 정도면 많은 정보를 표시할 수 있는데 string 값을 넣는 일은 하지 마시기 바랍니다.

    Variable 값의 경우는 반드시 내용 앞에 별도의 Length를 기록하도록 한다.

Fixed Size가 아닌 경우에는 모든 대상항목 앞에 별도로 Length를 기록해야 합니다. 여기서 얘기하는 부분은 위에서 언급한 Buffer Header 항목이 아닌 Buffer Data 항목도 포함되는 부분입니다.

    추후 프로그램의 확장을 고려하여 작성하도록 한다.

많은 분들이 간과하고 지나가시는 부분입니다. 프로토콜의 확장성을 고려하지 않는다면, 버전별 하위호환은 그냥 포기해야 한다는 얘기와 마찬가지입니다. 꼭 이부분도 신경 써 주시기 바랍니다.

          

           이제 실제 사용할 프로토콜과 구조체를 만들어 봅니다.

 

먼저 Buffer Header 입니다

Length = 8 byte

Field     { 타입 , 상태, 버젼 , 길이, 패킷 순번, reserved }

Type (타입)        : 1 byte { 메시지, 파일, 개인별 설정내용, 온라인, ping }

Status (상태)      : 1 byte { 서버 전송, 클라이언트 전송, Broadcast 전송 }

Version (버전)    : 2 byte { Main Version , Minor Version }

Length (길이)     : 2 byte – unsigned short

Packet no          : 1 byte (max 4)

Reserved           : 1 byte

 

위 내용을 그대로 구조체로 만들어 보겠습니다.

typedef struct _BUF_GHEADER

{

BYTE pkType;

BYTE pkStatus;

USHORT Version;

USHORT pkLength;

BYTE pkNo;

BYTE reserved;

} BUF_GHEADER, *pBUF_GHEADER;

 

Buffer Data 항목은 만들려면 실제로는 더 복잡합니다만, 이곳에서는 기능 구현의 목적보다는 프로토콜 작성에 의미를 두기 위해 간단하게 만들도록 하겠습니다.

 

Length = variable or fixed

Field  { message format, file format, personal setting format }

각 필드에 대한 형식은 다음과 같습니다.

message format :

전체길이

길이

타입

데이터

길이

타입

데이터

 

File format :

전체 길이

세부 길이

데이터

 

Personal setting format

전체 길이

세부 길이

데이터

세부 길이

데이터

          

단순하게 만들긴 했습니다만, 조금 더 부연설명을 하겠습니다.

 

Message format의 경우에는 세부 항목으로 나뉘어진 이유가 이모티콘 등의 삽입으로 인한 것입니다. 다음과 같은 메시지를 보낸다고 해 볼까요?

사랑해 자기. ~ 알라븅

여기에서 쪽~ 이 이모티콘의 그림이라고 할 경우 상대방 컴퓨터에서 움직이는 입술이 움찔움찔 하고 보여져야 한다면 위의 message format 같은 방식으로 해결해야 하는 것이 좋을 것입니다. 텍스트 + 이모티콘 + 텍스트의 형태로 전송되어져야 합니다. 대충 이해가 되시죠? 위와 같이 할 경우 파싱에 도움을 주는 프로토콜이 됩니다.

 

Personal setting도 마찬가지입니다. 네이트온의 경우에는 어디에서 설치해서 접속하던지, 자신의 친구목록이 저장되어져 나타나는 것을 알 수 있죠. 이것을 위한 기능이라고 생각하시면 됩니다. 그것을 전송하기 위한 포맷이 대충 저렇다고 보시면 됩니다.

 

, 이제 위의 포맷을 어떻게 헤더로 정의할 것이냐.. 하는 문제가 남았는데요. 일부러 프로토콜을 만들면서 통일시키려고 한 것이 보일 것입니다. [전체길이] [세부길이] [내용] 의 경우인데요. 물론, buffer header에 뒤에 따라 나오는 데이터의 타입이 정해져 있긴 하지만, 각자의 포맷에 맞춰서 각자의 헤더를 만드는 것도 약간 비효율적이라 생각되죠. 같은 형식이 존재하니까요.. 그래서 다음과 같이 하나로 만들어 보겠습니다.

 

typedef struct _BUF_DATA

{

UINT tLength;

char* Data;

} BUF_DATA, *pBUF_DATA;

 

이전 강좌에서 variable length를 가지는 프로토콜의 경우에 어떻게 만들어야 하는지에 대해서 설명하겠다고 한 부분이 이것입니다. , 통일되는 부분을 빼내어 구조체로 만들어 버리게 되면 그 다음은 타입에 의해서 구분만 해 주면 되는 것이지요. 위의 예는 사실 너무 간단해서 이렇게 작성하는 것에 대한 이점을 찾을 수 없을 지 몰라도 프로토콜이 좀 더 복잡해 지는 순간 제가 하고자 하는 이야기를 뼈져리게 느끼실 수 있을 겁니다.

 

헤더를 두 개로 나누어도 상관은 없습니다만, BUFDATA 자체가 반복되는 것이 아니므로 하나의 헤더로 처리하도록 하겠습니다.

 

typedef struct _HMSG_BUFFER

{

BYTE pkType;

BYTE pkStatus;

USHORT Version;

USHORT pkLength;

BYTE pkNo;

BYTE reserved;

UINT tLength;

char* Data;

} HMSG_BUFFER, *pHMSG_BUFFER;

 

실제 프로토콜은 위의 구조와는 비교도 안 될 정도로 복잡합니다만, 거의 모든 프로토콜은 위와 같은 골격을 가지고 있다고 생각하셔도 됩니다. 물론 BUFFER DATA 부분에서도 프로토콜안에 다른 종류의 프로토콜이 다중으로 포함되는 등의 아주 쉣한 경우도 많이 있지만 처음 배우는 입장에서 그런 것은 신경쓰지 않으셔도 됩니다.

 

 

저번에 약속드렸던 wireshark 대신에 사용한다던 프로그램을 자료실에 올려드리도록 하겠습니다. 오늘은 아니구.. 다음주 까지는 올려 드리겠습니다.

 

해당 프로그램에 기능 리스트를 정리 해 봅니다.

 

1.     ethereal이나 wireshark에서 dump 한 파일 읽어들이는 기능

2.     엄청나게 빠른 속도로 파일을 읽는 기능 (1G 이상의 파일을 wireshark에서 읽어들이신다면 뭔 말인지 알게 됩니다)

3.     내부적으로 내용은 변경할 수 없으나 Hexa Editor Data View 기능을 능가하는 변환기능 (내부적 패킷 검색은 안됨)

4.     내맘대로, 원하는 형식대로 파일 쪼개주는 기능 (이 기능은 없을 수도 있습니다)

 

저처럼 덤프 패킷을 껴안고 살아가는 사람들에게는 가뭄에 단비와도 같은 희망을 던져주는 프로그램입니다. 실제 이 프로그램 때문에 작업시간을 하루에서 1시간으로 단축시켰다면 믿어지시겠는지요.. 꼭 필요하신 분이 있으실 거라 믿습니다.

 

다음 시간에는 많이들 사용하고 계시는 MS-SQL 서버의 공개된 프로토콜 분석 자료를 통한 Parsing 강좌를 하도록 하겠습니다. 좋은 정보가 되길 바랍니다.

TCP/IP 프로토콜 제작 / 파싱 및 Auditing 강좌 - 3

저번 강좌에 빼먹은 내용중에서 Little Endian / Big Endian 을 언급했어야 하는 후회가 듭니다만, 추후에 프로토콜 분석 시에 간단하게 설명드릴 것으로 약속드리고 넘어가겠습니다 ^^;

 

 

이번에는 여러분들이 패킷을 수집하는데 있어서 가장 많이 사용되는 wireshark (ethereal)에 대해서 알아보도록 하겠습니다.

 

일단 이름이 wireshark가 된 이유에 대해서 간단하게 짚고 넘어가야 하겠네요.

많은 분들께서 아직도 Ethereal이라는 용어에 익숙하실 것으로 알고 있습니다.

그러나 이름이 wireshark로 변경되게 된 이유는 ethereal 개발자가 회사를 옮기게 되면서 그렇게 된 것이지요.

 

Wireshark라는 이름은 2006 5월에 Gerald Combs라는 ethereal 오리지날 개발자가 WinPcap 으로 유명한 CACE라는 회사에 입사하게 되어, ethereal이라는 이름을 더 이상 사용하지 못하게 되면서 사용법부터 기능까지 99% 이상 동일한 wireshark라는 프로그램이 탄생하게 된 것입니다. Ethereal은 업데이트가 이루어지지 않고 있는 반면에 wireshark는 그 이후로 계속해서 꾸준하게 업데이트가 지속되고 있습니다. (사실 ethereal 사이트도 아직 존재합니다)

 

자세한 내용은 원문을 참조하시고.. http://www.wireshark.org/faq.html#q1.2

이러한 이유로 앞으로는 그냥 wireshark라고 부르도록 하겠습니다.

 

wireshark의 경우에는 내부적으로 무척 방대한 기능이 존재하며 저도 필요한 부분만을 사용하고 있을 뿐 더 이상 자세한 것은 알고 있지도 못합니다.

단지, 일정 부분만을 알더라도 패킷을 수집하거나 분석하는 데에는 전혀 문제가 되지 않으므로 필요한 부분을 알리는 선에서 넘기도록 하겠습니다.

 

자세한 것을 알고싶은 분들 께서는 http://www.wiresharktraining.com/ 으로 고고~

 

먼저 프로그램이 필요하겠지요.

http://www.wireshark.org/ 이곳에서 안정버전을 다운로드 받아서 설치하시고 다음의 강좌를 보시기 바라겠습니다.

, 설치할 때 winpcap 드라이버를 설치하겠느냐고 물어보는데 같이 설치하셔야 합니다. 네트워크의 패킷을 수집하는 녀석은 winpcap 드라이버가 하는 일이고, 그것을 수집해서 파일로 떨구거나 분석 해 주는 부분이 ethereal이 하는 일입니다. pcap이라는 의미가 packet capture의 줄임말입니다. 다운로드방법이나 설치방법을 설명하느라 공간을 낭비하진 않겠습니다.

 

목차

2. Wireshark 기본적인 툴 사용법

2.1 이 툴은 무엇?

2.2 어떻게 캡쳐하나요?

2.3 캡쳐한 데이터는 어떻게 사용하나요?

2.4 보다 전문적인 사용법을 위해서는 (TIP)

2.5 원하는 프로그램에 사용하기 위한 코드는?

 

2.1 이 툴은 무엇?

앞에서 언급한 네트워크 패킷을 캡쳐하는 프로그램입니다.

, 네트워크 카드로 들어오는 RAW(가공되지 않은) 패킷들을 집적 받아볼 수 있는 프로그램이란 얘기지요. 더군다나, 알려진 프로토콜 중에서 일부분에 대해서는 직접 파싱을 하여 내용을 보여주기도 합니다. (TCP/IP 같은 공개된 프로토콜의 경우에는 파싱해서 내용을 다 보여줍니다) 또 무료로 사용할 수 있으니 대중적으로 사랑받는 프로그램이 아닐 수 없겠지요.

 

지겹긴 하지만, 프로그램의 특징에 대해서 나열해야 할 것 같습니다.

내용은 wireshark.org 에 있는 내용 중에서 발췌했으며, 알아두어야 할 부분을 정리하겠습니다.

 

-       몇백개의 프로토콜을 분석할 수 있으며 계속 추가되고 있음

-       멀티플랫폼에서 사용 가능함 (Windows / Linux / Solaris / OS X )

-       여러 개의 파일포맷을 읽고/쓰기 가능 (tcpdump, Microsot Network Monitor )

-       캡쳐하면서 gzip으로 압축 가능

-       Ethernet / IEEE802.11, PPP/HDLC 등을 읽어들일 수 있음

-       패킷을 분석하면서 색상을 적용하여 분석해 줌

-       암호화 패킷들을 분석해 줌 (IPsec, Kerberos, SSL/TLS )

-       XML 등으로 내보낼 수 있음

 

대충 이정도로 정리할 수 있겠습니다.

 

잠깐! 본 강좌에서는 WinPCAP 드라이버를 사용하기 때문에 802.11 등의 무선 프로토콜은 캡쳐하지 못합니다. 강좌를 따라하다가 왜 내 노트북 네트워크 카드는 안나와!!’ 라고 소리치지 마시고, 꼭 무선랜이 아닌 유선랜을 사용해야 한다는 것을 기억 해 주시기 바랍니다. 만약 무선랜 패킷을 캡쳐하시려면 wireshark.org 내에서 AirPCAP 이라는 드라이버를 별도로 구매하셔야 합니다. 가격은 기능에 따라서 대충 20만원에서부터 60만원대 까지 다양하게 있습니다.

 

2.2 어떻게 캡쳐하나요?

일단 wireshark를 실행시킵니다. 그러면 아래와 같은 황당한 화면이 나옵니다.

 

 

설명도 없고, 아무런 UI도 안보입니다. 단지 메뉴와 도구버튼만 있을 뿐. .

일단 행동으로 옮겨보도록 하지요.

 

Capture 메뉴버튼을 누르고 맨 위의 Interfaces… 항목을 선택합니다. 아래 같은 창이 열립니다.

 

 

여기에 현재 자신이 가지고 있는 Ethernet 카드 들이 보이게 됩니다. (다시 한 번 말씀드리지만 무선 랜카드일 경우에는 AirPCap 드라이버를 구매/설치하지 않았을 경우 나오지 않습니다)

 

궁금한 내용은 사항은 왼쪽 하단의 ‘Help’ 형님께 맡겨두도록 하고 일단 캡쳐를 하기 위한 준비를 하도록 하지요.

먼저 자신의 실제 네트워크 카드가 무엇인지를 알아야 합니다. 저 같은 경우에는 Realtek 어쩌구.. 가 실제 네트워크 카드가 되겠습니다. 그럼 그 카드 옆에 있는 ‘Start’ 버튼을 눌러주시면 바로 패킷이 수집되기 시작합니다.

 

오오.. 드뎌 휑 하던 메인화면에 무엇인가 복잡한 글씨들이 보이기 시작합니다.

 

 

, 대충 화면을 구경했으니 Capture -> Stop 을 누르시던지, Ctrl + E 를 누르시던지, 아니면 도구모음의 Stop Capture 버튼을 눌러서 수집을 종료 해 주시기 바랍니다. (도구모음에 마우스를 갖다대면 풍선이 뜹니다)

참, 광랜의 속도로 다운로드 받으면서 Start 버튼을 눌러서 다운받지는 마시기 바랍니다. 컴터가 버틸지 장담 못합니다.

자세한 내용은 다음장에서 다시 설명 드리도록 하고, 수집하는 방법에 대해서 한 가지의 방법을 더 설명 드리도록 하겠습니다.

 

대충 어떻게 수집하는지는 알았는데, 원하는 프로토콜 ( TCP) 만을 잡는법을 알려드리겠습니다.

 

패킷의 수집을 종료시켰으면, 다시 Capture->Interfaces… 항목을 선택하시기 바랍니다.

위에 나와있는 그림을 참조하시고, 수집하고자 하는 네트워크 카드 항목의 우측에 Start 버튼 대신에 Option 버튼을 눌러주시기 바랍니다.

 

 

 

파일이나 기타 등등은 다 무시하시고, Capture Filter 항목을 보시기 바랍니다.

그곳에 다음과 같이 적어봅니다. (앞으로 따옴표는 빼세요)

‘tcp’

그리고는 맨 아랫쪽에 있는 Start 버튼을 눌러보시기 바랍니다. 패킷이 잘 수집되지 않을 경우에는 인터넷을 띄우고, 메신저 통신을 좀 해 보시면 아주 주루룩~ 하고 데이터들이 수집되는 것을 확인하실 수 있습니다.

 

 

 

머 대충 이 정도의 메시지 들이 나올 것이라 생각됩니다.

 

2.3 캡쳐한 데이터는 어떻게 사용하나요?

, 타이틀을 그냥 처음에 정했던 것으로 가자니 좀 우습게 되어버렸습니다만..

어쨌건 이번에는 수집한 데이터를 어떻게 보는지에 대해서 알려드리겠습니다.

 

일단 위와 같은 화면을 다들 보셨다는 가정하에 내용을 설명드립니다. (자꾸 언급합니다만, 자세한 사항은 Help 형님에게 ^^)

 

메인 화면에는 세 가지의 창이 있습니다.

맨 위에 창은 수집한 패킷들을 정렬하여 간단한 대표정보만을 보여주고 있습니다.

두 번째 창에서는 현재 선택한 패킷에 대한 상세 정보를 파싱하여 보여주고 있습니다.

세 번째 창에서는 실제 수집한 데이터를 표시 해 주고 있습니다.

 

먼저 맨 위에 창의 내용을 요약해보겠습니다.

순서대로 No, Time, Source, Destination, Protocol, Info 등이 보이는데요.

각각의 항목은 다음과 같습니다.

 

-       No : 패킷의 순번을 가리킵니다.

-       Time: 시간을 microsecond 단위까지 보여줍니다 (상당히 유용하죠)

-       Source: 패킷을 보낸쪽의 주소입니다.

-       Destination : 패킷을 받는쪽의 주소입니다.

-       Info: 패킷의 주요 내용을 요약해서 보여줍니다.

 

굳이 설명 드리지 않아도 충분히 아실 거라 생각됩니다. 중요한 것은 두 번째 창과 세 번째 창의 내용을 볼 줄 알아야 한다는 것입니다.

 

두 번째 창에서 나오는 내용은 패킷의 내용을 파싱 한 경우입니다. 위의 예제에선 TCP 항목을 파싱하였기 때문에 내용은 (data) 영역에 그냥 뿌려지지만 나머지 헤더의 내용들은 모두 파싱해서 메뉴트리 형태로 보여주고 있습니다. 대충 내용을 예로들어 볼까요?

 

 

 

중간에 Internet Protocol (TCP/IP 중의 IP 영역입니다) 헤더를 확장하여 보았습니다.

마우스로 해당 항목을 클릭 할 경우 실제 패킷의 내용 중 어디에 해당하는지 아래에 표기가 되며 그것이 어떤 내용을 뜻하는지 보여주고 있습니다.

 

저번 강좌에 있었던 내용 중 하나를 복습해볼까요?

 

0xC0 0xA8 0x02 0x6F 값이 192.168.2.111 이라는 값으로 파싱되었네요. 어떻게 한 것일까요?

그냥 C0 10진수로 하면 192가 됩니다. 마찬가지로 A8 168이란 값이겠죠. 이렇게 16진수 값을 단순하게 10진수로 변경만 하면 원하는 값이 나오게 되네요. IP 주소를 전달하는게 무척 간단하지요?

물론 이처럼 단순하게 1바이트(CHAR) 10진수로 변경하면 맞는 값이 나오는 경우도 있지만 그렇지 않은 경우가 더 많겠죠. 왜 그런걸까요? 프로토콜이니까.. 만든 사람 마음대로니까.. 라고 할 수 있겠습니다요.

 

2.4 보다 전문적인 사용법을 위해서는 (TIP)

거창하게 제목이 붙었습니다만, 슬슬 제가 강좌를 쓰면서도 지겨워지기 시작해서, 간단하게 몇 가지만 알려드리겠습니다. 제가 지겨운데 읽는 분들은 오죽 하시겠습니까.

 

-       데이터 수집 시 원하는 내용만 필터링 하기

-       모은 데이터를 편하게 볼 수 있도록 필터링 하기

-       패킷 데이터를 파일에 저장하기

-       모은 데이터를 다른 프로토콜로 파싱하기

 

- 데이터 수집 시 원하는 내용만 필터링 하기

특정 IP와 특정 포트의 TCP 데이터를 수집하고자 할 경우에 다음과 같이 적습니다.

‘host 192.168.0.1 and tcp port 3333’

위와 같은 경우는 특정 Application이 통신하는 내용을 확인하고자 할 때 사용합니다. 앞의 host 대신에 source destination을 사용할 수 있습니다.

 

- 모은 데이터를 편하게 볼 수 있도록 필터링 하기

몇 가지의 방법이 있지만, 수집 시 필터링을 못하였다 하더라도 모은 데이터에 Filter 항목에 내용을 적용함으로써 데이터를 필터링 할 수 있습니다. 예를 들어봅니다

화면 상단의 도구모음 바로 밑에 Filter 항목에 ip.dst == 192.168.2.111 라고 적는다고 가정합니다.

위와 같이 적을 경우 destination ip 주소가 192.168.2.111인 경우를 다 보여주게 됩니다.

Filter 항목은 워낙에 다양해서 다 설명드릴수가 없습니다. 사실 Expression 항목을 눌러보시면 그 양이 얼마나 방대한지를 아실 수 있으리라 생각됩니다. 그리고, 다 알려고 하지 않으셔도 됩니다.

어쨌든 내용을 적용하려면 Apply를 눌러 데이터를 필터링하세요.

 

- 패킷 데이터를 파일에 저장하기

저장하는 방법에는 두 가지 방법이 있습니다. 수집을 완료해서 정지시킨 후에 저장하는 방법이 첫 번째이고, 수집하기 전 위에서 언급하였던 Options 항목을 통해 수집 방식을 설정하여 저장하는 것이 가능합니다.

Capture File(s) 항목에서 저장 옵션을 정할 수 있으며 대표적인 기능으로는, 크기별로 나누어 저장하기, 일정한 크기나 파일수에 도달하면 수집 종료하기, 일정 파일수에 도달하면 기존 파일 덮어써서 저장하기 등등이 있겠네요. 효율적으로 저장하기 위해서는 여러가지 옵션을 사용하여 저장해야 할 것입니다. 실제 서버에서는 몇초만에 1GB의 데이터가 저장이 되어버리는 경우도 많이 보았습니다. 유용한 옵션이죠.

 

- 모은 데이터를 다른 프로토콜로 파싱하기

, 자주 사용되는 항목은 아닙니다만, 만약 TCP 데이터임에도 불구하고 다른 필터를 적용해서 해독하고자 할 경우에는 (종종 프로그램이 프로토콜 종류를 인식하지 못하기도 합니다) 이것을 사용하게 됩니다. 예를 들어 TCP로 전송 된 메신저 프로토콜은 wireshark에서 ‘MSNMS’ 프로토콜로 자동 인식하여 파싱하여 주지만, 자동으로 변환하지 못할 경우에는 사용자가 직접 그 프로토콜로 강제변환 시킬 수 있다는 의미입니다.

방법은 첫 번째 창에서 오른쪽 버튼을 눌러 ‘Decode As’ 항목을 눌러서 선택하시면 되겠습니다.

 

자세한 내용은 책을 펴고 공부를 좀 해 보시거나 Help 형님을 꼭 불러주세요.

 

2.5 원하는 프로그램에 사용하기 위한 코드는?

, 이번 강좌의 마지막입니다만..

근데 뜬금없이 여기서 코드를 나열하는 것도 우습고, devpia에 강좌를 올릴 때 코드 하나 잘못올리면 엄청나게 욕먹는다는 얘길 들어서 강좌 전체에 코드를 싹 빼버릴까 하는 심각한 고려를 하고 있습니다. 워낙에 고수 분들이 많으셔서 사실 코드를 올리는 것도 겁나네요. 소심한 A형이라.. ㅋㅋ

 

어쨌든 이번 강좌에는 코드부가 빠졌습니다.

사실 wireshark로 덤프시킨 패킷을 실제 프로그램에서 사용할 수 있는 코드를 샘플로 넣어드리고자 했습니다만, 강좌 전체에 코드를 넣을까 말까 계속 고민 해 보고 결정하겠습니다.

 

, 그리고 다음 번 강좌 내용을 좀 변경할까 생각 중입니다.

챕터 하나가 추가될 수 있고 아니면 필요한 내용만 끼워넣을 수도 있겠네요.

그리고 까막(crowback__)님의 유틸리티를 언제 올려드려야 하는 지 순서도 지금 못정하겠네요. .

 

일단 wireshark로 이것저것 패킷을 수집하셔서 데이터를 보시다 보면 참 재미있을 것이라 생각됩니다. 솔직히 TCP/IP 프로토콜 책 한 권을 옆에 두고서 공부한다면 금상첨화이겠지만, 강좌를 따라오는 데에는 그 정도까지의 지식은 필요치 않습니다. 그냥 재밌게 보시길 바랍니다. 신기한 정보들이 참 많이 왔다갔다 합니다.

 

몸이 아파서 휙 잠적해 버릴까 생각도 해 보았는데 꾸준하게 몇 분이서 올려달라고 해 주셔서 감사한 마음에 이번 장 올립니다.

틀린 내용이나 지적하실 내용이 있으면 언제든지 남겨주시길 바랍니다.

 

항상 말씀드리지만, 마지막까지 읽느라 고생하셨습니다. 홧팅~

TCP/IP 프로토콜 제작 / 파싱 및 Auditing 강좌 - 2

약속 드렸던 대로 먼저 기본이론에 대해서 정리 하도록 하겠습니다.

사실, 글을 쓰기 전에 쓸 내용을 머리속에 떠올리다가 보니 자세하게 하자면 한도끝도 없이 이론만 주구장창 설명해야 할 것 같아 고민이 많이 되었습니다만, 그냥 편하게 생각하기로 했습니다.

아무튼 여기 설명되는 내용은 정말 필요한 내용을 이해하기 위해서 개인적으로 쓴 글이기 때문에 전문성을 기대하지는 않으시는 편이 정신건강에 도움이 될 것 같습니다.

 

읽어 보시고 궁금하신 내용이나 틀린 부분이 있다면 게시판을 이용 해 주시거나, 아니면 제 메일로 보내시면 되겠습니다. 혹은 더 설명이 필요하다고 생각되는 부분이 있다면 저도 자료를 찾아서 보충내용을 올리도록 하겠습니다.

참고로 제 메일 주소는 ihopi73골뱅이gmail.com 입니다.

 

흠.. 안전벨트 매셨죠? 그럼 갑니다 ^^

 

목차

 

1. 기본 이론

    1.1 프로토콜이란?

    1.2 파싱이란?

    1.3 네트워크 패킷?

    1.4 헥사 코드보기

 

1.1 프로토콜이란?

 

쉽습니다. 서로간에 통신 규약이라고 생각하시면 됩니다.

예를 들어서 같은 나라에 사는 동일한 언어를 사용하는 사람들이라도, 업무에 따라서 서로가 이해해야 하는 언어가 다릅니다.

짜장면 집에 가서 짜곱 2개요! 하고 외쳐도 알아듣는 그 무엇이란거죠. (사실 정해지지 않은 규약은 프로토콜이 아닙니다만.. ㅎㅎ)

 

가상의 예를 한 번 들어보도록 하지요.

통신보안이 무척 필요한 곳에 전화가 걸려옵니다. 그럴 경우 전화를 해도 될 사람인지 아닌지를 판단하기 위해서 다음의 절차를 거치게 됩니다.

 

질 - 통신보안! 소속과 암호를 대주세요.

답 - 통신보안! 아무개 과 김대리입니다.

질 - 해당 팀 오늘의 접속코드를 불러 주시기 바랍니다.

답 - 네. 무엇무엇 무엇입니다.

질 - 어떤일로 전화를 하셨습니까?

답 - 네. 본 부서에서 누구누구 사원에 대한 정보를 알고 싶은데요...

 

사실, 간단하게 쓴 글이긴 합니다만, 실제로 SSL 통신 같은 곳에서 인증을 위해 사용되는 방식을 글로 적은 것입니다.

SSL(Secure Socket Layer)에 대해서는 별도 설명드리지 않겠습니다만, 기회가 된다면 강좌에 포함하고 싶은 항목이기도 합니다.

위와 같은 통신 규약을 정해놓고 그에 따라 적절히 응대를 하였을 경우 통신을 할 수 있도록 만들어 주는 규약이라고 생각하시면 됩니다.

 

두산 동아백과사전에 보면 이렇게 나오네요.

 

'정보기기 사이 즉 컴퓨터끼리 또는 컴퓨터와 단말기 사이 등에서 정보교환이 필요한 경우, 이를 원활하게 하기 위하여 정한 여러 가지 통신규칙과 방법에 대한 약속 즉, 통신의 규약을 의미한다.'

 

그러니까 앞으로 프로토콜 이라고 하면 기기간의 통신 등을 하기 위한 규약이다, 라고 생각 하시면 되겠습니다.

 

1.2 파싱이란?

 

음, 사실 쉽게 생각하면 쉽고, 어렵게 풀어쓰자면 어려워지는 제목이네요.

좀 쉽게 설명하자면 다음으로 축약될 수 있을 것 같습니다.

 

- 어떤 데이터를 원하는 모양으로 만들어 내는 것

- 입력된 데이터를 분석하는 것

- 소스코드의 문장구조를 분석하는 작업

 

즉, 특별한 데이터를 분해하고, 분리해서 원하는 형태로 가공하는 것 정도로 생각하면 되겠지요.

 

코딩을 처음 시작할 때 파싱한다, 토큰이다, 라는 말 등이 무척 어렵게 느껴졌을 때가 있었기 때문에 제 설명이 충분하게 될 지는 미지수입니다만, 여하튼 파싱이란 위에 설명된 것이라고 생각하시면 됩니다.

 

단, 향후 사용되는 파싱이라는 용어는 강좌인 프로토콜에 한정되어져 있기 때문에, 프로토콜을 파싱한다.. 라는 용도로 사용됨을 알려드립니다.

 

1.3 네트워크 패킷?

 

네트워크 패킷이란 무엇일까요?

실제로 설명드리기 위해서는 아주 깊~숙하고 근본적인 얘기까지 파고들어야 될 위험이 있어서 정말 간단하게 설명드리고자 합니다.

단 여기에서 설명되는 내용을 이해하기 위해서 기본적인 이론은 OSI 7 Layer 등이나 TCP/IP 프로토콜을 설명한 책을 꼭 한번 읽어보시길 권해드립니다.

 

컴퓨터와 컴퓨터가 통신을 하기 위해서는 통신규약, 즉 프로토콜을 사용한다고 말씀드렸습니다.

대표적인 것으로는 TCP/IP, UDP 등이 있겠지요.

본 강좌의 중심인 TCP/IP를 보자면, 대충 Application 에서 통신하기 위해서 다음의 절차를 거칩니다.

 

예를 들어 A 컴퓨터에서 B 컴퓨터로 '지금 바빠?' 라는 메시지를 보낸다고 하지요.

그러면, 이런 메시지를 A 컴퓨터에서 보내기 위해서는 B 컴퓨터의 정보를 미리 알아야 하겠지요. 일단 알고 있다고 가정하고..

 

그러면 A 컴퓨터에서 SEND를 눌렀을 경우 데이터가 감싸져서 네트워크 카드를 통해서 발사됩니다. 네트워크 카드를 통해서 나간 데이터가 제대로 B 컴퓨터를 찾아가기 위해서 데이터 덩어리 앞단에 B 컴퓨터의 주소를 넣어서 보내게 되겠지요.

자, 여기에서 나온 용어가 바로 주소 등을 기록하는 머릿부분인 헤더와 실제 내용인 바디 부분으로 나뉘게 됩니다. 이것 전체를 '패킷' 이라고 부르게 됩니다.

 

자, 그럼 이론은 여기까지 하고, 굳이 네트워크 패킷을 설명 한 이유가 무엇일까요?

 

사실, wireshark를 이용한다거나 한 부분에서 벌써 패킷 스니핑을 이용한 capture를 한다는 것 쯤은 눈치 채신 분 계신가요?

즉, 본 강좌의 핵심 중 하나인 Auditing(감사) 부분은 Packet Sniffing 기법으로 처리하려고 합니다.

그렇기 때문에 패킷을 설명 드렸던 것이고 우리가 알아야 할 중요한 것만 적고서 끝내겠습니다.

 

자, 우리가 앞으로 보게 될 데이터는 패킷 데이타란 겁니다. 즉, 헥사 코드만 열라게 들여댜 봐야 한단 소리죠.

자, 샘플입니다.

 

00 1a 4d 52 3e 5a 00 02 b3 af d1 f7 08 00 45 00

00 32 95 8f 40 00 80 06 df 0f c0 a8 02 67 c0 a8

02 6f 75 31 c0 de b3 76 8d 7e c5 4b a9 6b 50 18

ff 9a a9 e7 00 00 00 00 00 00 37 2e 33 2e 30 00

 

아아.. 벌써 머리가 아픈 분이 계시지 않나요?

우리는 저것이 먼소린지를 알아야만 합니다!!

 

도저히 들여다 볼 생각이 안드시는 분은 이쯤에서 코멘트에 '훗, 내 체질이 아니야' 라고 외치신 후에 자게에 분풀이를 하시면 됩니다.

 

아무튼, 패킷을 분석하기 때문에 중요한 사항은 다음과 같습니다.

 

- 패킷은 전송하기 쉬운 형태로 쪼개어져서 보내진다. 이것을 MTU라고 하는데, 기본적으로는 1500 Byte이지만 변경이 가능하다.

- 사실 Application에서 MTU 사이즈를 조정해서 보내어져도, 라우터 등에서 못받아주는 경우도 있다. 그러므로 1500을 기억하자.

- 물론 1500 바이트가 넘는 데이터를 보냈을 경우 그것이 쪼개져 있다는 것을 알아내고서 우리가 합쳐줘야 한다.

 

흠, 가장 중요한 내용을 적은 것 같네요. 네, 사실은 컴퓨터에서 하는 모든 일들을 우리가 해야 하는거죠.

즉, 패킷을 쪼개고 다시 합치는 일은 컴퓨터에서 알아서 다 해 주는 것입니다만, 실제로 패킷스니핑을 하게 될 경우 가공되지 않은 데이터, 즉 RAW 데이터가 올라오게 된다는 겁니다. 우리가 이것을 파싱하기 위해서는 RAW 데이터를 가공해야 한다는 의미란 거죠.

 

데이터를 합치기 위해서는 TCP/IP의 프로토콜 문서를 볼 줄 알아야 합니다. 기본적인 이론을 알고 있어야 한단 얘기지요.

네. 거짓말이 아닙니다. 그러나, 강좌 마지막 부분에 되도록이면 많은 부분을 풀어서 알아야 할 부분에 대해서는 알 수 있도록 해 드리고, wireshark를 이용하거나 crowback__님이 제공 해 주실 유틸리티에는 이 헤더정보가 graphical 하게 보여지고 있으므로 굳이 신경쓰지 않아도 되는 부분이 있습니다. (이 자리를 빌어 crowback__님께 다시한 번 감사의 인사를)

 

자꾸 글이 길어집니다. 이만 마쳐야지 다음것으로 넘어가겠네요.

모르겠으면 자꾸 질문을 해 보세요. 그러면 숨겨진 고수분들이 불쑥불쑥 나타나서 아주 시원한 해답을 주실겁니다.

단, 본인도 어느정도 노력을 하고 자료를 찾아보는 수고는 거치셔야겠죠?

 

1.4 헥사 코드보기

 

음, 위의 샘플에 잠깐 등장했던 헥사 코드를 보기 위해서는 어떠한 정보를 알고 있어야 하나요?

 

요놈도 사실 깊게 가면 한도끝도 없이 설명해야 할 것 같아서, 정말 필요한 내용만을 간단히 설명합니다.

순서대로 갑니다. 하나하나 따라오세요.

 

BIT

 

컴퓨터는 0과 1로 이루어진 숫자를 이해한다고 하지요? 들어보셨죠?

그럼 비트에서 우리가 알아야 할 것은 무엇인가요?

 

- 한 바이트는 8개의 비트로 구성되어져 있다.

- 계산할 때는 짝수의 배수로 생각하면 쉽다.

 

1 바이트는 8개의 비트라는 것이 매우 중요합니다.

즉 한 바이트가 표현할 수 있는 비트의 최대는 1111 1111 이란 것입니다.

실제 바이트 데이터를 보고서 값을 어케 읽나? 하는 것은 다음을 기억하세요.

 

0000 0001 일 경우 1 입니다.

0000 0010 일 경우 2 입니다.

0000 0100 일 경우 4 입니다.

0000 1000 일 경우 8 입니다.

 

자 그럼 1이 맨 앞에 있는 경우는? 쉽게 128이 되겠지요.

그러면 모두가 1111 1111인 경우는? 모두를 다 더해버리면 됩니다.

 

128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = 255 입니다. 어디서 많이 본 숫자 아닌가요?

모두가 0인 경우를 포함해서 한 바이트가 가질 수 있는 최대의 숫자는 256이 되겠지요.

 

자 그럼 2 바이트일 경우는 어케 됩니까?

솔직히 적기 무지 귀찮지만, 그래도 적어봅니다. 1이 하나씩 된다고 가정하면 다음의 숫자가 됩니다.

 

256, 512, 1024, 2048,    4096, 8192, 16384, 32768 이 되겠습니다. 앞의 숫자랑 다 더하면 어케 되는지요?

정답은 65535 입니다. 진짜 많이 본 숫자지요?

 

네. 위의 숫자들은 컴퓨터에서 가장 많이 보는 숫자 중의 하나겠지요.

메모리의 숫자라던가, 화면의 해상도라던가.. 하는 곳에서 말이지요.

 

자, 중요한 것은 여기에서 나오게 됩니다.

실제 컴퓨터에서 표기할 때 '65535' 란 값은 어떻게 표기하는게 가장 좋을까요?

 

솔직히 저 같으면 65535라는 값을 그대로 적었으면 좋겠습니다만, 다음을 한 번 생각 해 보시기 바랍니다.

 

65535를 비트로 표현하면 1111 1111  1111 1111 이 됩니다.

10진수로 표현하면 당연히 '65535' 겠지요.

16진수로 표현하면 FF FF 값이 됩니다. (알기 쉽게 0xFF 0xFF 라고 표현하기도 합니다)

 

음, 아쉽지만 승자가 여기에서 가려지게 되는군요.

네트워크 패킷을 전송할 때 가장 적은 코드로 보낼 수 있는 데이터가 된다는 이야기지요.

사실 잘 느낌이 안온다면 4byte의 int가 표현하는 숫자를 생각 해 보시면 됩니다.

 

int가 표현할 수 있는 한계치는 -2,147,483,648 to 2,147,483,647 입니다.

한계치의 값을 표기할 때 FF FF FF FF 이면 되는 값이 10진수로는 너무 길지요?

 

HEX라고 줄여부르는 이 hexadecimal (16진수)는 우리가 사용하는 10진수와 다르게 16까지 하나의 수로 표기합니다.

즉 1 , 2 , 3 ... 8 , 9 , 10, 11 ... 이렇게 두 자리로 넘어가는 것이

1 , 2 , 3 ... 8 , 9 , A , B , C , D , E , F 까지 표기할 수 있는 것이지요.

그러므로 더 많은 데이터를 더 적은 공간에 표현할 수가 있게 되는 것입니다.

 

꼭 알아야 하는 것만 정리하죠.

 

CHAR        - 1 byte    8 bit

SHORT      - 2 byte    16 bit signed

INT            - 4 byte    32 bit signed

DOUBLE     - 8 byte    64 bit signed

 

추가) LONG은 32bit 이며 LONGLONG이 64bit 입니다.

기본 타입은 32bit system의 microsoft windows (2000 이상)라는 가정하에서 작업합니다. 혼동 없으시길 바랍니다.

 

각 타입 별 바이트 길이는 꼭 레퍼런스 자료를 참조 해 보시기 바랍니다. 앞으로 지겹게 보시게 될 테니까요.

signed , unsigned 등은 각자 자료를 찾아보시기 바랍니다.

 

마지막으로 한 가지만 더 언급하고 마쳐야겠네요.

 

실제 프로토콜을 사용하다가 보면 BIT는 flag 방식으로 사용되는 경우가 많습니다.

즉 몇 번째 비트가 '1' 로 되어져 있으면 어떠한 값이 'ON' 되어있다고 생각해라.. 라는 것이지요.

 

실제 코딩을 할 때 가장 많이 접하게 되는 자료이기도 하므로 언급하고 넘어갑니다.

 

 

다음 강좌부턴 좀 정리하고 올리겠습니다. 온라인 상에서 글을 작성하다가 보니 도대체 제대로 적고있는지도 모르겠네요.

의미인 즉슨, 다음번 강좌부터는 조금 기간이 오래 걸려서 나오게 된다는 의미지요 ^^

 

강좌 마지막까지 읽으신 분들께 박수 보내드립니다. 수고하셨습니다.

TCP/IP 프로토콜 제작 / 파싱 및 Auditing 강좌 - 1

 

일단, 제가 몸담고 있는 회사에서 얻은 지식과 경험을 되도록 회사에 누를 끼치지 않는 범위 내에서 여러분과 공유 해 볼까 합니다.

강좌를 작성하는데에 있어서 목표로 첫 번째로는 초보자가 쉽게 접근할 수 있도록 설명하는 것과 두 번째로는 한 번 보고 끝내는 것이 아니라 추후에도 이용할 수 있는 자료를 만들도록 하는 데 있습니다.

 

처음에는 기본적인 이론만을 되도록(^^) 재미있고 쉽게 설명드리겠으며 중반부에 가서는 wireshark (구 ethereal이죠)를 이용한 패킷을 캡쳐하는 방법 등, 그리고 그것을 분석하는 방법에 대해서 설명드리구요. 마지막으로는 실제 장비와 통신할 수 있는 간단한 테스트 샘플을 제작하고 (아마도 걍 TCP로 할 확률이 높습니다만) 실제로 전송되는 패킷들을 잡아서 데이터를 분석 한 후에 그 데이터를 다시 데이터베이스에 적재하는 샘플 프로그램을 제작 할 예정입니다. 헉헉...

 

먼저, 당부의 말씀을 드릴 것은 제가 회사에서만 작업을 하게 되기 때문에, 한 번에 그다지 많은 양을 올릴 수 없음을 양해의 말씀 드립니다. 언제 끝낼 지 장담을 못드리겠다는.. 그리고 강좌의 글을 쓴 적이 너무나도 오래되어서 글을 알아보기 힘들 수도 있다는 점을 미리 알려드립니다. 또한 바램이 있다면 이 자료는 네트워크 패킷에 대한 분석작업을 동반하기 때문에, 실제로 안좋은 일에 사용 될 확률도 높다는 것을알고 있습니다만, 절대 그런일이 없길 바라는 마음입니다.

 

마지막으로, 이 작업은 쉽게 쓰려고 해도 이해하기가 난해한 일이 될 수 있습니다. 이 강좌를 같이 하시기 위해서는 주변에서 무한한 인내심의 소유자 라는 소리를 듣거나 프로그래머이긴 하지만 종종 하늘을 쳐다보며 남들이 알아듣지 못하는 말을 중얼거리는 사람 등의 특이한 성격을 가진 소유자라면 언제든 환영한다는 점을 밝혀드리고 싶네요.

 

대충 목록을 만들어 보았습니다.

목록은 추후에 언제든 제 맘대로 바꿀 예정입니다. 그런 일이 생겨도 노여워 하지 마시기를 부탁드립니다.

 

1. 기본 이론

    1.1 프로토콜이란?

    1.2 파싱이란?

    1.3 네트워크 패킷?

    1.4 헥사 코드보기

 

2. wireshark 기본적인 툴 사용법

    2.1 이 툴은 무엇?

    2.2 어떻게 캡쳐 하나요?

    2.3 캡쳐한 데이터는 어떻게 사용하나요?

    2.4 보다 전문적인 사용법을 위해서는?

    2.5 원하는 프로그램에 사용하기 위한 코드는?    - 코딩

 

3. TCP/IP SAMPLE Application 제작

    3.1 원하는 작업 리스트 정하기

    3.2 프로토콜 만들기

    3.3 실제 Application 제작    - 코딩

    3.4 프로그램 테스트

    3.5 프로토콜 파싱하기    - 코딩

    3.6 Auditing 데이터로 활용하기 위해서는?

    3.7 데이터베이스에 적재하기 SAMPLE Application 제작    - 코딩

 

대충 이정도가 되겠네요.

옆에 '코딩' 이라고 써놓은 곳에서는 실제 프로그램을 만들게 됩니다.

개발환경은 Visual C++ 6.0을 사용합니다. 혹 원하시는 분이 계시다면 2008로 만들어볼 수도 있겠습니다만, 너무 기대하지 마세요.

제가 워낙에 구세대라서.. 훗.

 

사실 wireshark 를 이용하기도 하지만, 실제 회사에서 사용하는 프로그램은 여기서 활동하고 계신 crowback__ 님께서 만들어 놓으신 것으로 사용합니다. 바로 제 옆자리에 있기 때문에 갖은 협박과 회유를 통해 얻어보긴 하겠습니다만, 본인의 의사도 중요하니까요.

 

강좌도 아니면서 벌써부터 글만 올려놓은 것은 제가 제 스스로에 대한 다짐을 하기 위해서라고도 할 수 있겠네요. 중간에 흐지부지 끝날확률이 높기 때문에, 반응을 해 주시는 분이 계시다면 그만큼 힘이 나지 않을까요?

네트워크를 대한 기초적인 용어

1. Sub Network

인터넷을 사용하기 위해 네트워크를 세팅해 보면 서브넷 마스트라는 것을 세팅해야한다.

서브 네트워크는 하나의 네트워크를 여러 물리적,논리적 네트워크로 나누어 쓰기 위해 만든것으로 서브 네트워크란 많은 네트워크 단위를 만들기 위해 필요한것이다.

 

2.Host ,Peer

Host-각각의 시스템을 의미한다.즉, 네트워크에 존재하는 컴퓨터 시스템 하나 하나를 호스트라고 생각하면 된다.

Peer(피어)-일종의 논리적 개념이다. 피어는 네트워크의 끝,즉 종단점을 의미하는 것이다. 예를 들어 호스트 A와 호스트 B가 통신을 할 경우 네트워크의 각 종단점 (피어)은 호스트 A와 호스트 B가 된다.Peer to Peer 란 각 종단점을 연결한다는 의미로 호스트가 서버에 의지하지 않고 직접 통신을 하는것 을 말한다.

3.LAN

Local Area Network의 약자로 우리말로 표현하면 "근거리 네트워크"이다. 즉 LAN은 근접한 컴퓨터 시스템을 이용해서 네트워크를 구성하는것을 의미한다.

4.Packet(패킷),Protocol(프로토콜)

네트워크에서는 서로 데이터를 주고받아야만 한다. 사람과 사람이 말로 자신의 뜻을 전달하듯이 네트워크에서는 서로 데이터를 주고받는데, 이러한 데이터의 묶음을 패킷이라곻 나다.

즉 팻킷은 편지의 뭉치 등으로 생각할수 있다.

 

프로토콜은 데이터 패킷을 전송하기 위해 만든 것이 바로 프로토콜이다(컴퓨터간에 정보를 주고받을 때의 통신방법에 대한 규칙과 약속)

정보기기 사이 즉 컴퓨터끼리 또는 컴퓨터와 단말기 사이 등에서 정보교환이 필요한 경우, 이를 원활하게 하기 위하여 정한 여러 가지 통신규칙과 방법에 대한 약속 즉, 통신의 규약을 의미한다.

통신규약이라 함은 상호간의 접속이나 절단방식, 통신방식, 주고받을 자료의 형식, 오류검출방식, 코드변환방식, 전송속도 등에 대하여 정하는 것을 말한다. 일반적으로 기종()이 다른 컴퓨터는 통신규약도 다르기 때문에, 기종이 다른 컴퓨터간에 정보통신을 하려면 표준 프로토콜을 설정하여 각각 이를 채택하여 통신망을 구축해야 한다. 대표적인 표준 프로토콜의 예를 든다면 인터넷에서 사용하고 있는 TCP/IP가 이에 해당된다.

정보통신의 상대방은 일반적으로 원격지에 있다. 따라서 정보를 전송하기 위해서는 정보를 전기적인 신호의 형태로 변환하고 그 변환된 신호가 통신망을 통해 흐르도록 하는데, 통신망에는 정상적인 신호의 흐름을 훼방하는 여러 가지 현상이 존재하게 된다. 이러한 현상은 정확한 정보의 전송을 방해하여 도중에 오류가 발생되는 원인이 된다.

프로토콜이라는 규약의 집합 속에는 이러한 오류에 대응하기 위한 약속이 대단히 중요하다. 또한 정보를 정확하고 효율적으로 전송하기 위해서는 송수신 개체 간에 서로 정보의 전송 시점과 수신 시점을 맞추는 일(동기화)도 수행해야 하고, 정보 흐름의 양을 조절하는 흐름 제어방법도 역시 사전에 약속하여 프로토콜 속에 포함해야 한다.

이러한 오류제어, 동기, 흐름제어, 코드변환, 전송속도 등에 대한 약속 이외에도 통신하는 상대방의 위치에 따라 통신 개체가 어느 OSI 계층에 있는 가와 효율적인 정보전송을 위한 기법, 정보의 안전성(보안)에 관한 약속들도 프로토콜의 범주에 포함되어야 한다.

 

5.NIC(Network interface card)

네트워크상에서 다른 컴퓨터 시스템에 데이터를 보내거난 받을 때 사용하기 위한 하드웨어를 말한다. 이런 종류의 하드웨어는 모뎀이나 랜카드 등이 해당되는데, 각각의 NIC는 자신에게 주어진 유일한 식별 번호를 가지고 있다. 이 식별 번혼는 네트워크에서 아주 중요하게 쓰인다.

 

6.NDIS(Network Driver interface Specification)

Microsoft사와3Com사가 개발한 것으로 네트워크 장치의 드라이버를 연결하는 일종의 인터페이스이다. 이 인터페이스를 통하여 프로그래머는 상대 시스템의 장치의 종류에 상관없이 NDIS만 호출하여 프로그래밍을 할수 있도록 한다.

 

7.Checksum(체크썸)

데이터의 수신과정에서 데이터에 오류가 발생했는지 확인하는 작업이다. 송신측에서 데이터를 보낼 때 디지털의 데이털를 아날로그 신호로 바꾸고, 이 신호는 통신 케이블을 통해 전달된다. 통신 케이블은 어떤한 종류랃도 주위의 간섭으로 인해 신호가 바뀌어 데이터가 유실되거난 변할 수 있기 땜누에 송신측은 체크썸을 통해서 받은 데이터의 비트 수를 체크한다.전달된 결과가 참이라면 참이라면 데이터는 오류가 없는것으로 간주된다.

 

8.Deamon(System Deamon)

데몬은 일종의 백그라운드 작업(Background process)을 의미하는 용어이다. 네트워크에서의 데몬은 대부분이 서버인데, 서버는 보통 사용자의 접근을 계속해서 기다려야 하기 때문에 백그라운드

프로세싱이 필요하다.윈도우즈 서버 패밀리는 각종 데몬들이 시스템 윈도우즈 부팅과 동시에 실행되는 telnet,ftp,http등의 잘 알려진 서버들이 모두 데몬이다.

 

9.inetd

데몬은 항상 서버에서 실행된다. 데몬이 실행되면 시스템의 자원을 소비하는데, 많은 수의 데몬이 동싱에 실행되면 상당한 자원이 낭비된다. 즉 서버는 클라이언트의 접근이 언제 이루어질지 모르므로 항시 대기하고 있어야만 한다. 하지만 언제 접근할지도 ,어느 정도의 숫자가 접근할지도 모른다. 심한 경우 하루에 단한번 접근 하지 않을 수있다. inetd는 이런 데몬들이 시스템 자원들을 쓸데 없이 낭비하지 않도록 교통 정리하는 하는것으로 inetd역시 데몬의 일종이다.inetd는 클라이언트의 접근이 있으면 그때서야 필요한 데몬을 실행 시킨다. 즉,시스템의 자원을 효율적으로 관리해 주는 데몬이다.

 

10.Hop(hop count)

홉(hop)은 하나의 링크를 의미한다.링크란 네트워크 내에서 경유하게 되는 하나의 단계를 의미한다.예를 들어 송신측을 출발한 패킷은 몇번의 라우터를 거쳐 수신측에 이르는데,여기에서 각 라우터를 링크라고 생각하면된다.따라서 hop을 많이 거칠수록 목적지를 찾는데 오래 걸린다는 의미가 된다. 홉 카운트(hop count)는 패킷이 목적지까지 도착하는 데 경유하는 링크의 수를 의미한다.홉 카운트는 패킷의 헤더에 기록되는데 라우터는 보통 이 홉 카운터를 체크해서 비정상적으로 높은 패킷은 삭제를 해서 더 이상 네트워크상에서 돌아다니지 못하게 막는다.이는 네트워크의 리소스가 목적지를 잃은 비정상적인 패킷들로 부하가 걸리는 것을 막아 네트워크의 자원을 관리하려는 것이다.

특히 스트림 패킷 (보통 TCP)의 경우에는 보통 목적지 까지 의 경유가 유지되지만 데이터그램 패킷(보통 UDP)의 경우에 는 길을 잃은 패킷이 많으므로 홉 카운트의 관리는 필수적이다.

WSA 함수군

앞서 설명했던 소켓 함수들은 OVERLAPPED된 입출력과는 무관한 함수들이다. 따라서 이 구조체를 사용하기 위해서는 기존의 recv()등의 표준 함수 이외에  WSA접두어가 붙은 함수를 이용해서 소켓을 생성하고 데이터 패킷을 주고받아야한다.

함수:WSASocket()

설명:socket()함수와 마찬가지로 소켓을 생성한다.

원형: SOCKET WSASocket(

int                                    af,

int                                    type,

int                                    protocol,

LPWSAPROTOCOL_INFO    lpProtocollnfo,

GROUP                             g,

DWORD                            dwFlags

);

리턴값 :성공하면 생성된 소켓을 리턴하고, 실패하면 INVALID_SOCKET을 리턴한다.

1.af

네트워크 주소 패밀리를 의미하는 지정자.

2.type

새로운 소켓의 타입.

3.protocol

사용할 프로토콜의 종류를 지정한다. 이것은 주소 패밀리와 함께 사용된다.

4.lpProtocollnfo

WSAPROTOCOL_INFO 구조체의 주소. 생성되는 소켓의 속성을 정의하는 구조체이다.

5.g

예약된 멤버 변수이다.

6.dwFlags

소켓 속성을 지정하는 플래그. 이 플래그에서 WSA_FLAG_OVERLAPPED를 지정하면 OVERLAPPED를 상요할 수 있다.

=WSASocket()함수는 마지막 dwFlags인자에 WSA_FLAG_OVERLAPPED를 지정하여 OVERLAPPED 입출력을 이용할 수 있도록 소켓을 생성할 수있다.

 

함수: WSAAccept()

설명: listen()함수로 대기 상태가 된 서버가 접속을 기다리다 실제 접속을 해 주는 함수이다.

원형: SOCKET WSAAccept(

SOCKET                          s,

struct sockaddr FAR         *addr,

LPINT                              addrlen,

LPCONDITIONPROC          lpfnCondition,

DWORD                           dwCallbackData

);

리턴값: 접속한 peer에게 할당된 소켓

1.s

listen()함수가 호출된 후 연결에 이용될 소켓.

2.addr

연결 엔티티의 주소의 버퍼 포인트로 ,옵션 이다.

3.addrlen

addr 인자의 크기

4.lpfnCondition

컨디션 함수의 주소.

5.dwCallbackData

콜백 함수의 데이터 . lpfnCondition 컨디션 함수의 인자이다.

 

함수:WSAConnect()

설명: WSAConnect()함수는 다른 어플리케이션의 소켓으로 접속을 시도한다.

원형: int WSAConnect(

SOCKET                             s,

const struct sockaddr FAR   * name,

int                                      namelen,

LPWSABUF                         lpCallerData,

LPWSABUF                         lpCalleeData,

LPQOS                               lpSQOS,

LPQOS                               lpGQOS

);

리턴값 : 성공할 경우에는 0을 ,실패할 경우에는 SOCKET_ERROR값을 리턴한다.

1.s

클라이언트에게 할당된 소켓.

2.name

서버의 소켓 주소,

3.namelen

name 인자의 길이.

4.lpCallerData

클라이언트가 연결을 할때 정보를 제공하는 WSABUF 구조체의 포인터.

5.lpCalleeData

서버가 클라이언트에게 제공해 주는 정보.

6.lpSQOS

클라이언트 소켓에 QOS를 요청하는 인자.

7.lpGQOS

소켓 그룹의 QOS를 요청하는 인자.

 

 

함수 : WSASend()

설명:  이 함수는 지정된 소켓을 통해서 다른 어플리케이션으로 패킷을 전송한다.

원형: int WSASend(

SOCKET                              s,

LPWSABUF                          lpBuffers,

DWORD                               dwBufferCount,

LPDWORD                           lpNumberOfBytesSent,

DWORD                               dwFlags,

LPWSAOVERLAPPED           lpOverlapped,

LPWSAOVERLAPPED_COMPLETION_ROUTINE       lpCompletionROUTINE

);

리턴 값: 성공할 경우 보내어진 바이트의 수를 리턴

1.s

클라이언트의 소켓.

2.lpBuffers

WSABUF 구조체 배열의 포인터. 각 WSABUF 구조체는 버퍼의 포인터와 버퍼의 길이를 가지고있다.

 3.dwBufferCount,

lpBuffers 배열에 있는 WSABUF 구조체의 인덱스.

4.lpNumberOfBytesSent,

I/O 완료 포트를 연산할 경우 이 인자를 호출하여 바이트의 수를 리턴 받는다. 

5.dwFlags

호출 방식을 지정하는 플래그.

6.lpOverlapped,

WSAOVERLAPPED 구조체의 포인터.

7.lpCompletionROUTINE

보내기가 완료(complete)되었을때 호출하는 루틴.

 

함수: WSARecv()

설명: 이 함수는 지정된 소켓을 통해서 다른 어플리케이션으로 패킷을 수신한다.

원형: int WSARecv(

SOCKET                                  s,

LPWSABUF                              lpBuffers,

DWORD                                   dwBufferCount,

LPDWORD                               lpNumberOfBytesRecvd,

LPDWORD                               lpFlags,

LPWSAOVERLAPPED               lpOverlapped,

LPWSAOVERLAPPED_COMPLETION_ROUTINE        lpCompletionROUTINE

);

1.s

송신할 소켓.

2. lpBuffers

WSABUF   구조체의 배열의 인덱스.각 WSABUF 구조체는 버퍼와 버퍼의 길이 포인터를 포함하고 있다.

3. dwBufferCount

lpBuffers 배열에서의 WSABUF  구조체의 인덱스.

4. lpNumberOfBytesRecvd

바이트의 수를 지정하는 포인터로, 받기 연산 완료를 받았을대 호출된다.

5.lpFlags

포인터의 플래그.

6.lpOverlapped

WSAOVERLAPPED 구조체의 포인터.

 

7.lpCompletionROUTINE

받기 연산이 완료되었을때 호출되는 함수의 Completion 포이터.

IOCP 사용하기

IOCP와 이벤트를 연결시키면 우리는 입출력이 완료 되는 시점에서 이벤트를 발생시킬 수 있다.

IOCP와 이벤트 핸들을 연결시키려면 우선 소켓을 만들고 입출력 완료 포트를 생성해야 한다.

함수 :CreateloCompletionPort()

설명:이 함수는 IO Completion port를 생성하여 핸들 값을 반환한다.

원형: HANDLE CreateloCompletionPort(

HANDLE              FileHandle,

HANDLE              ExistiongCompletionPort,

DWORD               completionKey,

DWORD               NumberOfConcurrentThreads);

 

리턴값:성공하면 I/O Completion Port의 핸들 값을 리턴하고, 실패할 경우 NULL을  리턴한다.에러의  상태를 확인하려면 GetLastError()함수를 호출하면 된다.

1.FileHandle

Overlapped I/O completion을 연결할 파일 핸들.CreateFile()함수를 이용할 경우에는 핸들을 얻기위해 반드시 FILE_FLAG_OVERLAPPED 플래그를 지정해야한다.

 

2.ExistinggCompletionPort의 핸들. 이 파라미터에 이미 존재하는 Completion Port를 지정하면 해당 핸들로 연결된다. 새로 생성하고 싶다면 이 인자에 NULL을 지정한다.

 

3.CompletionKey

완료키를 지정한다. 모든 I/O completio Prot는 CompletionKey를 포함한다.

 

4.NumberOfConcurrentThreads

동시에 발생을 허용하는 쓰레드의 숫자를 지정한다.

Completion Port 생성에 성공했다면 이제 GetQueuedCompletionStatus()함수를 호출해서 이벤트를 기다린다. 만일 이벤트가 발생하지 않는다면 이 함수는 블로킹 상태로 있을 것이며, 이벤트가 발생한다면 블로킹을 해제한다. 마치 WaitForMultipleObject()함수와 같은 역활을 수행하는것이다.

 

함수:GetQueuedCompletionStatus()

설명:완료 포트 (Completion port)의 상태를 얻어온다.

원형: BOOL GetQueuedCompletionStatus(

 HANDLE              CompletionPort,

 LPDWORD           lpNumberOfBytesTransferred,

 LPDWORD           lpCompletionKey,

 LPOVERLAPPED *lpOverlapped

 DWORD               dwMilliseconds);

리턴 값: 성공할 경우 TRUE를 , 실패할 경우 FALSE를 리턴한다. 완료 포트의 상태는 네 번째 인자인 lpOverlapped에 리턴한다.

 

1.CompletionPort

이벤트 발생을 대기할 I/O Completion port의 핸들.

 

2.lpNumberOfBytesTransferred

입출력된 데이터의 크기를 리턴 받을 변수

 

3.lpCompletionKey

지정된 CompletionKey.  이 Key 값으로 누구에게 (어떤 파일로 )데이터가 입출력되었는지를 알수있다.

4.*lpOverlapped

입출력 결과를 리턴 받을 OVERLAPPED 구조체의 포인터.

 

5.dwMilliseconds

밀리세컨 단위의 숫자로 Completion port에 패킷 전달이 완료되기를 기다리는 시간이다. 즉 이 인자로 주어진 시간이 지나면 블로킹이 해제 된다.

Overlapped IO와 IOCP 이야기 - 4번째

좀 많이 늦었죠? 죄송합니다 ^^ 이것 저것 하는 일이 많아서 말이죠. 그리고 예제 코드도 만든다고 삽질한 지라…… ^^: 여하튼 시작해보겠습니다.

♠ IOCP를 이용한 프로그래밍의 실제
이번은 실제로 어떻게 프로그래밍을 해나가느냐에 초점을 맞추도록 하겠습니다. 예제코드를 분석하는 식으로 하겠습니다. 아마 이 정도만 하시면 Overlapped IO 그리고 IOCP의 개념과 흐름을 정리하는 시간이 될 수 있을 걸로 생각이 됩니다.

자 이제 작성한 제가 조금 허접하게 ^^: 작성한 예제 코드를 보시면서 하나하나 설명해 나가겠습니다.

1. IOCP 초기화 처리

첨에 나오는 것은 초기화 시킬 때 처리입니다. 보시면 몇 줄 정도의 코드가 나오죠? 물론 클래스화 처리가 되어서 조금 간단합니다만 천천히 설명해보도록 하겠습니다. 일단 주의할 것 소켓 생성입니다. 이렇게 WSASocket으로 생성할 시에는 반드시 WSA_FLAG_OVERLAPPED로 옵션을 주어서 생성시켜주셔야 합니다. 안 그러면 리슨 소켓에서 Accept시에 나온 클라이언트 소켓에 Overlapped 속성이 붙질 않습니다. 물론 socket으로 하신다면 이 점은 신경 쓰실 필요가 없겠습니다.
그리고 이런 코드가 나왔네요?

If(!m_IocpHandler.Create(0,&ErrCode))

이건 보면 아시겠지만 IOCP 커널 객체를 생성하는 코드 입니다. 현재 클래스화 되어 있어서 무척 간단하게 보이실 겁니다. 그럼 이 내부 코드를 한 번 볼까요?

위의 간단한 한 줄 코드가 호출하는 함수의 내부 모습입니다. 내부도 그리 복잡하게 보이지는 않아 보입니다. 그렇지 않으시나요? 동의 못하신다면 ^^ 어쩔 수 없고요. 자 살펴봅시다. 먼저 제가 작성한 이 IocpHandler란 클래스는 IOCP 객체에 해당되는 모든 동작을 클래스화 시킨 클래스 입니다. (☞ 모두 설명하지는 않을 겁니다. 하지만 보시면 어떤 작용을 하는지 파악하실 수 있을 거라고 생각됩니다.)
그리고 그 중에서도 가장 중요한 함수가 바로 이 Create 함수입니다. Create 안 하면 처리를 시작할 수 없잖아요.
이 Create 함수 내부를 보시면 CreateIoCompletionPort() 란 함수가 보입니다. 앞에서도 설명했던 함수 입니다. 앞 강좌를 열심히 읽어보신 분들이면 아마 기억하실 겁니다. 그 때 인자를 설명하면서 뭐라고 그랬죠? IOCP 커널 객체를 생성하려면 첫 번째 인자에 INVALID_HANDLE_VALUE 라는 값을 줘야 한다고 했죠? 그래서 이렇게 첫 번째 인자에 저런 값이 들어간다면 아 생성하는 구나! 하고 생각하시면 되겠습니다. 그리고 두 번째 인자 Existing이라는 단어가 변수 명에 들어가 있었죠? 일전에 설명했듯이 이건 이미 생성된 IOCP 객체에 대한 핸들을 가리킵니다. 그래서 NULL 을 주었습니다. 막 생성하는데 줄 것도 없고 또 줄 것도 없고요. 그리고 세 번째 0으로 되어 있는 것은 Completion Key 입니다. 생성하는 곳에서는 Completion Key를 신경 쓰실 필요가 없습니다.
그리고 가장 중요하다고 느껴지는 Concurrent Thread 수입니다. 이것은 실제 얼만큼만 돌아가게 허용할 것인가를 정해주는 것이라고 했습니다. 3번째 강좌 문서를 열심히 보셨던 분이라면 아시겠네요. 일반적으로 0을 줍니다. 0으로 주면 CPU 개수를 Concurrent Thread 수로 잡습니다. 그래서 디폴트 인자로 0으로 주어서 특별하게 뭘 쓰지 않으면 0이 되도록 만들었습니다. 그래서 에러 코드를 쓰지 않으신다면 아무 인자도 주지 않아도 되겠죠? 다음 코드로 넘어가보죠.

저번에 했던 강좌들을 생각해보면서 코드를 봅시다. 제가 객체를 생성하고 나서 해야 할 일이 뭐라고 했나요? 쓰레드 풀을 생성 하는 것이라고 했죠? 그래서 CreateThreadPool이라는 함수를 호출하여 IOCP Worker 쓰레드 풀을 생성합니다. 이것의 내부를 한번 살펴봅시다.
함수 내용을 살펴보면 먼저 두 번째 인자로 들어온 지정할 쓰레드 개수에 따라 쓰레드를 생성합니다. 하지만 0(0은 디폴트 값입니다.)으로 들어오게 된다면 2 * 프로세서 수 + 2 의 공식에 의하여 쓰레드를 생성합니다. 이 공식은 꼭 이렇게 만들어야 하는 것은 아니지만 이렇게 해보니 일반적으로는 괜찮더라 라고 하는 공식입니다. 물론 혹자는 뒤에 + 2를 빼서 하는 경우도 있습니다. 하지만 이런 공식보다는 자신의 서버 어플리케이션의 성격을 파악해서 더 필요하다면 더 만들어 주는 것이 좋을 것입니다. 그리고 IIocpProcessThread* piProcessThread 이 인자가 궁금하실 것으로 보입니다. 이것은 구현의 분리를 위해 IIocpProcessThread 라는 인터페이스를 정의하고 그 인터페이스를 상속 받은 클래스에서 쓰레드 함수를 구현하기만 하도록 만든 것입니다. 아주 간단한 클래스 설계이니 한번 보시면 아실 것이라고 생각합니다. 그래서 여기서는 IocpWorkerThreadStartingPoint 이라는 static 함수에서 저 인터페이스를 상속, 구현한 클래스의 함수를 호출하여 쓰레드를 만들게 됩니다. 이 구현 함수가 IOCP 에서 가장 중요하다고 할 수 있는 Worker 쓰레드입니다. 이건 조금 있다 설명하겠습니다.
그리고 밑에서는 MemPooler라는 템플릿 클래스를 쓰는 것을 볼 수 있습니다. 이것은 메모리 풀링을 위해서 쓰는 것입니다. 이건 하이텔의 안기찬(아이디 빌려 쓰신다고 하신 것 같은데 확실치 않군요.)님이 올린 소스 코드를 참조하고 조금 고쳤습니다. 구현을 잘 해 놓으셨더군요. 여하튼 서버에서 일일이 new, delete 한다는 것은 상당한 시간 손실, 오버헤드를 발생시킵니다. 그래서 이렇게 메모리 풀링을 하는 것이죠. 메모리 풀링이라는 것은 어렵게 생각하실 필욘 없습니다. 일전에 설명 드렸던 쓰레드 풀링의 개념을 생각하시면 됩니다. 미리 만들어 놓고 필요할 때 마다 불러 쓰고 필요 없으면 다시 풀에다 돌려 놓는 것이죠. 일종의 고정 메모리라고 생각하시면 되겠습니다. 뭐 아주 간단히 구현하지만 고정 배열을 많이 잡아두셔도 되겠네요. 여하튼 메모리 할당을 이런 식으로 해서 처리를 합니다. 그래서 일일이 new, delete 등을 하지 않도록 하고 있습니다.

그럼 Init() 란 함수가 끝났습니다. 실질적으로 IOCP 서버에서 초기화 과정이 이 함수에서 처리되는 겁니다. 위에는 코드가 생략되어 있지만 리슨 소켓 생성하고 bind 시키고 그리고 listen 모드로 해놓고, 그리고 IOCP 객체를 생성하고 쓰레드 풀을 만드는 과정까지가 IOCP서버가 초기에 하는 일이 되겠습니다. 여기서 메모리 풀을 만드는 것은 선택 사항이 되겠죠. 간단하게 공부를 위해서 만드시는 것이라면 하실 필요가 없겠습니다. 저는 어디까지나 이런 방식을 사용한다는 것을 보여드리고 위해서 한번 사용해 봤습니다.

2. Accept 시에 해야 될 일은?
그럼 다음에는 해야 할 일이 뭐라고 생각하시나요? 소켓도 초기화 하고 IOCP 객체도 생성하고 했으니 이제는 외부 접속을 받아들여야 하겠죠? 그럼 일반적인 서버에서 외부 접속을 뭘 했죠? 예! 그렇습니다. Accept를 했죠. 만약 이 말이 0.5초 만에 튀어 나오지 않는 분이라면 아마 소켓 프로그래밍을 공부 안 하신 분일 겁니다. 그런 분은 소켓 프로그래밍부터 공부해보시길 권해드립니다.
자 그럼 accept 시에도 IOCP 서버라면 뭔가 특별한 일을 더 해야 하겠죠? 그 부분을 살펴봅시다.

accept 시에 처리하는 코드입니다. 아 참고로 여기 그리고 위에 있는 코드들은 생략된 것이 많습니다. 에러 처리라던가 등등 이런 것은 생략되어 있으니 생략되어 있는 부분은 같이 있는 예제 코드를 참조하시길 바랍니다. 그럼 보죠. 일단 accept를 합니다. 그럼 클라이언트가 올 때까지 대기하다가 접속해오면 클라이언트에 대한 소켓을 리턴 할 겁니다. 여기까지는 일반적인 얘기겠죠? 그럼 그 다음부터 조금 다릅니다. 보통은 이 소켓을 인자로 넘기는 쓰레드를 만들거나 할 터인데, 여기서는 약간의 옵션을 처리합니다. 위에 보시면 setsockopt 함수로 소켓 옵션을 조절하는데 인자 내용을 보시면 아시겠지만 소켓 버퍼 크기를 조절합니다. 반드시 해야 된다고 하는 강제성은 없지만 하면 좋다고 하는 권고사항이 되겠습니다. 아시다시피 IOCP가 Overlapped IO에 대한 결과를 통보 받는 메커니즘이기 때문에 커널 단 버퍼를 사용하지 않고 직접 제공된 버퍼를 사용한다고 일전에 설명했습니다. 그래서 이 버퍼를 사용하지 않기 때문에 0으로 만들어 버리는 것이죠. 그래서 저런 코드가 들어간 것입니다. 그런데 지금 코드상으로 recv, send 버퍼 두 개를 모두 0으로 만들었지만 recv 는 해도 소용이 없다거나 오히려 성능 안 좋게 한다는 얘기도 더러 있습니다. 확인된 사실은 아닙니다만 보시는 분들은 사용하실 때 한번 정도 실험을 해보심이 좋을 듯 합니다.
그리고 다음에 할 일은 뭐냐 하면 소켓 컨텍스트를 할당하는 일입니다. 이것은 서버 구성에 따라 틀려질 수 있는 일입니다. 뭐 일단은 다른 분들은 확장 오버랩드 구조체를 하나 정의하고 new 해서 사용하시거나 하십니다. 그런데 저는 소켓 컨텍스트, 그리고 IO 컨텍스트 두 가지를 사용합니다. 그리고 이 소켓 컨텍스트를 Completion Key로 사용합니다. 이것 또한 설계에 따라 틀려지는 일이기 때문에 옵션이라고 볼 수 있습니다만 클라이언트에 대한 어떤 확장 Overlapped 구조체를 할당하는 작업만은 거의 필수라고 보시면 되겠습니다. 아 참 컨텍스트, 컨텍스트 하는데 이거 뭐냐는 분들도 있으실 것 같군요. Context 이 것은 뭐라고 해야할까요? 일종의 정보의 단위라고 보시면 되겠습니다. Socket Context는 소켓과 관련된 정보의 모음, IO Context 는 IO 와 관련된 정보의 모음이라고 생각하시면 되겠습니다.

그럼 기왕에 컨텍스트 얘기가 나왔으니 이걸 한번 보죠.

이것은 제가 정의한 Context 의 정의입니다. 위의 PerIoContext 라는 것은 IO와 관련된 정보를 저장하는 구조체 입니다. 보시면 내부에 OVERLAPPED 구조체가 있는 것을 보실 수 있을 겁니다. 이렇게 멤버로 두거나 상속해서 처리하시면 되겠습니다. 그리고 그 외에는 IO와 관련된 정보를 두는데, IO를 한 결과를 담는 Buffer 라던지, 아니면 이 IO가 어떤 IO다라는 것을 알려주는 Flag 라던지 이런 변수들을 담아두는 겁니다. 다른 분들은 보니까 여기다 Socket 변수도 두시고 그리고 게임이라면 x, y 위치라던가 뭐 등등의 정보를 담아두시는 분들도 있었습니다. 이건 서버의 성격에 따라, 그리고 프로그래머가 필요로 하는 정보에 따라 틀려집니다.
그런데 저는 위와 같이 하질 않고 PerSocketContext 라는 구조체를 따로 정의해서 소켓과 관련된 정보를 따로 정리했습니다. 그리고 이것을 Completion Key로도 활용하는 것이죠.
그리고는 IO Context를 두 개 연결해두는 겁니다. Recv 에 대한 IO Context 하나, Send 에 대한 IO Context 하나. 이렇게 두 개 둔 이유는 Full Duplex라 할까요? Recv 와 Send를 동시에 하기 위해서입니다.
그럼 제가 정의한 것은 어느 정도 설명된 듯 합니다. 하지만 알아 두실 점은 이것은 정답이 아니라는 겁니다. 확장 오버랩드 구조체를 하나 정의해서 그것만 쓸 수 있습니다. Completion Key로는 소켓을 넘긴다던 지 해서 말입니다. 저는 이런 스타일을 즐깁니다만 보시는 분들은 님 편하게 쓰시기 바랍니다. 저는 이런 스타일이다라고 예시를 보여드렸을 뿐입니다.

자 그럼 다시 저 위에 코드로 넘어가봅시다. 이렇게 Socket Context를 하나 할당했습니다. 위의 PerSocketContext만큼의 공간을 메모리 풀에서 얻고 여기다 방금 받은 클라이언트 소켓을 대입해서 받아왔습니다. 소스를 자세히 분석해보신다면 아실 겁니다. 그래서 이 할당된 내용 가지고 뭘 해야 되겠죠? 저번에 제가 뭐라고 했던가요? IOCP 에서는 Completion Key와 socket 또는 device를 연결해야 된다고 했죠? 그걸 지금 하는 겁니다. IocpHandler의 멤버 Associate 라는 함수를 호출하여 연결합니다. 여기서 Completion Key가 PerSocketContext 즉 방금 할당했던 것이 넘어가도록 만들었습니다. 그럼 Associate 이 것의 내용이 궁금하실 터인데 이것은 여러분께 넘기겠습니다. 별로 어려운 내용은 아닐 겁니다. 함수의 핵심은 CreateIoCompletionPort에다 2번째 인자를 사용한다는 것이죠. 저번 강좌에서도 언급했던 사항이니 특별한 설명이 필요치 않을 것이라고 봅니다. 아마 저번 내용을 기억하시는 분들이라면 내용을 안보셔도 대충 그렇게 되겠구나 하고 추측하실 수 있을 겁니다.
그리고 그 다음은 가장 중요한 초기 Recv 걸기 입니다. 지금은 RecvPost 란 함수로 래핑되어 있지만 실제 내용은 WSARecv를 하는 것입니다. 그 코드는 다음과 같습니다.


// RECV 요청
BOOL NetworkController::RecvPost(PPerSocketContext pPerSocketCtx)
{
DWORD dwRecvBytes=0;
DWORD dwFlags=0;

ZeroMemory(&pPerSocketCtx->recvContext->overlapped,sizeof(WSAOVERLAPPED));

int ret=WSARecv(pPerSocketCtx->socket,&(pPerSocketCtx->recvContext->wsaBuf),1,
&dwRecvBytes,&dwFlags,&(pPerSocketCtx->recvContext->overlapped),NULL);

if(SOCKET_ERROR==ret)
{
int ErrCode=WSAGetLastError();
if(ErrCode!=WSA_IO_PENDING)
{
TRACE("[%d] Recv Request Error(WSASend Function Failed): %d ",GetTickCount(),ErrCode);;
TRACE("Client will Close. ");
return FALSE;
}
}

return TRUE;
}

오버랩드 구조체를 초기화 시키고 WSARecv를 호출하는 것이죠. 참고로 WSARecv 할때 인자를 잘 넣으시길 바랍니다. 제가 예전에 실수한 부분이었는데 이거 잘못 넣었다 왜 안되지 하던 경험이 있었습니다. 인자의 포인터를 잘 보시고 넣으시길......
여하튼 중요한 것은 초기 Recv를 반드시 해줘야 한다는 사실 입니다. 처음 짜보시는 분들이 빼먹는 부분이 이것인데요. Overlapped IO 라는 것을 한마디로 축약하자면 선 요청 후 결과처리 입니다. 그러니까 미리 recv를 socket 에 걸어줘야만 리시브가 된다는 것입니다. 안 그러면 워커 쓰레드로 처리결과가 오질 않고 recv 도 되질 않습니다. 그래서 접속만 되고 왜 내용은 안 받느냐 하시는 분들은 이 부분을 빼먹은 겁니다. 이렇게 초기 recv 를 걸어두면 recv 를 받을 수 있습니다. 단 이 부분에서 알아두실 것은 반드시 recv를 먼저 해야 하는 것은 아니라는 점입니다. 보통 서버에서는 recv를 먼저 하지만 때에 따라서는 send를 먼저 할 수도 있습니다. 그래서 그럴 경우에는 send를 걸어두셔도 됩니다. 즉 그러니까 정리하자면 어떤 경우든 간에 send,recv 든 IO를 하나 초기에 걸어주셔야 나머지 처리가 된다는 겁니다. 그래야 뭐든가 되겠죠? (물론 극소수의 예겠지만 안 하셔야 할 때도 있겠습니다. 이건 서버 프로그램의 설계에 따라 달라지는 사항이겠죠.)
자 그럼 Accept 과정 처리 부분이 끝났습니다. 그럼 다시 정리해볼까요? 처음 초기화를 끝냈습니다. 그리고는 accept를 하죠. 그리고 클라이언트가 접속해서 accept 함수가 리턴 되면 그 소켓과 관련된 여러 가지 정보를 확장 오버랩드 구조체에 집어넣습니다. 그리고는 그것을 이용하거나 또는 소켓 등을 Completion Key 로 삼아 IOCP 객체와 연결을 시킵니다. 그럼 이 때부터 소켓에 요구하는 IO는 IOCP를 통해 통보를 받을 수 있습니다. 그런 다음에는 뭘 한다고 했죠? 예 초기 IO를 요청합니다. 그래야 recv를 받든, send를 하든 처리할 수 있습니다.

3. Worker Thread 처리
3번째로 워커 쓰레드에 대한 처리가 되겠습니다. IOCP 서버를 짜면서 신경 쓰셔야 할 분은 첫 번째 초기화에서의 처리, 그리고 Accept 시의 처리, 그리고 어째 보면 가장 중요한 이 Worker Thread 에서의 처리입니다. 이 워커 쓰레드의 역할은 아시겠지만 정리하자면 이전의 요청한 IO의 완료 결과를 통보 받고 그 결과에 따라 어떤 IO를 더 요청하거나 아님 다른 작업을 하거나 합니다. 거의 중심적인 IO 처리는 여기가 될 것입니다.
그럼 코드를 볼까요?

워커 쓰레드 함수의 구현입니다. 아까 얘기했던 인터페이스에서 정의했던 함수가 이 void ProcessingThread(void) 함수입니다. 아까 CreateThreadPool 에서 쓰레드 만들 때 이 함수를 부르게 되는 거죠.
그래서 여기에서는 결과 패킷을 받아오고 그것을 처리하게 됩니다. 그래서 그런 정보를 받기 위해서 PerSocketContext의 포인터 변수, PerIoContext 포인터 변수, 그리고 전송 받은 바이트 수를 저장하는 변수를 선언합니다. 그리고 while 루프 안에서는 GetCompletionStatus 함수를 호출합니다. 눈치 채셨겠지만 이것은 내부적으로 GetQueuedCompletionStatus 함수를 싸고 있습니다. 이 함수는 결과 패킷을 없을 때는 블러킹 된다는 것은 알고 계시죠? 그리고는 쓰레드 풀에 돌아갑니다.
그래서 리턴 된 정보를 가지고 오는데 첫째 Completion Key, 이것은 제가 넣은 PerSocketContext 구조체에 대한 인스턴스가 될 겁니다. 그리고 그 외의 정보들 그리고 Overlapped 구조체에 대한 포인터가 리턴 되는데 이것은 PerIoContext의 주소를 집어 넣습니다. 그리고는 LPOVERLAPPED로 캐스팅을 하죠. LPOVERLAPPED 인 이유는 Out 파라미터이기 때문입니다. 그래서 이 인자에서 완료된 IO에 대한 오버랩드 구조체의 인스턴스에 대한 포인터가 리턴 되는데요. 이 포인터가 가리키는 것은 제 프로그램에서는 PerIoContext로 IO를 요청하므로 실제적으로는 PerIoContext에 대한 포인터가 리턴됩니다. 그래서 저렇게 캐스팅을 해서 집어넣는 것입니다. 좀 이해되셨나요? 자 그럼 내용이 다 넘어왔습니다.
그럼 우리는 이 것을 가지고 처리를 해야 되겠지요? 그 처리를 한번 봅시다.


일단 첨에 보이는 것은 dwBytesTransferred가 0이면 클라이언트가 Close 되었다는 처리입니다. 이렇게 IO를 요청했는데 그 결과가 0이었다는 것은 즉 클라이언트가 접속을 끊었다는 것입니다. 그래서 소켓을 닫아주는 처리를 해야 합니다. 물론 이 때에는 연결된 메모리를 해제하는 작업도 병행되어야 하겠죠. 저의 프로그램 같은 거라면 메모리 풀에다 다시 메모리를 돌려주는 작업이 되겠습니다.
그리고 다음과 같은 코드가 보이실 겁니다. 주석으로 IO 성격에 따른 처리라고 되어 있습니다.

// IO 성격에 따라 그에 따른 처리
if(pPerIoCtx==pPerSocketCtx->recvContext)

즉 지금 완료된 결과가 recv완료냐 send완료냐를 결정하는 겁니다. 실제로 GetQueuedCompletionStatus 함수에서 리턴 되는 정보로는 IO 가 어떤 IO인지 알 방법이 전혀 없으므로 이렇게 우리가 그에 따른 처리를 직접 해줘야 합니다. 즉 어떤 IO인지를 플래그를 통해 알아내던 가를 해야 합니다. 그래서 보통 사람들은 Flag 를 두어서 IO_READ 이면 READ 라고 처리하고 IO_WRITE라면 WRITE 라고 처리하는 등의 간단한 상수를 정의해서 씁니다. 하지만 저는 최인호 님이 쓰셨던 방식처럼 이렇게 나온 포인터와 소켓 컨텍스트의 recv 쪽 IO Context의 포인터를 비교합니다. 이렇게 함으로써 플래그를 쓰지 않고 처리 어떤 IO인지를 알 수 있게 되는 겁니다. 그런데 이런 처리는 Context 구조체를 어떻게 설계했느냐에 좌우 됩니다. 저처럼 설계 했으면 이렇게 가능할 수 있고요. 확장 오버랩드 구조체를 하나만 정의했다면 불가능 할 수 있습니다. 즉 자신의 어떻게 정의했느냐에 따라 달라질 수 있다는 말입니다.
자 여하튼 초기에 recv를 걸었을 터이니 아마 첫 번째 if 문을 먼저 들어올 것입니다. 그럼 여기서 Recv 가 완료된 상황을 처리하는 겁니다. 이 완료라는 의미를 잘 생각해보시길 바랍니다. 이 예제는 에코 서버 이므로 단순히 첨 Recv 가 완료되면 그것을 보내주는 역할을 하게 됩니다. 그것은 RecvCompleteEvent란 함수 안에서 처리하게 되는데 저처럼 하지 않고 if 문 안에 바로 넣을 수도 있겠습니다. 그건 그렇고 이 함수에서는 받은 값을 그대로 Send IO Context 에 복사하고 Send를 요청합니다. 아 이 쪽을 보실 때에는 소스 코드를 한번 보시면서 읽어보세요. 그럼 다음에는 ProcessingThread 함수로 들어온다면 else if 로 들어가겠죠? 하지만 에코의 경우 Send 완료시에는 할 일이 없으므로 아무 일도 안 합니다.
그리고는 기억할 사항이 다시 Recv 를 요청해 두어야 한다는 사실입니다.
만약 다시 요청해두지 않는 다면 다음 번에 보내는 패킷을 받을 수 없습니다. 왜냐하면 IO요청은 일회성입니다. 이걸 두 번 세 번 우려먹을 수 없습니다. 한번 요청하고 완료 결과를 받으면 그걸로 끝이지요. 그래서 마지막에 RecvPost해서 Recv 를 요청하는 겁니다.

자 그럼 정리해봅시다. Worker 쓰레드에서 하는 일을 무엇이던가요? 완료된 IO 결과를 뽑아내고 그 내용을 분석하여서 그 분석된 결과에 따라 Send 면 Send, Recv면 Recv완료 처리를 하는 것입니다. 그래서 처음 프로그래밍 하실 때에는 초기 IO에 따른 흐름을 생각해보시면서 이건 이렇게 저건 저렇게 순서도를 그려놓으시고 그에 따라 프로그래밍 하시면 되겠습니다. 단 이것을 여러 번 불려지게 되므로 한번하고 말 그런 내용으로 프로그래밍하시면 안 되겠죠? 여러 가지 상황에 대처할 수 있도록 프로그래밍 해주셔야 한다는 겁니다.
그래서 에코 서버 말고 좀 더 일반적인 상황에서 처리를 보면 recv 완료 후에 처리는 패킷(요 패킷은 데이터를 주고 받는 그 패킷입니다.) 받을 것을 보고 덜 받았다. 그럼 다시 recv를 요청합니다. 그리고 만약 다 받은 것 같다. 그럼 그 패킷에 따라 다른 작업을 하는 것입니다. 그리고 send 완료는 이번 완료된 send 는 제대로 다 간 것인가? 라는 것을 체크하고 아니라면 더 요청하는 식으로 합니다.
이렇게 IO 하나 처리에 따라 정보가 다 받아졌는지 덜 받아졌는지 체크를 해서 그에 따라 처리하는 루틴이 이 쪽 Worker Thread 쪽에서 할 일이 되겠습니다.

4. 정 리
이제 제 강좌를 총 정리하겠습니다. 첨에 우리는 IOCP에서 IO를 하는 중심이 되는 Overlapped IO라는 것을 알아 봤습니다. 이번 강좌까지 그 이 Overlapped IO 에 대한 말을 하면서 중요한 것을 한 마디로 정리하자면 Overlapped IO 라는 것이 미리 요청한다. 그리고 그 결과는 나중에 받는다 라는 것이었습니다. 그리고 2번째 강좌에서는 뭘 말했었나요? 흠…… 생각해보니 IOCP API를 정리하고 그에 대한 주변 이야기를 했었지요. 그리고 3번째 강좌는 IOCP 메커니즘 얘기였습니다. 그 5개 구조체와 그 사이의 관계. 이걸 알아두시면 소스 코드를 이해하시는데 큰 문젠 없을 겁니다. 그리고 마지막 이번 강좌에서는 간단한 예제를 보고 어떻게 프로그래밍 해나가는가에 대한 순서를 봤습니다. 아마 강좌 설명으로는 조금 부족할지도 모르겠습니다. 예제 소스 코드를 좀 더 보시면 될 것 같습니다. 일단 IOCP 핵심 3부분을 설명은 했으니 큰 어려움을 없을 것이라고 생각합니다. 그럼 이번 강좌를 끝으로 저의 강좌는 마치겠습니다.


드디어 허접한 강좌가 끝을 맺는군요. 그 동안 슬럼프에도 빠지고, 놀기만 하고 또 예제 코드도 제대로 작성해볼 거라고 노력 좀 했는데…… 역시나 예제도 제 생각엔 허접 코드이고 그러네요. 여하튼 저의 첫 번째 강좌를 종료하면서 여러분의 최종 평가를 듣고 싶네요. 나중에 혹시라도 강좌를 하면 참고하게 말입니다. 물론 그 때까지 기억할 수나 있을는지 모르겠지만요 ^^:
참 이번 소스 코드에 기본적인 쓰레드 풀링 외에 메모리 풀링도 넣고 커넥션 풀링도 넣어보려고 했습니다만 그럼 이해의 측면에서도 어려워 지고 막상 해놓으니 고치기 어렵더군요, 이 쪽은 다른 소스 코드를 보시면서 해보심이 좋을 듯 합니다.
그럼 다음 강좌를 한다면 그 때 뵙죠.