앞서 파일 디스크립터에 대해 알아보면서, 데이터 파일과 마찬가지로 소켓에서도 fd값을 이용해 네트워크간 통신을 할 수 있다 고 했다.
따라서 이번에는, fd값을 할당받을 수 있는 socket() 함수와 이 소켓을 통해 서버와 클라이언트가 통신하기 위한 정보에는 어떤 것들이 있는지, 어떻게 지정할 수 있는지 알아보려고 한다.
가장 간단한 예시로, 클라이언트에게 "서버와 연결되었습니다." 문자열을 전송하는 서버 소켓을 만들고, 어떤 식으로 주소를 할당하는지 한 문장씩 해석해보려고 한다.
전체 서버 프로그램(C언어)
1. 소켓의 생성
int serv_sock;
serv_sock=socket(PF_INET,SOCK_STREAM,0);
위의 두 문장이 소켓을 생성하는 문장이다. 정확히는 socket 함수를 통해 소켓을 생성할 수 있다.
이 때, serv_sock 값이 파일 디스크립터 번호에 해당하며, 운영체제가 이를 할당 (실패시 -1) 한다.
socket 함수의 3가지 형식 매개변수는 순서대로 (프로토콜 체계, 데이터 전송방식, 프로토콜 정보) 에 해당하며, 아래와 같다.
PF_INET
: IPv4를 의미SOCK_STREAM
: TCP 소켓임을 의미0 (프로토콜 정보)
2. 소켓의 주소 할당
socket() 함수를 통해 fd값은 할당받았고, 소켓의 유형까지 지정 완료했다.
이제 이 서버 소켓은 다른 네트워크 어딘가의 클라이언트와 통신을 해야한다 .
그렇다면 이 소켓에 추가적으로 정의되어야하는 정보는 무엇이 있을까?
바로 IP 주소와 port번호이다. 즉, 소켓에는 두 주소가 할당되어야한다.
IP주소로 컴퓨터를 특정, Port번호로 프로세스를 특정할 수 있어야 하기 때문이다.
struct sockaddr_in serv_addr;
serv_addr 변수가 바로 소켓의 주소가 할당될 변수이다.
그 구조체인 sockaddr_in의 선언문을 보면, 다음과 같다.
struct sockaddr_in {
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
sin_family
: 주소 체계 (IPv4, IPv6, 로컬통신 등)sin_port
: port 번호sin_addr
: ip주소sin_zero
:
구조체의 각 변수들의 의미를 알았으니, 이제 이를 토대로 주소를 부여해주면 된다.
해당 문장들을 하나씩 살펴보자.
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_addr.sin_port=htons(atoi(argv[1]));
memset(&serv_addr,0,sizeof(serv_addr));
bind(serv_sock,(struct sockaddr*) &serv_addr,sizeof(serv_addr))
sockaddr_in 구조체는IPv4의 주소정보를 담기 위한 구조체이다. 반면, 실제로 bind에 할당되는 sockaddr 형은 다음과 같이 이루어져 있다.
struct sockaddr {
__uint8_t sa_len; /* total length */
sa_family_t sa_family; /* [XSI] address family */
char sa_data[14]; /* [XSI] addr value (actually larger) */
};
즉, sa_data에 해당하는 부분을 통해 ip주소와 port번호를 특정할 수 있어야 하는데, IPv4의 경우, 14바이트를 채우지 못한다. 따라서 나머지 바이트들을 0으로 채우기로 약속한 것이다.
앞에서 확인했듯이, IPv4의 port번호는 2바이트 + ip주소는 4바이트 = 6바이트이기 때문에, 14-6=8바이트를 sin_zero로 채워준 것을 알 수 있다.
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_addr.sin_port=htons(atoi(argv[1]));
여기까지, 소켓을 생성하고 그 주소를 초기화하는 과정까지 완료했다.
이제 해당 소켓을 클라이언트와 통신할 수 있도록 실제로 열어놓고, 대기하는 과정을 알아보자.
** 윤성우의 열혈 TCP/IP 소켓 프로그래밍을 토대로 공부하며 정리한 글입니다