윤성우의 열혈 TCP/IP Ch.2

KSH·2022년 7월 21일
0

열혈강의 TCP/IP

목록 보기
2/4

윤성우의 열혈 TCP/IP Ch.2

원본 도서
http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&ejkGb=KOR&barcode=9788996094036


소켓의 타입과 프로토콜 설정

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

프로토콜
(멀리 떨어진)두 컴퓨터 상호간의 대화에 필요한 상호간의 통신규약

2. 소켓의 생성

I . socket() 함수 원형


  • 헤더

    #include<sys/socket.h>
  • 함수 원형

    int socket(int domain, int type, int protocol)
  • 파라미터

    int domain  : 소켓이 사용할 프로토콜 체계 정보를 전달한다.
    int type	 : 소켓의 데이터 전송방식에 대한 정보 전달(ex. SOCK_STREAM, SOCK_DGRAM)
    int protocol: 두 컴퓨터간 통신에 사용되는 프로토콜 정보 전달

3. 프로토콜 체계(Protocol Family, 1번인자)


  • sys/socket.h에 선언되어 있는 프로토콜 체계들

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

4. 소켓의 타입(2번 인자)


소켓의 전송 방식을 의미한다

I. 소켓의 타입 1: 연결지향형 소켓(SOCK_STREAM, TCP)

  • 연결 지향형 소켓의 특징
    i. 중간에 데이터가 소멸되지 않고 목적지로 전송된다
    ii. 전송 순서대로 데이터가 수신된다
    iii. 전송되는 데이터의 경우 경계가 존재하지 않는다.

  • 데이터의 경계
    데이터가 어떤 통에 가득 찰때까지 기다렸다가 전송되는 것이 아닌 버퍼가 가득 차면 한번에 전송하는 방식이다.

II. 버퍼(buffer)

  • 데이터 송수신을 위해 소켓 내부에 마련해놓은 바이트 배열
    물건을 하역하기 전에 물건을 임시로 미리 적재해두는 공간 이라고 비유 가능
    데이터를 수신했다고 해서 반드시 read함수를 수신하여 buffer 내부에 데이터를 긁어올 필요는 없다.

  • 수신된 데이터가 이 buffer 배열의 용량을 초과하지 않는 한
    i. read 함수로 한번에 데이터를 읽어들이거나
    ii. write 함수로 여러번에 걸쳐 전체를 읽어들일 수 있다.

  • 소켓에 존재하는 버퍼가 꽉차면 데이터가 소멸되는가?
    i. read로 항상 읽어올 때마다 버퍼는 비워져서 상시에 문제가 되지 않는다.
    ii. 데이터가 버퍼 용량초과서 더이상 수신은 불가하지만 데이터가 소멸되진 않는다.
    iii. 연결지향형 소켓의 가장 큰 특징 중 하나로 서버 자신과 연결된 클라이언트의 소켓의 상태를 파악해 가며 전송을 하기 때문에 데이터 손실에 대해서는 걱정할 필요가 없음

  • 소켓대 소켓의 연결은 반드시 1:1이다

III. 소켓의 타입2 : 비 연결 지향형 소켓(SOCK_DGRAM, UDP)

  • 비 연결 지향형 소켓의 특징
    i. 전송 순서에 상관없이 가장 빠른 전송을 지향한다.
    ii. 전송된 데이터는 손실의 우려가 있고, 파손의 우려가 있다.
    iii. 전송되는 데이터의 경계가 있다
    iv. 한번에 전송할 수 있는 데이터의 크기가 제한된다.

  • 이외의 특징은 대부분 연결지향형 소켓의 반대 특징을 가진다
    대표적으로 버퍼에 관련해서도 서버는 클라이언트의 소켓을 확인하지 않는다.


5. 프로토콜의 최종 선택(3번 인자)


socket 함수의 세번째 인자

I. 굳이 필요한 이유

  • 대부분의 경우에는
    그냥 숫자 0을 전달해도 큰 문제가 발생하지 않는다.
  • 그런데 문제가 발생하는 이유는
    하나의 프로토콜 체계 안에 데이터의 전송방식이 동일한 프로토콜이 둘 이상 존재하는 경우가 있기 때문이다.

II. 예시

int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);

6. 예시코드


  • 사용 환경 : WSL(Windows10) LINUX
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

void error_handling(char *messgae);

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 소켓을 생성하는 부분, 3번인자에 0이 들어간 것을 확인가능
    memset(&serv_addr, 0, sizeof(serv_addr));
    // 메모리를 지정한 문자로 채워버린다. 여기선 메모리를 0으로 초기화 해버리는 방법이된다.
    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바이트씩 데이터를 읽어들인다.
        //이렇게 가서 끝에선 0을 읽기때문에 false가 되어 while문이 종료된다
        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);
}

7. WINDOWS 기반 구현


#pragma comment(lib, "ws2_32.lib")
#include<stdio.h>
#include<stdlib.h>
#include<winsock2.h>
void ErrorHandling(char* message);

int main(int argc, char* argv[]) {
	WSADATA wsaData;
	SOCKET hSocket;
	//socket 함수의 반환 값 저장을 위해 선언하는 SOCKET 형 변수
	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("hSocket() error");

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

	memset(&hSocket, (SOCKADDR*)&servAddr, 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)) {
		//여기서 recv 호출을 통해서 1바이트씩 데이터를 읽는다
		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, stdout);
	fputc('\n', stderr);
	exit(1);
}
profile
대충 개발자 비슷한거

0개의 댓글