본문 바로가기

[Clet]주노's Clet강좌-8. API 맛보기 - Network (1) 편

[펌]http://juno.springnote.com/pages/1003734

컴퓨터 역사에 있어서 네트워크는 꽤나 큰 가능성과 기회를 주었다고 생각되는 상당히 비중있고 중요한 요소라고 생각합니다. 이렇게 글을 적어서 많은 분들에게 도움이 되길 바랄 수 있는 것도 어찌보면 넷 세상의 매력이 아닐까 싶네요. 대개 WIP가 설치된 기기인 휴대폰에서의 네트워크는 어플리케이션 입장에서는 좀 더 많은 가능성을 가지게한 중요한 부분이라고 생각합니다. 간단히 생각해봐도 어디서나(^^) 편하게 전화를 할 수 있다는 것은 어디서나 그 무선망을 사용해서 넷 사용도 가능하다는 의미니깐요. 다만 무선망이기 때문에 일반적으로 유선 인터넷상의 네트워크 프로그램보다는 조금 더 신경 써야 하는 부분들이 존재할꺼라는 것도 어느정도 예상하고 염두해두어야 할 부분입니다. 이번 시간에는 어찌보면 WIPI 어플의 매력(사실 폰의 다른 플랫폼도 뭐 다를바는 없겠지만요^^)이라고 생각할 수도 있는 네트워크에 대해서 살펴보도록 하겠습니다.

/* 지금까지 다뤄온 내용에 비해서 이번 파트는 네트워크 프로그래밍 경험이나 기타 이런저런 프로그램 경험이 적다면 조금 이해 하기가 어려운 부분이 있을 것입니다.  최대한 필요한 부분은 설명을 덧 붙이겠습니다만, 확실히 단기간에 이해하기가 어렵다는 것이 분명한 동네이고, 언급하는 것 이외에도 알아야 할 내용이 많은 동네가 이 네트워크 동네입니다. 만약 실제로 네트워크를 사용하는 상용 어플을 제작하시기 위해 지금 이 글을 보고 계신다면 많은 테스트와 여러 문서나 책등을 통해 의문이 나는 부분들을 적극 찾아서 알아두시길 권해드립니다. 그렇다고 지금 바로 이해가 안 간다고 해서 OTL 이 되실 필요는 없으시다고 생각합니다. 그냥 지금은 넘겨두시고 훗날 다시 쳐다보고 그러다보면 어느날 문득 딱~ 감이 오는 날이 올꺼라고 생각합니다. 그럼 힘내서 화이팅~*/


Network API

  • Network 시작과 끝
    MC_netConnect MC_netClose MC_netSocket MC_netSocketClose
  • Network API에 필요한 UTILITY API
    MC_utilHtonl MC_utilHtons MC_utilNtohl MC_utilNtohs MC_utilInetAddrInt MC_utilInetAddrStr
  • TCP
    MC_netSocketConnect MC_netSocketWrite MC_netSocketRead
  • UDP
    MC_netGetMaxPacketLength MC_netSocketSendTo MC_netSocketRcvFrom
  • 그외 소켓 API
    MC_netSetWriteCB MC_netSetReadCB MC_netGetHostAddr MC_netSocketBind MC_netSocketAccept
  • HTTP - Web 호출
    MC_netHttpOpen MC_netHttpClose MC_netHttpConnect
  • HTTP - Web 정보얻기
    MC_netHttpSetRequestMethod MC_netHttpGetRequestMethod MC_netHttpSetRequestProperty MC_netHttpGetRequestProperty
    MC_netHttpSetProxy MC_netHttpGetProxy MC_netHttpGetResponseCode MC_netHttpGetResponseMessage
    MC_netHttpGetLength MC_netHttpGetType MC_netHttpGetEncoding

이제부터 봐야할 API 들을 쭉 나열했습니다. 뭐 생각보다 별로 안 많은 것 같기는 합니다만, 저는 왠지 오늘도 무자게 타이핑해야할 것 같은 느낌이 팍팍 오는지라...일단 분량을 보면서 적당히 나눌 수 있는데까지 전진해보고 그래픽 API때 처럼 다음 강좌에서 계속 이어서 보는 방향으로 진행하려고 합니다.


Network API 파트는 크게 나누면 소켓(socket) 사용 파트와 HTTP 파트로 나눌 수 있습니다. HTTP는 아마 다음 시간에 살펴보겠지만, 사실 흡사 전 시간에 FILE API 를 좀 더 사용하기 쉽게(???) 구현한 Database API 와 느낌이 비슷하다고 생각하시면 되겠습니다. 결론적인 이야기입니다만, HTTP 프로토콜도 사실상 TCP/IP를 이용해서 그 내용의 표기 형식이 정의된 형태이니, 좀 노가다를 뛰어주면(^^) 사실 일반 Network API 로도 구현은 할 수 있죠. 따라서 일단 Network API 를 스윽~ 살펴보게 되면 아마 HTTP 쪽은 어느정도 이해하기 더 쉬워질꺼라고 생각합니다.


그럼 Network API 부터 시작해보도록 하겠습니다.


Network API 시작과 끝 : 네트워크의 기본 내용 복습(?)도 함께~

Network API 에서 각각 시작과 끝을 담당하고 있는 기본적인 API 들 부터 살펴보도록 하겠습니다.

  1. M_Int32 MC_netConnect(NETCONNECTCB cb, void *param);
  2. typedef void (*NETCONNECTCB)(M_Int32 error, void *param);
  3. void MC_netClose(void);
    M_Int32 MC_netSocket(M_Int32 domain,M_Int32 type);
    M_Int32 MC_netSocketClose(M_Int32 fd);

Network API(이하 넷API)의 시작과 끝에는 항상 MC_netConnect 와 MC_netClose 가 꼭 있어야 합니다.

MC_netConnect는 우리가 소켓 통신을 시작하기전에 일단 그 기반이 되는 네트워크 연결을 시도하는 함수입니다. 쉽게 생각하면, 핸드폰에서 이 함수를 호출해서 호출이 성공하면, 각 폰의 화면 상단에 그려진 안테나 표시 옆에 뭔가 통화중(☎)표시와 같은 기호가 뜨면서 전화를 거는 것과 같은 동작을 하는 것을 볼 수 있습니다. /* 사실 PPP 와 같은 넷 커넥션이 동작하는 것이죠. */ 즉 우리가 네트워크 사용을 할 수 있는 환경을 만들어 주고 그 환경 설정이 완료되면 파라메터 cb의 인수로 넘긴 해당 콜백 함수에 그 결과를 알려주게 됩니다. 콜백함수의 형식은 위의 해당 API 바로 아래 보이는 것과 같이 2개의 파라메터를 가지는 함수입니다. param으로 전달되는 포인터 값은 콜백 함수가 호출되어질때 전달하게 되는 값이죠.(콜백 함수 2번째 파라메터로 오겠죠? 앞으로 몇번 더 이런 형태의 콜백 함수가 나오는데, 모든 param 포인터는 전부 같은 의미라고 보시면 됩니다.)

MC_netConnect 는 일단 연결을 시도함과 동시에 특정 값을 반환하는데요. 0 이 반환되면 일단 연결 시도는 정상적으로 시작된 것이고, 그외 아래와 같은 에러 메세지가 반환되면 해당 메세지에 맞는 처리를 해주어야 합니다. /* 당연히 0이 반환되지 않으면 콜백 함수도 불리지 않게 됩니다. */

  1. M_E_INPROGRESS : 현재 어플리케이션이나 다른 어플리케이션이 이미 인터넷 접근을 시도하고 있는 경우 발생
    M_E_ISCONN         : 현재 어플리케이션이 이미 인터넷 접근이 허용된 경우
    M_E_ERROR           : 기타 에러

/* 특정 플랫폼 SDK의 에뮬레이터의 경우 그저 컴퓨터니깐 network에 당연히 온라인되어 있다는 이유에서 그런지 반환 값이 무조건 M_E_ISCONN 이 오는 에뮬레이터가 있습니다.(이런 증상보면 그저 개발하신 분이 밤샘을 많이 하셨나보구만~이라는 생각뿐이 안듭니다..--;) 이런 경우 어떻게 하는게 방법일까요^^. 뭐 그냥 연결 되었다 생각하고 바로 위의 콜백 함수로 건너가면 되겠죠^^. 아마 그 SDK의 최근 버전도 여전했으니 이미 경험하신 분들도 있을지도 모르겠네요... 이런 일 뭐 종종 있는거니 그러려니~ */


네트워크 작업이 완료되서 통신을 종료할때는 간단하게 MC_netClose 함수를 살포시~ 불러주시면 됩니다. 스펙 정의상으로는 이녀석이 불리면 닫히지 않은 소켓도 전부 닫아버린다고 되어 있습니다. 당연한거겠죠. 쉽게 생각해서 인터넷 하던 중에 네트워크 다운된(랜줄을 빼던가, 무선랜이 죽던가 하는 상황을 상상해보세요^^)거니깐, 그냥 싸그리 클리어 해버리는거겠죠. 그래도 이녀석을 너무 믿고 열었던 소켓을 작업이 다 끝나서 네트워크를 종료할때 안 닫고 그냥 MC_netClose만 부르는 방식은 가급적 머리속에 넣지 마시기 바랍니다. 프로그램의 세계에서는 연게(open)있으면, 반드시 닫아야(close)하는게 원칙입니다. 기본이죠. 달랑 코드 하나인데, 안해도 잘 되더만~ 하고 안하시다가 어떤 케이스를 만나게 될지 모르는 겁니다. 최소한의 예의와 같은 규칙은 지켜주는게 당연한 도리인겁니다.^^


MC_netConnect의 결과로 해당 콜백 함수에서 에러없이 연결이 확인 되었다면, 이제 소켓을 생성해야 합니다. 이때 사용하는 함수가 MC_netSocket 입니다. 혹시 과거에(?) 컴퓨터에서 C언어로 네트워크 프로그램을 해보셨다면 socket 함수를 보셨을 껍니다. /* 굳이 과거라고 한 이유는 최근에는 대부분 윈도우 환경에서 프로그램을 작성하고 윈도우에서는 기본적으로 winsock 이라는 환경을 사용하므로, 네트워크 함수가 유사는 하지만, 상당히 생김새(^^)가 다르기 때문입니다. 뭐 그래도 linux 등의 넷 프로그램을 하신다면 아마 친숙하실지도 모르겠군요. 여전히 잘 사용할 수 있는 함수이니깐요. */ 파라메터는 컴퓨터의 socket 함수와 유사합니다. domain 파라메터에는 통신 도메인을 요구하는 것인데 우리는 일반적인 IP,port 로 주소를 구성하는 인터넷망을 사용하므로 MC_AF_INET 이라는 값을 입력합니다. 이거 하나뿐이니 언제나 고정이라고 보셔도 되겠습니다.^^ /* 컴에서는 대개 AF_INET 의 값을 씁니다. 뭐 Clet 답게 MC_ 만 붙은거죠^^ */ type 파라메터에는 현재 여는 소켓으로 사용할 전달 프로토콜을 설정하는데 Clet 에서는 현재 TCP 와 UDP 가 사용 가능합니다. 각각 MC_SOCKET_STREAM , MC_SOCKET_DGRAM 으로 입력합니다. 제대로 소켓이 생성되면, 반환 값으로 앞으로 계속해서 사용해야 하는 소켓 식별자(0이상의 값)를 반환하게 됩니다. 만약 기기의 한계상 더 이상 소켓 생성이 불가능 하거나 넷이 이미 끊겨 버렸거나 입력한 파라메터가 이상한 값인 경우 전부 0보다 작은 값의 에러값이 반환됩니다.


생성된 소켓을 다 사용했다면, MC_netSocketClose 함수를 통해 소켓을 종료해야 합니다. 이때 파라메터 fd의 인수는 종료할 소켓의 식별자 값입니다. 만약 위에서 이야기한 MC_netClose가 먼저 불렸다면, 내부적으로는 이미 스펙 정의상 소켓도 종료되었겠지만, 스펙 문서에서는 그래도 소켓 식별자가 생성으로 나온 맞는 값이라면, 성공값인 0을 반환한다고 되어 있습니다. 그러나, 너무 이걸 믿으시면 안되겠고요. 위에서 언급한데로 net을 연결하고 socket 생성했으니, 닫을 때는 반대로 socket부터 닫고 net을 닫는다라는 것을 습관처럼(^^) 생각하시기 바랍니다. 이런 소소한걸 안 지키다 언제 어떻게 폰이 동작할지 아무도 모를 일이니깐요.


별로 어렵지 않은 내용이었습니다. 그쵸? 넷도 별거 아닌 것 같네요. ㅎㅎ

다 별로 어렵지 않은 간단한 내용이었으니 중간 복습 차원에서 한가지 상식적인(?) 질문을 드려보겠습니다.


위에서 제가 소켓을 생성하고 닫는다라는 이야기를 했습니다. 뭐 MC_netConnect 나 MC_netClose 같은 경우 통신 자체를 연결한다라는 의미로 보면 어려운 개념도 아닌 것 같은데요, 그런데 그 다음으로 나왔던 socket~시리즈...대체 소켓이라는게 뭘까요? 뭔가 위의 문장으로만 보면 TCP,UDP 라는 단어도 나오고 왠지 이런저런 책이나 기사 글등에서 TCP/IP 라는 말도 본 것 같고...그러니 대략 어렴풋이 저런걸 가능하게 해주는 뭔가이려나~ 하는 생각을 하셨나요? (전 예전에~ 그렇게 생각했던 것 같네요^^)


소켓에 대해서 검색엔진에서 찾아보니 어떤 용어 사전에 '어떤 통신망의 특정 노드상의 특정 서비스를 식별하는 식별자' 라고 나오더군요. 그런데 왠지 저건 용어 사전적인 해석, 그리고 위에서 말한 파라메터 fd 값...즉 프로그램 상에서 직접적으로 보이는 그 소켓 식별자를 의미하는 글 같아 보이는데요.(사실 저 용어 정의..너무 두리뭉실한 정의가 아닌가 생각합니다만^^)


소켓이란 네트워크 시스템적 입장에서 보면 어플리케이션 프로그램이 네트워크 통신 서비스를 받기 위한 API 라고 볼 수 있습니다. 우리가 계속 이야기하는 그 API와 같은 약자, 같은 의미입니다.^^ 오호~ 뭔가 이상한가요. 왠지 제가 API 와 함수 라는 단어를 자꾸 혼용해서 사용해버린지라(저도 잘 안 고쳐지는 애매한 습관입니다 ㅠㅠ 그저 이 부분은 저도 죄송할뿐ㅠㅠ) 의미상의 차이에 대해서 이 기회에 좀 더 이야기를 해보려고 합니다. API 는 Application Program Interface의 약자인 것은 이미 알고 계시죠. 단어상의 의미에서 Interface에 주목하면 좋겠는데요. 어플리케이션 프로그램과 커뮤니케이션의 접점? 뭐 그정도의 느낌이라고 생각하면 좋을 것 같은데요. 제가 말하는 함수는 그런 접점중 프로그램에서 그 기능을 사용하기 위해 호출하는 녀석을 가르킨다고 보면, 위에서 나온 MC_AF_INET 같은 이미 정의된 값도 해당 기능을 사용하기 위해 필요한 값을 알려주고 그 값을 이용하게 되므로 API 라고 할 수 있는 것입니다. 그럼 다시 하던 이야기로 돌아와(^^) 소켓이 API 라는 이야기에 대해 좀 더 이야기를 진행해 보겠습니다.


혹시 OSI 7 Layer 라는 용어를 들어보신 적이 있으신가요? 전산을 전공하셨거나 네트워크에 어느정도 관심이 있으셨다면 들어보신 분이 계실 것입니다. 쉽게 설명해서 현재의 컴퓨터 네트워크 환경을 이해하기 쉽도록 계층을 나눠서 표준화 한 것인데요. 뭐 굳이 언급한 이유는 나중에라도 네트워크에 대해 더 공부하실 분들이 계신다면, 한번은 집고 넣어가셔야 하는 부분이기 때문이기도 하고 이 7개의 계층중 1~4 계층에 해당한다고 (왠지 1계층은 빼야할듯 하지만요) 이야기하는 Ethernet , IP , TCP/UDP 로 이어지는 네트워크 패킷의 생성 방식과 구조에 대해서 살짝 아시면 소켓이 왜 API 인가를 이해하는데 도움이 되기 때문에도 언급하는 것입니다. /* 네트워크 동네는 확실히 알면 알수록 기본을 제대로 알아야 한다는 걸 매번 깨닫는지라 기왕 설명할때 저도 정리할겸 조금 자세히 보려고 합니다. 왠지 이런건 내 취향에 맞지 않는다는 분은 스킵하셔도 괜찮겠습니다만, 기왕 한번쯤은 봐두는 것도 계속 개발쪽 일을 할꺼라면 나쁘지 않을꺼라고 생각합니다.^^ 뭐 일종의 서비스~ 인걸로 이해해 주세요^^*/

clet8_1.png

대충 워드에서 이쁘게 그린(?)다고 그려봤는데, 아무튼 흔히 이야기하는 TCP/IP 계층과 비교해서 한눈에 보이도록 표시한 것입니다.

각각의 계층별 자세한 설명은 직접 책이나 검색을 통해서 찾아보셨으면 하고요(대략 저런 모양과 똑같은 표로 설명된 책이나 웹페이지도 여럿 있을 것입니다.), 굳이 도표까지 보이면서 이해하면 좋겠다고 열심히 떠들고 있는 부분이 TCP/IP 계층도 상에서 Application layer와 Transport layer이하 단계 사이를 연결하는 인터페이스...그것이 소켓이라는 것을 아셨으면 하는 것입니다. 물론 TCP/IP 만을 보면 단순히 L4 와 L3 (TCP/IP 기준) 에 존재해서 두 레이어 사이의 다리 역활을 해주는 것을 소켓이라고 이야기할 수도 있지만, 사실 일반적인 네트워크에서 말하는 소켓은 L4와 L3 사이만 연결할 뿐만 아니라 L4와 L2, L4와 L1도 연결 가능한 인터페이스 입니다.


다르게 이야기하면, OSI 계층도로 봤을때 L5~L7 영역은 실제로도 어플리케이션의 영역입니다만, L4이하는 OS로 생각하면 kernel부에 위치하는 직접 컨트롤하는 것을 허용하지 않는 영역에 속합니다. 따라서 L4와 L5 사이에도 마찬가지로 소켓과 같은(!) 인터페이스가 존재할 수 있습니다. 사실 이러한 어플리케이션과 그 하위 레이어를 연결해주는 인터페이스는 소켓이외에도 여러가지가 있을 수 있습니다. 아마 라우터쪽 공부를 하신 분들이라면 저보다 더 잘 아실 것 같지만, 뭐 그 동네도 복잡복잡하죠. /* 예를 들면 보신 적이 있는 용어일지 모르겠습니다만, BPF(BSD Packet Filter)와 같은 FreeBSD에서 직접 L2(Data Link)를 다루기 위한 인터페이스같은 것이 있습니다. 패킷 캡쳐하는 유명 라이브러리인 libpcap 같은 녀석이 이 개념을 사용한다고 하더군요. 저도 뭐 그래서 알았습니다만^^ */ 암튼 일반적인 컴의 네트워크 프로그램에서는 소켓만으로도(사실 제대로 지원만 한다면), L4~L2 레벨 전부 다 직접 제어가 가능합니다. 따라서 소켓이 단순히 TCP, UDP 만 접근하는 인터페이스가 아니라는 것을 알아두셨으면 하는 맘으로 저런 이상한(^^) 계층도를 살펴보았습니다.^^


기왕 OSI 7 Layer 까지 봤으니 좀 더 상식적인(--;) 네트워크 이야기를 좀 더 해보도록 하겠습니다. /* 아무래도 읽으시는 지금은 이런 관계없어 보이는 이야기를 왜 하나 싶으시겠지만, 결국 네트워크쪽의 별 이상한 증상들을 이해하고 파악해 나가려면 다 필요한 부분이기도하고 제 개인 머리속(^^) 정리 차원이기도 하니 이해해주셨으면 합니다. 꾸벅 _(__)_ */


흔히 네트워크상에서 움직이는 데이터(^^)를 우리는 패킷(packet)이라고 부릅니다. 그럼 패킷은 어떻게 생긴 녀석을 이야기하는 걸까요?

그냥 단순히 넷에서 흘러다니는 연속된 0,1 조합의 데이터 흐름을 한 뭉치 잡아서 패킷이다~ 라고 할 수 있는걸까요?

패킷은 원래 패키지(package)에서 만들어진 단어라고 합니다. 패키지는 소포 같은걸 의미하죠. 소포를 보내려면 우리는 겉면에 발신인과 발신주소, 수신인과 수신주소등을 써서 보내야 제대로 받고자 하는 사람에게 전달되게 될껍니다. 대개 패킷은 쉽게 '헤더(header)'와 '페이로드(payload)'로 구성되는데요, 헤더가 소포 겉면에 적은 주소 정보라고 하면 페이로드가 소포 내용물 이라고 생각하면 딱 정확한 비유일껍니다.(사실 제대로 설명하는 책에서 다들 이런 똑같은 이야기를 적더군요. 그래서 저도 리플레이~ ㅎㅎ)

따라서 흔히 TCP/IP 를 이야기하는 동네에서는 패킷이 네트워크에 흘러다닌다라고 하는게 틀린 의미가 아니게 됩니다. 우리가 앞으로 보게될 socket 연결에서 IP와 Port 값을 통해 연결할 대상을 지정할 것이니깐, 그걸 주소라고 생각하면 그게 헤더에 들어갈 것이고 내용물까지 포함해서 패킷이라고 불리는 단위로 뭉쳐진(^^) 데이터가 휙~ 네트워크를 통해 날라가는 것일테니깐요.

기왕 좀 더 알아보기로 한 거, 실제 네트워크에서 떠돌아 다니는 데이터는 어떻게 구성되었는가를 어렴풋이라도 보고 지나가면 나중에 혹 애매한 문제들이 발생했을때 어느 레이어의 문제로 어떻게 대처해야 할지를 조금이라도 더 빠르게(아니면 제대로^^) 확인할 수 있지 않을까 하는 심정으로 좀 더 진행해보겠습니다.^^


OSI 계층도상에서 TCP/IP 라고 불리는 것을 매핑시키면 L4 가 TCP동네, L3가 IP동네라고 할 수 있습니다. 흠 그럼 L2 는 대체 뭘까요? 흔히 MAC 어드레스라고 불리는 하드웨어의 주소 정보를 추가적으로 가진 녀석이 한층 더 있는 것이죠. 우리가 흔히 쓰는 TCP/IP는 이 계층에서 Ethernet헤더를 덧 붙이게 되는데 이 동네에서는 패킷이라는 용어보다는 프레임(frame)이라는 용어를 사용하게 됩니다. 프레임은 헤더+페이로드+트레일러(trailer)로 구성됩니다. 사실 Ethernet 동네에서만 사용되는 방식이라서 그런지 IP레이어 이후부터는 패킷이라는 용어를 쓰지만, 좀 더 공부하다보면 Ethernet 동네에서는 프레임이라는 용어를 사용하게 됩니다. 고로 왠지 L1, 즉 물리계층에서 케이블등을 통해 돌아다니는 데이터는 어찌보면 프레임이라고 불러주는게 더 맞는 것 같기도 하기는 하네요. 덤으로~, 폰에서 MC_netConnect와 같은 통신 연결시 PPP 등을 사용하는데, 이때 이야기하는 PPP는 L2, 즉 Data Link 레이어에 속하는 프로토콜입니다. 즉 핸드폰에서 우리가 TCP데이터 보내기 함수로 A라는 데이터를 쓱 보내면, 아래같이 되서 날라가는거죠.

  1. PPP헤더 + IP 헤더 + TCP 헤더 + 데이터 A    // 어디까지나 대략의 이해를 위한 대충 구조입니다.^^

/* 그냥 제 생각입니다만, 흔히들 TCP/IP 라고 잘 부르는데는 우리가 주소로서 사용하는 방식인 IP 와 port 때문이 아닐까 하는 생각을 해봅니다. 이미 눈치채신 분도 있을 듯하지만, 실제 IP헤더에는 IP주소가, TCP/UDP헤더에는 해당 포트 번호가 들어가게 됩니다.^^ */


사실 WIPI Clet 에서는 현재 TCP , UDP 레이어에만 접근이 가능하기 때문에 그외의 레이어를 굳이 알아야 할 이유는 없습니다. 그렇지만, 뭐 실제 폰에서 넷을 돌려보다보면 패킷이 이상하게 변조도 되고 사라지기도 하고 뭐 별별 일들이 벌어지기 때문에 결국 제대로 추측을 하고 대응을 하기 위해서는 이래저래 알아둬야할 내용이 점점 늘어나게 됩니다.(하다못해 아래의 그림과 같은 패킷 캡쳐라도 서버단에서 해서 대체 뭔 차이가 있는건지 알아볼 수라도 있겠죠.) 위에서 정말 수박 겉핥기 식으로 대강대강 적어둔 이야기들이 나중에 혹 다른 네트워크 환경에서 프로그래밍을 할때 약간이라도 도움이 되신다면 좋겠다는 심정으로 적어보았습니다. 마지막으로 한번 아래 스샷을 살짝 살펴보고 다음으로 넘어가겠습니다.

clet8_2.png

이 어플은 Packetyzer 라는 공개 어플중에서 꽤나 자세한 패킷 캡쳐가 가능한 툴입니다.(이런 어플은 쓸줄 알면 나중에라도 도움이 되니 사용해보세요^^)

[링크] Packetyzer Official Site : http://www.paglo.com/opensource/packetyzer

 

현재 가르키고 있는 데이터는 제 개인 블로그를 웹브라우저에서 URL을 입력하고 전송(GO)~했을때 제 컴에서 웹서버를 향해 나간 HTTP Request 패킷입니다. 각 데이터의 흐름 단위를 Frame 이라고 구분한 것이 맨 위에 보이실껍니다. 그리고 Ethernet 부분에는 MAC 주소들이 보이는군요. Internet Protocol(=IP)레이어에는 빨간색이 좀 많아서 잘 안 보이실지 모르겠습니다만(^^) 제 컴의 IP 와 목적지 IP가 보입니다.(전 공유기 밑에 있어서 저런 IP가 나오는군요^^) Transmission Control Protocol(=TCP)를 보시면 각각 발신된 포트번호와 수신할 포트 번호가 보이죠. 흠..그런데 재미있는건 웹브라우저가 보낸 데이터일텐데 어째 TCP체크섬이 맞지 않다고 나오는군요..ㅎㅎ IE 같은 웹브라우저가 XP 위에서 동작할때도 저런데...폰이라고 저 체크섬만 믿었다가는 이거 문제가 있을 것 같다고 생각되지 않으신가요^^ /* 제 컴에서 발신되는 TCP 체크섬이 깨지는건 다른 이유가 좀 있는 부분입니다만^^ 뭐 저야 이런건 공유기가 알아서 수정하니 신경 안씁니다만^^ 아무튼 폰 동네에서도 저 TCP 체크섬은 믿을게 되지 않는 것 같습니다. 그냥 경험상의 느낌이랄까요^^ 실제 저 체크섬이 틀리다고 패킷을 버리거나 하는 일도 거의 없습니다. ~.~ */


Network API에 필요한 UTILITY API

바로 소켓을 통해 데이터를 보내고 받고 하는 동네로 가기에 앞서서 먼저 네트워크상에서 사용되는 주소와 포트 값 표기를 위해 도움을 주는 동네를 잠깐 보고 가도록 하겠습니다. Clet 표준 스펙에서는 이동네를 Utility API 라고 정의했네요.

  1. M_Int32 MC_utilHtonl(M_Int32 val);
    M_Int16 MC_utilHtons(M_Int16 val);
    M_Int32 MC_utilNtohl(M_Int32 val);
    M_Int16 MC_utilNtohs(M_Int16 val);
    M_Int32 MC_utilInetAddrInt(M_Byte *addr);
    void MC_utilInetAddrStr(M_Int32 ip, M_Byte* addr);

역시나 이미 네트워크 프로그래밍을 컴에서라도 해보신 분들은 자주 봤을 함수들입니다만, 일단 확실히 알아둬야 하는 부분이니 살펴보도록 하겠습니다.


다음 내용에서 보게 되겠지만, TCP 연결을 시도하는 MC_netSocketConnect 함수의 경우 연결하고자 하는 목적지의 IP 와 Port 값을 입력하도록 되어 있습니다. 이때 스펙 문서등을 보면 입력되는 파라메터는 네트워크 바이트 순서(network byte ordering)에 따른다고 적혀 있습니다. 흠...저건 대체 뭘 이야기하는걸까요~


리틀 엔디언(little endian), 빅 엔디언(big endian) 녀석과 관련이 있는 이야기인데, 쉽게 두 녀석의 차이는 아래와 같습니다.

  1. 32비트 정수값 0x12345678 을 각각 위의 방식에 맞춰 저장하면 아래와 같은 순서로 메모리상에 기록됩니다.
  2. big endian     : 12 34 56 78  // 데이터 값의 최상위 비트가 어드레스 값의 선두에 오도록 저장
  3. little endian  : 78 56 34 12  // 데이터 값의 최상위 비트가 어드레스 값의 후미에 오도록 저장(원래의 의미는 이렇습니다)
  4. /* 다만 원래 문자적인 의미대로 해석해버리면 위의 little endian값은 전혀 다른 값이 되어 버릴 수 있습니다.(bit 단위로 뒤집어버린거니)
  5. 그래서 추가적으로 바이트 레벨에서는 어떠한 데이터형이라도 big endian식으로 상위 비트가 선두에 오도록 쓴다라는 규칙이 존재합니다.
  6. 따라서 위와 같은 little endian식 표현이 가능해지는 것입니다. 그런 이유로 1바이트형의 경우 엔디안을 신경쓰지 않아도 되는 것이죠.
  7. 뭐 이해하실때의 편의를 위해서는 그냥 위의 최상위 비트 -> 최상위 바이트로 바꿔서 기억해 두시는게 편할 것 같습니다. */

대개 흔히 우리가 접하는 Intel CPU는 little endian 방식을 사용합니다. 반면 간혹 볼 수 있는 Motolora CPU의 경우는 big endian 방식을 사용합니다. /* 실제로 해외 노키아 폰에 포팅하던 시절 이 엔디안 문제에 당해보았습니다.^^ */ 그런데 네트워크 상에는 어떤 컴퓨터나 기기던 연결이 가능할 수 있으니 표준적인 데이터 표기법이 필요해지게 됩니다. 만약 그런게 없다면 서로 엔디안이 다른 기기끼리는 완전히 엄한 값으로 인식하는 사태가 발생할테니깐요.

그래서 네트워크에서 사용되는 표준화된 데이터(우리가 넣는 어플리케이션의 데이터가 아닙니다)는 모두 빅 엔디언과 똑같이 상위 비트를 선두에 배치하는 방식으로 값을 기록합니다. 현재 우리가 사용하는 Intel 환경도 그렇고 ARM 환경에서도 대부분 리틀 엔디언을 사용하니 이 부분은 정말 햇갈리기 그지없는 부분이 될 소지가 많습니다.


그래서 위와 같은 함수들이 존재하게 되는 것이죠. 뭔가 상당히 유사해보이는 MC_util 로 시작하는 Htonl , Htons , Ntohs , Ntohs 가 위와 같은 고민을 해결하기 위해 만들어진 함수들입니다. 그런데 뭔가 이름이 묘하죠. 무슨 약자 모음 같기도 한 것이...ㅎㅎ

위 함수들은 아래 같이 해석하면 됩니다.

  1. [ Host 혹은 Network ] to [ Host 또는 Network ] [ Long 또는 Short ]

감이 오시나요^^ Host 는 현 어플이 동작하는 환경을 의미하고 Network는 이름 그대로 네트워크인거죠.

HtonlHost byte order 를(to) Network byte order형식의 Long형으로 변환한다 라는 의미입니다. 그럼 나머지 함수들도 대략 이해가 되시죠^^.

위에서 Long형은 사실 4바이트(32비트) 변수형을 의미하는 것이고 Short형은 2바이트(16비트)변수형을 의미하는 것입니다. 각각 M_Int32 와 M_Int16 에 대응하겠죠.


MC_utilInetAddrInt 은 addr 에 입력된 문자열로 표기된 IP 값을 M_Int32 정수형으로 변환해서 반환해줍니다. 이때 IP 값은 당연히(^^) Network Byte Order...즉 빅엔디언 형식의 값이 넘어 옵니다. MC_utilInetAddrStr 은 반대로 Network Byte Order 형식의 IP 정수값으로 IP 문자열을 반환 받는 함수입니다.


Clet 에서는 이 함수만으로도 충분히 필요한 작업을 할 수 있습니다. 정작 쓰는 곳은 IP 와 Port 값을 입력할때 뿐이니깐요. 그러니 확실히 기억해두시고 넘어가야 이제부터 보게될 API 들에서 이상한 값을 보는 일이 없으실 것입니다.^^


TCP : Transmission Control Protocol

드디어 네트워크를 통해 데이터를 주고 받는 함수들을 보게 되는군요. 우선 위에서 계속 언급한 TCP/IP 와 같은 용어에서 계속 나왔던 TCP 프로토콜을 이용하는 방법에 대해서 살펴 보겠습니다.

컴퓨터에서 C 로 TCP 를 사용하는 프로그램을 한다면 아래와 같은 순서로 흘러가게 됩니다.(클라이언트 기준입니다.)

소켓을 연다.(socket) -> 서버에 연결한다.(connect) -> 데이터를 주고 받는다.(send or recv)


Clet이라고 해서 크게 다르지는 않습니다. 아래 함수들만으로 위와 같은 과정을 유사하게 처리할 수 있습니다.

  1. M_Int32 MC_netSocketConnect(M_Int32 fd,M_Int32 addr,M_Int16 port,NETSOCKCONNECTCB cb, void* param);
  2. typedef void (*NETSOCKCONNECTCB)(M_Int32 fd,M_Int32 error, void *param);
  3. M_Int32 MC_netSetReadCB(M_Int32 fd,NETSOCKREADCB cb,void *param);
  4. typedef void (*NETSOCKREADCB)(M_Int32 fd,M_Int32 error,void *param);
  5. M_Int32 MC_netSetWriteCB(M_Int32 fd,NETSOCKWRITECB cb,void *param);
  6. typedef void (*NETSOCKWRITECB)(M_Int32 fd,M_Int32 error,void *param);
  7. M_Int32 MC_netSocketWrite(M_Int32 fd,M_Byte *buf,M_Int32 len);
    M_Int32 MC_netSocketRead(M_Int32 fd,M_Byte *buf,M_Int32 len);

일단 MC_netConnect 와 MC_netSocket 이 성공하면, TCP 연결을 시도하는 경우 MC_netSocketConnect 에 연결하려고 하는 목적지의 IP 와 Port 값을 각각 네트워크 바이트 오더(Network Byte Order)로 파라메터 addr 과 port 에 인수로 전달합니다. 해당 함수가 제대로 처리되면 반환되는 값은 0 이며 이 경우 콜백 함수를 통해서 연결 후에 연결 결과등이 전달됩니다. /* 계속 Clet 의 각 함수들이 반환하는 값을 보셔서 이미 눈치 채셨겠지만, 대개 0이면 성공, 0 보다 작은 값들은 에러로 인식하면 됩니다. */


자 그럼 소켓 연결까지 성공하면 이제부터는 데이터를 전달하고 받을 수 있게 됩니다. 그런데 어떻게 데이터를 보내고 받을까요? 일단 직관적으로 떠오르는건 보내는거야(send) 이쪽의 의지로(즉 어플리케이션이 능동(active)적으로 보내는거죠) 전달하는 것이지만, 데이터를 받는건 언제 받게 될지 모르는 거죠.(즉 어플리케이션 입장에서는 수동(passive)적) 그렇다면 데이터가 왔다고 알려줄 방법이 필요하겠죠. 그래서 Clet 에서는 시스템에서 데이터 패킷이 왔다가 알려줄 콜백 함수를 등록해야 합니다. MC_netSetReadCB 함수로 파라메터 fd 의 인수로 전달되는 소켓 식별자를 통해 데이터가 전달될 경우 해당 콜백 함수를 호출해줍니다. 그런데 받는거야 수동적이니 그렇다치고, 왜 보내는건 우리 맘대로 가능한데 MC_netSetWriteCB 함수가 존재하는 것일까요? 이것을 이해하려면 TCP 의 패킷 송수신 원리를 살펴봐야 합니다. 일단 나머지 함수를 보고 나서 살펴보기로 하죠.


해당 소켓(fd)을 통해 데이터를 보내고자 하는 경우는 MC_netSocketWrite 를 사용하며, 데이터를 받을 수 있는 상태에서 데이터를 받기 위해 MC_netSocketRead 를 사용합니다. 각각 동일한 파라메터 buf 와 len을 사용하죠. 이제는 특별히 이런 파라메터가 어떤 역활을 할지 이야기 안해도 슬슬 감이 오시겠죠.^^

두 함수 모두 보내거나 받은 만큼의 바이트 값을 반환해줍니다. 따라서 우리가 보내거나 받으려고 했던 길이를 안다면 작업이 완료된 것이니 더 보내거나 받아야 할지 알 수 있겠죠. 0 미만의 값은 당연히 에러인데요. 대개는 중간에 네트워크가 끊어졌거나 넘긴 버퍼가 이상하거나 하는 상황입니다. 다만 이때 주의해야 하는 값이 있는데, M_E_WOULDBLOCK 이라는 값이 넘어오는 경우는 다시 콜백으로 해당 작업을 설정해줘야 합니다. 즉 MC_netSocketWrite 에서 이 값이 반환되면 MC_netSetWriteCB을 통해서 다시 보낼 수 있을때 불러달라는 의미로 우리가 직접 작성한 보내기용 콜백 함수를 전달해야 합니다. MC_netSocketRead 에서 이 값이 반환되면 MC_netSetReadCB를 통해 데이터가 수신되서 읽을 수 있을때가 되었을때 우리가 작성한 읽기 함수를 불러달라고 콜백 함수로 등록해야 합니다.


이러한 콜백으로 상태를 전달 받는 방식을 잘 이해해야 Clet 에서 네트워크를 통해 자유롭게 데이터를 주고 받을 수 있게 됩니다. 그럼 왜 이런 식의 동작이 일어나는 것인지, 그리고 이러한 동작으로 인해 TCP를 사용하는 프로그램에서는 어떤 작전(^^)을 세워야 할지 생각해보도록 하겠습니다.


대개 TCP / UDP 의 큰 차이점이 신뢰성이 있고 없고 라고 배우셨을 것이라 생각합니다. 물론 이 글을 통해서 TCP 라는 단어도 처음 보신 분도 계시겠지만, 일단 대개 간단하게 설명하는 글등에서 두 프로토콜의 차이를 그렇게 정의하고 있습니다.

TCP에 대해서 설명하라고 한다면 저는 아래와 같이 이야기할 수 있을 것 같습니다. /* 뭐 제가 아는 대로의 적어 본 것이니 제대로 된 정의는 찾아보시기 바랍니다.^^ */

TCP는 IP 프로토콜 위에서 신뢰성(reliable) 있는 통신을 제공하기 위한 커넥션 지향 프로토콜(connection-oriented protocol)입니다. 커넥션 지향이란 두 대상이 통신을 시작할 때 커넥션을 연결하고 통신이 종료되면 커넥션을 끊는 것을 의미합니다.(end-to-end communication) 또한 신뢰성있는 통신을 제공하기 위해 TCP헤더에는 송신하는 데이터의 순번(sequence number)등을 보내고 이를 확인하는 응답(ack)을 받는 방식으로 제어합니다.(flow-controlled)


TCP는 신뢰성 있는 통신을 제공하기 위해 TCP헤더에 독특한 데이터 순번을 가지고 있습니다. 더불어 수신측에서는 제대로 수신을 알리기 위해 확인 순번도 설정하게 됩니다. 이를 각각 Sequence Number 와 Acknowledgement Number 라고 부릅니다. 물론 TCP 헤더에는 이외에도 여러 값이 존재하지만, 일단 여기서는 왜 M_E_WOULDBLOCK 이 불리는지에 대해서 살펴보기 위해 TCP 헤더의 값들중 위 두값에 집중하겠습니다. /* 정확히는 왜 내가 데이터를 보내려고 하는데(MC_netSocketWrite)도 위의 값이 발생하는 것인지 알기 위한 것이라고 하는게 맞겠습니다. 받는 경우(MC_netSocketRead)는 사실 수신된 데이터가 아직 없다는 의미니깐요. */

clet8_3.png /* 대충 PPT 에서 그린거니 뭐 그러려니 하세요 ^^ */

일단 TCP의 기본 데이터 전송을 쉽게 A가 데이터를 보내고 B는 받기만 한다고 생각하고 위와 같은 상황을 살펴보도록 하겠습니다. TCP 헤더에는 언제나 Sequence Number(SEQ)와 Acknowledgement Number(ACK)가 존재하는데, 이때 SEQ 송신측의 현재 데이터의 순번을 의미하며, ACK는 내가 받은 데이터의 길이가 전부 합산된 데이터 순번을 의미합니다. 위에서 일단 A가 B를 향해 현 데이터 순번 1000(SEQ)번째의 데이터 1000 바이트(옥텟)만큼 보냈습니다. 이를 받은 B는 제대로 데이터를 수신했음을 확인하는 ACK 패킷을 A에게 날리게 되는데 이때 ACK 의 값으로 받은 패킷의 SEQ값에 데이터 길이를 더한 2000 을 기록해서 보냅니다.

단편적으로 아주 간략화 해서 TCP의 동작을 알려드린 것이기 때문에 실제와는 조금 차이가 있을 수 있습니다만, 일단 기본적인 데이터의 송신과 송신 확인 구조는 이런 식으로 이루어 집니다.

/* 위에 그림의 글에도 그렇고 제가 바이트(옥텟) = byte(octet) 이라는 단위를 사용했는데, 원래 통신쪽에서는 데이터의 길이를 옥텟(octet)이라고 표현합니다. 이는 원래 1바이트가 8비트라는 정의가 정확한 정의가 아니기 때문에 이런 표현이 생긴 것입니다. 바이트(byte)의 의미는 사실 일정한 개수의 비트로 이뤄진 연속된 비트열입니다. 지금이야 흔히 1바이트=8비트 라고 관습(^^)과 같이 정의되고 있지만, 사실 배우기 편하라고 아니면 뭐 대부분 그렇게 쓰니깐 그렇게 쓰는 것이지 실제로는 정확한 정의는 아닌 것이죠. 그러나 통신과 같이 상대가 어떤 기기와 환경을 쓰던 동일한 표현식이 필요한 동네에서는 정확한 규칙이 필요하게 됩니다. 따라서 옥텟(octet)이라는 단위를 사용하는데요. 이는 8bit에 해당한다는 정의가 정확히 되어 있습니다. 위에서 IP, Port 를 적을때 big endian이라고 하지 않고 Network Byte Order라고 한 이유도 사실 그런 것이겠죠.^^ */


그럼 데이터의 송신과 수신측의 응답이 위와 같이 일어난다는 것은 알았는데, 실제 데이터의 전달이 보내고 확인하고 보내고 확인하고~ 의 반복이라면 A와 B 사이의 전송 시간이 긴 경우(특히 우리가 사용하는 폰의 무선 환경이 그렇죠^^ 아니면 뭐 집에서 유선망을 통해서 저 멀리 아프리카의 이름도 잘 모르는 나라의 웹서버의 페이지를 보려고 한다고 생각해보세요. 아무래도 시간 좀 걸리겠죠^^) 아마 무자게 데이터 전달이 느리게 될 것입니다. 따라서 실제 TCP 프로토콜의 동작은 위의 동작에 송신 버퍼라는 것을 별도로 두고 송신할 수 있는 한계까지 좌르륵~ 하고 날립니다. 아래 그림과 같다고 보시면 됩니다.

clet8_4.png

저 위에서 본 그림보다 이렇게 보내는게 아무래도 전체 전달 시간을 생각하면 훨씬 빠를겠죠. 다만 이 경우 문제가 되는 것은 송신(A)측에서 데이터를 보내놓고 ACK를 받을 때까지 송신 버퍼내의 데이터를 삭제할 수가 없다는 것입니다. 왜냐면, 만약 ACK 1001 이 오지 않고 2001 부터 그 뒤의 ACK만 왔다고 생각하면 TCP는 바로 데이터 1~1000 범위를 다시 재전송해야 하기 때문입니다. 이렇게 패킷의 전달에 신뢰성을 확보하는 것이죠.(물론 이런 설명은 비약이 좀 있는 설명이고 사실 좀 더 복잡하게 동작합니다만, 아무튼 기본적인 동작 방법은 이런식이라는 것입니다.^^) 따라서 송신한 A측은 사실 ACK를 받기 전까지 해당 송신 버퍼를 비울 수가 없습니다. 그런데 폰과 같은 기기는 저런 버퍼가 상대적으로 컴에 비해서 훨씬 작습니다. 그렇다 보니 우리가 MC_netSocetWrite 로 신나게 사이즈가 적당히 큰 데이터를 마구마구 전송하면 느린 무선망의 속도와 함께 시너지효과를 발휘해서(^^) 송신 버퍼가 금방 가득차게 되겠죠. 그럼 시스템 입장에서는 현재 데이터를 보낼 수 있는 상태가 아니니 좀 있다가 보내라라는 의미로 M_E_WOULDBLOCK 과 같은 메세지를 알려주는 것이고 나중에 보낼 수 있는 상태가 되면 알려주겠다라는 것이 MC_netSetWriteCB 로 콜백 함수를 등록하라는 것입니다.

대략 이해가 되셨는지 모르겠습니다만, 이외에도 TCP는 연결과 종료시에도 독특한 신호를 보내서 연결을 성립시키고 연결을 종료하게 됩니다. 연결의 성립은 그리 문제가 되는 부분이 아니니 그러려니 해도 되는데, 종료는 실제 예제 코드를 실행해 본 후에 좀 더 살펴보도록 하겠습니다.


[좀 더 생각해볼만한 부분 ^^ : 코딩 작전(^^)]

위와 같은 TCP 의 전송 방식은 송신 버퍼에만 문제를 주는 것이 아니라 수신 버퍼에도 문제를 발생하게 됩니다. 즉 우리가 B의 입장이라면 A가 무턱대로 데이터를 마구마구 쏴버리면 더이상 패킷을 받아둘 공간(수신 버퍼)이 부족한 경우 결국 도착한 데이터도 버려야 하는 사태가 발생하겠죠. 다행히 TCP의 헤더에는 이런 부분을 방지하기 위해 서로간에 수신 가능한 양을 알려주는 값(이를 window size, 윈도우 크기 라고 부릅니다.)도 포함하고 있습니다. 따라서 수신 버퍼가 넘치는 사태는 발생하지 않도록 미연에 막아주는 것이죠. 다만 여기서 우리가 집중해서 생각해야 할 부분이 존재하는데, 즉 수신 버퍼를 빨리 비워주지 않으면 결국 통신 속도 저하를 발생 할 수 있을 것이라는 것입니다.

즉 MC_netSetReadCB에 등록해둔 패킷 읽기 함수에서 시간을 끌면 안된다는 것입니다. 최악의 경우 너무 패킷이 몰려 들어와서 읽어야 할 양이 많아졌는데, 우리가 조금씩만 읽었다면(즉 쌓인 양이 100 바이트인데 우리는 한번에 1바이트씩 읽었다는 가정이라면^^) 계속 읽기 함수가 시스템에서 호출이 될 것입니다. 이후부터는 시스템이 어떻게 설계되었냐의 문제지만, 경험상 최악의 경우는 결국 우리가 설정해둔 타이머 루프에 한참 접근하지 못할 수도 있고, 만약 일정 프레임 출력이 필요한 게임인 경우 데이터 수신부를 어떻게 설계하고 작성했냐에 따라 데이터 수신시에 애매한 문제가 발생할 수 있는 가능성이 있는 것입니다. 즉 이런 부분을 이해하면, 해당 환경(우리는 폰 환경)의 특성을 이해하고 어떻게 프로그램을 설계해야 할지에 대한 작전(^^)을 세우는데 꽤 도움이 되겠죠.

사실 Network API 에 대한 내용을 구상하면서, 그냥 예제 코드도 복잡할텐데 예제만 잔뜩 넣고 API만 보고 동작은 이렇게 하면 됩니다~라고 편하게 적어보자라는 생각도 했습니다만, 최근에 보면 학교에서 조차 이런 내용을 잘 못 접하는 것 같아서 아쉬운 마음에(솔직히 열심히 코드만 작성하는게 전부가 아닐텐데 말입니담--;), 좀 더 어려울 수 있지만, 그래도 지금 봐두면 나중에(몇년후라도)라도 새로운 앎의 씨앗이 될 수 있기에, 떠들어 두기로 생각하고 열심히 적고 있습니다만, 역시 더 세세하게 파고 들어가면 거의 네트워크 전공 수업이 될 것 같아서 실무선에서 필요한 정도로 이정도만 언급하려고 합니다.

따라서 더 상세한 내용을 알고 싶으시다면, 저는 이런게 있다는 것들은 소개해 드렸으니 이후는 스스로 책등을 통해 더 공부하시길 권해드리겠습니다. 화이팅!


아무래도 이 강좌를 보시는 분들이 네트워크에 대해 어느 정도의 지식이 있는지 모르기 때문에 설명이 꽤 길어 졌습니다만, 이제 드디어 기다리던(ㅎㅎ) 예제를 만들어서 테스트를 해 보겠습니다. 다만 HTTP쪽이라면 그냥 웹페이지를 불러보면 되지만, TCP 테스트는 TCP 연결과 데이터를 주고 받을 수 있는 서버가 필요하기 때문에 직접 예제 서버도 만들어 보았습니다. 일단 그럼 클라이언트부의 코드부터 에뮬레이터에 작성해 보겠습니다. 이쁘게 화면에 출력하면 좋겠지만, 사실 코드 작성도 꽤 일이라서리(^^) 그냥 디버그 출력에 의존하는 걸 양해해주시길 바랍니다.

  1. #include "WIPIHeader.h"
  2. // 이 예제에서 사용하는 패킷의 구조는 첫 4바이트에 이어지는 데이터의 길이가 little-endian 형으로 저장되어 있고,
  3. // 그후 데이터가 바로 이어지는 구조입니다. 이런 식으로 해야하는건 결국 도착한 데이터 길이를 알 방법이 없기 때문이죠.
  4. // 따라서 읽어야 하는 데이터 길이를 대개 첫부분에 넣어서 그 부분부터 확인하게 됩니다. 대부분이 이런 방식을 사용하죠.
    #define DEF_TIMERLOOP_TIME    1000                // 타이머의 루프 타임
    #define SERVER_IP             "192.168.0.2"       // 서버 IP 주소 문자열
    #define SERVER_PORT           10000               // 서버 포트 번호
    #define PACKET_HEADER_SIZE    4                   // 패킷의 헤더 사이즈
    #define SEND_DATA_STRING      "TCP TEST DATA!!!"

    M_Int32 flagNetConnect=0; // 연결 상태 표시 1이면 연결, 0이면 연결 아님
    M_Int32 socketFD=-1;      // socket 식별자
    M_Int32 sendIdx,sendLen;
    M_Int32 recvIdx,recvLen;
    M_Char sendBuf[1024];
    M_Char recvBuf[1024];
    MCTimer tm;

    void timerCB(MCTimer* ptm, void *param);
    void Clet_NetConnect();
    void Clet_NetDisconnect();
    void Clet_netConnectCB(M_Int32 error, void * param);
    void Clet_netSockConnectCB(M_Int32  fd, M_Int32  error, void*  param);
    void Clet_WriteData();
    void Clet_NetWriteCB(M_Int32  fd, M_Int32  error, void*  param);
    void Clet_NetReadCB(M_Int32  fd, M_Int32  error, void*  param);

    void startClet(int argc, char* args[])
    {
        MC_knlPrintk("* Test Application Start\n");
        MC_knlDefTimer(&tm,timerCB);   // 타이머 초기화 및 콜백함수 등록  
        MC_knlSetTimer(&tm,DEF_TIMERLOOP_TIME,1);    // 타이머 설정
    }
    void handleCletEvent(int type, int param1, int param2)
    {       
        switch(type)
        {
        case MV_KEY_PRESS_EVENT:
            MC_knlPrintk("[KEYPRESS] [%c]\n",param1);
            switch(param1)
            {
            case MC_KEY_1:    // Network Connecton 시도
                Clet_NetConnect();
                break;
            case MC_KEY_2:    // Send Data
                if (flagNetConnect) Clet_WriteData();
                break;
            case MC_KEY_3:    // Network Disconnect
                Clet_NetDisconnect();
                break;
            }
        }
    }
    void pauseClet() {}
    void destroyClet()
    {   // 사실 종료시에 이런 처리를 해주는게 좋은 습관이겠죠.(플랫폼에 따라 이녀석이 안 불리는 Clet도 있습니다만--;)
        MC_knlUnsetTimer(&tm);
        if (flagNetConnect) Clet_NetDisconnect();
    }
    void resumeClet() {}
    void paintClet(int x, int y, int w, int h) {}
    void timerCB(MCTimer* ptm, void *param)
    {
        MC_knlPrintk("* timerCB Call(%d)\n",(int)param);
        MC_knlSetTimer(ptm,DEF_TIMERLOOP_TIME,(int)param+1);   // 타이머 설정
    }
    void Clet_NetConnect()
    {
        M_Int32 retVal;
        memset(sendBuf,0,1024);
        memset(recvBuf,0,1024);

        retVal = MC_netConnect(Clet_netConnectCB, NULL);
        if (retVal==0)
            MC_knlPrintk("* MC_netConnect : OK\n");
        else
            MC_knlPrintk("* MC_netConnect : Err[%d]",retVal);
    }
    void Clet_NetDisconnect()
    {  
        if (socketFD >= 0) {
            MC_netSocketClose(socketFD);   // 소켓이 열린 상태라면 넷을 끊기전에 먼저 끊어주는게 좋겠죠.
            socketFD=-1;
        }
        MC_netClose();
        flagNetConnect = 0;
        MC_knlPrintk("* Net Disconnect.\n");
    }
    void Clet_netConnectCB(M_Int32 error, void * param)
    {
        M_Int32  addr;
        M_Int16  port;
        M_Int32 retVal;

        if (error!=0)
        {
            MC_knlPrintk("* Connect CB Error [%d]\n",error);
            return;
        }
        flagNetConnect = 1;
        socketFD = MC_netSocket(MC_AF_INET, MC_SOCKET_STREAM); // TCP 소켓임을 알려줍니다.
        if (socketFD < 0)
        {
            MC_knlPrintk("* MC_netSocket : Err[%d]\n",socketFD);
            Clet_NetDisconnect();
            return;
        }
        MC_knlPrintk("* MC_netSocket : OK [%d]\n",socketFD);

        addr = MC_utilInetAddrInt(SERVER_IP); // Network Byte Order로 IP,Port 값을 구합니다.
        port = MC_utilHtons(SERVER_PORT);
       
        retVal = MC_netSocketConnect(socketFD,addr,port,Clet_netSockConnectCB,NULL);
        if (retVal < 0)
        {
            MC_knlPrintk("* MC_netSocketConnect : Err[%d]\n",retVal);
            Clet_NetDisconnect();
            return;
        }
        MC_knlPrintk("* MC_netSocketConnect : OK\n");
    }
    void Clet_netSockConnectCB(M_Int32  fd, M_Int32  error, void*  param)
    {
        if (error!=0)
        {
            MC_knlPrintk("* SockConnect CB Error [%d]\n",error);
            Clet_NetDisconnect();
            return;
        }
        sendIdx=0;
        sendLen=0;
        recvIdx=0;
        recvLen=PACKET_HEADER_SIZE;
        Clet_NetReadCB(fd,0,NULL);  // 연결이 완전히 성공된 후 왜 이 함수를 부른걸까요? 이건 다음 강좌까지 숙제^^
    }
    void Clet_WriteData()
    {
        M_Int32 dataLen;
        dataLen = (M_Int32)strlen(SEND_DATA_STRING);
        MC_knlSprintk(&sendBuf[4],"%s",SEND_DATA_STRING);
        memcpy(sendBuf,&dataLen,4);
        sendIdx=0;
        sendLen=dataLen+4;
        MC_knlPrintk("* Write Data : size[%d]\n",sendLen);
        Clet_NetWriteCB(socketFD,0,NULL);
    }
    void Clet_NetWriteCB(M_Int32  fd, M_Int32  error, void*  param)
    {
        M_Int32 retVal;

        if(error<0)
        {
            MC_knlPrintk("* Write CB : Err[%d]\n",error);
            Clet_NetDisconnect();
            return;
        }
        MC_knlPrintk("* Write CB Start\n");

    GOTO_SendData:

        retVal = MC_netSocketWrite(fd,&sendBuf[sendIdx],sendLen);
        if (retVal == M_E_WOULDBLOCK) // 꼭 이런 처리를 해줘야 합니다.
        {
            MC_knlPrintk("* MC_netSocketWrite : M_E_WOULDBLOCK\n");
            MC_netSetWriteCB(fd, Clet_NetWriteCB, NULL);
            return;
        }
        else if (retVal < 0)
        {
            MC_knlPrintk("* MC_netSocketWrite : Err[%d]\n",retVal);
            Clet_NetDisconnect();
            return;
        }
        MC_knlPrintk("* MC_netSocketWrite : OK : size[%d]\n",retVal);
        sendLen -= retVal;
        if (sendLen > 0)
        {
            sendIdx += retVal;
            goto GOTO_SendData;
        }
        else
        {
            sendIdx = 0;
            sendLen = 0;
        }
    }
    void Clet_NetReadCB(M_Int32  fd, M_Int32  error, void*  param)
    {
        M_Int32 retVal;

        if(error<0)
        {
            MC_knlPrintk("* Read CB : Err[%d]\n",error);
            Clet_NetDisconnect();
            return;
        }
        MC_knlPrintk("* Read CB Start\n");

    GOTO_readData:

        retVal = MC_netSocketRead(fd,&recvBuf[recvIdx],recvLen);
        if (retVal == M_E_WOULDBLOCK)  // 여기도 마찬가지로 꼭 빼먹지 말아야 하는 부분입니다.
        {
            MC_knlPrintk("* MC_netSocketRead : M_E_WOULDBLOCK\n");
            MC_netSetReadCB(fd, Clet_NetReadCB, NULL);
            return;
        }
        else if (retVal < 0)
        {
            MC_knlPrintk("* MC_netSocketRead : Err[%d]\n",retVal);
            //Clet_NetDisconnect();
            return;
        }
        MC_knlPrintk("* MC_netSocketRead : OK : size[%d]\n",retVal);

        recvLen -= retVal;
        recvIdx += retVal;
        if (recvLen > 0)
        {       
            goto GOTO_readData;
        }
        if (recvLen == 0 && recvIdx == 4)
        {  
            memcpy(&retVal,recvBuf,4);    // 헤더에서 데이터 길이 읽기
            recvLen = retVal;
            MC_knlPrintk("* Read Header : Data Size [%d]\n",retVal);
            goto GOTO_readData;
        }
        recvBuf[recvIdx] = 0;    // 문자열 종결자
        MC_knlPrintk("* Read Data : [%s]\n",&recvBuf[4]);

        // 읽기 초기화
        recvIdx=0;
        recvLen=PACKET_HEADER_SIZE;
        goto GOTO_readData;
    }

예제 코드가 살짝 길어졌습니다만, 네트워크로 간단히 데이터를 주고 받기 위해서 만큼 코드가 이정도면, 꽤 적은 사이즈가 아닐까 싶은 느낌이 듭니다만^^. 사실 간단히(급하게ㅠㅠ) 만드느라 군데군데 허점도 존재하고 뭐 완벽하지는 않은 코드입니다만, 일단 동작 순서만큼은 확실히 제대로 동작하는 형태를 지킨 것이니깐, 어디까지나 이런 구조로 Clet 에서는 네트워크 프로그램을 만들 수 있다는 것에 의의를 두고....아무튼 실행해 봐야겠죠.


그전에 네트워크 테스트이니만큼 이 예제 코드에서 만든 패킷 형태를 읽어서 반응해줄 수 있는 녀석이 있어야 겠죠. 그래서 만들었습니다.(단순한 에코 서버이기는 합니다만^^)

일단 다운로드 : simpleServer_tcp.zip (소스코드는 다음 강좌때 배포시 넣도록 하겠습니다. 아직 udp 부분 못 만들었거든요 ㅠㅠ)

윈도우 콘솔 환경에서는 어디서든 동작할 수 있도록 일부러 cygwin 을 사용하는 어플로 만들었습니다. 안에 dll 도 함께 들어 있으니 그냥 압축 풀고 아래와 같이 실행하면 됩니다.

simpleserver_tcp.png

솔직히 너무 급하게 만든거라 뭐 동작하는데 의의가 있는 단순 1 커넥션만 받아들이는 초 단순 서버 어플입니다만, 일단 그래도 어딥니까...테스트를 위해 이런 것도 제공하다니..흑흑..덕분에 시간이 너무 걸렸어요.../* 간만에 표준 C 소켓 프로그래밍해 본거라 정말이지... 엄청난 삽질이었습니다. ㅠㅠ */

일단 실행은 저런 식으로 파라메터로 tcp 형식, 10000번 포트, 에코할 패킷을 받고 나서 다시 보내기까지의 딜레이 값을 넣어 주면 됩니다. (아직 udp 옵션은 동작하지 않습니다.^^)

제대로 실행이 되면 저런 상태로 접속을 기다리게 됩니다.


클라이언트 코드에서는 IP 입력 부분에 자신의 컴에서 서버도 실행한거라면 자신의 컴 IP 를 넣으면 되겠죠.

그럼 에뮬을 실행해서 디버그 창을 띄우시고 타이머가 돌고 있는게 보이시면 1번키를 눌러서 접속을 시도해보죠.

clet8_5.png

헉....소켓까지 제대로 연결이 되었는데 어째서 첫 read 함수에서 에러가 나오는걸까요....--;

-14 번은 현 SDK에서는 M_E_NOTCONN (연결이 설정되어 있지 않음) 이라는 에러인데요. 코드상에는 전혀 이상이 없는 구조인데 이상하다 싶어서 좀 알아보니...

이거 현재 공개된 SDK의 버그입니다. ㅠㅠ OTL........ ㅠㅠ........


그래도 일단 다른 SDK도 쓰시는 분들도 있을테고, 저도 여기서 좌절할 수 없어서(저걸 다 작성했는데..서버도 만들고..ㅠㅠ) CP 에게만 제공되는 SDK를 무작정 설치해서 실행 결과를 얻어냈습니다. /* 아쉽게도 공개된 것 이외에 것들은 CP 레벨에게만 공개되는 것이기에 제가 배포할 수가 없습니다. 사실 인증키 방식도 공개 SDK와 달라서 다운받은 컴의 MAC도 함께 인증하는지라 어찌 하기가 그렇네요. 이 부분은 다음 강좌때 추가적으로 공개되어 있는 SDK(LGT Clet SDK등이 있겠네요) 로 한번 시도하는 것도 넣어야 할 것 같은데...과연 다음 강좌에 제가 그 내용을 쓸 여력이 될지....흐...아무튼 언젠가는 이런 버그를 만날 것 같았는데...엄청 아쉽네요...--; */

clet8_6.png

/* 1번키를 눌러서 연결후 2번키를 눌러서 패킷을 쏘고 2초후에 제대로 돌아온 것을 한번 더 반복 후 3번키로 끊었습니다. */


아무튼, 코드 자체에는 이상이 없는 예제 코드이니 나중에 기회가 다시 되실때 다른 SDK 환경에서 실험해보시는 방법뿐이 현재로서는 어찌 대안을 바로 찾기가 쉽지 않을 것 같습니다. 미리 이런 문제가 있다는 걸 몰랐던지라 제가 더 죄송하네요. 다음 강좌때까지 한번 다른 방안을 찾아보도록 하겠습니다.(ㅠㅠ)

/* 혹시나 tpak 등에서 현재 CP에게 주어지는 SDK를 받으셨다면, 설치에 주의하셔야 할게 있습니다. 동일한 SDK 구조이다보니 레지스트리가 중복되는 현상이 일어나서 인증때 어플이 다운되는 현상이 발생합니다. 고로 왔다갔다 하려면 레지스트리를 직접 건들여 주셔야 합니다. 이건 좀 요령이 필요한 부분이고 사실 공개할만한 부분도 아닌지라 좀 뭐시기 합니다만, 아무튼 혹시 새 SDK 를 설치하시겠다면, 기존 SDK는 깨끗히 날려주시고 설치하시기 바랍니다. */


다시 본론으로 돌아와서, 실제 write 시에 M_E_WOULDBLOCK 이 나오는 것을 보려면 폰에서 좀 열심히 패킷을 날려봐야 합니다. 컴에서는 버퍼 사이즈 자체가 워낙 크다보니 그런 조건을 만들려면 서버 어플이 일부러 TCP ACK를 안 보내도록 좀 골치 아픈 코딩을 해야합니다. /* 즉 TCP 헤더를 직접 제어해야 하는거죠. 쉽지 않을 것 같다는 생각이 팍팍 드시죠...실제로도 직접 만들려면 너무 신경써야 할게 많아집니다. */ 대신 read 시에는 확실히 M_E_WOULDBLOCK의 효과를 보고 있네요.


코드상에 주석으로 적었습니다만, 왜 connect이 성공한 후에 Clet_NetReadCB 를 호출 했을지 한번 생각해보시기 바랍니다. 이 부분은 다음 강좌때 다른 SDK 환경에서 테스트가 가능해지면 그때 실제로 어떤 차이가 있는지 살펴보도록 하겠습니다. /* 그래서 숙제인거죠. 뭐 위에서 부터 제대로 읽어 오셨다면, 너무나 당연한 질문을 하고 있다고 생각하실 듯합니다만 ^^ */


일부러 타이머 루프를 돌린 이유는 대개 Clet 에서는 저 타이머 루프를 이용해서 메인 어플의 루프를 동작 시키는데 그것과 별개로 콜백 함수들이 호출된다는 걸 보여드리려고 한 것입니다. 실제로 상용 어플을 제작할때도 이 부분은 꽤나 어플 성능에 영향을 줄 수 있는 부분입니다. 실제로 위의 코드는 구조야 제대로 동작할 수 있게 만든 것 이지만, 내용은 꽤 단순화 해서 표현 한 것 입니다. 어떤 부분을 어떻게 고쳐서 쓰는게 좋을지는 계속 해서 작업 하시다보면 자연스럽게 느낄 수 있는 부분이 아닐까 하는 생각이 드네요. 이런 부분은 사실 정답이라는게 없고 계속 노력하고 생각해보는게 좋은 접근법이라고 생각합니다.


마지막으로 TCP 방식에서 고려해야하는 부분중 폰 동네에서 가장 이슈가 있는듯한(^^) 통신 종료 부분에 대한 이야기를 잠깐하고 마무리하도록 하겠습니다.

/* 이 내용은 폰 네트워크를 통해 연결되는 서버 프로그래밍을 할 경우에 필요한 내용이기는 합니다. 그래도 클라이언트 개발자라고 해도 일단 어느정도 알고 개발하는 것이 나중을 위해서도 좋지 않을까 하는 마음에 간단히 정리해둡니다. 마찬가지로 그냥 학교 과제등으로 Clet 을 익히고 계신 분들이라면 뭐 이런 이상한 이야기가 다 있나~ 하고 그냥 넘기셔도 무방할 것 같기도 합니다. 어디까지나 상당히 실무적인 이야기니깐요.^^ (사실 뭐 위에서 이야기한 내용들도 좀 실무스럽기는 합니다만, 원래 넷을 사용할 정도가 되면 실무적이어야 하지 않을까요^^) */


TCP는 커넥션 지향의 프로토콜 이라고 위에서 이미 설명하였습니다. 따라서 통신의 종료도 서로 정확히 종료를 전달하고 그것을 인지했다는 신호를 보내는 것을 원칙으로 하고 있습니다. 즉 위에서 데이터를 전달할때 SEQ 번호를 보내서 ACK 를 받는 것과 같은 유사한 동작히 우리가 Socket Close 를 명령하면 소켓내에서 행해지게 됩니다.

문제는 폰은 무선망을 사용하고 일단 속도도 느립니다만, 더불어 MC_netClose가 불리면 가차없이 아예 폰이 무선 망 접속을 확 끊어 버리는게 문제가 되는 부분입니다.

이게 어떻게 문제가 되는지에 대해서 이해하려면 TCP state 변화도를 보셔야 하는데요. 아래와 같은 것입니다.

clet8_8.png/* 엄청 유명한 TCP/IP 고전책에 있는 그림^^ */

TCP의 상태 변화 과정을 나타내는 것인데, 우리가 봐야할 부분은 아래 부분의 점선으로 그려진 네모 두군데에 해당합니다. /* 흔히 많이 사용하게 되는 TCP/IP 통신때 일어날 수 있는 여러가지 상황을 이해하기 위해서라도 관심있는 분들은 위의 다이어그램을 익혀두실 필요가 있습니다. 뭐 대략 이런 놈이 있었지~ 라는 느낌으로 기억만 해두셔도 나중에 문제를 찾기위해 패킷을 캡쳐해서 볼때 도움이 될 수 있겠죠^^ */

점섬으로 그려진 왼쪽 하단 박스 아래를 보면 active close 라고 되어있고 오른쪽의 점선 박스는 passive close 라고 되어있죠. 이는 TCP 연결중 close를 먼저 부른쪽이 FIN을 날리면서(send:FIN) FIN_WAIT_1 상태로 변하고(먼저 부른쪽이 그래서 active close), 이 FIN을 받은 쪽(recv:FIN)이 CLOSE_WAIT 상태로 변하면서(passive close) 종료 과정이 일어나는 것을 보여주는 것입니다.


좀 더 이해하기 좋도록 괜찮은(^^) 그림을 어딘가에서(사실 이런 그림을 언제 모아두었는지도 기억이 안 납니다만--;) 들고 왔습니다.

clet8_9.png

위 흐름을 보면 client 가 먼저 close을 시작해서 FIN 과 ACK 의 흐름에 따라 어떤 상태로 움직이는가가 보이고 있습니다.

문제는 이건 어디까지나 깔끔하게 종료되는 경우의 그림이라는 것이죠.


이제부터 무선망을 이용한 폰 어플과 연결되어 있던 서버 간의 일어날 수 있는 상황에 대해서 생각해보도록 하겠습니다.

  • 폰 어플이 먼저 끊는 경우(close 주체가 어플리케이션, 즉 위 그림에서 client가 폰)

    • 대개 socket close후 바로 network close 를 부르게 되므로 사실 client 측의 상태 변화로 봤을때 어느 상태까지 갔는지 확인하지 않고 망이 단절될 수 있습니다.
    • 즉 server 측은 client에서 socket close 후 FIN-ACK의 흐름이 어디까지 진행될 수 있을지가 TCP 상태를 결정하는데 중요한 역활을 합니다.
    • 최악의 경우로 client가 FIN 을 보내지도 못하고 망이 단절(무선망이니 상상에 맡기겠습니다.^^)되면 사실 server는 끊어졌는지도 모르게 되는 것이죠.
      실제 이런 현상은 폰 네트워크를 다룰 때 꽤 잘 나타나는 증상입니다. 자 그럼 이런 경우 어떻게 처리해야 할까요^^
      이런 경우의 대응 방법이 딱 정답이 정해진게 없는 동네이니만큼, 직접 생각해보시기 바라겠습니다. 어디까지나 일어날 수 있는 상황을 생각해보자는 것이니깐요.^^
      (사실 여러 책들을 보다보면 좋은 해결 방안들이 잘 언급되기도 하고 그러니깐 이 기회에 공부해보는 것도 나쁘지 않겠죠^^)
    • 만약 FIN-ACK 의 흐름이 진행중에 망이 단절된다면 어떨까요? 그럼 서버의 상태는 LAST_ACK에 멈출 확율이 높겠죠. 즉 흐름이 일단 진행이 되었다면 client가 보낸 FIN은 받았을 것이고 그럼 거기에 ACK를 보내면서 CLOSE_WAIT 로 상태 변화후 서버 구조에 따라 이를 감지해서 (socket) close를 부르겠죠. 그럼 서버도 FIN 을 보내고 LAST_ACK 상태에 들어갈 것입니다. 그런데 흐름 진행중에 망이 단절된거니 사실 서버가 보낸 FIN이 client 에서 못 받을 가능성이 높고 설령 망의 단절이 client가 FIN을 받은 후에 단절 되었다고 해도 어짜피 서버는 마지막 ACK를 못 받은 것이니 계속 LAST_ACK 상태로 있을 것입니다. 아무리 PC상의 서버라고 해도 이런 식으로 사용하지 않지만 소켓을 점유하는(리소스 점유, 더 문제는 그 해당 포트를 점유하고 있다는거죠.) 양이 늘어난다면, 결국 서버는 새로운 연결을 못 여는 상태까지 갈 수 있습니다. 물론 현재의 OS들은 이런 문제를 해결할 방법을 제공하고 있습니다. 일정한 타임아웃값을 둔다거나 결국 TCP의 재전송 횟수 카운트등을 이용한 CLOSED 상태로의 점프를 제어하고 있죠. 이런 부분도 서버 프로그램시에는 상당히 신경 써야 하는 부분입니다.
  • 서버 어플이 먼저 끊는 경우 (close 주체가 서버, 즉 위의 그림에서 client가 서버고 폰이 server 인 상황)

    • 일단 서버가 먼저 close를 시도하니 분명 FIN 이 server(폰)에 도착하겠죠. 그후 폰에서도 ACK를 보낼테고 폰(server)에서 close가 불리면 마찬가지로 FIN이 나가게 될 것입니다.
    • 문제는 위와 같은 흐름에서 언제 무선 망이 단절될 것이냐는 것이죠. 실제로 서버(client)가 보낸 FIN에 대한 ACK는 돌아왔는데 폰(server)측의 FIN이 안 오는 경우가 꽤 잘 일어나게 됩니다. /* 물론 각 이통사마다의 망 구조와 망 타입에 따라서 조금씩 다른 결과를 보여줄껍니다. */ 즉 FIN_WAIT_2 같은 상태에 빠질 확율이 높다는 것이죠. 문제는 TCP의 스펙(RFC 793)상에 정의로는 FIN_WAIT_2 에서 상대측의 FIN을 받지 못하면 무한정으로 저 상태에 있는다는 정의가 있다는 것입니다. /* 아마 TCP/IP 관련 책을 잘 읽어 보셨다면 관련 내용을 보신 적이 있으실 껍니다. */ 사실상 저 상태로 멈춰버리면 이 또한 소켓 점유 상태로 머무는 것이므로, 결국 서버측의 소켓 리소스 부족으로 문제가 될 수 있습니다. 다행히 현대의 OS들은 이 문제를 별도의 타이머를 두던가(Win2003 server에는 아에 따로 이름이 있더군요.) TCP에 keepidle 시간등을 통해서 제어하도록 하고 있습니다. 위의 케이스와 마찬가지로 이 또한 서버 프로그램시에 꽤 조심해야 하는 부분이 될 것 입니다.(관심이 가시는 분들은 소켓 옵션 제어쪽을 보시면 감이 오실 것 같습니다.)

결국 뭔가 내용은 복잡해 보이지만, 결론을 간단하게 정리하면, 일반적인 유선 네트워크의 서버를 생각하고 서버 프로그램을 만들었다가는 이래저래 고생할 수 있다는 것입니다.^^ 물론 최근에 OS가 확실히 좋기는 합니다. 저런 문제 상황들에 알아서 대처하는 여러가지 방안을 가지고 있거든요. 그래도 주의해야할 부분이기 때문에 이야기해보았습니다. /* 실제 제가 임베디드 s/w를 만들던때에 저 FIN_WAIT_2 문제로 엄청 고생한 적이 있었습니다. 임베디드 기기에 들어가는 realtime os 들은 용량이 작아야 하고 덕분에 가볍고 저런 예외 사항 처리에 취약하죠. 결국 저런 이론을 잘 몰랐던 시절에는 소켓 리소스가 완전히 박살나는 것도 여러번 경험했었습니다. 그러고 나서 폰에서 다시 네트워크 어플을 경험하다보니 이제는 저런 TCP의 흐름 같은 것으로도 이런저런 문제를 생각해볼 수 있게 된 것 같습니다. 결국 첨에는 이런거 왜 알아야 하나 싶은 파트에 속하는 잼없는(^^) 내용입니다만, 결국 나중에는 다 필요해지는 것 같아서 살짝 무리해서(~.~) 남겨보았습니다. 실제 이런 문제로 고생하시는 분들에게 조금이나마 생각의 여지를 만들어 드렸다면 좋겠네요. */


정리

사실 Network API 편에는 어떤 내용을 적어야 하나를 생각하면서, 여러가지 단어들을 나열하다보니 참 뭐랄까...엄청 떠들고 싶은 이야기가 무지하게 많았습니다만 그래도 괜히 이상한 이야기로는 안 흘러가도록 참고 참아서 쓴게 이정도 입니다.(즉, 그동안 제가(^^) 폰 넷 동네에 쌓인게 무자게 많았다는 소리죠...--;;;)


사실 어떤 부분들에서는 현재로서는 이런 해법밖에는 방법이 없을 것 같다라거나 이럴때는 이렇게 해결하는게 3사 대응을 전부 하려는 부분에서 좋다와 같은 좀 더 현실적인 문제와 제 나름 해결해왔던 이야기들도 알려드렸으면 좋겠지만, 아무래도 이런 부분은 회사의 업무하고도 연관이 될 수 있는 부분이라 가급적 문제의 접근만 알려드리는 수준에서 더 진행할 수 없는 부분들이 있는 것은 내심 아쉽게 생각하고 있습니다.(지킬 것은 지켜야겠죠^^) 그래도 지금 이 글을 읽고 앞으로 폰 넷 플밍에 도전하실때 도움이 되길 바라는 맘으로 필요한 내용은 이야기했다고 생각합니다.^^


다음 시간에는 이번 시간에 이런저런 이야기를 하느라 미쳐 못 본(^^) UDP 부분과 나머지 소켓 API , 그리고 HTTP API 를 살펴보도록 하겠습니다. 더불어 확답을 드릴 수는 없지만, 소켓 예제 테스트를 할 수 있는 다른 에뮬 환경도 찾아보도록 하겠습니다.(그러나 솔직히 시간 여유가 점점 부족해지고 있어서 자신은 없네요^^)


마지막으로 폰에서 이 넷 동네는 정말 매일 새로운 동네입니다. 갑자기 출시된 새로운 폰에서는 전혀 다른 증상을 보이는 경우도 종종 보게 되고, 왜 안되는지 이유를 모르게 하는 폰들도 가끔 출현하고...그래도 폰의 가장 매력적인 부분이 어디서나 넷의 연결이 가능하다가 아닐까 싶습니다. 그만큼 도전의 가치도 있는 부분이겠죠. 고로 힘내서 도전하는 자세가 정말 필요한 동네라는 이야기를 꼭 드리고 싶었습니다.^^


그럼 다음 내용에서 뵙도록 하겠습니다.


본 내용은 크리에이티브 커먼즈 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.

분문의 오류나 오타등의 문의는 문의 게시판에 해주시기 바랍니다. 본 내용은 지속적으로 업데이트 될 수 있습니다.^^
Email : juno@evermore.pe.kr