Tuesday, June 24, 2008

Utility functions


struct hostent {
char *h_name;
char **h_aliases;
int h_addrtype; /* AF_INET */
int h_length; /* h_addr_list length */
char **h_addr_list;
}

위 hostent의 h_addr_list는 network byte order로 된 in_addr인데, h_addr_list는 char ** 이므로, 이를 실제로 사용할 때는 inet_ntoa(*(int *)h_addr_list[i]) 식으로 하면 4byte를 읽어 inet_ntoa에 넣게 되므로 값을 확인해볼 수 있다. 아래는 각 함수별 설명이다.

gethostbyname(...) 은 호스트 이름으로 hostent 를 가지고 오는 함수이다.

gethostbyaddr(...) 는 IP주소로 hostent를 가지고 오는 함수이다

getsockopt(...), setsockopt(...) 는 소켓 옵션 관련 함수이고, 앞에서 했고,..

getsockname(...) 은 bind된 sockaddr 정보를 얻어오는 함수이다 (자동 할당된 포트 번호 확인 시 사용)

getpeername(...) 은 반대로 연결된 상대편의 sockaddr정보를 얻어오는 함수이다

getservent(), getservbyname(), getservbyport() 함수들은 /etc/services파일을 가져오는 함수들이다(전체 다 가져오거나, 서비스 이름으로 찾거나, 포트 번호로 찾는 함수)


getprotoent(), getprotobyname(), getprotobynumber() 역시 마찬가지로 /etc/protocols 파일을 가져오는 함수들이다.


위 두그룹들은 각각 serverent, protoent 구조체를 알아야 한다.


주소변환 함수 그룹들은 지난번에 기록한것 같은데 이름만 다시 정리하면, inet_addr/inet_aton, inet_pton, inet_ntoa, inet_ntop 이다. pton, aton의 차이는 리턴되는 값을 static공간에 담아주냐 아니냐의 문제인데, reentrant를 고려하여 제공되는 함수가 pton, ntop 군이니 필요할 때 구별해서 쓰면된다.

Socket, options

소켓 사용시 여러 옵션을 줄 수 있는데, 아래 함수를 통해 set, get이 가능하다.

int getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int s, int level, int optname, void *optval, socklen_t *optlen);

설정할 수 있는 옵션은 영역이 매우 다양한데(이를 테면, TCP에서의 옵션이라던가, IP레이어에서의 옵션이라던가 등...), 어느 영역에서의 옵션을 설정할 것인지를 두번째 인자인 level에서 선택할 수 있다. 몇개의 옵션들은 아래와 같다.
  • SOL_SOCKET
    • SO_BROADCAST
    • SO_REUSEADDR
      • TCP의 TIME_WAIT상태의 소켓을 다시 재사용할 수 있게 해주는 옵션으로 서버쪽에서 active close해서 TIME_WAIT상태로 빠지면 이 시간동안 listen이 실패하므로, 바로 listen할 수 있도록 TIME_WAIT상태에서소 소켓을 사용할 수 있도록 하기 위해 사용한다.
    • SO_LINGER
      • struct linger { int l_onoff; int l_linger; } 를 사용한다.
      • 데이터를 전송 후 끊기 위해 기다리는 시간을 l_linger에 지정할수 있는데, 0인경우 버퍼를 없에버리고 바로 close하며 client 쪽에도 RST를 보내서 TIME_WAIT되지 않고 바로 끊도록 한다. (aborty shutdown, aborty close. 0이 아닌 경우라면 graceful close/shutdown)
    • SO_KEEPALIVE
      • 일정시간마다 연결상태 확인
    • SO_OOBINLINE
    • SO_RCVBUF
      • 수신 버퍼크기 조정(실제로는 지정한 값의 두배가 잡힘). 자동으로 조정되는 경우에는 설정할 수 없는 경우도 있음
    • SO_SNDBUF
    • SO_RCVTIMEO
      • blocking 함수를 사용하는 경우(recv, ...) block에서 깨어날 시간을 지정할 수 있음. timeval로 지정하면 됨. timeout으로 빠져나오면 -1이 리턴되고, errno가 EAGAIN이 됨 (아래 SNDTIMEO도 마찬가지)
    • SO_SNDTIMEO
    • SO_RCVLOWAT
      • watermark인데 I/O를 발생할 최소단위의 크기를 지정한다. default로 1이다. (버전에 따라 지원정도가 다르다)
    • SO_SNDLOWAT
    • SO_TYPE
    • SO_ERROR
  • IPPROTO_IP
    • IP_TTL
    • IP_MULTICAST_TTL
    • IP_ADD_MEMBERSHIP
    • IP_DROP_MEMBERSHIP
    • IP_MULTICAST_LOOP
    • IP_MULTICAST_IF
  • IPPROTO_TCP
    • TCP_NODELAY
      • 1이면 Nagle Algorithm을 사용안함
    • TCP_MAXSEG
      • MSS크기 조절함(잘못 조절하면 ,... ㅡ.ㅡ;)

UDP Broadcasting

매우 간단하다. 소켓 옵션에 SO_BROADCAST를 주고 수신측 주소에 INADDR_BROADCAST를 주면된다. 특정 네트웍으로 broadcast하고 싶으면 해당 네트웍 주소를 주면된다. 소켓 옵션에 값을 줄때는 아래처럼 setsockopt를 사용한다.

int sockopt = 1;
if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &sockopt, sizeof(sockopt)) == -1) {
/* Handle error */
}

TCP Options

TCP의 성능 향상을 위해 사용되는 여러가지 방법들을 정리해보자.

TCP autotuning은 소켓 버퍼의 크기를 동적으로 조절하는 기능인데, 2.6.8커널 이후 버전에서 지원되며, 아래 값들을 통해 설정할 수 있다.

  • net.ipv4.tcp_moderate_rcvbuf : 수신쪽의 autotuning 설정

  • net.ipv4.tcprmem : 수신 버퍼의 최소/기본/최대값 (송신쪽은 ...wmem)

  • net.core.rmem_max : 지정할 수 있는 수신 버퍼 크기의 최대값 (송신쪽은 wmem)


값들은 sysctl 커멘드로 확인할 수 있다.

그외, TCP timestamp 는 RTT 측정 오차를 줄이는 방법이고. WSCALE이라고 16bit로 제한된 윈도우 사이즈를 늘려주는 옵션이다.

그리고.. 여러개의 패킷 유실에 대해 ACK를 개별적으로 보내주는 selective ack도 있다.

Monday, June 23, 2008

TCP vs UDP

ALSP 책에서는 segment와 fragmentation을 구별하여 정의하고 있다. 둘다 데이터를 분할하는 것이지만 후자는 재결합이 가능하도록 쪼개는 것이고, 전자는 재결합과는 상관없이 데이터를 쪼개는 행위 자체를 말한다. TCP에서는 IP레이어로 내려가기 전에 segment 단위로 데이터를 쪼개는데, 이때 사용되는 크기가 MSS(Maximum Segment Size)이다. TCP에서는 이렇게 미리 데이터가 분할되어 내려오기 때문에 나중에 MTU 사이즈에 의해 재 분할 되지 않으며, UDP의 경우 IP레이어에서 MTU사이즈로 분할 된다.

TCP는 그 이름이 Transmission Control Protocol인 것처럼 헤더에 제어 flag이 붙는데, 이들은 URG, ACK, PSG, RST, SYN, FIN 으로 구성되어 있다.

UDP의 경우는 별도의 control flag 이 없어서 단순하게 사용 가능하나, 전송하는 데이터그램이 버퍼크기보다 작아야 함을 주의해야 한다. 버퍼보다 큰 데이터그램이 들어오는 경우 Drop되기 때문이다. 이것은 netstat -s 로 UDP overflow가 있는지 확인해 보므로써 알 수 있다.

UDP

UDP는 별도의 연결 과정이 없다. 때문에 매번 보낼때 송수신 주소를 담아서 보내고 받는다. 그리고 broadcast, multicast 식으로 한번에 여러 곳으로 데이터를 송신할 수 있다. 일단 bind()로 바인딩 후 sendto(), recvfrom()으로 주고 받는다. 송수신이 완료되면 close(), shutdown()으로 소켓을 닫는다. 연결이 없어서 server, client 구분이 없다. 간단하다.

int sendto (int s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);
int recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);

인자는, 뭐 대충,.. 소켓, 데이터, 데이터길이, 옵션, 송(수)신 주소, 주소 길이.. 식이다.

Friday, June 20, 2008

TCP Status

TCP연결시에는 알다시피 3way handshaking을 한다. close시에는 client, server 가 각각 FIN을 보낸 후 ACK를 받는다(총 4번의 트랜젝션).

TCP연결에는 여러개의 상태가 있는데, netstat으로 확인할 수 있는 상태들에 대해 간단히 정리해보면,

  • 서버에서 listen()이 호출되면 상태는 LISTEN으로 바뀐다.

  • 이 때 client에서 SYN을 보낸 후 SYN_SENT로 상태가 바뀌면

  • 서버에서 이를 받고 SYN_RCVD로 바뀌고 SYN ACK를 보내준다.

  • 그리고 client에서 SYN을 다시 받게 되면 비로소 ESTABLISHED로 바뀐다.

  • 이 후 send(), recv()군의 함수들로 계속 통신을 하다가

  • client에서 close()호출로 인해 FIN 을 보내고 FIN_WAIT1상태가 되면

  • 서버에서 이를 받고 ACK를 보낸 후 CLOSE_WAIT으로 상태를 바꾸고

  • client에서는 이를 받으면 FIN_WAIT2로 바뀌게 된다.

  • client에서 close할 준비가 끝났으므로, 이제는 서버에서 close()가 호출되면 FIN이 전송되고, 상태는 LAST_ACK이 된다.

  • client에서는 ACK를 보낸 후 TIME_WAIT상태가 되고, Maximum Segment Lifetime x 2시간 정도 기다리다가 소켓을 닫는다.

  • 서버에서는 이 ACK를 받고 CLOSED로 상태가 바뀐다.


close시 일반적으로 client에서 먼저 FIN을 보내고(active close), 서버는 이를 받으면 recv()에 EOF가 전달된다. 서버는 자신이 보낸 FIN에 대한 ACK를 받아야 close된다(passive close).

TIME_WAIT이 필요한 이유 client에서 ACK를 보냈는데, 서버가 못받으면, 서버는 LAST_ACK상태에서 일정시간이 지나도 ACK가 안오므로 FIN을 다시 보낸다. 이때 client가 이미 close 한 상태이거나 같은 포트번호로 다른 접속을 시도하고 있다면 처리할 수 없으니까, TIME_WAIT을 두어 다시 들어온 FIN에 대해 ACK를 주는 것이다. (client에서는 그래서 재접속시 다른 포트 번호를 받게 된다).