TCP 소켓에는 총 11가지의 상태가 있고(netstat를 이용해 소켓의 상태를 보면 CLOSED, LISTEN, SYN_SENT, SYN_RECV, ESTABLISHED, CLOSE_WAIT, LAST_ACK, FIN_WAIT1, FIN_WAIT2, CLOSING, TIME_WAIT..중에 하나가 나온다) 그 중 사람들이 자주 헷갈리는 것이 TIME_WAIT와 CLOSE_WAIT는 요 두가지 상태이다.

오늘은 이 TIME_WAIT와 CLOSE_WAIT, 요 두 상태에 대해 말해 보도록 하겠다.

들어가기에 앞서, 용어 정리를 먼저 하도록 하자.

  • Active Open : connect()를 호출 하는 쪽. 즉, SYN 세그먼트를 전송한 쪽. 종종 클라이언트가 된다.
    하지만  100% 클라이언트라고 할 수는 없다.
  • Passive Open : accept()를 호출 하는 쪽. 일반적으로 서버의 역할이다. 이 역시 100% 서버라고 단정지을 수는 없다.
  • Active Close : close()를 처음 호출 한 쪽. 즉 FIN 세그먼트를 전송 한쪽. 클라이언트, 서버 관계 없이 어느 쪽이든 다 active close를 할 수 있다
  • Passive Close : FIN 세그먼트를 받은 쪽.
  • MSL : maximum segment lifetime, 세그먼트가 네트워크내에서 폐기 되기 전까지 살아남을 수 있는 시간

Active Close를 하는 쪽(close() 함수를 호출한 쪽)은 TIME_WAIT상태가 되며, Passive Close 를 하는 쪽(FIN을 수신한 쪽)은 CLOSE_WAIT 상태가 된다. 아래 그림에서 A는 Active Close, B는 Passive Close를 하는 쪽이다 :

※ active close, passive close에 서버, 클라이언트 구분이 없는 것에 대해 주목하자.

TIME_WAIT 상태... 뭐할려고 있는가?
1. Active Close하는 쪽의 마지막 ACK가 소실 되었을 때, Passive Close하는 쪽은 자신이 보낸 FIN에 대한 응답을 받지 못했으므로 FIN(SEQ:6001, ACK:5001)을 재전송한다. 이 때 TCP는 connection 정보(주소와 포트)를 유지하고 있고, 이런 이유로 RST(에러라고 처리되는 세그먼트) 대신 ACK를 다시 보낼수가 있다.

2. 확률이 극도로 희박하긴 하지만 네트워크를 방황하던, Passive Close하는 쪽에서 보낸 중복된 FIN 메시지가 나중에 다시 생긴 connection에 영향을 주는 것을 방지하기 위해서 TIME_WAIT 상태를 2MSL 동안 유지한다.

그럼 CLOSE_WAIT 상태는?
CLOSE_WAIT는 Passive Close하는 쪽(FIN을 받는 쪽)에서 프로그램이 소켓을 종료 시키는 것을 기다리기위한 상태이다. 프로그래밍 관점에서 말하면, recv byte가 0으로 연결이 끊어 졌으나 close() 함수가 호출 되지 않은 상태라고 생각하면 된다.
TCP connection은 close() 함수가 명시적으로 호출되지 않으면 CLOSE_WAIT 상태에서 영원히 멈춰 있을 수 있고 이것은 자원 누수로 이어진다. 소켓 사용하고 나면 close()를 명시적으로 호출 해 주어야만 한다.

ref.
 - A Word on TIME_WAIT and CLOSE_WAIT 
 -
소켓 종료와 TIME_WAIT(Socket termination and TIME_WAIT)
 - TIME_WAIT state vs SO_REUSEADDR option

Posted by kukuta
TAG socket

댓글을 달아 주세요

  1. Favicon of http://devbear.tistory.com BlogIcon devJun 2011.09.26 19:49  댓글주소  수정/삭제  댓글쓰기

    좋은글 보고갑니다~ 담아가도 되겠죠~~??? ㅎㅎ

  2. 하드코어 2011.10.19 11:07  댓글주소  수정/삭제  댓글쓰기

    좋으네요... 오늘도 레벨업됬습니다..

  3. 로이리 2012.02.23 20:00  댓글주소  수정/삭제  댓글쓰기

    알기 쉽고 이해 잘되는 좋은글 보고 갑니다. ^-^

    항상 힘내시고 즐겁게 사세요! ㅋ

  4. Favicon of http://blog.daum.net/smufu BlogIcon kris.j 2012.08.03 10:27  댓글주소  수정/삭제  댓글쓰기

    이렇게 전체적으로 정리해둔 자료는 얼마 없는데 잘 정리 해두셨네요..

    거기다 엄청난 미남이시군요.. ^^

    참고로 2MSL이라는건 OS에 설정된 패킷(엄밀히 말하면 TCP Segment)이 사라지는 시간

    을 말하는겁니다.

    RFC 793 문서를 잠깐 보니 1MSL은 2분을 말하는군요(구현된 OS에 따라 다르겠지만.)

    그러니 대략 4분으로 보면 됩니다.

  5. hpux 2012.11.29 10:28  댓글주소  수정/삭제  댓글쓰기

    잘 정리된 글입니다. 도움이 아주~~많이 됐습니다. ^^!!!

  6. BlogIcon 프로그램중 2014.04.25 01:26  댓글주소  수정/삭제  댓글쓰기

    소중한 정보 감사드립니다

  7. 3F21 2014.07.18 10:21  댓글주소  수정/삭제  댓글쓰기

    잘보고 갑니다~ 글이 정리가 잘되어 담아가겠습니다~
    오늘도 좋은하루되세요~

얼마전에 간단한 서버/클라이언트 프로그램을 만든적이 있다. 어떤 놈이 나에게 못 미더운 듯이 묻기를 왜 recv를 처리하는 곳에는 while을 이용해서 read operation에 대한 오류 처리를 해주는데 send에 대해서는 아무런 처리를 하지 않냐고 했다.

블록킹 소켓일 경우 send 함수는 지정된 바이트를 모두 전송하기 전까진 리턴을 하지 않기 때문에 괜찮다라는 이야기를 친절(?)하게 해주었지만 이 샹콤한 놈의 자식이 아니라고 바득 바득 우기는데야...결국에 실험으로 보여주기는 너무 귀찮아져 man page를 찾아 보여주는 수고까지 해야 했다.

When the message does not fit into the send buffer of the socket, send normally blocks, unless the socket has been placed in non-blocking I/O mode. 
위의 내용을 간단하게 이야기 하자면 blocking 소켓에다가 send 버퍼를 넘어가는 데이터를 전송하라고 요청하면 블록킹 된다는 것이다. 다만 non-blocking 소켓은 EAGAIN 에러를 떨어 뜨린다는 것이다(여기서 우리는 non-blocking 모드에서는 errno를 철저하게 점검해야 하는 필요성을 느낄 수 있다).

한동안 포스팅 안하다가 포스팅이 이런 것이라니...혹시나 또 모르는 사람이 있을까 싶어서 적어 본다.

오늘의 교훈 : 우기지 말것(너 뿐만 아니라 나도-_-;;).
Posted by kukuta
TAG socket

댓글을 달아 주세요

  1. 천재 2009.01.09 18:19  댓글주소  수정/삭제  댓글쓰기

    기억은 정확히 나지 않지만 EINTR 이 온 경우 재시도 하기도 함.
    못 미더운 녀석

  2. Favicon of https://www.ikpil.com BlogIcon 최익필 2009.01.26 04:55 신고  댓글주소  수정/삭제  댓글쓰기

    http://msdn.microsoft.com/en-us/library/ms740149(VS.85).aspx 의 설명을 보면, "If no error occurs, send returns the total number of bytes sent, which can be less than the number requested to be sent in the len parameter" 이렇게 되어 있는데, 혹시 리눅스 계열은 다른가요?

    ... 이거 도통 모르겠네요;

    • Favicon of http://kukuta.tistory.com BlogIcon kukuta 2009.01.26 15:51  댓글주소  수정/삭제

      후움...알아 보고 연락 드리도록 합죠. 요것도 하나의 포스팅이 될 수 있겠는 걸요?

    • Favicon of http://kukuta.tistory.com BlogIcon kukuta 2009.01.26 15:56  댓글주소  수정/삭제

      동일한 페이지를 읽다 보니 아래와 같은 말도 있네요..
      "If no buffer space is available ..., send will block unless the socket has been placed in nonblocking mode..."
      라고 되어 있는데요. 아마도 위에서 익필님이 말씀하신 것은 blocking, non-blocking을 모두 통털어서 이야기 하는 것 같네요.

  3. Favicon of https://rhea1st.tistory.com BlogIcon Rhea Strike 2009.01.29 15:23 신고  댓글주소  수정/삭제  댓글쓰기

    암튼 동기소켓도 보낼때 Send()의 리턴값 구해 뺑뺑이 돌려주는게 정석... .

  4. Favicon of http://azena.info BlogIcon azena 2009.02.06 15:01  댓글주소  수정/삭제  댓글쓰기

    send() 함수에서 전송이 완료되는 시점까지 블럭킹 걸리는 것은 사실입니다만
    무한대로 전송을 기다리지는 않습니다.

    setsockopt 에서 지정할 수 있는 SO_SNDTIMEO 까지 기다리게 되는데
    문제는 타임아웃이 되어서 리턴이 되어도 리턴값은 보낸 바이트 수가 됩니다.

    즉, 100 바이트를 보내라고 시켰는데, 네트워크가 너무 느려서 50 바이트 밖에 못 보냈는데
    TIMEOUT이 되어서 리턴을 하면, 리턴값은 에러가 아니라 50 이라는 이야기죠.

    이것을 처리해주지 않으면 나머지 50 바이트는 안드로메다로...

    즉, 샹콤한 놈의 자식님의 말이 맞습니다. ㅎㅎ
    그동안 에러가 없이 동작한 이유는 타임아웃 동안 데이터를 다 못 보낼만큼 큰 데이터를 보낸 적이 없거나 네트워크 상태가 좋았기 때문입니다. 기본 타임아웃 시간이 긴 것도 한 몫하죠.

    • Favicon of http://kukuta.tistory.com BlogIcon kukuta 2009.02.06 17:32  댓글주소  수정/삭제

      오...넹!!
      우기지 않겠습니다. 샹콤한 놈의 자식한데 알려줘야 겠네요.

      알려주셔서 감사합니다.

  5. mhpark 2009.10.20 19:17  댓글주소  수정/삭제  댓글쓰기

    blocking은 영원히 blocking은 아니지만 #define LONG_MAX ((long)(~0UL>>1)) 시간만큼 기다리게 됩니다.

    2147483647초이지요. 물론 시그널에 의한 EINTR발생시에는 리턴하게 되지만요.

  6. 2010.07.25 10:03  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

RST 메시지

진리는어디에 2007. 10. 10. 13:54

1. Connect 시도시 목적 host와 port에 connection을 대기하는(listen)하는 프로세스가 없는 경우 발생
2. TCP가 현재 맺고 있는 연결을 날려 버리고자 하는 경우 발생.
3. TCP가 이전에 맺고 있던 연결이 더 이상 유효 하지 않다고 알리는 경우 발생

'진리는어디에' 카테고리의 다른 글

Flyweight Pattern  (2) 2007.10.24
Composite Pattern  (2) 2007.10.23
RST 메시지  (0) 2007.10.10
XML 기초  (9) 2007.10.04
정규 표현식(Regular Expressions)  (0) 2007.08.31
커널 오브젝트 - 핸들  (0) 2007.07.23
Posted by kukuta
TAG socket, TCP/IP

댓글을 달아 주세요

 일반적으로 UDP는 비연결 지향형 프로토콜로 알려져 있다. 맞는 말이다. 하지만 디폴트 UDP에 connect라는 것을 첨가하여 보다 편리 하게 쓸 수 있는 것도 사실이다.

 UDP소켓에다 connect 함수를 사용 한다는 것은 TCP의 그것 처럼 동작한다는 뜻은 아니다(Three-way handshaing 이라던지 하는 과정 들이 없다). 다만 커널에다 소켓이 향하고 있는 주소를 박아 넣어 send 할 때 마다 주소를 넘겨 줘야 한다는 것 외의 모든 동작방법이나 기타등등은 다 똑같다.

 자꾸 말을 번복하는 것 같지만, 내부에서 돌아가는 것은 다 똑같으나 사용법에 있어서 connected UDP와 unconnected(default) UDP는 다소 다른 점이 있다.

 * 더 이상 목적지 주소와 포트를 지정 할 필요가 없다.
   일반적으로 sendto 대신에 write를 사용하고, 꼭 sendto를 사용하고 싶다면, 목적지 주소(다섯번째 파라메터)
  는 NULL을 지정해야 하며, 주소 구조체의 크기(여섯 번째 파라메터)는 0이 되어야만 한다.

 * 데이터그램을 받기 위해 recvfrom을 사용 할 필요가 없다.
   read, recv, recvmsg를 대신 사용하게 된다. connect로 목적지 주소를 지정한 소켓은 그 주소에서 온 데이터그
  램만을 받아 들이고 다른 주소지의 데이터그램들은 무시하게 되므로, 주소를 알아내야 할 필요가 없다.


 * Asyncronous Error가 프로세스에게 리턴된다.
   예를들어 상대에게 UDP메시지를 보냈지만 목적 호스트에 메시지를 수신하는 프로세스가 없다고 가정하자. 상
  대측의 UDP는 ICMP unreachable 메시지를 다시 이쪽으로 반환 하겠지만 커널은 어떤 소켓에 그 메시지를 되
  돌려 줘야 할지 모른다. ICMP메시지가 UDP의 헤더의 사본을 가지고는 있지만, 스택은 어떤 어플리케이션이 그
  데이터그램을 보냈는지에 대한 정보가 없다.  하지만 소켓을 상대측의 주소와 연결한다면 이 사실은 소켓과 관련
  된 PCB에 기록될 것이고, UDP헤더의 사본을 PCB에 매치시켜 어떤 소켓이 ICMP메시지를 전달하는지 알 수 있
  게 된다.

 * BSD의 경우에는 약 1/3 정도의 성능 향상을 기대 할 수 있다.
   BSD에서 sendto는 특수한 경우로, sendto를 이용해 데이터가 전송 될 때, 커널은 소켓을 임시로 연결하고, 데
  이터그램을 전송하고, 그 후 소켓에 대한 연결을 끊는다.
 
   아래는 unconnected(default) UDP의 sendto 과정을 나타낸다.

    1. Connect the socket
    2. Output the first datagram
    3. Unconnect the socket
    4. Connect the socket
    5. Output the second datagram
    6. Unconnect the socket

   목적지로 두개의 데이터그램을 날려 보냈고, 하나의 데이터그램을 날릴때 마다 connect/unconnect과정을 거
  치고 있다. 하지만 어플리케이션이 여러개의 데이터그램이 한 peer로만 전송된다고 가정하고 connected UDP
  를 사용하게 되면 아래와 같은 과정으로 줄어 든다.

    1. Connect the socket
    2. Output the first datagram
    3. Output the second datagram

   4.3BSD와 이와 유사한 SunOS 4.1.1에 대한 연구 결과 [Partridge and Pink 1993]에서는 이런 방법으로 연결을
  설정하고 종료하는 것이 UDP 데이터그램 전송에 걸리는 시간의 1/3을 차지 한다고 한다. 이러한 UDP 코드는
  4.4BSD와 그 다음 버젼인 FreeBSD 등에서도 사실상 변하지 않고 남아 있다. 특히 이 스택들은 여전히 임시 연
  결과 임시 종료를 구현하고 있다. 따라서 연속되는 UDP데이터그램을 같은 상대측에 보내려고 한다면 먼저
  connect를 사용해서 효육의 향상을 기대 할 수 있다.

참고 : UNIX Network Programming
         Effective TCP/IP
         http://coolengineer.com/214

'진리는어디에' 카테고리의 다른 글

정규 표현식(Regular Expressions)  (0) 2007.08.31
커널 오브젝트 - 핸들  (0) 2007.07.23
connected UDP socket  (0) 2007.05.14
C++ operator 사용하기  (3) 2007.04.22
Threads Scheduling  (1) 2007.04.19
RTP(RealTime Transfer Protocol)  (2) 2007.04.12
Posted by kukuta

댓글을 달아 주세요

/**
 1699년 걸리버가 표류한 소인국 릴리푸틴(Lilliputin)들은 삶은 달걀을 먹는 방법에 생사를 걸었다. 왕은 달걀의 양끝 중 작은 쪽부터 깨서 먹을 것을 명령했으나 큰 끝부터 깰 것을 고집하는 빅-엔디안(Big-Endians)들이 사생결단해 저항했다.
 36 월력(月曆)간 6차례 대 폭동이 일어나고 1만1천명이 작은 끝부터 깨기를 거절하고 죽음을 택했다.

 걸리버 여행기 中
*/
 
네트워크 바이트 순서(Network-byte order)

 CPU가 데이터를 처리하기 위해서는 가장 먼저 메모리에 데이터가 적재가 되어 있어야만 한다. 그런데 이 세상에는 CPU의 종류가 한 두 가지가 아니고, CPU를 만들어 내는 회사 또한 한 두군데가 아니다. 그리고 그 회사들은 저마다 각각의 바이트 순서(byte order)를 사용하고 있다.

 바이트 순서(byte order)란, 데이터를 메모리에 적재하는 방법이라고 생각하면 된다. 그 방법에는 Big-endian(상위 바이트가 메모리에 먼저 기록 되는 방식)과 Little-endian(하위 바이트가 메모리에 먼저 기록 되는 방식)이다.

사용자 삽입 이미지

big endian and little endian

 위의 그림을 보면 메모리 번지가 증가 하는 순서 대로 바이트를 증가 시키는 것을 Big endian, 그 반대의 것을 Little endian이라고 한다. 그리고 시스템이 사용하는 바이트 순서를 호스트 바이트 순서(Host byte order)라고 하며 위에서도 이야기 했다 싶이 CPU마다 다르므로 네트워크로 데이터를 주고 받는 시스템들이 같은 호스트 바이트 순서를 가진다는 보장이 없다.

 이렇게 서로 바이트 순서(byte order)가 다른 두 호스트가 통신을 할 경우에는 바이트를 읽어 들이는 순서가 달라 같은 데이터를 가지고도 서로 다르게 인식 하는 경우가 발생한다. 아래의 그림을 보도록 하자.

사용자 삽입 이미지

comunication between Big endian and Little endian

 호스트 A에서 0x12, 0x34라고 보낸 데이터가 동일하게 호스트 B에 도착 하였지만, Little endian 방식의 호스트 B는 0x34, 0x12 순서로 바이트를 읽어 버리기 때문에 동일한 값을 다르게 처리해 버린다.

 이런 문제를 해결하기 위해서 네트워크 바이트 순서(Network-byte order)라는 것이 만들어 졌고, 네트워크 바이트 순서는 Big-endian만을 사용한다. 따라서 시스템이 Little-endian방식을 사용하고 있다면 반드시 Big-endian방식으로 변경해서 전송을 해야 하고, 받을 때에는 Little-endian으로 변경해서 받아야 한다.

 그리고 sockaddr_in 구조체 안에 존재하는 모든 값들은 반드시 네트워크 바이트 순서로 채워 져야 한다.

관련 함수 : hotns, ntohs, htonl, ntonl
참고 : Unix Network Programming
         열혈강의 TCP/IP
         http://www.terms.co.kr/big-endian.htm
         http://blog.naver.com/ssadaegee?Redirect=Log&logNo=110011193494
         http://blog.naver.com/process3?Redirect=Log&logNo=20029237790
         http://blog.naver.com/cybeagle/4074876

Posted by kukuta
TAG C/C++, socket

댓글을 달아 주세요

       #include <sys/socket.h>
       #include <netinet/in.h>
       #include <arpa/inet.h>

       int inet_aton(const char *cp, struct in_addr *inp);
       in_addr_t inet_addr(const char *cp);
       in_addr_t inet_network(const char *cp);
       char *inet_ntoa(struct in_addr in);

inet_aton()
 IP address를 in_addr 구조체로 변환 한다. 주소가 정상적인 것이라면 0이 아닌 값을 리턴하고, 그렇지 않다면 0을 리턴한다.

inet_addr()
 IP address를 네트워크 바이트 순서(Network-byte order)의 바이너리 데이터를 리턴한다. 만일 입력값이  부적절 하다면 INADDR_NONE(대체로 -1)을 리턴한다.
/**
inet_addr() 함수는 위에 나온 inet_aton() 함수 보다 좀 덜떨어진(?) 함수로써 255.255.255.255가 -1을 리턴하기 때문에 코딩을 하면서 오류의 소지가 있을 수 있다. 그리고 inet_aton() 함수는 에러 발생시 어떤 에러가 발생했는지 보다 자세히 리턴 한다. 하지만 inet_addr() 함수가 쓰기 편하다.
*/


inet_network()
 위의 inet_addr과 비슷한 역할을 하지만 호스트 바이트 순서(Host-byte order)의 바이너리 데이터를 리턴한다.

inet_ntoa()
 네트워크 바이트 순서(Network-byte order)의 바이너리 주소를 IP address로 변경한다. 단, 리턴되는 버퍼가 static으로 잡혀 있으므로 threadsafe하지 않다. 만일 쓰레드에 대한 고려를 해야 한다면 _r이 붙은 함수를 사용하여야 한다.
/**
  원래는 reenterent에 안전하지 않은 것이지만, 어차피 reenterent에 안전하지 않으면 threadsafe하지 않으므로 그냥 threadsafe하지 않다고 했다.
*/

inet_ntoa에서 사용되는 in_addr 구조체는 netinet/in.h에 정의 되어 있으며 아래와 같다.

              struct in_addr {
                      unsigned long int s_addr;
              }

/**
 열혈강의 TCP/IP에 따르면 인텔계열의 CPU의 host byte order는 network byte order와 반대라고 한다. 코딩할때 꼭 유의 하도록 한다. byte order에 관련 자료는
http://kukuta.tistory.com/41 를 참고 하도록 하자.
*/

Posted by kukuta
TAG C/C++, socket

댓글을 달아 주세요

/**
 간만에 주말이 왔다. 내일은 약속도 없고(뭐 평소에도 그렇게 약속이 있었던 편은 아니지만), 할 일도 없으니(뭐 평소에도 주말이 바빴던 것은 아니지만) 오랫동안 생각해 왔던 것의 처음을 시작 하려고 한다. 지금 부터 얼마나 걸릴지 모르겠지만 리눅스 소켓 프로그래밍에 관련된 글을 쓰고자 한다.

 이 문서는 오로지 리눅스/유닉스 계열의 소켓 프로그래밍에 관려해서만 다룰 예정이며, 내가 아직 소켓프로그래밍의 정점에 선 사람이 아니기 때문에 내 머리 속에 있는 내용을 정리 하기 보다는 다른 사람들이 적어 놓은 책들을 보고 정리 하는 차원이 될 지도 모르겠다.
*/
 
 아래의 코드는 단순히 하나의 컨넥션을 받아 들이고, 딱 한번만 클라이언트에서 오는 메시지를 에코하고 종료 되는 초 간단한 에코서버다. 다른 설명 보다도 일단 아래의 코드를 작성 해보도록 하자.

example1.cpp
      1 #include <iostream>
      2 #include <sys/socket.h>
      3 #include <arpa/inet.h>
      4
      5 #define BUFSIZE 256
      6
      7 int main(int argc, char** argv) {
      8     if(argc != 2) {
      9         std::cerr << "Usage : " << argv[0] << " PORT" << std::endl;
     10         return -1;
     11     }
     12
     13     int listenSock = ::socket(AF_INET, SOCK_STREAM, 0);
     14     if(listenSock < 0) {
     15         std::cerr << "socket() error" << std::endl;
     16         return -1;
     17     }
     18
     19     struct sockaddr_in addr;
     20     memset(&addr, 0, sizeof(sockaddr_in));
     21     addr.sin_family = AF_INET;
     22     addr.sin_addr.s_addr = htonl(INADDR_ANY);
     23     addr.sin_port = htons(atoi(argv[1]));
     24
     25     if(0 > ::bind(listenSock, (struct sockaddr*)&addr, sizeof(struct sockaddr_in))) {
     26         std::cerr << "bind() error" << std::endl;
     27         return -1;
     28     }
     29
     30     if(0 > ::listen(listenSock, 5)) {
     31         std::cerr << "listen() error" << std::endl;
     32         return -1;
     33     }
     34
     35     int clntSock = -1;
     36     socklen_t len = sizeof(struct sockaddr_in);
     37     struct sockaddr_in clntAddr;
     38
     39     if((clntSock = ::accept(listenSock, (struct sockaddr*)&clntAddr, &len)) < 0) {
     40         std::cerr << "accept() error" << std::endl;
     41         return -1;
     42     }
     43
     44     char recvBuffer[BUFSIZE];
     45
     46     int recvByte = ::read(clntSock, recvBuffer, BUFSIZE-1);
     47     if(recvByte == 0) {
     48         std::cerr << "read EOF" << std::endl;
     49         ::close(clntSock);
     50         ::close(listenSock);
     51         return -1;
     52     }
     53     recvBuffer[recvByte] = '\0';
     54     std::cout << "receive : " << recvBuffer;
     55
     56     int writeByte = ::write(clntSock, recvBuffer, recvByte);
     57     if(writeByte < 0) {
     58         std::cerr << "write() error" << std::endl;
     59         ::close(clntSock);
     60         ::close(listenSock);
     61         return -1;
     62     }
     63
     64     ::close(clntSock);
     65     ::close(listenSock);
     66
     67     return 0;
     68 }

 위의 코드를 정상적으로 작성 했다면 이제 컴파일을 하도록 하도록 하자.

$ g++ example1.cpp

 아무런 메시지 없이 프롬프트가 뜬다면 정상적으로 컴파일이 이루어 졌다는 뜻이다(보통 리눅스는 무슨 잘못이 있기 전 까지는 아무런 이야기를 해 주지 않는 성향이 있다). 이제 실행을 시켜 보자.

$ a.out 19999

 이제 19999번 포트로 서버가 실행 되고 있다. 이제 클라이언트를 실행 시켜 보도록 하자. 정석대로라면 클라이언트 프로그램을 작성함이 옳지만 지금은 당장 실행 시켜 보고 싶으므로(그리고 내가 클라이언트 프로그램을 작성하기 귀찮으므로) 리눅스에서 제공하는 telnet을 이용하기로 한다. 자, 그럼 다른 터미널을 하나 더 띄워 보도록 하자. 같은 서버로 접속했다고 가정하고 터미널에서 아래와 같이 커맨드를 넣어보자(만일 다른 서버에서 telnet을 실행 시켰다면 localhost가 아니라 서버가 떠있는 주소가 되어야 한다).

$ telnet localhost 19999
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.


 위와 같이 telnet을 실행 시키고 나면 무엇인가 커맨드를 입력 받기 위해서 대기 상태로 들어간다. 아무 것이나 타이핑해 보자. 필자는 오랜 전통과 역사를 가진 "Hello, world"를 쳐보도록 하겠다.

$ telnet localhost 19999
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello, World!!
Hello, World!!
Connection closed by foreign host.
$

 클라이언트 telnet 측에서 Hello, World!! 를 타이핑하자 같은 글자가 한번 더 출력 되고 종료 되었다. 이번에는 아까 띄워놨던 서버를 살펴 보자

$ ./a.out 19999
receive : Hello, World!!
$

 성공적으로 클라이언트 telnet이 보낸 메시지가 수신되었음을 확인 할 수 있다.
 이것으로 간단한 에코 서버의 제작이 완료 되었다(하지만 아직 어디가서 자랑하지는 마시길...)
 
/**
 이것으로 처음을 마치도록 하겠다. 다음번 문서에는 소스코드를 한줄씩 분석하면서 서버 프로세스가 시작하면서 종료 할때 까지의 오퍼레이션에 대해서 알아보고 각각의 오퍼레이션에서의 예외 상황들과 그에 대한 해결 방안 들을 살펴 보도록 하겠다.
*/
Posted by kukuta
TAG C/C++, socket

댓글을 달아 주세요

  1. Favicon of http://blog.ggamsso.wo.tc/ BlogIcon 깜쏘 2007.04.12 14:59  댓글주소  수정/삭제  댓글쓰기

    이거 차근차근 들으면 서버 하나 만들 수 있어요?
    지금 xmlrpc로 검색엔진용 서버를 뚝딱 만들어 놨지만, thread에 대해서는 전혀 생각 없이 짜놔서 걱정입니다.
    것보단 thread 강의 해줘요.

    • Favicon of https://kukuta.tistory.com BlogIcon kukuta 2007.04.12 15:43 신고  댓글주소  수정/삭제

      최종까지 소스 코드는 만들어 놨는데..하나하나 쓰기가 상당히 귀찮네..
      그건 그렇고 니가 나보다 더 많이 알고 있어..
      니가 강의해..

gethostbyname

 함수의 이름 대로 이름에서 host 주소를 얻어 올 수 있는 함수다. 그렇다고 해서 꼭 IP주소만을 가져 오느냐 하면 그것도 아니다. struct hostent 라는 구조체를 이요해서 호스트의 대표이름, 별명, 주소의 타입, 주소 리스트들을 얻어 올수 있다. IPv4에서만 정상적으로 돌아가는 함수이며 다음 POSIX에서 퇴출 당할 가능성이 있다고는 하지만 여지껏 일반적으로 잘 사용 되어져 왔던 함수다.

#include <netdb.h>

extern int h_errno;
struct hostent *gethostbyname(const char *name);

struct hostent {
          char    *h_name;        /* official name of host */
          char    **h_aliases;    /* alias list */
          int     h_addrtype;       /* host address type */
          int     h_length;          /* length of address, if IPv4 4 bytes, IPv6 16 bytes return*/
          char    **h_addr_list;  /* list of  IP addresses */
}
#define h_addr  h_addr_list[0]  /* for backward compatibility */

 이 함수는 다른 함수들과 달리 errno를 셋팅하지 않는다. 대신 h_errno를 사용한다. h_errno에서 문자열로 표현된 에러를 얻기 위해서는 hstrerror() 함수를 사용한다.

Posted by kukuta
TAG C/C++, socket

댓글을 달아 주세요

  1. 2007.09.13 22:16  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

  2. Favicon of http://www.turace.com/ BlogIcon d 2007.09.13 22:17  댓글주소  수정/삭제  댓글쓰기

    iiiiiiiiii

 TCP는 연결지향의 통신에 신뢰성을 보장하고 UDP는 비연결 지향의 통신에 신뢰성을 보장하지 못한다는 것 같은 지루하고 이론적인 이야기는 저기 나 보다 더 많이 공부하신 분들이 써 놓은 책이나 웹페이지에게 떠넘겨 버리고(http://en.wikipedia.org/wiki/User_Datagram_Protocol), 여기서는 유닉스계열 쪽의 UDP소켓 API의 사용법에 대해 간략히 알아보고, 그에 발생 할 수 있는 예외 상황들을 다뤄 보고자 한다.

 UDP 통신은 위에서 언급했던것 처럼 말 그대로 비연결이다. 한글로 또박또박 풀어 쓰자면 연결하는 과정이 따로요 없다는 소리다. 어떻게 연결을 하지 않고 통신을 할 수 있는가라고 반문한다면 TCP four-way handshake처럼 쌍방간에 연결을 맺었다는 합의가 없다는 소리다. UDP는 단순히 데이터그램에 가야 할 주소를 적어 보내 줄 뿐그 이상 그 이하도 아니다. 하나의 데이터그램이 전송되기 위해서는 반드시 주소와 포트가 따라 붙어야 하고, 그러므로써 하나, 하나 라는 구분이 생기게 된다. 시작 할 때 부터 쌍방간에 연결을 맺고 시작하는 TCP는 데이터의 시작과 끝만 있을 뿐 그 사이에는 모두 하나의 커다란 흐름, 즉 Stream으로 보고, UDP는 그 '구분'이 되는 특성상 Datagram이라고 한다. 이 Datagram의 특성 때문에 TCP는 read와 write의 함수 호출 횟수가 일치 하지 않는 반면, UDP는 read/write의 수가 항상 일치한다. 즉 두번 쓰면 두번에 걸쳐 읽어야지 결코 한번에 읽을 수는 없다는 이야기다.

 

 UDP가 데이터그램을 주고 받는 함수는 아래와 같다. TCP read/write와 비슷하긴 하지만 데이터그램을 보낼 때 마다 주소를 넣어 주어야 하는 부분이 다르다. 받는 부분 역시 어디서 보내 왔는지 알기 위해 주소를 받아 오는 out parameter가 있다.

#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags,
                                                      
struct sockaddr *from, socklen_t *addrlen);

ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags,
                                                       const struct sockaddr *to, socklen_t addrlen
);

Both return: number of bytes read or written if OK, –1 on error
man page : http://www.die.net/doc/linux/man/man2/recvfrom.2.html
http://www.die.net/doc/linux/man/man2/sendto.2.html


 여기서 리턴 값에 잠시 주목 해보자. 리턴이 -1이 아닌 것이 나왔다는 것은 send/receive 작업이 성공했다는 뜻이다. 하지만 send에서 성공 했다는 것은 내가 보낸 데이터그램이 peer쪽으로 성공적으로 도착했다는 소리가 아니다. 다만 보내는 것 까지가 성공했다는 소리다. peer쪽의 소켓이 유효한지 안한지를 알 수는 없다는 소리다. 일단 커널 버퍼에 write 작업을 할 수 있는 공간만 있다면 무조건 OK.!!

 

 아래는 늘상 그렇듯이 간단한 UDP기반의 에코 서버/클라이언트다. 서버에 TCP 처럼 accept함수가 없다는 사실과, 클라이언트에 connect함수가 없다는 사실에 주목 하기 바란다.


 

'진리는어디에' 카테고리의 다른 글

Leader/follower pattern  (0) 2007.04.01
Condition Variables  (1) 2007.03.25
sendto & recvfrom  (0) 2007.03.23
half-close  (0) 2007.03.20
소켓 종료와 TIME_WAIT(Socket termination and TIME_WAIT)  (10) 2007.03.19
Read/Write lock  (8) 2007.03.18
Posted by kukuta
TAG C/C++, socket, udp

댓글을 달아 주세요

half-close

진리는어디에 2007. 3. 20. 17:06
 half-close의 의미는 말 그대로 열었던 소켓을 반만 닫겠다는 뜻이다.
 소켓의 레퍼런스 카운트나 기타 등등 들은 유지한체 write stream이나 read stream만을 선별적으로 닫는다는 뜻이다. 그에 대한 API는 아래와 같다.

#include <sys/socket.h>
int shutdown(int fd, int how);
성공시 0 리턴, 실패시 -1 리턴

fd : half-close 하고자 하는 파일 디스크립터
how : close 모드

상수값 매크로 역할
0 SHUT_RD 입력스트림 종료
1 SHUT_WR 출력스트림 종료
2 SHUT_RDWR 입출력스트림 종료

 "도데체 이게 왜 필요한거지? 그냥 닫아 버리면 무슨 문제라도 생기나?" 같은 의문을 충분히 가질 수 있다. 하지만 다음과 같은 상황을 가정해 보자. 서버와 클라이언트가 있고, 서버는 클라이언트가 소켓을 닫는 시점에 서버의 시간을 클라이언트에게 알리고 클라이언트는 그 시간을 기록 해야 한다고 가정하자. 그런데 클라이언트가 종료 되버려 서버가 클라이언트에게 연락할 방법이 다 막혔는데 어떻게 서버가 클라이언트에게 시간을 전달 할 수 있단 말인가?

 이런 경우에 half-close를 유용하게 쓸 수 있을 것이다. 클라이언트는 SHUT_WR을 이용하므로써 더 이상 서버에게 할일 없다는 메시지를 전송하고, 서버는 클라이언트가 종료되는 것으로 인식 한다. 분명히 종료가 되었지만 반만 종료 되었기에 이번에 서버가 보내는 시간을 클라이언트는 제대로 인식하고 로그에 남길 수가 있다.

 이번에는 상황을 벗어 나서 좀 더 깊이 들어가보자. close함수를 호출하여 소켓을 종료 시키려고 한다는 것은 소켓의 레퍼런스 카운트 하나를 줄인다는 것이다. 만일 우리가 dup함수들을 이용해서 파일 디스크립터를 복사 했다고 하자. 그러면 그 파일의 레퍼런스 카운터는 2가 되고, 둘 중에 하나를 close해도 파일은 닫히지 않는다. 결국 dup를 두 번했으면 close도 두번 해줘야 한다는 소리다. 하지만 shutdown을 레퍼런스 카운터에 상관없이 정상적인 connection termination 과정을 시작 할 수 있다는 것이다.(FD가 확실히 닫히는지는 아직 실험 해보지 않아서 잘 모르겠다.)
 

'진리는어디에' 카테고리의 다른 글

Condition Variables  (1) 2007.03.25
sendto & recvfrom  (0) 2007.03.23
half-close  (0) 2007.03.20
소켓 종료와 TIME_WAIT(Socket termination and TIME_WAIT)  (10) 2007.03.19
Read/Write lock  (8) 2007.03.18
libevent and multithread  (6) 2007.03.06
Posted by kukuta
TAG C/C++, socket

댓글을 달아 주세요