상세 컨텐츠

본문 제목

네트워크 프로그래밍 - 소켓에서의 프로토콜 설정

Computer Science/Network

by 2021. 9. 25. 20:01

본문

반응형

프로토콜

데이터를 송수신 할 때 지정해놓은 통신 규약으로 소켓을 생성할 때 기본적인 프로토콜을 지정해야 한다.

 

#include <sys/socket.h>

int socket(int domain, int type, int protocol); // 
// domain : 소켓이 사용할 프로토콜 체계(Protocol Family) 정보 전달
// type : 소켓의 데이터 전송방식에 대한 정보 전달
// protocol : 통신에 사용되는 프로토콜 정보 전달

socket 함수에 들어가는 매개변수들이 프로토콜과 관련된 정보들인데,

domain은 프로토콜 체계 (protocol family)에 대한 정보이다.

type은 전송 방식에 대한 정보이다.

프로토콜 체계 (protocol family)

위 그림과 같이 다양한 체계가 있지만, 여기에서는 PF_INET 즉 IPv4 인터넷 프로토콜 체계를 기반으로 소켓 프로그래밍을 학습한다.

전송 방식

프로토콜 PF_INET의 전송 방식에는 두 가지 타입의 소켓이 있다.

연결지향형 소켓(SOCK_STREAM)

일반적으로 TCP소켓을 의미한다.

 중간에 데이터가 소멸되어도 TCP단에서 복구요청을 보냄으로 처리를 해주기 때문에, 사용자 입장에서는 데이터 소멸이 없는 것처럼 보인다.

 전송 중에 순서가 바뀔 수도 있지만 결과적으로 사용자에게 보여줄 때는 전송된 순서대로 보여지게 된다. 

 데이터의 경계가 존재하지 않아 데이터별로 전송을 보내더라도 경계없이 지정된 패킷의 크기만큼만 자른 cluster를 보낸다. 

 소켓 연결은 client - server 구조를 이룬다. 

 

아래는 IPv4 인터넷 프로토콜 체계에서 TCP 소켓을 사용하는 코드이다.

int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

socket의 domain과 type 매개변수를 통해서 프로토콜이 결정되므로 protocol값은 0을 주어도 상관없다.

비 연결지향형 소켓(SOCK_DGRAM)

UDP소켓을 의미한다.

 어떤 곳에서는 UDP에 대한 소개로 "전송순서에 상관없이 빠른 속도의 전송을 지향"이라고 하지만 이는 정확히는 오해의 소지가 많은 설명이다.

 TCP의 경우 각 패킷에 sequence number가 부여되어 손실을 확인하지만, UDP의 경우 sequence number가 부여되지 않기 때문에 손실이 발생되어도 별도의 처리를 하지 않는다. 

 TCP와 달리 데이터의 경계가 존재한다.

 

아래는 IPv4 인터넷 프로토콜 체계에서 UDP 소켓을 사용하는 코드이다.

int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);

TCP 소켓 예시코드

if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr))==-1)
	error_handling("bind() error");
if(listen{serv_sock, 5)==-1)
	error_handling("listen() error");
clnt_addr_size = sizeof(clnt_addr);
clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
if(clnt_sock==-1)
	error_handling("accept() error");
write(clnt_sock, message, sizeof(message)); // tcp_server.c의 데이터 전송
close(clnt_sock);
close(serv_sock);

clnt_sock에 message를 담아서 전달한다.

if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1)
 error handling("connect() error!");
while(read_len==read(sock, &message[idx++], 1)
{
    if(read_len==-1)
    {
    	error_handling("read() error!");
    	break;
    }
    str_len += read_len;
}
printf("Message from server: Xs \n", message);
printf("Function read call count: %d \n", str_len);

위 클라이언트 코드에서는 read를 통해 sock의 내용을 1byte 씩 message 버퍼로 읽어들인다. server에서 만든 경계는 client가 읽어들일 때 의미가 없어진다. 

 

write에서는 한 번에 패킷을 전달했지만, read에서 나눠서 읽었기 때문에 아래와 같은 결과가 나온다.

root@my_linux:/tcpip# gcc tcp_client.c -o hclient
root@my_linux:/tcpip# ./hclient 127.6.6.1 9190
Message from server: Hello World!
Function read call count: 13

 

반응형

관련글 더보기