[TCP/IP Socket]Chapter 02 - 소켓의 타입과 프로토콜의 설정

Lee Jeong Min·2021년 1월 25일
0

네트워크

목록 보기
2/17
post-thumbnail

02-1 소켓의 프로토콜과 그에 따른 데이터 전송 특성

프로토콜(Protocol)이란 무엇인가?

컴퓨터 상호간의 대화에 필요한 통신규약을 말한다.

소켓의 생성

#include <sys/socket.h>

int socket(int domain, int type, int protocol);

이 함수에 대해 제대로 이해하기 위해 하나하나 살펴보면 다음과 같다.

  • domain: 소켓이 사용할 프로토콜 체계 정보 전달
  • type: 소켓의 데이터 전송방식에 대한 정보 전달
  • protocol: 두 컴퓨터간 통신에 사용되는 프로토콜 정보 전달

프로토콜 체계

이름프로토콜 체계(Protocol Family
PF_INETIPv4 인터넷 프로토콜 체계
PF_INET6IPv6 인터넷 프로토콜 체계
PF_LOCAL로컬 통신을 위한 UNIX 프로토콜 체계
PF_PACKETLow Level 소켓을 위한 프로토콜 체계
PF_IPXIPX 노벨 프로토콜 체계

책에서는 주로 PF_INET에 초점을 맞추어서 설명!

소켓의 타입

  1. 연결지향형 소켓(SOCK_STREAM)
  2. 비 연결지향형 소켓(SOCK_DGRAM)

연결지향형 소켓(SOCK_STREAM)

  • 중간에 데이터가 소멸되지 않고 목적지로 전송된다.
  • 전송 순서대로 데이터가 수신된다.
  • 전송되는 데이터의 경계가 존재하지 않는다.

write와 read 함수에 이를 적용하면 다음과 같다.

"데이터를 전송하는 컴퓨터가 세 번의 write 함수 호출을 통해서 총 100바이트를 전송하였다. 그런데 데이터를 수신하는 컴퓨터는 한 번의 read 함수호출을 통해서 100바이트 전부를 수신하였다."

소켓은 내부적으로 버퍼를 지니고 있는데 데이터가 수신디었다고 해서 바로 read함수를 호출할 필요는 없다. 따라서 이 버퍼배열의 용량을 초과하지 않는 한 데이터가 다 채워진 후에 read함수를 호출할 수 있기 때문에 read함수의 호출횟수와 write함수의 호출 횟수는 연결 지향형 소켓의 경우 큰 의미를 갖지 못한다. --> 데이터의 경계가 존재하지 않는다는 의미.

연결 지향형 소켓은 자신과 연결된 상대 소켓의 상태를 파악해가면서 데이터를 전송하기 때문에 데이터가 제대로 전송되지 않으면 데이터를 재전송함. 특별한 경우가 아닌 경우 소켓의 데이터손실이 발생하지 않음.

연결 지향형 소켓의 경우 소켓 대 소켓의 연결은 반드시 1대 1이어야 한다.

연결 지향형 특성을 모두 반영하면 다음과 같이 정의할 수 있음

"신뢰성 있는 순차적인 바이트 기반의 연결지향 데이터 전송 방식의 소켓"

비 연결지향형 소켓(SOCK_DGRAM)

  • 전송된 순서에 상관없이 가장 빠른 전송을 지향한다.

  • 전송된 데이터는 손실의 우려가 있고, 파손의 우려가 있다.

  • 전송되는 데이터의 경계가 존재한다.

  • 한번에 전송할 수 있는 데이터의 크기가 제한된다.

    "신뢰성과 순차적 데이터 전송을 보장하지 않는, 고속의 데이터 전송을 목적으로 하는 소켓"

프로토콜의 최종선택

"IPv4 인터넷 프로토콜 체계에서 동작하는 연결지향형 데이터 전송 소켓" (TCP)

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

"IPv4 인터넷 프로토콜 체계에서 동작하는 비 연결지향형 데이터 전송 소켓" (UDP)

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

연결지향형 소켓! TCP 소켓의 예

  • hello_server.c -> tcp_server.c 변경사항 없음!
  • hello_client.c -> tcp_client.c read 함수의 호출방식 변경!

tcp_client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.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);
    if(sock == -1)
        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))
        {
            if(read_len == -1)
                error_handling("read()  error!");

            str_len += read_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 소켓의 특성은 전송되는 데이터의 경계가 존재하지 않는다는 것인데, 이 예제에서는 서버가 전송한 13바이트 짜리 데이터를 1바이트 씩, 총 13회의 read함수호출로 읽어 들였다.


02-2 윈도우 기반에서 이해 및 확인하기

윈도우 운영체제의 socket 함수

#include <winsock2.h>

SOCKET socket(int af, int type, int protocol);

반환형이 SOCKET형이고 이를 위해 SOCKET형 변수를 하나 선언해서 저장해야함. 또한 MS에서 INVALID_SOCKET이라는 상수를 정의해 놓았기 때문에 오류발생시 -1이 아닌 이 상수를 사용하는 것이 좋다.

윈도우 기반 tcp 소켓의 예

tcp_client_win.c

#include <stdio.h>
#include <stdlib.h>
#include <winSock2.h>
void ErrorHandling(char* message);

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	SOCKET hSocket;
	SOCKADDR_IN servAddr;

	char message[30];
	int strLen = 0;
	int idx = 0, readLen = 0;

	if (argc != 3)
	{
		printf("Usage: %s <IP> <port>\n", argv[0]);
		exit(1);
	}

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		ErrorHandling("WSAStartup() error!");

	hSocket = socket(PF_INET, SOCK_STREAM, 0);
	if (hSocket == INVALID_SOCKET)
		ErrorHandling("hSocket() error");

	memset(&servAddr, 0, sizeof(servAddr));
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = inet_addr(argv[1]);
	servAddr.sin_port = htons(atoi(argv[2]));

	if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
		ErrorHandling("connect() error!");

	while (readLen = recv(hSocket, &message[idx++], 1, 0))
	{
		if (readLen == -1)
			ErrorHandling("read() error!");
		strLen += readLen;
	}

	printf("Message from server: %s \n", message);
	printf("Function read call count: %d \n", strLen);

	closesocket(hSocket);
	WSACleanup();
	return 0;
}

void ErrorHandling(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}
profile
It is possible for ordinary people to choose to be extraordinary.

0개의 댓글