■ sockaddr 구조체

   소켓 주소를 표현하는 구조체다.
   아래 정의를 보면 주소 체계와 주소 두가지 정보만 갖고 있는 단순한 구조로 되어있다.
   원래 소켓 자체가 TCP/IP만을 목적으로 만들어진 것이 아니어서, 다양한 주소 체계에 맞게 범용 목적으로 사용하기 위해
   이런 구조를 가지고 있다.

  
struct sockaddr{
      sa_family_t sa_family;  // 소켓의 주소체계. PF_INET= IPv4 주소체계.
     
char sa_data[14];      // 해당 주소체계에서 사용하는 주소 정보.
   }


sockaddr_in 구조체

   IPv4 주소체계에서 사용하는 구조체다.
   소켓 프로그램은 범용 주소 구조체로 sockaddr을 사용하지만, 주소체계의 종류에 따라 별도의 전용 구조체를 만들어
  사용하는게 아무래도 편리할 것이다. 참고로, 기타 다른 주소체계 중 Local Unix 주소 체계는 sockaddr_un 구조체를 사용한다. 

  
소켓라이브러리는 sockaddr을 사용하므로 라이브러리에 주소 정보를 넘길 때는 sockaddr로 형변환을 하여 넘긴다.
   그러므로 당연히 구조체의 크기는 동일하다.  아래 세부 구조를 살펴보자.

   struct sockaddr_in{
      sin_family_t sin_family;   // IPv4 주소체계에서 사용하므로 항상 AF_INET으로 설정
     
unist16_t sin_port;         // 포트 번호
     
struct in_addr sin_addr; // IP주소를 나타내는 32비트 정수 타입 구조체
     
char sin_zero[8];         // sockaddr과 같은 크기를 유지하기 위해 필요한 패딩(padding) 공간. 항상 0.
  
}

솔라리스에서 PF_INET과 AF_INET의 정의를 찾아보니 아래 결과가 나온다. 즉 둘은 같은 값이다.


 # grep PF_INET *.h
socket.h:#define        PF_INET         AF_INET
socket.h:#define        PF_INET6        AF_INET6
socket.h:#define        PF_INET_OFFLOAD AF_INET_OFFLOAD /* Sun private; do not use */

위의 sin_zero는 항상 0이어야 하는데 이를 어기면 간혹 IP 주소를 터무니 없는 값으로 인식하는 경우가 생긴다.
그래서 일반적으로 memset (유닉스)이나 ZeroMemory (윈도우즈) 등으로 초기화한 후 사용한다.

위 두 구조체(sin_addr까지 포함하면 3개)의 구조는 각 운영체제마다 사용하는 타입 이름이 다르므로 약간씩 차이가 있다. 

 
소켓 페어 (socket pair)

  
TCP/IP 프로토콜 체계에서 사용하는 주소 체계는 아래의 주소 형식을 가진다.
   이것은 프로그램상의 규칙이 아니라, IP 프로토콜의 정의라는 것을 기억하자.

   소켓 주소 ::= <IP 주소> + <트랜트포트 포트 번호>

   이 주소를 클라이언트 측과 서버 측의 주소로 결합해서 하나의 소켓 페어(socket pair)라고 부르며
   이 소켓페어가 하나의 가상의 통신회선이 된다. 

    서버 소켓 주소(IP:주소포트번호) *------------* 클라이언트 소켓 주소(IP:주소포트번호)

  
만약 포트를 사용하지 않고 그냥 IP주소로만 소켓 페어를 구성한다고 생각해보자.
   그러면 서버와 클라이언트 사이의 가상 회선은 오직 1개만 연결되는 셈이다. 
   그럴 경우 지금 내가 글을 쓰고 있는 이 네이버의 스마트 에디터 화면에서 네이버 검색을 이용하려면,
   이 편집 창을 닫아야만 가능하다는 얘기다 (새로운 창으로 네이버를 접속할 수 없으므로..).
   더군다나 웹브라우저는 웹서버를 통해 텍스트, 이미지, 동영상등 무수히 많은 파일을 여러 포트를 통해 동시에 다운받는데
   이 회선이 모두 사라지면 그 느려 터질 속도 또한 짐작이 가지 않는가?


BSD기반의 소켓 및 윈속 라이브러리는 클라이언트에서 서버로 연결할 때 connect 함수를 사용하는데 어디에도 클라이언트의 IP 주소 및 포트를 설정하는 부분이 없다. 이유는 서버 연결시 클라이언트의 IP주소와 임의로 자동 생성한 포트 번호를 사용하여 접속하기 때문이다. 그러므로 서버와 연결하는 클라이언트 프로그램을 내 컴퓨터에서 2번 이상 실행하더라도 클라이언트측 포트 번호가 무작위로 설정되어, 충돌없는 소켓 페어(가상 회선)가 자동으로 생성되므로 오류없이 실행할 수 있다.

참고로 .NET에서는 소켓 주소 표현으로 EndPoint라는 클래스를 사용하는데, 클라이언트 측 포트 번호를 임의로 지정할 수 있다(0을 입력하면 라이브러리에서 중복되지 않게 자동으로 할당한다). 이 경우 클라이언트 프로그램을 2번 실행하면 어떤 일이 벌어지겠는가? '이미 연결된 포트로 바인딩할 수 없다'는 에러가  발생할 것이다. 꼭 한번 테스트해 보시라.


표준 예제

/* 서버용 */

struct sockaddr_in server_address;


memset(&server_address,0,sizeof(server_address));
server_address.sin_family=AF_INET;
server_address.sin_addr.s_addr=htons(INADDR_ANY);

server_address.sin_port=htons(PORT); 

 
● INADDR_ANY는 서버의 IP주소를 자동으로 찾아서 대입해주는 함수이다(복잡한 #define문으로 정의되어 있다. long형값 0).
    INADDR_ANY를 지정할 경우 2가지 이점이 있다.

(1) 멀티 네트워크 카드 동시 지원
서버는 NIC을 2개 이상 가지고 있는 경우가 많은데 만일 특정 NIC의 IP주소를 sin_addr.s_addr에 지정하면 다른 NIC에서 요청된 연결은 서비스 할 수 없게 된다. 이때 INADDR_ANY를 사용하면 두 NIC을 모두 바인딩해주므로 어느 IP를 통해 접속하더라도 정상적인 서비스가 가능하다.

(2)
이식성
:
또 다른 이점은 이식성인데, 특정 IP를 지정했을 경우 다른 서버 컴퓨터에 프로그램이 설치된다면 주소값을 변경(소스 수정)해야 하지만, INADDR_ANY를 사용하면 소스 수정없이 곧바로 사용 또는 컴파일할 수 있는 장점이 생긴다.


서버의 주소와 포트 번호는 IP헤더에 저장되어 전송되는데 이를 중계하는 라우터들은 항상 네트워크 바이트 방식, 즉 빅 엔디언으로 처리 한다. 그러므로, 소켓에서도 빅 엔디언 방식으로 정렬되어 있어야 한다. 그래서 항상 htons(host to network short) 함수로 주소와 포트번호를 변환해서 사용해야 한다.

/* 클라이언트용 */

struct sockaddr_in client_address;


memset(&client_address,0,sizeof(client_address));
client_address.sin_family=AF_INET;
client_address.sin_addr.s_addr= inet_addr("192.168.56.1"); // 서버 주소

client_address.sin_port= htons(PORT); 

 
P.S) 위 예제 중 inet_addr 함수는 문자열을 받아 long형 값을 돌려주는데 Network 바이트 형식을 가진다. 예전에 아무 생각없이 htonl(inet_addr("ip"))로 사용했다가 반나절을 삽질한 적이 있다. 이런 오류는 화면상에 표시도 되지 않으므로 디버거를 통해야한 확인이 가능하다. 몇 가지 안되는 소켓관련 함수는 사용법을 꼭 주지해야 한다.



※ 출처 : http://blog.naver.com/jingyoohan?Redirect=Log&logNo=40063156302

by 민트앤라떼 2011. 4. 6. 17:40