프로토콜이란?
프로토콜이란 대화에 필요한 통신규약을 의미합니다. "컴퓨터 상호간의 대화에 필요한 통신규약"
쉽게말해서 프로토콜은 약속이다. 서로 데이터를 주고 받기 위해서 정의해 놓은 약속을 뜻합니다.
소캣의 생성
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
성공시 파일 디스크립터, 실패 시 -1 반환
domain 소켓이 사용할 프로토콜 체계(Protocol Family)정보 전달.
type 소켓의 데이터 전송방식에 대한 정보 전달.
protocol 두 컴표터간 통신에 사용되는 프로토콜 정보 전달.
프로토콜 체계(Protocol Family)
소켓이 통신에 사용하는 프로토콜도 부류가 나뉩니다.
그리고 socket함수의 첫번째 인자로, 생성되는 소켓이 사용할 프로토콜의 부류정보를 전달해야 합니다. 이러한 부류정보를 가르켜 '프로토콜 체계'라고 하며, 프로토콜 체계의 종류는 다음과 같습니다.
이름 | 프로토콜 체계(Protocol Family) |
PF_INET | IPv4 인터넷 프로토콜 체계 |
PF_INET6 | IPv6 인터넷 프로토콜 체계 |
PF_LOCAL | 로컬 통신을 위한 UNIX프로토콜 체계 |
PF_PACKET | LOW Level 소켓을 위한 프로토콜 체계 |
PF_IPX | IPX 노벨 프로토콜 체계 |
PF_INET에 해당하는 IPv4 인터넷 프로토콜 체계가 이 책에서 주로 설명하는 프로토콜 체계 입니다. 이 외에도 몇몇 프로토콜 체계가 존재하지만 중요도가 떨어지거나 아직 보편화 되지 않은 프로토콜 체계이기 때문에, PF_INET에 해당하는 프로토콜 체계에만 초점을 맞추겠습니다. 그리고 실제 소켓이 사용할 최종 프로토콜 정보는 socket 함수의 세번째 인자를 통해서 전달하게 되어있습니다. 단, 첫번째 인자를 통해서 지정한 프로토콜 체계의 범위 내에서 세번째인자가 결정되어야 합니다.
소켓의 타입(Type)
소켓의 타입이란 소켓의 데이터 전송방식을 의미합니다. 바로 이 정보를 socket 함수의 두번 째 인자로 전달해야 합니다. 그래야 생성되는 소켓의 데이터 전송 방식을 결정할 수 있기 때문입니다. socket의 첫 번째 인자를 통해서 프로토콜 체계정보를 전달하기 때문에 애매하게 느껴 질 수도 있습니다. 하지만 프로토콜 체계가 결정되었다고 해서 데이터의 전송방식까지 완전히 결정되는 것은 아닙니다. 즉, socket 함수의 첫번째 인자로 전달되는 PF_INET에 해당하는 프로토콜 체계에도 둘 이상의 데이터 전송방식이 존재합니다.
대표적인데이터 전송 방식은 두가지가 있습니다.
소켓의 타입 1: 연결지향형 소켓(SOCK_STREAM)
socket 함수의 두번째 인자로 SOCK_STREAM을 전달하면 '연결지향형 소켓'이 생성됩니다. 그렇다면 연결지향형 소켓은 어덯나 특성을 지닐까요?
위 그림은 두 사람이 하나의 라인을 통해서 물건을 주고 받는 상황을 보여주는데, 연결지향형 소켓의 데이터 송수신 방식은 이에 비유할 수 있습니다.
위 그림이 보이는 데이터 송수신 방식의 특징을 정리하면 다음과 같습니다.
- 중간에 데이터가 소멸되지 않고 목적지로 전송된다.
- 전송 순서대로 데이터가 수신된다.
- 전송되는 데이터의 경계(Boundary)가 존재하지 않는다.
우선 위 그림에서는 독립된 별도의 전송라인을 통해서 데이터를 전달하기 때문에 라인상의 문제만 없다면, 데이터가 소멸되지 않음을 보장 받을 수 있습니다. 뿐만 아니라, 먼저 보내진 데이터보다 뒤에 보내진 데이터가 소멸되지 않음을 보장받을 수 있습니다.
위의 상황은 앞서 설명한 write와 read 함수에 적용하면 다음과 같은 상황으로 이어질 수 있음을 의미합니다.
'데이터를 전송하는 컴퓨터가 세번의 write 함수호출을 통해서 총 100바이트를 전송하였다. 그런데 데이터를 수신하는 컴퓨터는 한번의 read함수호출을 통해서 100바이트 전부를 수신하였다.'
데이터 를 송수신하는 소켓은 내부적으로 버퍼 (buffer) 쉽게 말해서 바이트 배열을 지니고 있습니다. 그리고 소켓을 통해 전송되는 데이터는 일단 이 배열에 저장됩니다. 때문에 데이터가 수신되었다고 해서 바로 read 함수를 호출해야 하는 것은 아닙니다. 이 배열의 용량을 초과하지 않는 한 데이터가 채워진 후에 한 번의 read 함수호출을 통해서 데이터 전부를 읽어 들일수도 있고 반대로 한번의 wríte 함수호출로 전송된 데이터 전부를 여러 번의 read 함수 호출을 통해서 읽어 들일수도 있습니다. 즉. read 함수의 호출횟수와 write 함수의 호출횟수는 연결지향형 소켓의 경우 큰 의미를 갖지 못합니다. 때문에 연결지향형 소켓은 데이터의 경계가 존재하지 않는다고 말하는 것입니다.
"신뢰성 있는 순차적인 바이트 기반의 연결지향 데이터 전송방식의 소켓"
소켓에 존재하는 버퍼가 꽉 차면 데이터가 소멸되나요?
바로 위에서 , 수신되는 데이터의 저장을 위한 바이트 배열로 이뤄진 버퍼가 소켓에 존재한다고 설명하였다. 그렇다면 이 버퍼가 수신되는 데이터로 꽉 채워지면 어떻게 될까? 그 이후로 전송되는 데이터는 그냥 소멸이 될까?
일단 이 버퍼에 수신된 데이터는 read 함수호출을 통해서 데이터가 읽혀지면 얽혀진 만큼 버퍼에서 비워지게 된다. 따라서 버퍼가 마냥 채워진 상태에 놓이진 않는다 . 하지만 read 함수호출로 읽혀지는 데이터의 양보다 많은 양의 데이터가 수신되면 버퍼도 꽉 찰 수 있다.
그리고 이 상태에 놓인 소켓은 더 이상 데이터 를 수신할 수 없다. 하지만 이 상황에 놓여도 전송되는 데이터가 소멸되는 일은 발생하지 않는다. 데이터를 전송하는 영역의 소켓이 더 이상 데이터를 전송하지 않기 때문이다. 즉, 지금 설명하는 연결지향형 소켓은 자신과 연결된 상대 소켓의 상태를 파악해가면서 데이터를 전송한다. 혹 데이터가 제대로 전송되지 않으면 데이터를 재전송하기까지 한다. 따라서 연결지향형 소켓의 데이터손실은 특별한 경우가 아니면 발생하지 않는다.
연결지향형 소켓의 가장 큰 특성은 소켓 대 소켓의 연결은 반드시 1대 1이어야 한다.
소켓의 타입 2 : 비 연결지향형 소켓(SOCK_DGRAM)
soket 함수의 두 번째 인자로 SOCK_DGRAM을 전달하면 '비 연결지향형 소켓'이 생성된다. 비 연결지향형 소켓은 연길지향형 소켓에 비해 데이터의 존송속도는 빠르나, 데이터의 손실 및 훼손이 발생하지 않음을 보장한지 않는다. 그리고 한번에 전송할 수 있는 데이터의 크기가 제한이 되며, 데이터의 경계가 존재한다. 데이터의 경계가 존재한다는 데이터를 전송할 때 두 번의 함수 호출이 수반되었다면 데이터를 수신할 때에도 두 번의 함수 호출이 수반되어야 함을 의미한다. 그럼 지금까지 설명한 비 연결지향형 소켓의 특성을 한 문장으로 정리해 보겠다.
“ 신뢰성과 순차적 데이터 전송을보장하지 않는,고속의 데이터 전송을 목적으로하는 소켓 ”
참고로 비 연결지향형 소켓은 연결지향형 소켓과 달리 연결이라는 개념이 존재하지 않는다.
프로토콜의 최종선택
이제 soket 함수의 세번째 인자에 대해 살펴볼 차례이다. 이는 최종적으로 소켓이 사용하게 될 프로토콜 정보를 전달하는 목적으로 존재한다. socket 함수의 첫 번째 그리고 두 번째 전달인자를 통해서도 충분히 원하는 유형의 소켓을 생성할 수 있다. 따라서 대부분의 경우 세 번째 인자로 그냥 0을 넘겨줘도 우리가 원하는 소켓을 생성할 수 있다 . 하지만 다음과 같은 상황 때문에 세 번째 인자는 필요하다.
“하나의 프로토콜 체계안에 데이터의 전송방식이 동일한 프로토콜이 둘 이상 존재하는 경우”
즉,소켓의 데이터 전송방식은 같지만, 그 안에서도 프로토콜이 다시 나뉘는 상황이 존재할 수 있다. 그리고 이러한경우에는세 번째 인자를통해서 원하는프로토콜정보를조금더 구체화해야한다.
“ IPv4 인터넷 프로토콜 체계에서 동작하는 연결지향형 데이터 전송 소켓 ”의 요구사항을 만족하는 소켓의 생성문을 구성해본다.
여기서 'IPv4'라는 것은 인터넷 주소체계와 관련이 있는데, 이 책에서는 IPv4기반으로 내용이 전개된다는 것만 알아두면 된다.
PF_INET이 IPv4인 터넷 프로토콜 체계를 의미하고, SOCK_STREAM이 연결지향형 데이터 전송을 의미한다. 그런데 이 두가지 조건을 만족시키는 프로토콜은 IPPROTO_TCP하나이기 때문에 다음과 같이 socket 함수 호출문을 구성하면 된다. 그리고 이때 생성되는 소켓을 가리켜 "TCP소켓"이라고 한다.
int tcp_socket=soket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
이번에는 "IPv4 인터넷 프로토콜 체계에서 동작하는 비 연결지향형 데이터 전송소켓" 이라는 요구사항을 만족하는 소켓의 생성문을 구성해 본다.
SOCK_DGRAM이 비 연결지향형 데이터 전송을 의미하고, 위의 조건을 만족하는 프로토콜은 IPPTOTO_UDP하나이기 때문에 다음과 같이 socket 함수 호출문을 구성하면 된다. 그리고 이때 생성되는 소켓을 가리켜 'UDP 소켓'이라 한다.
int udp_socket=socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
연결지향형 소켓 TCP소켓의 예
앞서 작성한 예제 hello_server.c와 hello_client.c가 TCP 소켓 기반의 예제이니, 이를 조금 변경해서 TCP소켓의 다음 특성을 확인해 보려고 한다.
- hello_server.c →tcp_server.c 변경사항 없음!
- hello_client.c → tcp_client.c read 함수의 호출방식 변경!
"전송되는 데이터의 경계(Boundary)가 존재하지 않는다."
이를 확인하기 위해서는 write함수의 호출횟수와 read함수의 호출횟수를 불일치 시켜봐야한다. 때문에 read함수를 호출하는 클라이언트 프로그램에서는 여러번의 read함수를 호출해서 서버 프로그램이 전송한 데이터 전부를 수신하는 형태로 변경하였다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
void error_handling(char *message);
int main(int argc, char* argv[])
{
int sock;
struct sockaddr_in serv_addr;
char message[30];
int str_len=0;
int idx=0, read_len=0;
if(argc!=3){
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
sock=socket(PF_INET, SOCK_STREAM, 0); // TCP소켓을 생성하고 있다. 첫 번째 인자와 두번째 인자로
if(sock == -1) // 각각 PF_INET, SOCK_STREAM가 전달되면 세 번째 인자인 IPPROTO_TCP은 생략 가능하다.
error_handling("socket() error");
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
serv_addr.sin_port=htons(atoi(argv[2]));
if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1)
error_handling("connect() error!");
while(read_len=read(sock, &message[idx++], 1)) //while문 안에서 read함수를 반복하고 있다. 중요한 것은 이 함수가 호출될 때마다
{ //1바이트씩 읽어 들인다는 것이다. 그리고 read함수가 0을 반환하면 이는 거짓을 의미하기 때문에
if(read_len==-1) //while문을 빠져나간다.
error_handling("read() error!");
str_len+=read_len; //이 문장이 실행될 때 변수 read_len에 저장되어 있는 값은 항상 1이다. while문에서 read가 1바이트씩
} //읽고 있기 때문이다. 결국 while문을 빠져나간 이후에 str_len에는 읽어들인 바이트 수가 저장된다.
printf("Message from server: %s \n", message);
printf("Function read call count: %d \n", str_len);
close(sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
실행결과
'TCP&IP' 카테고리의 다른 글
TCP/IP 5장 기반서버/클라이언트 2 (0) | 2024.09.04 |
---|---|
TCP/IP 4장 기반서버/클라이언트1 (0) | 2024.09.03 |
TCP/IP 3장 주소체계와 데이터 정렬 (0) | 2024.09.03 |
TCP/IP 1.장 네트워크 프로그래밍과 소켓의 이해 (0) | 2024.09.03 |
Tcp/ip 사전 학습 (0) | 2024.09.02 |
댓글