반응형

0. 시작하기

 

1. 메모리 구조

C, Java와 같은 언어를 이용한 프로그래밍에 익숙하다면 많이 들어봤고 잘 알고있을 내용일 겁니다.

[ Code, Data, Stack, Heap ] 이라는 3개의 공간에 대해서 간단히 언급 정도하겠습니다. (알고있다면 넘어가도 좋습니다.)

 

프로그램은 운영체제에 의해서 관리되는 메인 메모리(Ram)의 일부를 할당받아 사용하게됩니다. 프로그램을 사용하는 사용자는 잘 모르지만, 프로그래머가 생성한 변수들은 코드에 따라 할당받은 메모리 이곳저곳에 저장되게됩니다.

 

무질서하게 배치되는 것은 아닙니다. 위에서 언급한 [ Code, Data, Stack, heap ] 이라는 영역에 적절하게 배치되는데요. 쉽게 본다면 아래와 같습니다.

 

  • Code 영역 : 말 그대로 실행할 코드가 저장되어있는 영역입니다. (기계어 형태로 존재합니다.)
  • Data 영역 : 전역변수, Static변수 들이 저장됩니다.
  • Stack 영역 : 지역변수, 매개변수들이 저장됩니다.
  • Heap 영역 : malloc 함수 등에 의해서 동적으로 할당되어 사용되는 영역입니다.

 

위의 내용이 중요하지 않다고 생각할 수 있지만, 앞으로 클래스(객체지향), 동적할당 등에 대해서 다루다보면 이러한 메모리 구조를 몰라서 발생하는 문제들을 겪을 수 있습니다.

 

때문에 앞으로 다루는 포스터에서 위의 메모리 영역에 대한 언급이 있을 수 있기 때문에 앞서 간단히 다루고 넘어가는 것입니다.

 

2019/01/03 - [프로그래밍/C언어] - C언어 메모리 구조 - 실제 메모리로 확인하기 (1/3)

 

C언어 메모리 구조 - 실제 메모리로 확인하기 (1/3)

C언어가 사용하는 메모리 구조 XCode를 이용하였지만, 보는데에는 큰 지장이 없습니다. 1. C언어가 사용하는 메모리 C언어의 메모리 관리와 관련된 이미지를 검색해보면 정말 다양한 내용들을 볼 수 있었다. 근데..

youngq.tistory.com

 

반응형
반응형

0. 시작하기

 

 

1. 네임스페이스 란?

직역하면 '이름 공간'이라는 의미인, NameSpace는 다소 생소한 개념일 수 있습니다.

 

네임스페이스는 변수/함수등을 유관함 하나의 집합으로 묶어 프로젝트간 발생할 수 있는 변수/함수명의 중복등의 문제를 해결할 수 있게해줍니다.

 

단순하게 생각한다면, 기존 변수/함수명 앞에 새로운 변수를 붙여주는 것과 큰 차이가 없지 않는가? 라고 생각할 수 있지만, 여러사람이 프로젝트를 진행할 때, 각자가 독립적인 네임스페이스에서 작업을 한다면, 자신의 작업공간이 어디서부터 어디인지 정확하게 알 수 있으며, 용도에 따라 네임스페이스를 구분한다면, 코드를 수정하고 확인할 때, 쉽게 원하는 부분을 탐색할 수 있습니다.

 

위와같은 이유로 네임스페이스를 적절하게 사용한다면 프로젝트 진행에 있어서 불필요하게 발생할 수 있는 문제들을 효율적으로 해결해줄 수 있습니다.

 

 

 

2. 샘플코드 ( CPP/Namespace1.cpp )

말로 설명하는 것보다 한번 코드로 확인하는게 더 좋겠죠.

 

#include <iostream>

namespace name1{
    int i = 10;
    void out(){
        std::cout << "namespace 1 의 out함수" << std::endl; 
    }
}

namespace name2{
    int i = 20;
    void out(){
        std::cout << "namespace 2 의 out함수" << std::endl; 
    }
}


int main(void){
    std::cout << (name1::i) << std::endl;
    name1::out();

    std::cout << (name2::i) << std::endl;
    name2::out();

    return 0;
}

실행결과

 

위의 예제는 동일한 변수/함수명을 갖고있는 네임스페이스를 다루고있습니다.

 

main함수를 보게되면 두 개의 콜론( :: )을 이용하여 네임스페이스 내에 존재하는 변수/함수를 참조하는 것을 볼 수 있습니다.

 

두 개의 콜론이 네임스페이스의 함수를 갖고오는 녀석이라면 'std'라는 녀석도 네임스페이스라는 의미가 되겠죠. 즉, std라는 녀석은 내부에 cout, cin, endl같은 함수를 갖고있는 네임스페이스고, 이 내용으 iostream 이라는 헤더파일에 작성되어있다는 것을 알 수 있습니다.

 

실제로 iostream의 내용을 확인해보면 아래와 같이 네임스페이스 'std'에 각 함수들이 존재하는 것을 볼 수 있습니다.

 

cat 명령어로 본 iostream 헤더파일

 

 

2. Using ( CPP/Namespace2.cpp )

네임스페이스의 경우 네임스페이스 내부에 또 다른 네임스페이스가 존재할 수 있습니다. 이와같은 경우 name1::name2::name3::func() 형태와 같이 호출이 가능합니다. 다만, 매우 비효율적이고 불편할 수 있습니다.

 

또한 자주사용하는 함수들(cout,cin,endl)은 호출시마다 앞에 std라는 네임스페이스를 적어줘야하는 불편함도 있습니다.

 

이러한 문제를 해결해주는 것이 Using을 이용한 선언입니다.

 

  • Using + namespace + 네임스페이스이름
  • Using + 네임스페이스이름::함수명

을 통해서 네임스페이스의 모든 함수 또는 특정 함수를 코드내에서 직접 호출하여서 사용할 수 있습니다.

 

예제코드를 보겠습니다.

 

 

#include <iostream>
using namespace std;

namespace name1{
    int i = 10;
    void out(){
        cout << "namespace 1 의 out함수" << endl; 
    }
}

namespace name2{
    int i = 20;
    void out(){
        cout << "namespace 2 의 out함수" << endl; 
    }
}

using name1::out;

int main(void){
    cout << (name1::i) << endl;
    out();      // name1 의 out 함수

    cout << (name2::i) << endl;
    name2::out();

    return 0;
}

실행결과

실행결과는 동일합니다. 다만 [using namespace std]를 통해서 'std' 네임스페이스의 함수들은, 메인함수에서 std:: 없이 사용할 수 있습니다.

 

또한 using name1::out을 통해서 name1의 out이라는 함수는 메인함수에서 그냥 out()을 통해서 호출이 가능하다는 차이만 있습니다.

 

주의할 부분은, C언어 계열의 프로그래밍 언어들은 위에서 아래로 코드를 읽어가기 때문에 using을 통해 선언해주는 부분은 네임스페이스를 선언한 이후가 되어야 한다는 것입니다.

반응형
반응형

0. 시작하기

 

1. 인라인 이란? (Inline)

'인라인'이란 말그대로 라인안에 들어가는 함수라는 의미입니다.

 

프로그램을 수행하다가 프로그램이 함수를 만나게되면, 스택에 현재의 상태를 저장하고 프로그램의 수행위치를 함수의 시작부분으로 이동시킵니다. 그리고 반환값을 받으면 반환값을 갖고 원래의 프로그램 수행위치로 돌아오는 과정을 갖게됩니다.

 

하지만, '인라인'함수는 컴파일러가 해당 함수를 원래코드에 삽입해주기 때문에, 위의 [함수의 호출]과정이 생략됩니다.

 

쉽게말해서 빠릅니다.

 

때문에 빠르게 처리가 필요하고 자주 수행되는 기능의 경우 인라인처리를 하는것이 유리할 수 있습니다.

 

물론 단점도 있습니다.

 

과도하게 인라인처리를 하게되면, 불필요한 부분까지 인라인 함수가 삽입되여, 프로그램이 비대해진다는 문제가 생기게됩니다.

 

따라서 개발시 인라인처리를 하면 무조건 좋은 성능을 낸다라고 맹목적으로 생각하시면 안됩니다.

( 컴파일러를 전적으로 믿지 마세요. )

 

 

 

2. 매크로 함수 ( CPP/Macro.cpp )

C++의 인라인 함수는 C언어의 매크로 함수와 자주 비교되어 언급됩니다.

이유는 두가지 모두 코드에 삽입되는 형태로 작동하기 때문이라고 생각됩니다.

하지만, '인라인 함수'와 '매크로 함수'는 본질적으로 큰 차이가 있습니다.

이를 비교하기 위해 인라인 함수를 다루기 전에 먼저 매크로 함수를 집고 넘어가겠습니다.

 

메크로 함수에 대해서 말해보자면, 메크로 함수는 [전처리기]에서 작동하는 함수입니다.

따라서 아래와 같은 메크로 함수를 사용하는 코드가 있다고 보겠습니다.

 

#include <iostream>
#define ADD(x,y) ((x+y))

int main(void){
	std::cout << ADD(1,2) << std::endl;
    
    return 0;
}

 

실행하면 "3"이라는 결과를 얻을 수 있습니다.

 

위의 코드에서 #define 으로 명시된 부분이 매크로함수입니다. 이부분은 전처리기에서 ADD(x,y)를 ((x+y))형태로 변환시키라는 명령을 갖고있습니다. 전처리기가 위와같은 명령을 수행한 후 컴파일러로 해당내용을 전달하게됩니다.

 

이게 무슨 문제가 되는가?

 

매크로 함수는 [한글 프로그램]의 [찾아 바꾸기]기능과 큰 차이가 없습니다.

 

정확하게, '잘~' 사용하면 편리한 기능입니다. 하지만, 다르게 본다면 의도하지 않은 프로그램이 탄생합니다.

 

간순히 입력구문을 치환하는 것이기 때문에 단순한 형변환의 과정에 있어서도 위의 경우 x,y가 정수라면 int형 결과를 발생시키고, 소수라면 double이나 float형의 결과를 발생시킵니다.

 

위와같은 정형화되지 못한 결과가 다른 함수의 입력으로 들어갈 경우 데이터 손실 또는 프로그램 자체가 애러를 내뱉은 문제를 야기할 수 있습니다.

 

때문에 사실상 매크로 함수는 잘 사용하지 않습니다.

(#define pi 3.14 에만 사용합니다.)

 

 

 

3. 인라인 함수 ( CPP/Inline.cpp )

다음으로 인라인 함수에 대해서 다뤄보겠습니다.

 

인라인 함수는 매크로함수와 다르게 [컴파일러]에 의해서 처리되는 합수입니다.

 

컴파일러가 처리한다는 의미는 다시말해, 해당 함수가 인라인으로 처리됨에 따라 성능에 저하가 발생할 경우 컴파일러가 인라인 키워드를 무시하고 처리하는 경우도 발생할 수 있다는 뜻이기도 합니다.

 

예제를 한번 보겠습니다.

 

#include <iostream>

inline int ADD(int x, int y){
    return x+y;
}

int main(void){
    std::cout << ADD(1,2) << std::endl;

    return 0;
}

 

위의 함수도 실행하면 3이라는 결과를 내뱉습니다.

 

매크로 함수와 비교해서 본다면 확실하게 차이를 알 수 있습니다.

 

바로 함수의 입력으로 들어오는 인자와 반환값에 대한 정확한 데이터 타입이 정해져 있다는 것입니다.

(사실 inline 이라는 부분이 없으면 일반 함수와 차이가 없습니다.)

 

위와같이 데이터 타입을 정의해줌으로 매크로함수에서 발생할 수 있는 문제들을 예방할 수 있습니다.

반응형
반응형

0. 시작하기

 

1. 매개변수의 기본값 ( CPP/F_DefaultValue.cpp )

C++ 역시도 다른 언어와 마찬가지로 매개변수의 기본값을 설정할 수 있습니다.

방법 역시도 동일합니다.

 

아래는 이전에 다루었던 '함수 오버로딩'에 매개변수 기본값을 정해준 코드입니다.

 

#include <iostream>

int func(int x=10){  // 1번 함수
    return x;
}

int func(int x, int y, int z=10){  // 2번 함수
    return x+y+z;
}

int main(void){
    std::cout << func() << std::endl;  // 1번 함수 수행
    std::cout << func(10) << std::endl;  // 1번 함수 수행
    std::cout << func(10, 20) << std::endl;  // 2번 함수 수행
    std::cout << func(10, 20, 30) << std::endl;  // 2번 함수 수행


    return 0;
}

 

실행결과

 

여기서 주의할 부분은 '매개변수 기본값'을 정해줌에 따라서 '함수 오버로딩'의 조건인 '인자에 의한 구분가능성'이 유지되어야 한다는 것입니다.

 

만약 위의 함수에 아래와 같은 함수가 함께있다면 [구분가능성]이 사라져 애러가 발생합니다.

 

int func(int x=10){  // 1번 함수
    return x;
}

int func(int x, int y, int z=10){  // 2번 함수
    return x+y+z;
}

int func(int x, int y=10){  // 오버로딩 규칭 위반
    return x+y;
}

 

 

함수 선언 위치에 따른 기본값 설정 (

코드를 작성할 때, 보통 [함수 선언 -> 메인함수 -> 함수 정의]의 순서로 작성하는 경우가 많습니다.

 

함수의 정의부분이 너무 길게되면 자주 사용되는 메인함수가 최하단으로 밀려나 불편하기 때문이죠.

 

이와 같은경우 매개변수의 기본값 설정은 [함수 선언]부분에 작성합니다.

 

예제 코드를 봐주세요.

 

#include <iostream>

int func(int x=10, int y=10);  // 함수 선언시에 기본값 설정

int main(void){
    std::cout << func() << std::endl;

    return 0;
}

int func(int x, int y){
    return x+y;
}

 

실행결과

 

반응형
반응형

0. 시작하기

 

1. 함수 오버로딩 ( CPP/F_Overloading.cpp )

C++도 다른언어와 마찬가지로 함수와 함수 오버로딩을 제공합니다.

 

오버로딩을 구현하기 위해서의 필수 조건은 동일한 함수명이라 하더라도, 입력하는 인자의 값에 따라 수행할 함수를 구분할 수 있어야합니다.

 

예제를 보면 쉽게 이해가 가능합니다.

 

#include <iostream>

int func(int num){
    return num;
}

int func(int num1, int num2){
    return num1 + num2;
}

int main(void){
    std::cout << func(10) << std::endl;
    std::cout << func(10, 10) << std::endl;

    return 0;
}

실행결과

 

보통의 경우 유사한 기능을 하는 함수에 동일한 이름을 주어 함수 오버로딩을 구현합니다.

반응형
반응형

0. 시작하기

 

1. 확장자

기존 C++은 확장자로 '---.c'를 사용하였습니다. 하지만, C++에서는 '---.cpp'이라는 확장자를 사용합니다.

일반적으로 IDE가 확장자에 따라서 적용하는 컴파일러를 결정하는 경우가 많기 때문에 C++ 사용시 꼭 cpp확장자를 사용해야합니다.

 

헤더파일의 경우 C와 마찬가지로 '---.h' 형태의 확장자를 사용합니다. 다만, 코드에서 헤더파일을 선언할 때, 기존 C에서는 <stdio.h>의 형태로 [파일명+확장자]형태로 선언했다면, C++에서는 <iostream> 이라는 파일명으로 선언을 합니다.

 

 

 

2. 기본 입출력 ( CPP/iostream.cpp )

기본 입출력은 <iostream> 헤더를 사용합니다.

 

샘플 코드는 아래와 같습니다.

 

#include <iostream>

int main(void){
    int temp = 0;
    std::cout << "Console Out" << std::endl;
    std::cout << "Hello" << "World~!" << std::endl;

    int a, b;
    std::cout << "숫자 2개입력 > ";
    std::cin >> a >> b ;
    std::cout << a+b << std::endl;
    return 0;
}

실행결과

 

 

 

3. 배열 입출력시 주의사항 ( CPP/iostream_arry.cpp )

배열을 이용해서 단어나 문자를 입력하는 경우가 많이 있습니다.

 

아래 코드를 보고 한눈에 잘못된 부분을 찾으셨다면 '짝짝짝~'

#include <iostream>

int main(void){
    char str[100] = "배열 입출력 주의점";
    std::cout << "str 값 : " << str << std::endl;

    char temp[100];
    std::cout << "값 입력 > ";
    std::cin >> temp;  // "test test test" 를 입력할 예정
    std::cout << "입력한 값 출력 : " << temp << std::endl;
    
    
    return 0;
}

 

위의 코드에서 문제인 부분은 [입력] 부분입니다.

 

temp에 "test test test"를 입력할 경우 [std::cin]은 스페이스바를 기준으로 입력값을 정하기 때문에 처음 'test'만 temp값에 저장되고 나머지 'test test'는 버퍼에 남게됩니다.

 

실행결과

 

때문에 char 배열에 cin을 이용해서 값을 지정할 경우 공백이 없는 문자열을 이용하던가 혹은 위의 문제를 해결할 코드를 사용해야합니다.

반응형
반응형

WOL 기능에서 사용되는 매직패킷을 전송하는 프로그램

python + pyqt

 

0. 개발환경

  • 맥북 OSX
  • PyCharm (Python 3.7x) + pyqt

1. GUI

 

먼저 대략적인 앱 디자인을 구성합니다.

저는 위와같이 대충 만들었습니다.

정확한 크기는 코딩단계에서 조절해줍니다.

 

[QT 디자이너]를 사용하지 않을 예정이기 때문에 대충 목표디자인을 파워포인트로 잡았습니다.

 

 

 

 

2. Coding

udp통신이고 기존에 파이썬으로 WOL패킷전송을 수행하는 코드를 작성해둔 적이 있었기 때문에 해당코드를 그대로 이용했습니다.

 

2019/05/14 - [이론/네트워크] - [네트워크 패킷] Python으로 WOL패킷 송신하기(1/2)

2019/05/14 - [이론/네트워크] - [네트워크 패킷] Python으로 WOL패킷 송신하기(2/2)

 

 

import sys
import socket, struct, time
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QPixmap, QIcon
from PyQt5.QtCore import Qt

class LayoutWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        wg = WidgetSet()
        self.setCentralWidget(wg)
        self.statusBar().showMessage('Youngq.tistory.com')

        self.setWindowTitle('WOL Program')
        self.setWindowIcon(QIcon('web.png'))
        self.setGeometry(500, 500, 350, 180)
        self.show()

class WidgetSet(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        # Line 1 : Wol 이미지 + msg 영역
        hbox0 = QHBoxLayout()
        hbox0.setAlignment(Qt.AlignVCenter)

        pixmap = QPixmap('wol_mini.png')
        pixmap = pixmap.scaledToWidth(35)
        lbl_img = QLabel()
        lbl_img.setPixmap(pixmap)
        hbox0.addWidget(lbl_img)
        hbox0.addStretch(200)

        msg = QLabel()
        msg.setFixedSize(200,25)
        msg.setText("Ready")
        msg.setAlignment(Qt.AlignRight)
        self.err = msg
        hbox0.addWidget(msg)

        # Line 2 : 대역폭입력 + 콤보박스
        hbox1 = QHBoxLayout()
        hbox1.setAlignment(Qt.AlignVCenter)

        qle1 = QLineEdit(self)
        qle1.setFixedSize(170, 25)
        qle1.setPlaceholderText("목적지 IP주소 또는 망주소 입력")
        self.ip = qle1
        hbox1.addWidget(qle1)
        hbox1.addStretch(20)

        cIP = QLabel()
        cIP.setFixedSize(110, 30)
        currentIP = self.ipaddr()
        cIP.setText("IP : "+currentIP)
        hbox1.addWidget(cIP)

        # Line 3 : 맥주소 입력 + 전송버튼
        hbox2 = QHBoxLayout()
        hbox2.setAlignment(Qt.AlignHorizontal_Mask)
        qle2 = QLineEdit(self)
        qle2.setFixedSize(170, 25)
        qle2.setPlaceholderText("MAC 주소 입력")
        self.mac = qle2
        hbox2.addWidget(qle2)
        hbox2.addStretch(20)

        btn = QPushButton(self)
        btn.setText('Send')
        btn.setFixedSize(70, 30)
        self.sendBtn = btn
        btn.clicked.connect(self.sending) # EventH

        hbox2.addWidget(btn)
        # Stacking : 레이아웃 배치
        vbox = QVBoxLayout()
        vbox.addLayout(hbox0)
        vbox.addLayout(hbox1)
        vbox.addLayout(hbox2)
        self.setLayout(vbox)

    def sending(self):
        try:
            WOL(self.mac.text(), self.ip.text())
            self.err.setText("신호 전송완료")
        except:
            self.err.setText("잘못된 입력이 존재합니다.")

    def ipaddr(self):
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(("8.8.8.8", 80))
        return s.getsockname()[0]

def WOL(macAddr, network):
    sep = macAddr[2]
    macAddr = macAddr.replace(sep, '')

    data = b'FFFFFFFFFFFF' + (macAddr * 16).encode()
    send_data = b''

    for i in range(0, len(data), 2):
        send_data += struct.pack('B', int(data[i: i + 2], 16))

    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
    for i in range(3):
        sock.sendto(send_data, (network, 9))
        time.sleep(0.05)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = LayoutWindow()
    sys.exit(app.exec_())

 

특별한 알고리즘이 필요없는 단순 프로그램이지만, GUI 구성까지 포함해서 100줄 조금 넘어가는 코드로... 역시 파이썬입니다...

 

해당 코드는 다음 링크에서 복사 가능합니다.

 

https://github.com/yekyu94/WOL_python/blob/master/wol.py

 

yekyu94/WOL_python

python WOL GUI Coding. Contribute to yekyu94/WOL_python development by creating an account on GitHub.

github.com

 

Pyqt를 통해서 제작한 코드이기 때문에 pyqt가 설치되어있지 않는 경우 애러가 발생합니다.

 

 

 

 

3. Testing

 

 

실행화면
와이어샤크로 확인

 

위와같이 전송이 완료되면 입력한 IP주소와 MAC주소를 타겟으로한 WOL패킷이 3개 전송되는 것을 볼 수 있습니다.

 

이렇게 만든 프로그램을 pyinstaller같은 라이브러리를 이용하여 쉽게 exe파일등으로 변환이 가능합니다.

 

해당부분은 넘어가겠습니다.

반응형
반응형

0. 정의

  • 그래프(G)는 정점(Vertex)와 간선(Edge)로 이루어집니다.   * G = (V, E)
  • 인접(Adjacent) : 간선(Edge)으로 연결된 두 정접(Vertex)
  • 완전그래프(Complete Graph) : 간선(Edge)의 개수가 최대인 그래프
  • 경로(Path) : 경로의 길이는 경로사이의 간선(Edge)의 수
  • Directed Graph : 방향성이 있는 간선(Edge)을 갖는 그래프
  • Undirected Graph : 방향성이 없는 간선(Edge)을 갖는 그래프

 

그래프는 아래와 같이 표현합니다.

그래프 예제

 

표를 이용한 그래프 표현

 

 

1. Graph Traversal

그래프의 탐색방법은 크게 두가지로 나눠집니다.

 

  • Depth First Search(DFS) : 깊이 우선 탐색
  • Breadth First Search(BFS) : 너비 우선 탐색

두 개념은 그래프와 트리와 같이 Vertex와 Edge로 이루어진 자료구조에서 사용되는 탐색방법입니다.

 

두개의 차이는 아래를 참고하면 쉽게 이해할 수 있습니다.

 

출처 : http://blog.hackerearth.com/wp-content/uploads/2015/05/dfsbfs_animation_final.gif

 

DFS는 순환호출 형태로 많이 구현하며 BFS는 Quere를 이용하여 쉽게 구현할 수 있습니다.

 

void DFS(Vertex* V)
{
	Edge* E = NULL;
    printf("%d ", V->Data);
    
    V->Visited = Visited;
    
    E = V->AdjacencyList;
    
    while(E != NULL){
    	if( E->Target != NULL && E->Target->Visited == NotVisited )
        	DFS( E-> Target );
        
        E = E->Nest;
    }
}
       

전체 알고리즘자료는 깃헙에 올리고있습니다. => https://github.com/yekyu94

 

yekyu94 - Overview

네트워크 프로그래밍 / 텐서플로우 / 보안 / 고양이. yekyu94 has 15 repositories available. Follow their code on GitHub.

github.com

 

 

반응형
반응형

1. DNS Query Message

DNS는 UDP 53번 포트를 사용합니다.

 

DNS Packet

Google.com에 접속했을 때 위와같은 DNS query와 DNS response가 전송되는 것을 볼 수 있습니다.

 

먼저 DNS Query Message를 확인해보면 아래와 같은 것을 볼 수 있습니다.

 

DNS Query Message

위의 붉게 표시한 부분이 DNS Header에 해당하는 부분입니다.

 

DNS message Header는 기본적으로 [트랜잭션 ID, flag, Question count, Answer count, Name server count, Additional Recode count]를 갖고 있습니다.

 

현재 해당 Packet의 DNS Message 는 아래와 같습니다.

 

Transaction ID = 0b 8e

Flags = 0100

Questions = 1

Answer = 0

Authority RRs = 0

Additional RRS = 0

Queries = www.google.com

 

 

2. Query Flag

트랜잭션 ID는 매 Query에 대한 식별 ID이며, Flags 0x0100은 아래와 같이 맵핑됩니다.

QR

Opcode

AA

TC

RD

RA

000

rCode

0

0000

0

0

1

0

000

0000

 

  • OR 0 : 이 메시지가 Query 라는 의미
  • Opcode 0000 : 표준 질의 또는 표준 질의에 대한 응답
  • AA 0 : 네임서버 권한이 인정 X
  • TC 0 : 응답메시지가 512 바이트 이하
  • RD 1 : 응답 형태가 Recursive인지 Iterative인지 정해주는 것으로 여기서 1은 Recursive 응답을 요구한다는 의미
  • RA 0 : 네임서버가 Recursive 질의가 가능한지 나타내며
  • rCode : 0 = No Error (1 = Format Error, 2 = ServFail, 3 = DNS존재 X)

 

3. DNS Response Message

DNS Response Message

 

마찬가지로 해당 패킷의 Response Message는 아래와 같은 내용을 갖고있습니다.

Transaction ID = 0b 8e

Flags = 81 80

Questions = 1

Answer = 6

Authority RRs = 4

Additional RRS = 8

Queries = www.google.com

Answers = 

www.google.com addr 108.177.97.104

www.google.com addr 108.177.97.105

www.google.com addr 108.177.97.147

www.google.com addr 108.177.97.99

www.google.com addr 108.177.97.103

www.google.com addr 108.177.97.106

Authoritative Nameservers =

google.com ns ns4.Google.com

google.com ns ns1.Google.com

google.com ns ns2.Google.com

google.com ns ns3.Google.com

Additional records =

ns1.google.com addr 216.239.32.10

ns2.google.com addr 216.239.34.10

ns3.google.com addr 216.239.36.10

ns4.google.com addr 216.239.38.10

ns1.google.com addr 2001:4860:4802:32::a

ns2.google.com addr 2001:4860:4802:34::a

ns3.google.com addr 2001:4860:4802:36::a

ns4.google.com addr 2001:4860:4802:38::a

 

 

4. Response Flag

이 Packet의 Transaction ID는 Query의 Transaction ID와 동일하며, Flags는 아래와 같이 매핑됩니다.

QR

Opcode

AA

TC

RD

RA

000

rCode

1

0000

0

0

1

1

000

0000

 

  • OR 1 : 이 메시지가 response 라는 의미
  • Opcode 0000 : 표준 질의 또는 표준 질의에 대한 응답
  • AA 0 : 네임서버 권한이 인정 X
  • TC 0 : 응답메시지가 512 바이트 이하
  • RD 1 : 응답 형태가 Recursive인지 Iterative인지 정해주는 것으로 여기서 1은 Recursive 응답을 요구한다는 의미
  • RA 1 : 네임서버가 Recursive 질의가 가능한지 나타내며
  • rCode : 0 = No Error (1 = Format Error, 2 = ServFail, 3 = DNS존재 X) 

이 응답 메시지로부터 client는 www.google.com이라는 사이트의 address와 Google.com의 네임서버를 얻어올 수 있습니다.

반응형
반응형

0. 시작하기

  • 와이어샤크 프로그램을 통한 HTTP Handshake 과정을 다룹니다.
  • 와이어샤크 프로그램에 대한 설명은 제외합니다.
  • 맥(OSX)환경에서 실습 및 작성되었습니다.

1. TCP 통신 과정

 

기본적인 TCP 통신과정을 그려보면 아래와 같습니다.

 

TCP 통신

[연결] - [데이터 통신] - [종료] 의 3단계로 진행되며 이제부터는 HTTP 통신을 예제로하여 해당 과정을 살펴보겠습니다.

 

그전에 앞서 각 과정에서 전송되는 패킷의 구조와 Flag들이 의미하는 것은 아래와 같습니다.

 

출처 : http://www.ktword.co.kr/abbr_view.php?m_temp1=2437

 

  • URG(긴급 데이터 표시) : 상위계층에서 긴급데이터임을 표시한 패킷으로 전송시 우선적으로 전송됨
  • ACK(응답확인) : 패킷을 정상적으로 수신했다는 것을 알리기위한 패킷
  • PSH(즉시 확인 요청) : 해당 표시가된 패킷은 수신한 경우 버퍼가 차는 것을 기다리지 않고 즉시 상위계층으로 전달
  • RST(연결 초기화 요청) : TCP 연결상 문제가 발생시 현재 연결을 끊고 다시 연결할 것을 요청함
  • SYN(회선 연결 요청) : TCP 연결을 요청하는 패킷이며, 현재 자신의 준비상태를 상대방에게 전달
  • FIN(회선 연결 종료) : 모든 데이터 전송이 완료되었음으로 연결을 종료할 것을 알림

 

 

2. HTTP Connect - 연결과정

 

연결과정

HTTP 는 TCP 80번 포트를 이용합니다. (HTTPS 는 443번)

 

테스트할 사이트는 통계청 사이트(http://kostat.go.kr)입니다. 통계청 사이트가 https를 사용하지 않고 있어서 선정했습니다.

 

아래는 통계청 사이트에 접속할때 교환되는 패킷 내용입니다.

 

Packet Chapture 시작부분

내 PC : Youngui-MacBook-Pro

통계청 : 125.60.43.187

 

 

3 Way Handshake

처음 3줄을 보면 위와깉이 SYN, SYN ACK, ACK가 전송되는 것을 볼 수 있습니다.

 

연결과정은 위와같은 3개의 패킷이 핵심입니다.

 

  • SYN (클라이언트 -> 서버) : 클라이언트가 서버에 TCP연결을 요청합니다. 이때 자신의 연결준비 상태를 함께 전송합니다.
  • SYN + ACK (서버 -> 클라이언트) : 서버가 클라이언트의 연결 요청에 응답(ACK)하며, 자신의 연결 준비상태를 알립니다.(SYN)
  • ACK (클라이언트 -> 서버) : 클라이언트가 서버의 연결준비 상태(SYN)를 포함한 패킷을 잘 수신했다고 알립니다.(ACK)

위의 단계를 3 Way-Handshake라고 부릅니다. 정상적으로 완료가 될 경우 이후부터 데이터 전송이 가능합니다.

 

 

 

3. HTTP Data Transmission - 데이터 통신

데이터 통신

여기서는 HTTP 프로토콜을 이용하여 TCP 전송에 대해서 다루기 때문에 전송되는 데이터는 [HTTP 프로토콜]에서 사용하는 메서드와 해당 메서스에 대한 응답등이 해당됩니다.

 

 

데이터 전송

위의 3개의 패킷은 TCP Connect 이후 최초의 데이터 패킷입니다. 각 패킷은 아래와 같은 역할을 합니다.

 

  • HTTP (클라이언트 -> 서버) : 기본 url(kostat.go.kr) /(최상위) 위치에 대한 html파일을 요청합니다. 즉, kostat.go.kr에 접속했을때 최초로 보여줄 html파일에 대한 정보를 서버에 요청하는 과정입니다.
  • HTTP 200 OK (서버 -> 클라이언트) : 200번 응답은 정상적으로 요청한 내용을 전송했다는 의미이며, 위에서 요청한 kostat.go.kr/ 에 접속시 보여줄 html파일을 전송했다는 의미입니다.
  • ACK (클라이언트 -> 서버) : 서버가 클라이언트에게 전송한 html파일을 정상적으로 수신했다는 ACK응답입니다.

 

데이터 통신과정에서 발생하는 대부분의 통신내용은 위와같은 방식을 갖고있습니다.

 

물론 데이터의 크기가 MTU(최대전송단우) 이상일 경우 단편화(Fragmentation)를 통해서 데이터를 조각내서 정송하게 됩니다.

 

아래는 단편화된 패킷의 예제입니다.

단편화 예제

kostat.go.kr/protal/kor_nw/1/1/index.board?bmode=read&Seq=376435 에 대한 GET요청에 대해서 서버는 응답을 여러개로 나눠서 보내는 것을 볼 수 있습니다.

 

이에따라 클라이언트도 여러개의 ACK를 생성해 전송하게 됩니다. (PDU = 프로토콜 데이터 유닛)

 

여기서 전송되는 데이터의 최대크기가 1514 Byte인 것을 볼 수 있습니다. 이를통해 1514 Byte가 일반적인 이더넷 패킷의 MTU가 된다는 것을 알 수 있습니다.

 

 

 

 

4. HTTP Disconnect - 연결종료

 

 

4 Way-HandShake

위와같은 전송이 TCP의 연결종료에 해당합니다.

 

하지만, 항상 위와같은 상태는 아닐 수 있습니다. FIN메시지는 데이터 전송이 완료되었다고 서버가 판단하고 먼저 보낼 수도 있습니다.

 

연결종료

 

  • FIN + ACK (클라이언트 -> 서버) : 이전에 서버로부터 받은 데이터를 잘 수신하였으며(ACK), 모든 데이터 전송이 끝났음으로 연결종료를 요청합니다.(FIN)
  • FIN + ACK (서버 -> 클라이언트) : 서버가 정상적으로 FIN 메시지를 수신하였으며(ACK), 서버는 더 이상 처리할 데이터가 없음으로 마찬가지로 즉시 FIN메시지를 전송합니다.(만약 더 통신할 데이터가 있을 경우 ACK만 전송할 후 데이터 교환 후 FIN 메시지를 보냅니다.)
  • ACK (클라이언트 -> 서버) : 서버의 FIN 메시지를 정상적으로 수신했음을 알립니다.

 

위의 모든 과정이 끝나면 서버와 클라이언트 모두 연결을 위해 할당했던 리소스를 회수합니다.

반응형

+ Recent posts