스택의 용량이 무제한이라면, 힙이 필요할까?

 

📌 스택과 힙 간단 비교

특징 스택
할당 방식 자동 ( 컴파일러가 관리 ) 수동 ( 프로그래머가 malloc / free 관리 )
메모리 크기 일반적으로 작음 상대적으로 큼
할당 속도 매우 빠름 상대적으로 느림
데이터 수명 함수 실행 동안만 유지 free() 호출 전까지 유지
사용 방식 지역 변수, 함수 호출 스택 동적 할당된 객체, 대량 데이터

 

스택이 무제한이라면, 할당 속도가 빠르고, 자동 해제되므로 힙을 대체할 수 있을것 처럼 보인다.

하지만, 다음과 같은 이유 때문에 여전히 힙이 필요하다.

 

1️⃣ 데이터의 수명이 함수 호출을 넘어야 하는 경우

스택 변수는 함수가 종료되면 자동으로 해제된다.

 

int* createNumber() {
    int num = 42;  // 스택에 할당됨
    return #   // 잘못된 포인터 반환! (dangling pointer)
}

 

위 코드는 num의 주소를 반환하나, 함수가 끝나면 해당 변수의 메모리는 해제(소멸) 된다.

이때 힙을 사용하면, 함수가 끝난 뒤에도 데이터를 유지할 수 있다.

 

2️⃣  동적으로 크기가 변하는 데이터를 다룰 때

스택에서는 변수가 컴파일 타임에 크기가 정해져야 한다.

 

void stackExample(int size){
    int arr[size];
}

 

위 코드에서 size가 런타임에 결정되면, 일부 컴파일러에서 오류가 발생할 수 있다.

반면, 힙은 런타임에 동적으로 크기를 정할 수 있어 유연하다.

void heapExample(int size){
    int* arr = (int*)malloc(size * sizeof(int));
    free(arr);
}

 

동적 크기 조정이 필요한 경우, 힙이 필수적이다.

 

3️⃣ 동적으로 크기가 변하는 데이터를 다룰 때

스택은 Last In, First Out ( LIFO ) 구조이므로 중간 데이터를 자유롭게 관리하기 어렵다.

 

void example(){
    int a = 10;
    int b = 20;
    // 스택에서 b를 해제하고 a만 유지하는 것이 불가능하다.
}

 

힙은 필요할 때마다 메모리를 할당(malloc)하고, 필요 없을 때 해제(free)할 수 있어 더 유연하다.

 

4️⃣ 멀티스레드 환경에서 공유 메모리가 필요

스택은 각 스레드마다 독립적으로 할당되므로, 스레드 간 공유 데이터를 저장할 수 없다.

힙에 할당된 메모리는 여러 스레드에서 공유할 수 있다.

 

 

결론, 대부분의 현실적인 프로그램에서는 힙이 꼭 필요하다.

'CS' 카테고리의 다른 글

[CS] 부동소수점  (0) 2025.04.15
[CS] 힙 단편화  (0) 2025.04.09
[CS] 메모리 보호 기법 ( ASLR )  (0) 2025.03.11
[cs] 문자열 상수  (0) 2025.02.23
[CS] 빅 엔디안, 리틀 엔디안  (0) 2025.02.23
Echo Server는 클라이언트가 보낸 데이터를 그대로 다시 반환하는 서버를 말한다.
즉, 클라가 서버에 메세지를 전송하면, 서버는 그 메세지를 그대로 클라이언트에게 다시 보낸다.

 

🔹 Echo Server, Echo Client 의 주요 특징

  • 구현이 간단하기 때문에 네트워크 프로그래밍을 학습하는 초보자에게 적합하다.
  • 클라이언트와 서버 간 통신이 정상적으로 이루어지는지 확인할 때 유용하다.

 

TCP 기반 Echo Server

#include<winsock2.h>
#pragma comment(lib, "ws2_32")

int main()
{
    // 윈속 초기화
    WSADATA wsa = { 0 };
    if(::WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
    {
        puts("ERROR: 윈속을 초기화 할 수 없습니다.");
        return 0;
    }
    
    // 1. 접속대기 소켓을 생성한다.
    SOCKET hSocket = ::socket(AF_INET, SOCK_STREAM, 0);
    if(hSocket == INVALID_SOCKET)
    {
        puts("ERROR: 접속 대기 소켓을 생성할 수 없습니다.");
        return 0;
    }
    
    // 2. 포트 바인딩
    SOCKADDR_IN svraddr = { 0 };
    svraddr.sin_family = AF_INET;
    svraddr.sin_port = htons(25000);
    svraddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
    if(::bind(hSocket, (SOCKADDR*)&svraddr, sizeof(svraddr)) == SOCKET_ERROR)
    {
        puts("ERROR: 소켓에 IP주소와 포트를 바인드 할 수 없습니다.");
        return 0;
    }
    
    // 3. 접속대기 상태로 전환
    if(::listen(hSocket, SOMAXCONN) == SOCKET_ERROR)
    {
        puts("ERROR: 리슨 상태로 전환할 수 없습니다.");
        return 0;
    }
    
    // 4. 클라이언트 접속 처리 및 대응
    SOCKADDR_IN clientaddr = { 0 };
    int nAddrLen = sizeof(clientaddr);
    SOCKET hClient = 0;
    char szBuffer[128] = { 0 };
    int nReceive = 0;
    
    // 4.1 클라이언트 연결을 받아들이고 새로운 소켓을 생성한다. (개방)
    while((hClient = ::accept(hSocket,
        (SOCKADDR*)&clientaddr,
        &nAddrLen)) != INVALID_SOCKET)
    {
        puts("새 클라이언트가 연결되었습니다."); fflush(stdout);
        // 4.2 클라이언트로부터 문자열을 수신한다.
        while((nReceive = ::recv(hClient, szBuffer, sizeof(szBuffer, 0)) > 0)
        {
            // 4.3 수신한 문자열을 그대로 전송한다.
            ::send(hClient, szBuffer, sizeof(szBuffer), 0);
            puts(szBuffer); fflush(stdout);
            memset(szBuffer, 0, sizeof(szBuffer));
        }
        
        // 4.4 클라이언트가 연결을 종료한다.
        ::shutdown(hClient, SD_BOTH);
        ::closesocket(hClient);
        puts("클라이언트 연결에 끊겼습니다."); fflush(stdout);
    }
    
    // 5. 리슨 소켓 닫기
    closesocket(hSocket);
    
    // 윈속을 해제 한다.
    ::WSACleanup();
    return 0;
}

 

 

TCP 기반 Echo Client

#include <winsock2.h>
#pragma comment(lib, "ws2_32")


int main()
{
	// 윈속 초기화
	WSADATA wsa = { 0 };
	if (::WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
	{
		puts("ERROR: 윈속을 초기화 할 수 없습니다.");
		return 0;
	}

	//1. 접속대기 소켓 생성
	SOCKET hSocket = ::socket(AF_INET, SOCK_STREAM, 0);
	if (hSocket == INVALID_SOCKET)
	{
		puts("ERROR: 소켓을 생성할 수 없습니다.");
		return 0;
	}

	//2. 포트 바인딩 및 연결
	SOCKADDR_IN	svraddr = { 0 };
	svraddr.sin_family = AF_INET;
	svraddr.sin_port = htons(25000);
	svraddr.sin_addr.S_un.S_addr = inet_addr("192.168.77.2");
	if (::connect(hSocket,
		(SOCKADDR*)&svraddr, sizeof(svraddr)) == SOCKET_ERROR)
	{
		puts("ERROR: 서버에 연결할 수 없습니다.");
		return 0;
	}

	//3. 채팅 메시지 송/수신
	char szBuffer[128] = { 0 };
	while (1)
	{
		//사용자로부터 문자열을 입력 받는다.
		gets_s(szBuffer);
		if (strcmp(szBuffer, "EXIT") == 0)
        {
            break;
        }

		//사용자가 입력한 문자열을 서버에 전송한다.
		::send(hSocket, szBuffer, strlen(szBuffer) + 1, 0);
		//서버로부터 방금 보낸 문자열에 대한 에코 메시지를 수신한다.
		memset(szBuffer, 0, sizeof(szBuffer));
		::recv(hSocket, szBuffer, sizeof(szBuffer), 0);
		printf("From server: %s\n", szBuffer);
	}

	//4. 소켓을 닫고 종료.
	//::shutdown(hSocket, SD_BOTH);
	::closesocket(hSocket);
	//※윈속 해제
	::WSACleanup();
	return 0;
}

 

🔹 Wire shark로 연결 상태 확인

앞서 배운 내용 처럼 서버를 실행하고 클라를 실행한후 port를 25000으로 지정하면 

3 Way Handshake를 수행한 결과를 위 그림처럼 와이어 샤크로 확인할 수 있다. [SYN], [SYN, ACK], [ACK]

 

 

일반적인 데이터 주고 받을때

 

 

일반적인 종료 상황에서의 4 Way Handshake

4 Way handshake 종료 과정에서 클라이언트가 FIN 패킷을 전송한 후, 상대방의 ACK를 받은 뒤 일정 시간
동안 유지 되는 상태를 말한다.

 

📌 이유

마지막 ACK 패킷이 손실될 가능성이 있기 때문에 재전송을 대비해 일정 시간 동안 종료하지 않고 머문다.

네트워크에 남아있는 패킷( 지연된 패킷 )으로 인해 문제가 발생할 수 있기 때문에 대기한다.

  • 앞서 말한 문제란, 이전 연결에서 남아 있는 패킷이 새로운 연결에 영향을 미칠 수 있는 것을 말한다.

 

✅ TIME_WAIT 기본 조건

  • 일반적으로 2 * MSL 동안 유지 된다.
  • MSL은 한 패킷이 네트워크에서 생존할 수 있는 최대 시간을 말한다. ( 보통 30초에서 최대 2분 )
  • 즉, TIME_WAIT 상태는 1 ~ 4분 정도 유지 된다.

 

TIME_WAIT 상태가 많아지면, 서버의 포트가 빠르게 소진되어 새로운 연결을 맺기 어려울 수 있다.

이를 해결하기 위한 방법은 아래와 같다.

 

  1. 소켓 옵션 조정
    • SO_REUSEADDR 옵션 사용
      • 동일한 포트를 재사용할 수 있도록 허용하는 옵션이다.
      • 단, 이는 TIME_WAIT 상태를 없애는 것이 아니고, 해당 포트를 다른 프로세스가 빠르게 재사용 할 수 있도록 해준다.
  2. TcpTimeWaitDelay 값 줄이기
    • TcpTimeWaitDelay는 TIME_WAIT 상태를 유지하는 시간을 설정하는 레지스트리 값이다. 기본값은 240초 이지만, 이를 30초 정도로 줄여주면 포트가 빨리 해제된다.
4 Way Handshake는 TCP 연결 종료 과정에서 사용되는 절차를 말한다.
TCP 연결을 종료할 때 양쪽이 각각 연결 종료를 확인해야 하기 때문에 4단계 절차가 필요하다.

 

📌 4 Way Handshake 과정

 (1) FIN -> 연결 종료 요청 ( Client -> Server )

  • 클라이언트가 더 이상 데이터를 보낼 필요가 없으면 FIN 플래그를 설정하여 서버에게 보낸다.
  • 클라는 더 이상 데이터를 보내지 않고, 서버의 응답을 기다리고 있는 상태

(2) ACK -> 종료 요청 확인 ( Server -> Client )

  • 서버는 클라이언트의 FIN을 받았다는 것을 확인한 후 ACK(응답) 패킷을 보낸다.
  • 아직 서버는 데이터를 더 보낼 수 있는 상태

(3) FIN -> 서버도 연결 종료 요청 ( Server -> Client )

  • 서버도 모든 데이터를 보낸 후, 클라이언트에게 FIN 패킷을 보내면서 연결 종료 요청을 한다.
  • 이제 서버도 더 이상 데이터를 보낼 필요가 없으므로 종료 절차를 시작한다.

(4) ACK -> 연결 종료 확인 ( Client -> Server )

  • 클라이언트는 서버의 FIN 패킷을 받으면 ACK(확인 응답)을 보낸다.
  • 이제 연결이 완전히 종료되고, 서버와 클라 모두 더 이상 데이터 전송을 하지 않는다.

 

📌 4 Way Handshake가 필요한 이유

TCP는 양방향 통신을 지원하기 때문에 양쪽이 각각 연결을 종료해야 한다.
  • 3 Way Handshake는 한쪽이 요청을 보내면 상대방이 바로 응답하는 방식
  • 4 Way Handshake는 두 개의 독립적인 FIN + ACK 과정이 필요하다.

'IT' 카테고리의 다른 글

[IT] TIME_WAIT  (0) 2025.04.09
[IT] Sliding Window ( 슬라이딩 윈도우 )  (0) 2025.04.08
[IT] 3 Way Handshake  (0) 2025.04.08
[IT] 운영체제 명령어 삽입 ( OS Command Injection )  (0) 2025.04.01
[IT] 크로스사이트 스크립트  (0) 2025.04.01
슬라이딩 윈도우는 TCP 흐름 제어와 패킷 전송 최적화를 위해 사용되는 기술이다.
데이터를 효율적으로 보내고, 수신 측이 처리할 수 있는 만큼만 보내도록 조절하는 메커니즘을 말한다.

 

📌 Sliding Window 개념

TCP는 데이터를 세그먼트 단위로 전송한다. 하지만 매번 한 개의 패킷을 보내고 ACK(응답)를 기다리면 속도가

너무 느려지기 때문에, 여러 개의 패킷을 연속적으로 보낼 수 있도록 허용하는 기법을 Sliding Window라 한다.

 

✅ Sliding Window 방식

  • 송신자는 미리 여러 개의 패킷을 보내고, ACK을 기다린다.
  • 수신자는 데이터를 받은 후 ACK를 보낸다.
  • 일정 개수의 세그먼트(윈도우 크기)만큼만 한 번에 전송 가능하다.

📌 Sliding Window 동작 방식

Sliding Window는 윈도우 크기에 따라 한 번에 보낼 수 있는 패킷 개수를 결정한다.

즉, ACK 없이 미리 보낼 수 있는 데이터 양을 정하는 것이다.

  • 윈도우 크기 = 4일 경우, 송신자는 4개의 패킷을 연속적으로 보낼 수 있고, 이후 ACK을 받아야 다시 보낼 수 있다.

  송신 윈도우와 수신 윈도우

  • 송신 윈도우 : 송신자가 한 번에 보낼 수 있는 데이터 크기
  • 수신 윈도우 : 수신자가 한 번에 받을 수 있는 데이터 크기
  • 수신 윈도우 크기에 따라 송신 윈도우의 크기가 조정된다.
송신자                                      수신자
---------------------------------------------------
SEQ=1 [패킷1]  ---->   
SEQ=2 [패킷2]  ---->   
SEQ=3 [패킷3]  ---->   
SEQ=4 [패킷4]  ---->  (한 번에 4개 전송 완료!)

(수신자가 패킷을 받은 후)  
<----  ACK=5  (1~4번 패킷 정상 수신!)

SEQ=5 [패킷5]  ---->   
SEQ=6 [패킷6]  ---->   
...

 

위 처럼 송신자는 ACK을 기다리지 않고, 미리 4개 패킷을 보낼 수 있다.

ACK을 받으면 다음 데이터를 연속적으로 보낼 수 있다.

 

📌 윈도우 크기 조절 ( 흐름 제어 & 혼잡 제어 )

TCP에서는 윈도우 크기를 동적으로 조절하여 네트워크 상태에 맞게 최적의 속도로 데이터 전송을 수행한다.

 

🔸 흐름 제어 ( Flow Control )

  • 송신자가 너무 많은 데이터를 보내면 수신자가 처리하지 못하는 경우가 발생한다.
  • 수신자는 자신의 수신 윈도우 크기를 조정하여 한 번에 받을 수 있는 데이터 크기를 제한한다.
  • 수신 윈도우 크기가 0이 되면 일시적으로 전송을 중지한다..

🔸 혼잡 제어 ( Congestion Control )

  • 네트워크에 트래픽이 너무 많으면 패킷 손실이 발생할 수 있다.
  • 송신자는 혼잡 윈도우를 활용하여 네트워크 상태에 맞게 전송량을 조절한다.
    • 혼잡 윈도우는 TCP에서 네트워크 혼잡을 방지하기 위해 송신자가 조절하는 윈도우 크기를 말한다.

'IT' 카테고리의 다른 글

[IT] TIME_WAIT  (0) 2025.04.09
[IT] 4 Way Handshake ( TCP 연결 종료 )  (0) 2025.04.09
[IT] 3 Way Handshake  (0) 2025.04.08
[IT] 운영체제 명령어 삽입 ( OS Command Injection )  (0) 2025.04.01
[IT] 크로스사이트 스크립트  (0) 2025.04.01
TCP 3-Way Handshake는 신뢰성 있는 연결을 설정하기 위해 클라이언트와 서버 간에 세 번의 패킷 교환을 수행하는 과정을 말한다.

 

📌 개요

TCP는 연결형 프로토콜로, 데이터 전송 전에 송신자와 수신자가 서로 연결을 설정해야한다.

이를 위해 TCP는 3 Way Handshake를 사용하여 다음을 보장한다.

  • 양쪽이 데이터 전송 준비가 되었는지 확인
  • 초기 시퀀스 번호( ISN )를 교환하여 데이터 순서를 유지
  • 네트워크 상태를 점검하여 적절한 패킷 크기 및 흐름 제어 설정

📌 과정

 Step 1 : 클라이언트 -> 서버 ( SYN )

클라이언트가 서버에 연결 요청을 보낸다.

  • 클라이언트가 TCP 소켓을 열고, SYN 플래르가 설정된 패킷을 전송한다.
  • 이 패킷에 초기 시퀀스 번호 ( ISN )가 포함된다.
  • 윈도우 크기( Window Size), MSS, 윈도우 스케일( Window Scaling ) 등의 옵션 정보가 포함된다.

 Step 2 : 서버 -> 클라이언트 ( SYN + ACK )

서버가 클라이언트의 요청을 수락하고 응답을 보낸다.

  • 서버는 클라이언트의 SYN 요청을 수락한 후, SYN + ACK 플래그가 설정된 패킷을 전송한다.
  • 이 패킷에 서버의 초기 시퀀스 번호( ISN ) 와 클라이언트의 SYN에 대한 응답 번호(ACK)가 포함된다.

 Step 3 : 클라이언트 -> 서버 ( ACK )

클라이언트가 최종적으로 서버에 연결 완료를 알린다.

  • 클라이언트는 서버가 보낸 SYN + ACK 패킷을 받은 후, ACK 플래그가 설정된 패킷을 다시 서버로 보낸다.
  • 이 패킷에 서버의 SYN에 대한 응답 번호 ( ACK )가 포함된다.

📌 3 Way Handshake에서 설정되는 TCP 옵션

  • MSS : 한 번에 전송할 수 있는 최대 세그먼트 크기
  • 윈도우 크기 : 수신 가능한 버퍼 크기
  • 윈도우 스케일링 : 윈도우 크기 확장 옵션
  • 타임스탬프 : 패킷의 RTT 측정

📌 3 Way Handshake가 실패하는 경우

  • SYN 패킷이 도착하지 않는 경우
  • SYN + ACK 패킷이 도착하지 않는 경우
  • ACK 패킷이 도착하지 않는 경우

📌 3 Way Handshake가 성공하고 나서 데이터 전송

  • 클라이언트가 데이터를 전송한다 ( Sequence Number = 1001 )
  • 서버가 응답한다. ( ACK = 1001 )
  • 이후, Sliding Window를 활용하여 빠르게 데이터를 전송한다.
외부 입력이 시스템 명령어 실행 인수로 적절한 처리 없이 사용되면 위험하다.
일반적으로 명령어 줄 인수나 스트림 입력 등 외부 입력을 사용하여 시스템 명령어를 생성하는 프로그램이 많이 있다.
하지만 이러한 경우 외부 입력 문자열은 신뢰할 수 없기 때문에 적절한 처리를 해주지 않으면, 
공격자가 원하는 명령어 실행이 가능하게 된다.

'IT' 카테고리의 다른 글

[IT] Sliding Window ( 슬라이딩 윈도우 )  (0) 2025.04.08
[IT] 3 Way Handshake  (0) 2025.04.08
[IT] 크로스사이트 스크립트  (0) 2025.04.01
[IT] 자원 삽입 ( Resource Injection )  (0) 2025.04.01
[IT] SQL 삽입 ( SQL Injection )  (0) 2025.04.01
웹 페이지에 악의적인 스크립트를 포함시켜 사용자 측에서 실행되게 유도할 수 있다.

 

아래 그림과 같이 검증되지 않은 외부 입력이 동적 웹페이지 생성에 사용될 경우, 전송된 동적 웹페이지를 열람하는 접속자의 권한으로 부적절한 스크립트가 수행되어 정보유출 등의 공격을 유발할 수 있다.

 

'IT' 카테고리의 다른 글

[IT] 3 Way Handshake  (0) 2025.04.08
[IT] 운영체제 명령어 삽입 ( OS Command Injection )  (0) 2025.04.01
[IT] 자원 삽입 ( Resource Injection )  (0) 2025.04.01
[IT] SQL 삽입 ( SQL Injection )  (0) 2025.04.01
[IT] 레이턴시 ( Latency )  (2) 2024.10.24
외부 입력값을 검증하지 않고 시스템 자원에 대한 식별자로 사용하는 경우, 공격자는 입력값 조작을 통해
시스템이 보호하는 자원에 임의로 접근하거나 수정할 수 있다.

 

📌 안전한 코딩 기법

  • 외부의 입력을 자원( 파일, 소켓의 포트 등 ) 식별자로 사용하는 경우, 적절한 검증을 거치도록 하거나 사전에 정의된 적합한 리스트에서 선택되도록 작성한다. 외부의 입력이 파일명인 경우에는 경로 순회를 수행할 수 있는 문자를 제거한다.
데이터베이스와 연동된 웹 어플리케이션에서 입력된 데이터에 대한 유효성 검증을 하지 않을 경우,
공격자가 입력 폼 및 URL 입력란에 SQL 문을 삽입하여 DB로부터 정보를 열람하거나 조작할 수 있는 보안약점을 말한다.

 

취약한 웹 어플리케이션에서는 사용자로부터 입력된 값을 필터링 과정없이 넘겨받아 동적 쿼리를 생성한다.

이는 개발자가 의도하지 않은 쿼리가 생성되어 정보유출에 악용될 수 있다.

 

📌 안전한 코딩기법

  • 외부 입력이나 외부 변수로부터 받은 값이 직접 SQL 함수의 인자로 전달되거나, 문자열 복사를 통하여 전달되는 것은 위험하다. 그러므로 인자화된 질의문을 사용해야 한다.
  • 외부 입력값을 그대로 사용해야 하는 환경이라면, 입력받은 값을 필터링을 통해 처리한 후 사용해야 한다. 필터링은 SQL문에서 사용하는 단어 사용 금지, 특수문자 사용금지, 길이 제한의 기준을 적용한다.

 

📌 예제

#include <stdlib.h>
#include <sql.h> 
void Sql_process(SQLHSTMT sqlh) 
{ 
    char *query = getenv("query_string"); 
    SQLExecDirect(sqlh, query, SQL_NTS);
}

 

위 코드를 살펴보자.

외부 입력이 SQL 질의어에 어떠한 처리도 없이 삽입되었기 때문에, name' OR 'a'='a 와 같은 문자열을 입력으로 주면

WHERE 절이 항상 참이 된다.

 

#include <sql.h> 
void Sql_process(SQLHSTMT sqlh) 
{ 
    char *query_items = "SELECT * FROM items"; 
    SQLExecDirect(sqlh, query_items, SQL_NTS);
}

 

위 코드는 인자화된 질의를 사용해 질의 구조의 변경을 막을 수 있다.

+ Recent posts