<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>테오의 학습기록</title>
    <link>https://dev-ws.tistory.com/</link>
    <description>slow but steady wins the race</description>
    <language>ko</language>
    <pubDate>Wed, 6 May 2026 11:28:54 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>teo_99</managingEditor>
    <image>
      <title>테오의 학습기록</title>
      <url>https://tistory1.daumcdn.net/tistory/5638556/attach/b13154e420934d649fce133b4f752e69</url>
      <link>https://dev-ws.tistory.com</link>
    </image>
    <item>
      <title>[BOJ] 1600 - 말이 되고픈 원숭이</title>
      <link>https://dev-ws.tistory.com/129</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2334&quot; data-origin-height=&quot;1034&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBUozL/btsKopULHZT/HrTh7302CryOlqNSAKCVPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBUozL/btsKopULHZT/HrTh7302CryOlqNSAKCVPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBUozL/btsKopULHZT/HrTh7302CryOlqNSAKCVPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBUozL%2FbtsKopULHZT%2FHrTh7302CryOlqNSAKCVPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;853&quot; height=&quot;378&quot; data-origin-width=&quot;2334&quot; data-origin-height=&quot;1034&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2300&quot; data-origin-height=&quot;492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bv8n1f/btsKnWS5xB9/kKaGnodo5WLKh3DfZnU4J1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bv8n1f/btsKnWS5xB9/kKaGnodo5WLKh3DfZnU4J1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bv8n1f/btsKnWS5xB9/kKaGnodo5WLKh3DfZnU4J1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbv8n1f%2FbtsKnWS5xB9%2FkKaGnodo5WLKh3DfZnU4J1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;855&quot; height=&quot;183&quot; data-origin-width=&quot;2300&quot; data-origin-height=&quot;492&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;풀이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 DP 문제라고 생각했으나, 부분 문제의 최적의 해가 전체 솔루션의 최적의 해가 될 수 없겠다는 생각이 들었다. 가령 아래와 같은 경우, K = 1이라고 가정해보자. 좌측 상단의 끝점이 [0,0]이라고 가정한다면 [1,2]까지의 최적해는 1이 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1730181166995&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;0000
0000
1111
1110&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 [0,0]부터 [1,2]를 거치고, [3,3]으로 가는 경우는 4가 된다. 그렇지만 이 단계에서의 [1,2]까지의 최적해는 3이다. K를 [0,0] -&amp;gt; [1,2]에 소모한다면 벽을 넘어갈 수 없기 때문이다. 따라서 탐색 문제라는 생각이 들어서 BFS로 해결할 수 없을지 고민했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 완전탐색과 다른 점은 K를 동적으로 사용할 수 있다는 점이다. 말처럼 이동하느냐 마냐에 따라 해당 격자까지의 거리가 달라진다. 처음엔 방문했는지 여부를 판단하는 visited 배열을 2차원으로 유지했었는데, 이 점 때문에 틀렸었다. 즉, 말처럼 이동을 했는지, 혹은 얼마나 말처럼 이동했는지 여부를 판단할 수 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 visited[x][y][k] 처럼 3차원 visited 배열을 활용한다. 단순히 visited 2차원 배열을 2개(말 이동, 원숭이 이동) 유지하는 것으로 풀기 어려운 이유는, 말처럼 한 번 이동하고 나서 원숭이처럼 이동하는 경우를 생각해보면 쉽다. 이는 원숭이처럼만 이동하는 경우와 구별이 되어야 하기 때문에 점프 횟수에 따라 구분되어야 할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드&lt;/h3&gt;
&lt;pre id=&quot;code_1730182643593&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        int K = Integer.parseInt(reader.readLine());
        StringTokenizer st = new StringTokenizer(reader.readLine());
        int W = Integer.parseInt(st.nextToken());
        int H = Integer.parseInt(st.nextToken());

        int[][] matrix = new int[H][W];
        for (int i = 0; i &amp;lt; H; i++) {
            st = new StringTokenizer(reader.readLine());
            for (int j = 0; j &amp;lt; W; j++) {
                matrix[i][j] = Integer.parseInt(st.nextToken());
            }
        }

        boolean[][][] visited = new boolean[H][W][K + 1];
        int[][] count = new int[H][W];
        for (int i = 0; i &amp;lt; H; i++) {
            for (int j = 0; j &amp;lt; W; j++) {
                count[i][j] = Integer.MAX_VALUE;
            }
        }
        if (matrix[0][0] != 1) {
            count[0][0] = 0;
        }

        Queue&amp;lt;Point&amp;gt; queue = new LinkedList&amp;lt;&amp;gt;();
        queue.add(new Point(0, 0, 0));
        visited[0][0][0] = true;

        int[] dx = {-1, 0, 1, 0};
        int[] dy = {0, -1, 0, 1};
        int[] horseDx = {-2, -2, -1, -1, 1, 1, 2, 2};
        int[] horseDy = {-1, 1, -2, 2, -2, 2, -1, 1};

        while (!queue.isEmpty()) {
            Point point = queue.poll();
            if (point.x == H - 1 &amp;amp;&amp;amp; point.y == W - 1) {
                break;
            }

            for (int i = 0; i &amp;lt; 4; i++) {
                int x = dx[i] + point.x;
                int y = dy[i] + point.y;

                if (x &amp;lt; 0 || y &amp;lt; 0 || x &amp;gt;= H || y &amp;gt;= W) {
                    continue;
                } else if (visited[x][y][point.horseCount] || matrix[x][y] == 1) {
                    continue;
                }
                visited[x][y][point.horseCount] = true;
                count[x][y] = count[point.x][point.y] + 1;
                queue.add(new Point(x, y, point.horseCount));
            }

            for (int i = 0; i &amp;lt; 8; i++) {
                int x = horseDx[i] + point.x;
                int y = horseDy[i] + point.y;

                if (x &amp;lt; 0 || y &amp;lt; 0 || x &amp;gt;= H || y &amp;gt;= W) {
                    continue;
                } else if (point.horseCount &amp;gt;= K) {
                    break; // K번 넘어가면 안됨
                } else if (visited[x][y][point.horseCount + 1] || matrix[x][y] == 1) {
                    continue;
                }

                count[x][y] = count[point.x][point.y] + 1;
                visited[x][y][point.horseCount + 1] = true;
                queue.add(new Point(x, y, point.horseCount + 1));
            }
        }

        System.out.println(count[H - 1][W - 1] != Integer.MAX_VALUE ?
                count[H - 1][W - 1] : -1);
    }

    static class Point {
        int x;
        int y;
        int horseCount;

        public Point(int x, int y, int horseCount) {
            this.x = x;
            this.y = y;
            this.horseCount = horseCount;
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Problem Solving</category>
      <author>teo_99</author>
      <guid isPermaLink="true">https://dev-ws.tistory.com/129</guid>
      <comments>https://dev-ws.tistory.com/129#entry129comment</comments>
      <pubDate>Tue, 29 Oct 2024 15:21:13 +0900</pubDate>
    </item>
    <item>
      <title>[Operating System] Process</title>
      <link>https://dev-ws.tistory.com/128</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 아티클에서는 Process에 관해 정리한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 새롭게 알게 되는 내용이 있다면 지속적으로 업데이트합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Program vs. Process&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램은 디스크에 저장되어 있는 실행 가능한 파일을 의미한다. 이는 커널에 의해 메모리로 로드되어 실행될 수 있는데, 이 경우 프로세스라고 한다. 따라서 프로세스를 프로그램의 '인스턴스'라고 표현하기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스는 실행과 스케줄링의 가장 기본적인 단위이다. 다만 스레드라는 개념이 도입되기 시작하면서, 스레드에 대한 스케쥴링도 가능해졌다. 하지만 스레드는 독립적인 실행 메모리를 가지지 않기 때문에, 실행의 기본 단위라고는 이야기하기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Process ID&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스는 Process ID(PID)로 식별되는데, 이를 통해 프로세스를 식별할 수 있다. 유닉스 계열 운영체제에서는 fork() 시스템 콜을 통해 PID가 반환되며, 이렇게 반환된 PID를 통해 자식 프로세스를 기다리거나 종료시키는 등의 작업을 수행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 PID는 0 혹은 양의 정수 범위에서 순차적으로 증가하면서 지정되는 경우가 대부분이다. 또한, 미리 정의된 PID를 가지고 있는 프로세스도 있는데, UNIX 계열에서 0번의 경우 메모리 페이징을 담당하는 swapper process, 1번의 경우 시스템을 시작하고 종료하는 데 사용되는 init process가 상주한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Process Address Space&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;992&quot; data-origin-height=&quot;726&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clwCVJ/btsJISqxQsj/k6yuSsoK1PSsLS7mQODMCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clwCVJ/btsJISqxQsj/k6yuSsoK1PSsLS7mQODMCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clwCVJ/btsJISqxQsj/k6yuSsoK1PSsLS7mQODMCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclwCVJ%2FbtsJISqxQsj%2Fk6yuSsoK1PSsLS7mQODMCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;506&quot; height=&quot;370&quot; data-origin-width=&quot;992&quot; data-origin-height=&quot;726&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스의 주소 공간은 위와 같은 구조를 띤다. 메모리 주소는 0번지부터 시작하는데, 이렇게 모든 프로세스가 0번지부터 시작하는 메모리 주소를 가질 수 있는 이유는 가상 메모리와 관련이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 세부 영역을 조금 더 자세히 설명하면 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;code (text segment)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;말 그대로 code, 즉 기계어 명령어들이 저장되는 위치&lt;/li&gt;
&lt;li&gt;read-only 영역&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;static data (data segment)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로그램이 실행되는 동안 항상 메모리를 차지하고 있는 변수들이 저장되는 곳&lt;/li&gt;
&lt;li&gt;초기화된 데이터 구역과 초기화가 되지 않은 데이터 구역(BSS) 으로 나뉨 (BSS 구역 데이터들은 디스크에 따로 저장하지 않은 상태로 프로세스를 시작할 수 있음 = 메모리 최적화)&lt;/li&gt;
&lt;li&gt;프로그램의 라이프사이클과 동일하게 맞춰 생성 및 소멸됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;heap (dynamically allocated memory)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;사용자에 의해 동적으로 할당되는 메모리가 위치하는 곳 (= 런타임에 크기가 결정)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;하위 주소에서 상위 주소로 메모리 공간을 사용&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stack (dynmically allocated memory)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지역변수, 매개변수 등이 저장되는 영역&lt;/li&gt;
&lt;li&gt;stack 영역의 값은 함수 호출 시 할당되며 호출 완료 시 소멸&lt;/li&gt;
&lt;li&gt;상위 주소에서 하위 주소로 메모리 공간을 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stack이나 heap은 동적으로 메모리를 할당하기 때문에, 미리 지정한 크기를 벗어나는 경우가 생길 수 있다. 그리고 이러한 경우를 stack overflow 혹은 heap overflow라고 부른다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유의해야 할 점은, 스레드가 생성되는 경우, 스레드는 프로세스의 stack을 공유하지 않고 새로운 stack을 만들어내는데, 이는 race condition 등을 해결하기 위함이다. 논리적으로 생각해보아도 스레드마다 지역변수나 파라미터를 공유한다면 신경써야 할 일이 많아질 것이다. 그렇지만 text 영역, data 영역, heap은 공유한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Stack Pointer&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stack 영역에서는 SP(Stack Pointer)를 가지는데, 이는 스택 영역에서의 마지막 요소 혹은 다음으로 사용될 메모리 주소를 가리키는 레지스터이다. stack 영역을 정의하는 방식은 프로세서마다 다를 수 있고, 정해진 표준도 없으나 대개 따르는 디자인은 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;972&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDtM7m/btsJJJGFAbq/7UzJobnmKRaTELEJRNj9z1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDtM7m/btsJJJGFAbq/7UzJobnmKRaTELEJRNj9z1/img.png&quot; data-alt=&quot;https://www.techtarget.com/whatis/definition/stack-pointer&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDtM7m/btsJJJGFAbq/7UzJobnmKRaTELEJRNj9z1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDtM7m%2FbtsJJJGFAbq%2F7UzJobnmKRaTELEJRNj9z1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;570&quot; height=&quot;462&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;972&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.techtarget.com/whatis/definition/stack-pointer&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수가 호출 될 때 스택 프레임이 생성되어 쌓이고, 호출이 완료되면 제거된다. 구체적인 스택 프레임의 구성요소는 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Local variables -&amp;gt; 함수 내에서 사용되는 지역변수&lt;/li&gt;
&lt;li&gt;Base Pointer -&amp;gt; 스택 프레임의 기준점을 정의하는 포인터&lt;/li&gt;
&lt;li&gt;Return address -&amp;gt; 호출한 위치로 돌아가기 위한 주소&lt;/li&gt;
&lt;li&gt;Parameters -&amp;gt; 함수에 전달된 인자들&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Process State&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1228&quot; data-origin-height=&quot;568&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xDX3u/btsJJQyTImw/5RR34B6mQYa76Dk5EGgH2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xDX3u/btsJJQyTImw/5RR34B6mQYa76Dk5EGgH2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xDX3u/btsJJQyTImw/5RR34B6mQYa76Dk5EGgH2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxDX3u%2FbtsJJQyTImw%2F5RR34B6mQYa76Dk5EGgH2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;730&quot; height=&quot;338&quot; data-origin-width=&quot;1228&quot; data-origin-height=&quot;568&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스는 여러 상태를 가진다. 각각의 상태에 대한 정의는 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;new -&amp;gt; 프로세스가 생성되는 단계&lt;/li&gt;
&lt;li&gt;ready -&amp;gt; 실행이 가능한 상태
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ready 상태의 프로세스를 running 상태로 전환하는 작업을 dispatch라고 하고, 이 작업은 스케쥴러가 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;running -&amp;gt; 프로세스가 실행되고 있는 상태
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 상태에서 인터럽트가 발생하면 ready 상태로 전환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;wating -&amp;gt; I/O 혹은 이벤트가 발생했을 때 대기하는 상태
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;I/O 작업 혹은 이벤트 처리 작업이 완료되면 ready 상태로 전환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;terminated -&amp;gt; 프로세스가 종료된 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;PCB(Process Control Block)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스들을 관리하기 위해서는 메타정보가 필요한데, 운영체제는 PCB라는 자료구조를 통해 이를 관리한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-23 오후 10.22.26.png&quot; data-origin-width=&quot;374&quot; data-origin-height=&quot;568&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eqJ0mp/btsJKb3JD4S/vOOXlIhBDz6Rvq4sidkgh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eqJ0mp/btsJKb3JD4S/vOOXlIhBDz6Rvq4sidkgh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eqJ0mp/btsJKb3JD4S/vOOXlIhBDz6Rvq4sidkgh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeqJ0mp%2FbtsJKb3JD4S%2FvOOXlIhBDz6Rvq4sidkgh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;204&quot; height=&quot;310&quot; data-filename=&quot;스크린샷 2024-09-23 오후 10.22.26.png&quot; data-origin-width=&quot;374&quot; data-origin-height=&quot;568&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 상태, PID, PC(Program Counter), 레지스터, 메모리 관리 정보, 입출력 상태 정보, 스케줄링 정보 등을 가진다. 프로세스는 running과 ready 상태 사이에서 전이를 반복하는데, 이 때 PCB를 통해 진행 상황에 대한 정보를 복구할 수 있다. 즉 인터럽트가 발생하면 PCB에 메타데이터를 저장하고, dispatch가 되면 PCB로부터 관련 정보를 복구한다. 즉 한마디로 말하면, Context Switching 과정에서 필요한 정보다. *참고로, 리눅스에서는 task_struct 구조체가 PCB(=TCB)에 해당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1006&quot; data-origin-height=&quot;750&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yazq9/btsJIph53B4/fPP8iEZrcdqKT7JZrJMhXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yazq9/btsJIph53B4/fPP8iEZrcdqKT7JZrJMhXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yazq9/btsJIph53B4/fPP8iEZrcdqKT7JZrJMhXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyazq9%2FbtsJIph53B4%2FfPP8iEZrcdqKT7JZrJMhXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;484&quot; height=&quot;361&quot; data-origin-width=&quot;1006&quot; data-origin-height=&quot;750&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Context Swtich&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행중인 프로세스는 지속적으로 전환된다. 이 과정에서 앞서 이야기한 PCB에 상태 Save, Reload를 한다. 다만 이러한 과정을 거치게 되면 레지스터, 메모리 맵, 캐시 등과 같은 자료구조들을 변경시켜야 하기 때문에 Administrative Overhead가 지속적으로 발생한다(운영체제 구조에 따라 Overhead 양은 다르다).&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼에도 Context Switch를 수행해야만 하는 이유는, 빠른 응답을 기대할 수 있기 때문이다. CPU는 한 번에 하나의 작업만 처리할 수 있기 때문에, Context Switch를 통한 '여러 프로세스를 실행하는 것처럼 보이게 해주는' 작업이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통상 Context Switch는 초당 100번에서 1000번 발생한다고 한다. (운영체제에 따라 다르겠지만)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Schedulers&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스케쥴러의 종류는 크게 세 가지가 있는데, Long-term, Medium-term, 그리고 Short-term이다. 각각의 책임은 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Long-term scheduler(job scheduler) -&amp;gt; ready queue로 들어올 프로세스를 선별함&lt;/li&gt;
&lt;li&gt;Medium-term scheduler(swapper) -&amp;gt; 메모리가 부족한 경우, ready queue에서 어떠한 프로세스를 제거할지 결정함 (swap in &amp;amp; swap out)&lt;/li&gt;
&lt;li&gt;Short-term scheduler(CPU scheduler) -&amp;gt;&amp;nbsp; &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;ready queue에서 어떠한 프로세스를 실행시킬지 선별함(dispatch)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 가상 메모리의 등장으로 Long-term, Medium-term scheduler는 time sharing system에서 그 중요도가 하락하게 되었는데, 실제 메모리량에 관계 없이 프로세스를 실행시킬 수 있기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Queue and PCB&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제는 PCB를 통해 프로세스의 논리적 식별을 수행한다. 즉 프로세스가 어떠한 상태에 접어들었다는 것을 PCB를 Queue에 집어넣음으로서 표현한다. 프로세스 상태(Ready, Waiting, New)에 따라 각각의 큐가 존재한다. 단, Running 상태의 경우는 하나의 프로세스만 존재하므로 별도의 큐가 존재하지는 않는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1220&quot; data-origin-height=&quot;700&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uAaht/btsJIrUud1U/K2gi7SyHgwh01EReFmiBO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uAaht/btsJIrUud1U/K2gi7SyHgwh01EReFmiBO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uAaht/btsJIrUud1U/K2gi7SyHgwh01EReFmiBO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuAaht%2FbtsJIrUud1U%2FK2gi7SyHgwh01EReFmiBO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;604&quot; height=&quot;347&quot; data-origin-width=&quot;1220&quot; data-origin-height=&quot;700&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Process Creation in Linux/Unix&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;fork()&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1258&quot; data-origin-height=&quot;706&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzx6kv/btsJI2GI2IN/mOKrb9th4qB069Byu8SrSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzx6kv/btsJI2GI2IN/mOKrb9th4qB069Byu8SrSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzx6kv/btsJI2GI2IN/mOKrb9th4qB069Byu8SrSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbzx6kv%2FbtsJI2GI2IN%2FmOKrb9th4qB069Byu8SrSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;609&quot; height=&quot;342&quot; data-origin-width=&quot;1258&quot; data-origin-height=&quot;706&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유닉스 및 리눅스 운영체제에서는 fork() 라는 시스템 콜을 통해 프로세스를 생성한다. 프로세스를 생성한다는 의미는 PCB를 생성한다는 의미이다. 또한, 프로세스 주소 공간 역시 생성한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 특이한 점은 fork()를 call한 Parent Process와 완전히 동일한 주소 공간을 생성한다는 점이다. 또한, PCB에서도 열려 있는 파일들과 같이 리소스들에 대한 정보를 그대로 복사한다(부모 프로세스와 동일한 환경에서 시작하기 위함).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 실제로는 진짜 복사를 하는게 아니라, CoW(Copy-on-Write)라는 지연 복사 방식을 사용한다. 우선은 동일한 페이지 테이블을 참조하고 있다가 쓰기가 발생하면 복사하는 방식이다. 이를 통해 메모리 최적화를 이뤄낼 수 있다. 심지어는 읽기만 발생하는 경우라면 추가적인 프로세스 주소 공간을 사용하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fork()는 부모 프로세스와 협력할 때나, 부모 프로세스의 데이터를 통해 작업을 처리할 때 유용하다. 예시로는 웹서버가 있는데, 부모 프로세스는 TCP 커넥션이 생성되면 이를 fork()를 통해 자식 프로세스에 맡길 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;exec()&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램을 실행시켜 프로세스로 만들고 싶다면 exec() 시스템 콜을 활용한다. 다만 이는 프로세스를 만드는(PCB를 만드는) 작업은 아니다. exec()을 실행한 프로세스는 타겟 프로세스로 완전히 대체되는데, 그렇기 때문에 fork() -&amp;gt; exec() 구조를 많이 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b19Wa6/btsJHSSx7cU/if7WPqQHhX2dXx02atFPf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b19Wa6/btsJHSSx7cU/if7WPqQHhX2dXx02atFPf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b19Wa6/btsJHSSx7cU/if7WPqQHhX2dXx02atFPf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb19Wa6%2FbtsJHSSx7cU%2Fif7WPqQHhX2dXx02atFPf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;684&quot; height=&quot;182&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;zombie &amp;amp; orphan process&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 부모 프로세스보다 자식 프로세스가 먼저 종료되었고, 부모 프로세스가 wait 혹은 waitpid (non-blocking wait)을 수행하지 않으면 좀비 프로세스가 된다. 좀비 프로세스는 별다른 메모리나 CPU를 점유하지는 않지만 엔트리로서 프로세스 테이블에 남아 시스템 리소스를 낭비한다. 즉, PCB가 그대로 유지된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고아 프로세스는 부모 프로세스가 자식 프로세스보다 먼저 종료된 경우를 일컫는다. 자식 프로세스가 고아 상태가 되면, 커널은 init 프로세스(PID 1)에 고아 프로세스를 넘겨준다. init 프로세스는 고아 프로세스가 종료될 때까지 기다리고, 상태를 수집한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;IPC(Inter-Process Communication)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 프로세스가  통신해야 할 일이 있다면 IPC 기법을 사용한다. 대표적인 커뮤니케이션 방식은 크게 1. 메세지 전달 방식(message passing), 2. 공유 메모리(shared memory)가 있다. 이외에도 Half-Duplex 방식인 pipe, 네트워크를 타는 socket이 IPC 기법으로 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 기법들을 사용하는 이유는 운영체제의 책임 중 하나인 protection과 관련이 있는데, 한 프로세스가 다른 프로세스의 메모리 영역을 마음대로 침범해서는 안되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1034&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bclihw/btsJJWeEzla/TAVB71c1Dk3kkVDtaIemk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bclihw/btsJJWeEzla/TAVB71c1Dk3kkVDtaIemk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bclihw/btsJJWeEzla/TAVB71c1Dk3kkVDtaIemk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbclihw%2FbtsJJWeEzla%2FTAVB71c1Dk3kkVDtaIemk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;523&quot; height=&quot;311&quot; data-origin-width=&quot;1034&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Shared memory&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유 메모리 방식은 프로세스 간 메모리를 공유하는 방식이다. 이 방식은 단순한 메모리 참조이기 때문에 접근 속도가 빠르다. 또한, 메모리 사용을 효율적으로 할 수 있다. 그렇지만 공유를 한다는 특성 상, 쓰기가 발생하는 경우 동기화 장치(세마포어, 뮤텍스)가 필요하다. Shared Memory를 사용하는 대표적인 예시로는 Bounded Buffer Problem(Producer-Consumer Problem)이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BSD 계열의 운영체제에서는 메모리맵(mmap)이 비슷한 기능을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Message queue&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 큐 방식은 메세지를 FIFO로 교환할 수 있게 해주는 방식으로, 동기화는 필요하지 않지만 공유 메모리 방식보다 느리다는 단점이 있다. 또한 이러한 메세지 큐는 커널에 의해 관리되는 큐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4_%EC%8B%9D%EB%B3%84%EC%9E%90&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4_%EC%8B%9D%EB%B3%84%EC%9E%90&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4_%EC%A0%9C%EC%96%B4_%EB%B8%94%EB%A1%9D&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4_%EC%A0%9C%EC%96%B4_%EB%B8%94%EB%A1%9D&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/heap-overflow-stack-overflow/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.geeksforgeeks.org/heap-overflow-stack-overflow/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.techtarget.com/whatis/definition/stack-pointer&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.techtarget.com/whatis/definition/stack-pointer&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/difference-between-short-term-medium-term-and-long-term-scheduler/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.geeksforgeeks.org/difference-between-short-term-medium-term-and-long-term-scheduler/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/process-scheduler-pcbs-and-queueing/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.geeksforgeeks.org/process-scheduler-pcbs-and-queueing/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Abraham Silberschatz, Peter B. Galvin, and Greg Gagne, Operating System Principles, 10th Edition, John Wiley &amp;amp; Sons, Inc. 2019.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>운영체제</category>
      <author>teo_99</author>
      <guid isPermaLink="true">https://dev-ws.tistory.com/128</guid>
      <comments>https://dev-ws.tistory.com/128#entry128comment</comments>
      <pubDate>Tue, 24 Sep 2024 00:19:47 +0900</pubDate>
    </item>
    <item>
      <title>[Data Structure] Trie</title>
      <link>https://dev-ws.tistory.com/127</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 아티클에서는 자료구조 중 하나인 트라이(Trie)에 대해 알아본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;트라이의 핵심&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 용어에 대해 이해하는 것이 도움이 될 것 같다. Trie는 Re&lt;b&gt;trie&lt;/b&gt;val Tree에서 추출한 단어라고 한다. 즉, 트라이는 트리이면서 검색(Retrieval)을 위해 사용될 수 있다. 그렇다면 트라이가 어떻게 검색에 활용될 수 있다는 것일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통은 문자열 검색에서 트라이를 많이 활용하게 된다. 필자를 포함하여 대부분의 사람들은 검색창 자동완성 기능을 사용해 본 경험이 있을 것이다. 'coffee'를 검색하려고 할 때, 'cof' 까지만 치더라도 coffee라는 단어가 자동완성이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 수많은 단어들 사이에서 cof가 coffee와 연관되어 있다는 사실은 어떻게 알아낼까? 사전에 기록된 모든 단어에 대해 브루트 포스로 순회하면서 비교할 순 없을 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 시점에 사용해볼 수 있는게 트라이라는 자료구조이다. 트라이는 트리 구조를 가지고 있는데, 노드가 하나의 Prefix를 가지고 있고, 해당 Prefix들을 접목시켜나가면서 단어를 완성하는 방식이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;250&quot; data-origin-height=&quot;235&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tVS6x/btsJElyUUjJ/dgRmYeZEky0yXxHAGcns80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tVS6x/btsJElyUUjJ/dgRmYeZEky0yXxHAGcns80/img.png&quot; data-alt=&quot;example of Trie&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tVS6x/btsJElyUUjJ/dgRmYeZEky0yXxHAGcns80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtVS6x%2FbtsJElyUUjJ%2FdgRmYeZEky0yXxHAGcns80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;235&quot; data-origin-width=&quot;250&quot; data-origin-height=&quot;235&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;example of Trie&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트 노드는 빈 문자열에 대응되며, 'ten'이라는 문자를 찾고 싶다고 가정하자. 그렇다면 t -&amp;gt; e -&amp;gt; n으로 해당 문자를 찾아낼 수 있다. 트라이의 핵심 특징 중 하나는 Prefix를 공유한다는 것인데, t -&amp;gt; e -&amp;gt; n과 t -&amp;gt; e -&amp;gt; a는 t -&amp;gt; e 라는 경로를 공유한다. 이에 따라 메모리 최적화가 가능하다. 필요한 모든 단어를 개별적으로 메모리에 저장할 필요가 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 트리 내에서 단어가 완성되었다는 것을 표기하기 위해 별도의 비트 플래그나 Data 필드를 추가해 해당 노드까지의 데이터를 저장하기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트라이는 Prefix Tree로 불리기도 하는데, 각 노드에서 문자열 등의 Prefix를 저장하는 과정을 거치기 때문이다. 이외에도 트라이의 변형된 종류로는 Radix Tree가 있고, 이는 한 노드에서 하나의 Prefix만 관리하는게 아니라, 메모리를 최적화하기 위해 여러 Prefix를 압축해 갖고 있는 자료구조를 의미한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로, 트라이는 문자열 검색 뿐만 아니라 비트열 검색이나 패턴 매칭에도 활용될 수 있다고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;시간복잡도&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트라이는 메모리를 많이 사용하는 대신, 시간적 효율성이 상당히 높다. 찾고자 하는 단어를 그대로 한글자씩 Prefix에 대입하여 트리를 탐색하면 되기 때문에, &lt;b&gt;조회의 경우 O(L)&lt;/b&gt;이라는 복잡도를 가진다. 여기서 L은 문자의 길이이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;삽입의 경우&lt;/b&gt;에는, 모든 문자가 Prefix로 등록되어있지 않은 상태가 최악의 시나리오인데, 이 경우도 탐색과 동일하게&amp;nbsp;&lt;b&gt;O(L)&lt;/b&gt; 의 복잡도를 가진다. 새로운 문자가 삽입되었다고 해서 트리를 재정렬할 필요가 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;장단점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공통 접두사(Prefix)를 단일 노드로 사용하기 때문에, 메모리 크기를 최적화할 수 있다.&lt;/li&gt;
&lt;li&gt;검색, 추가가 쉽다. 두 경우 모두 O(L)의 시간 복잡도를 가진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 노드가 다른 노드를 가리키기 위한 포인터를 추가로 가지기 때문에, 메모리 부담이 있다. (한국어의 경우를 생각해보자)&lt;/li&gt;
&lt;li&gt;정확히 일치하는 문자열을 찾는 과정은 트라이보다 이진 검색 트리(BST)나 해시가 효율적이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;번외 - 한국어의 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국어의 경우는 어떻게 될까? 알파벳의 경우 ASCII로 표현이 가능하지만, 한국어의 경우 2바이트 이상의 메모리를 사용하여야 한다. 또한, 자음과 모음의 조합이 많기 때문에, 노드 수가 급격하게 증가할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 경우 글 초기에 언급하였던 Radix Tree를 사용하여 노드를 압축하거나, 하위 노드들을 관리하기 위한 전략으로 고정 크기 배열이 아닌 연결 리스트를 사용할 수 있고, 사용 빈도에 따라 가중치를 부여하여 캐싱과 비슷한 기능을 부여함으로서 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/trie-meaning-in-dsa/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.geeksforgeeks.org/trie-meaning-in-dsa/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%ED%8A%B8%EB%9D%BC%EC%9D%B4_(%EC%BB%B4%ED%93%A8%ED%8C%85)#cite_note-KnuthVol3-3&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.wikipedia.org/wiki/%ED%8A%B8%EB%9D%BC%EC%9D%B4_(%EC%BB%B4%ED%93%A8%ED%8C%85)#cite_note-KnuthVol3-3&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>자료구조</category>
      <author>teo_99</author>
      <guid isPermaLink="true">https://dev-ws.tistory.com/127</guid>
      <comments>https://dev-ws.tistory.com/127#entry127comment</comments>
      <pubDate>Sun, 15 Sep 2024 15:59:58 +0900</pubDate>
    </item>
    <item>
      <title>LG CNS Software Engineer 인턴 및 최종 전환 회고</title>
      <link>https://dev-ws.tistory.com/126</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;지난 8월을 마지막으로 LG CNS Software Engineer 인턴을 마치게 되었다. 6월 중순부터 시작했으니 약 두 달을 다닌 셈인데, 시간이 정말 빠르게 지나간 것 같다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;564&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBCrOT/btsJsgEnJsP/X6dnSS6jE9ecLZoSvOjef1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBCrOT/btsJsgEnJsP/X6dnSS6jE9ecLZoSvOjef1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBCrOT/btsJsgEnJsP/X6dnSS6jE9ecLZoSvOjef1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBCrOT%2FbtsJsgEnJsP%2FX6dnSS6jE9ecLZoSvOjef1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;814&quot; height=&quot;280&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;564&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;br&gt;채용연계형 인턴이었는데, 운이 좋게도 정규직으로 전환되게 되었고 졸업 후 입사하게 될 예정이다! 졸업하기 전에 취업할줄은 꿈에도 몰랐는데, 정말 운이 좋았던 것 같다고 생각한다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;배운 점&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;입사하기 전까진 사실 실무 경험이 없어서, ‘이러이러할 것이다‘ 라고 예측하기 일쑤였다. 즉 판단 기준이 이론에 맞춰져 있고, 실제 경험에 맞춰져 있지 않는 경우가 대부분이었다. &lt;br&gt;&lt;br&gt;입사하고 나서 운이 좋게도 과제가 아닌 실제 현업 프로젝트에 투입되었고, 모 사를 대상으로 하는 프로토타이핑을 수행했다. 기술은 대부분 처음 사용하는 것 투성이었는데, 프로젝트에서는 React, Lambda 등을 활용하여 서버리스 애플리케이션을 구축했다. 이전까지는 스프링 부트만으로 개발을 했었는데, 새로운 기술을 사용해 오히려 좋았다(기존에 풀스택이 되고 싶다는 니즈도 있었다)&lt;br&gt;&lt;br&gt;또한 Bedrock을 활용하여 프롬프트 엔지니어링을 수행할 일도 있었는데, 그 과정에서 하이퍼파라미터 튜닝이나 프롬프트 엔지니어링, 그리고 파인튜닝과 RAG 등을 학습했고 사내 위키에 정리해두었다. 개인 블로그로 반출이 안되는 건 아쉽긴 하지만, 사내 위키에서 누군가 유용하게 봐주면 좋을 것 같다!&lt;br&gt;&lt;br&gt;그 과정에서 현재의 기술 트렌드를 느낄 수 있었다. 특히 생성형 AI를 기반으로 많은 경험을 했는데, 상당히 즐거운 경험이었다. &lt;br&gt;&lt;br&gt;이 과정에서 현업 개발자분들과도 협업을 이어나갔는데, 특히 소프트 스킬 측면에서 상당히 많은 성장을 이루었다고 생각한다. 사실 나는 개발에 있어 주관이 어느정도 있는 편이라, 토론에 있어 그렇게 정제되지 못한다는 느낌을 많이 받았었는데, 인턴을 수행하면서 협업을 능통하게 하는 방법을 터득하게 되었다. 소프트 스킬은 아무래도, 정말 중요한 포인트 같다고 생각한다.&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;좋은 사람들과 조직&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;많은 개발자분들을 만나면서 확실히 느끼는 게 많았다. 다들 일에 열정적이었고, 내가 생각하는 조직의 이상향과 어느정도 부합하는 듯 했다. &lt;br&gt;&lt;br&gt;인턴 과정에서 도움도 많이 받았고, 격려도 많이 받았기 때문에 잘 마무리할 수 있었던 것이 아닌가 싶다. 단순히 혼자서 처리하는 업무였다면, 성장하지도 못했을 것이고 포기하고 싶은 순간도 있었을 것이다. 조직 자체(런치센터)의 문화도 좋았다. 다들 상당히 유하시고 협업하기에 좋은 조직이라는 생각이 많이 들었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;다만 자체 서비스를 구현하는게 아니라 SI 기업이기에, 서비스 자체에 대한 애정심을 키우기 어렵다는 부분은 아쉬운 점으로 느껴졌다. 그렇지만 이는 단점이 아닌 하나의 특성으로, 서비스 기업과는 다르게 도메인을 다양하게 경험해볼 수 있다는 것은 장점일 것이다. 무엇을 선택하고 무엇을 택하느냐의 차이인 것 같다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;왕복 4시간에 달하는 통근 거리였는데, 정말 고생도 많이 했지만 즐거웠던 경험이었다!  첫 인턴 경험을 좋은 조직에서 했던 것 같아 좋다. 앞으로는 시간이 조금 남으니 자기계발에 조금 더 힘을 써야겠다. 책을 좀 많이 읽어보는 건 어떨까..&lt;/p&gt;</description>
      <category>회고</category>
      <author>teo_99</author>
      <guid isPermaLink="true">https://dev-ws.tistory.com/126</guid>
      <comments>https://dev-ws.tistory.com/126#entry126comment</comments>
      <pubDate>Thu, 5 Sep 2024 11:59:32 +0900</pubDate>
    </item>
    <item>
      <title>2024년 상반기 돌아보기</title>
      <link>https://dev-ws.tistory.com/125</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이제 6월도 마무리되고 있는데, 이쯤에서 한 번 회고를 작성하면 좋을 것 같다는 생각이 들었다. 일기도 쓰지 않기 때문에 돌아볼 일이 많이 없는데, 회고를 통해 개괄적이라도 돌아보는 계기가 되었으면 한다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인턴 준비 (1, 2월)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우아한테크코스 수료 이후, 12월은 대부분 쉬었다. 아무래도 우테코 생활동안 제대로 쉬어 본 적이 없기도 하고.. 리프레시 기간도 필요하다는 생각이 들었기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1월부터는 누구나 그렇듯 새해 다짐과 함께 인턴을 빡세게 준비해야겠다는 생각을 가지게 됐다! 1, 2월은 인턴 공고가 많이 뜨는 시즌이라 빠르게 준비를 시작하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 코딩테스트가 제일 관건이었는데, 알고리즘 문제 자체를 좋아하지 않기도 하고, 초반 진입장벽이 너무 높다는 생각이 많이 들었어서 포기하는 경우가 많았기 때문이다. 그래도 이번엔 피할 수 없으니.. 재미가 없어도 쭉 준비하게 되었고 골드2 까지는 승급했다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;371&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bK6UgQ/btsIghsJDdj/aKYvpV7KIadoscaJKgzJ51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bK6UgQ/btsIghsJDdj/aKYvpV7KIadoscaJKgzJ51/img.png&quot; data-alt=&quot;라이벌은 5기 민트..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bK6UgQ/btsIghsJDdj/aKYvpV7KIadoscaJKgzJ51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbK6UgQ%2FbtsIghsJDdj%2FaKYvpV7KIadoscaJKgzJ51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;348&quot; height=&quot;319&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;371&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;라이벌은 5기 민트..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 학교와 연계되는 여러 회사에 지원했다. 국내 기업 뿐만 아니라, 실리콘밸리에 있는 한국 기업들도 학교 연계로 지원하게 됐다. 이에 따라 영어 공부도 시작하게 되었는데, '스픽'이라는 앱을 통해 그나마 면접은 볼 수 있을 수준까지는 도달하게 됐다. 다만 면접까지 가지는 못했다..   (서류 탈)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;국내 기업에도 인턴을 지원했었는데, 에이비일팔공이라는 회사다. 우테코 채용설명회 때 인상깊었던 회사이기도 했고, 기술적으로는 매우 뛰어나다는 것도 알고 있어서 지원했다. 전형은 서류 - 코테 - 과제 - 면접 순으로 진행되었는데, 안타깝게 면접에서 떨어지게 되었다. Computer Science 지식이 부족하다고 느끼는 계기였기도 했어서.. 그 뒤로 열심히 기반지식을 다지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;웹 개발 동아리 운영 (1, 2월)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인턴 준비와 함께 웹 개발 동아리 운영도 같이 진행했는데, LINKHU라는 경희대학교 웹 개발 멘토링 동아리다. 우아한테크코스 수료 이후, 경희대학교에도 학습했던 것들을 공유하고자 하는 마음이 있었기 때문이다. 개인적인 생각이지만 학교에서 배우는 내용만으로는 좋은 백엔드 개발자가 되기에는 어렵지 않나 싶다. 이는 웹 개발자들이 동아리에 집착하는 이유이기도 하고  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 직접 만들어서 1, 2월동안 운영을 했었는데, 생각보다는 어려움이 많았다. 강의나 미션, 코드 리뷰 등을 제공하면서 스스로 학습할 수 있게 멘티분들에게 도움을 줬었는데 아무래도 운영이 처음이라는 점, 멘티들의 자율적인 학습을 지향한다는 점 때문에 성공적이진 못했다. 또한 안타깝게도 운영 난이도, 개개인의 사정으로 인하여 LINKHU는 2기 없이 파멸의 길(?)로 들어섰지만.. 이 또한 경험이지 않을까 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;583&quot; data-origin-height=&quot;226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dA0cHH/btsIizyb8Kg/XmPPyE1XtfwOkLFzrvZgXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dA0cHH/btsIizyb8Kg/XmPPyE1XtfwOkLFzrvZgXK/img.png&quot; data-alt=&quot;코드 리뷰 중 일부&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dA0cHH/btsIizyb8Kg/XmPPyE1XtfwOkLFzrvZgXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdA0cHH%2FbtsIizyb8Kg%2FXmPPyE1XtfwOkLFzrvZgXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;583&quot; height=&quot;226&quot; data-origin-width=&quot;583&quot; data-origin-height=&quot;226&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;코드 리뷰 중 일부&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 LINKHU를 통해 사람을 대하는 방법과 책임에 관해 고민하는 순간이 많았고, 이는 추후 있을 다양한 경험에서 자양분으로 작용할 것이라는 확신은 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트, 스터디, 멘토링 (3 ~ 6월)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개강 이후부터는 다양한 활동들을 진행했다. 학교 홈페이지나 단톡방에 어떤 공고가 떴는지를 매일같이 확인했고.. 넣을 수 있는 건 정말 다 넣었다. 그 결과 활동한/활동 예정인 내역은 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 교내 오픈소스 컨트리뷰션 모임 참여&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 경희대학교 SW 중심사업단 백엔드 멘토&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- SKT DEVOCEAN 네트워크 스터디&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 경희대학교 초록스터디 리뷰어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 교내 프로젝트 대회 참여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- SW경진대회 참여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- GDSC 1기 Lead 지원&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SW 경진대회나 교내 프로젝트 등은 이제 시작하고 있는 단계라, 아직 활동하고 있다고는 말하기 뭐하긴 하지만 일단 나열해봤다. 이제 4학년이기에 취업에만 집중해도 되긴 하지만, 학생일 때 할 수 있는 것들을 다 해보고 졸업하고 싶었다 (욕심이 많아서) 학술적인 것 이외에도 밴드 동아리도 하고 싶었는데, 정말 시간이 나지 않아서 2학기에 노려봐야 할 것 같다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;LG CNS 인턴 (현재)&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 530px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 530px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 530px;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;2532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/57GQ7/btsIhSkOJrs/2dOk4b3S4fsi2bXMv1waxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/57GQ7/btsIhSkOJrs/2dOk4b3S4fsi2bXMv1waxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/57GQ7/btsIhSkOJrs/2dOk4b3S4fsi2bXMv1waxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F57GQ7%2FbtsIhSkOJrs%2F2dOk4b3S4fsi2bXMv1waxk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;233&quot; height=&quot;504&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;2532&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 530px;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nKt3Q/btsIifNuVzI/r2FKfVo9VIHzfU0Y6O952k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nKt3Q/btsIifNuVzI/r2FKfVo9VIHzfU0Y6O952k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nKt3Q/btsIifNuVzI/r2FKfVo9VIHzfU0Y6O952k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnKt3Q%2FbtsIifNuVzI%2Fr2FKfVo9VIHzfU0Y6O952k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;376&quot; height=&quot;501&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글을 쓰는 지금은 LG CNS에서 Software Engineer로 인턴을 수행하고 있다. 이제 온보딩 과정을 마쳤고, 차주부터 팀에 배치되어 과제를 수행하게 된다. 풀스택 직무로 지원할지, 클라우드 AM으로 지원할지 고민하다가 개발 문화가 확실히 AM쪽이 잘 잡혀있는 것 같아 그쪽으로 지원하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀도 마찬가지로 클라우드 쪽으로 배정받아서, 'AWS 어플리케이션 팀'에서 근무하게 된다. 프론트엔드부터 백엔드, 클라우드 등 전반적인 IT 기술을 다 다루게 될 거 같은데, 사실 요즘  LLM의 부상으로 Specialist보단 Generalist쪽이 더 비전이 좋지 않을까, 생각하고 있기도 해서 괜찮은 것 같다. 나도 장기적으로는 백엔드보단 데브옵스나 소프트웨어 아키텍트쪽으로 성장하고 싶으니!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 1주차 온보딩이 끝났지만, 같이 인턴하시는 분들 중 정말 대단하신 분들이 많았다. 학벌이 상상 이상으로 다들 너무나 좋으셨고, 이야기도 나눠보니 똑똑하신 분들이 많았다. 초반에는 쭈구리가 되는 순간이 좀 많았다.. 어떻게 뽑힌거지 ㅋㅋ;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 다들 착하시고 재밌으셔서 일주일 기간동안 즐겁게 보냈던 것 같다! 앞으로는 부서에서 과제를 진행하게 되는데.. 아무래도 사회생활이 처음이다 보니 재밌을 거 같기도 하고 걱정도 되고 그런다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;고민하고 있는 부분들 (현재)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 고민하고 있는 부분도 있다. 어떻게 학습할 것인지에 대해 조금 고민인데, 집중력이 나날이 떨어지는 것 같다고도 느끼고 있고 시간이 더 지나기 전에 뇌를 잘 쓰는 법을 연구하면 좋지 않을까 싶어서 관련 서적들을 읽어보고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'도둑맞은 집중력'을 아주 재밌게 읽고 있는데, 이러한 영향인지 요즘 휴대폰이나 유튜브, 인스타를 보는 일이 줄어들었다. 예전에는 휴대폰이나 컴퓨터, 게임 없이는 못 사는 몸이었는데 요즘에는 생각이 좀 바뀌었다. 스크린 밖에도 재밌는 일은 많은 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외에도 요즘 다양한 경험을 해보고 싶어서 고민이다. 단순히 취업을 목표로 살아가기엔 20대의 시간이 아깝게 느껴지기도 해서, 최대한 여러 가지를 경험해보고 싶은 마음이 크다. 교환학생이나 해외 인턴을 해보고 싶은데, 4-2라서 기회가 있을지는 미지수이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 1, 2, 3학년 때 다양한 활동들을 많이 해봤어야 했는데 늦깍이 학생마냥 4학년부터 이것저것 활동하려고 하다 보니 쉽진 않은 거 같긴 하다   그래도 할 수 있는 건 다 해봐야지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로의 반년도 어떠한 활동들로 채워질진 모르겠지만 후회가 없었으면 한다~! 끝!&lt;/p&gt;</description>
      <category>회고</category>
      <author>teo_99</author>
      <guid isPermaLink="true">https://dev-ws.tistory.com/125</guid>
      <comments>https://dev-ws.tistory.com/125#entry125comment</comments>
      <pubDate>Sat, 29 Jun 2024 17:49:05 +0900</pubDate>
    </item>
    <item>
      <title>HTTP/1.1와 비교하면서 알아보는 HTTP/2</title>
      <link>https://dev-ws.tistory.com/124</link>
      <description>&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;341&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLcHGI/btsH1PgNlgW/fHIKPIq1jFlX5DcJmaadu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLcHGI/btsH1PgNlgW/fHIKPIq1jFlX5DcJmaadu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLcHGI/btsH1PgNlgW/fHIKPIq1jFlX5DcJmaadu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLcHGI%2FbtsH1PgNlgW%2FfHIKPIq1jFlX5DcJmaadu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;341&quot; height=&quot;148&quot; data-origin-width=&quot;341&quot; data-origin-height=&quot;148&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;HTTP/1.1과 HTTP/2&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP/2는 HTTP/1.1과 어떤 점이 다른지 알아보기에 앞서, 왜 HTTP/2가 등장하게 되었는지를 먼저 이해하도록 하자.&lt;br&gt;&amp;nbsp;&lt;br&gt;HTTP의 유래는 하이퍼텍스트 전송으로, 실은 논문을 전송하기 위해 만들어진 프로토콜이다. 즉, 텍스트로 구성된 문서 전송에 있어서 최적화된 프로토콜이라는 것이다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;하지만 최근의 웹 서비스를 살펴보면, 단순히 문서 교환을 위해 HTTP가 사용되는 것이 아니라, 이미지나 비디오와 같은 다양한 포맷의 정보를 전송하는 경우가 많아졌다. 즉, 요구사항이 시대의 흐름을 따라 변화하였다는 것인데, 기존의 HTTP/1.1로는 이러한 요구사항을 대응하기가 어려워졌다.&lt;br&gt;&amp;nbsp;&lt;br&gt;가장 큰 문제는 성능이었는데, 대표적으로는 헤더를 예시로 들 수 있겠다. HTTP 메세지의 경우 기본적으로 많은 헤더 정보를 가지고 있는데, HTTP/1.1에서는 이러한 헤더에 대한 압축 기술이나 최적화 기술이 없어 매번 똑같은 헤더를 전송해야만 했다. 심지어 쿠키를 사용하는 경우엔, 헤더 크기가 1KB 이상이 되는 경우도 있다 하니 상당히 네트워크에 부담이 갈 수 밖에 없다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;309&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdzpue/btsHZLgmgBg/owWSUq8JDMw7gE3Q9bTv51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdzpue/btsHZLgmgBg/owWSUq8JDMw7gE3Q9bTv51/img.png&quot; data-alt=&quot;Naver HTTP Request Message Headers&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdzpue/btsHZLgmgBg/owWSUq8JDMw7gE3Q9bTv51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbdzpue%2FbtsHZLgmgBg%2FowWSUq8JDMw7gE3Q9bTv51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;503&quot; height=&quot;268&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;309&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Naver HTTP Request Message Headers&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;헤더와 관련된 문제 이외에도, TCP와 관련된 문제도 상당히 성능에 병목이 되었다. TCP/IP 4 Layer를 기준으로 HTTP/1.1은 응용 계층 프로토콜이며, TCP 위에서 동작한다. 이는 반대로 말하면 &lt;b&gt;'HTTP/1.1이 TCP를 사용하는 클라이언트다'&lt;/b&gt; 라고도 볼 수 있을 것인데, 문제는 HTTP/1.1이 TCP를 사용하는 방식에서 발생하게 되었다. 이는 추후 설명하겠지만, 근본적으로는 &lt;b&gt;TCP와 HTTP의 특성&lt;/b&gt;이 일치하기 않기 때문이라는 사실만 이해하면 좋을 듯 하다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;그리고 이러한 이유들로 HTTP/2가 등장하게 되었다. 다만 짚고 넘어가야 할 부분 중 하나는 &lt;b&gt;'HTTP/2는 HTTP/1.1의 의미 체계를 수정하지 않는다는 것'&lt;/b&gt;이다. 즉 기존에 우리가 알던 HTTP의 인터페이스(메세지, 헤더, 바디, 메소드와 같은 개념들)는 변화하지 않는다. 다만 구조적인 부분에 있어서는 큰 차이가 있고 (바이너리 프레이밍 계층의 도입), 이는 HTTP/1.2가 아닌 HTTP/2라는 버저닝이 부여된 이유이기도 하다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;HTTP/1.1의 문제점과 HTTP/2의 개선안&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 HTTP/1.1에는 헤더, TCP와 관련된 문제점들이 있었다고 이야기했다. 여기서 문제점이라는 말은, 기술적으로 결함이 있다는 것이 아니라 &lt;b&gt;요구사항의 변화를 만족하지 못한다는 관점&lt;/b&gt;에서 말한 것임을 이해하자.&lt;br&gt;&amp;nbsp;&lt;br&gt;이제 하나씩 HTTP/1.1의 어떠한 부분이 문제가 되었고, 이를 HTTP/2에서는 어떻게 개선하는지를 알아보도록 하자.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Head Of Line blocking (HOL 차단)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째로 소개할 HTTP/1.1의 문제점은 Head Of Line blocking이다. 이는 컴퓨터 네트워크에서 흔히 사용되는 용어인데, 주로 큐에서 첫번째 요소로 인해 나머지 요소들이 정체되어 성능 제한 현상이 발생할 때 HOL 차단이 발생했다고 한다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;HTTP/1.1에서도 이러한 HOL 차단이 발생할 수 있는데, HTTP/1.1은 하나의 TCP 커넥션에서 전송된 세그먼트들을 순서대로 처리하기 때문에, 이전에 전송한 HTTP 메세지가 빠르게 처리되지 못하면 이후 전송한 HTTP 메세지는 대기하게 된다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;501&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1Z1Cj/btsH1bq8Bgf/pdimwmxPtmxtfYCbCXcxX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1Z1Cj/btsH1bq8Bgf/pdimwmxPtmxtfYCbCXcxX0/img.png&quot; data-alt=&quot;HTTP/1.1 HOL Blocking Example&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1Z1Cj/btsH1bq8Bgf/pdimwmxPtmxtfYCbCXcxX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1Z1Cj%2FbtsH1bq8Bgf%2FpdimwmxPtmxtfYCbCXcxX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;534&quot; height=&quot;382&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;501&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;HTTP/1.1 HOL Blocking Example&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;HTTP/1.1은 단순성을 중요시했기에, 위와 같이 설계되었고 이를 회피하기 위한 방법으로 TCP 커넥션을 여러 개 사용하는 경우도 있었다. 이후 살펴볼 예정이지만, HTTP/2에서는 이러한 문제를 &lt;b&gt;스트림이라는 개념과 멀티플렉싱&lt;/b&gt;을 도입함으로서 해결한다. (하나의 커넥션을 여러 논리적인 스트림으로 나누어서 사용하는 방법)&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTTP/2 개선안: 스트림과 멀티플렉싱&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP/2에서는 TCP 커넥션을 조금은 다른 방식으로 활용한다. HTTP/1에서는 TCP 커넥션을 단순히 바이트들을 전송하기 위한 도구로만 활용했기에, 어떠한 개념적인 분리라고 할 것이 없었다. 아래는 HTTP/1.1과 HTTP/2의 TCP 커넥션 활용에서의 차이점을 나타내는 그림이다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;706&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ltsjx/btsH15Yet2f/5Ad80GR3s0v7I1Y2vuflxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ltsjx/btsH15Yet2f/5Ad80GR3s0v7I1Y2vuflxK/img.png&quot; data-alt=&quot;https://freecontent.manning.com/mental-model-graphic-how-is-http-1-1-different-from-http-2/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ltsjx/btsH15Yet2f/5Ad80GR3s0v7I1Y2vuflxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fltsjx%2FbtsH15Yet2f%2F5Ad80GR3s0v7I1Y2vuflxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;723&quot; height=&quot;455&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;706&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://freecontent.manning.com/mental-model-graphic-how-is-http-1-1-different-from-http-2/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;쉽게 말해, &lt;b&gt;TCP 커넥션을 한 단계 추상화해 사용하는 것이라 보면 된다. &lt;/b&gt;스트림을 통해 연관된 HTTP 메세지들이 전송되고, 각각의 스트림은 고유한 식별자를 가진다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;스트림 이외에도 한 가지 개념이 더 추가되었는데, 바로 &lt;b&gt;프레임&lt;/b&gt;이다. 단, 여기서 말하는 프레임은 데이터 링크 계층(OSI Layer 2)에서의 프레임이 아니라, HTTP/2에서 사용하는 용어임을 인지하자. 프레임은 HTTP/2에서의 최소 통신 단위이며 프레임마다 헤더를 가진다. 이러한 프레임은 스트림 내에서 송수신되며, 하나의 HTTP 메세지가 여러 프레임으로 쪼개져 전송된다고 보면 된다.&lt;br&gt;&amp;nbsp;&lt;br&gt;굳이 HTTP 메세지를 스트림을 통해 그대로 전송하지 않고 프레임이라는 단위로 한번 더 나눈 이유는 곧바로 설명할 압축과 관련된 이유 때문이다. HTTP 메세지는 첫 줄에 위치하는 start line을 제외하면 헤더와 바디로 구성되는데, 이 둘은 성격 자체가 다르기 때문에 동일한 압축 알고리즘을 적용할 수 없고, 이에 Headers Frame, Data Frame으로 분리되어 압축되어 전송된다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;다시 돌아와서, 스트림이라는 개념을 이해했다면 추가적으로 한가지 개념을 더 이해할 필요가 있는데, 바로 멀티플렉싱이다. 스트림이라는 개념을 도입하면서 하나의 TCP 커넥션에는 여러 스트림이 존재할 수 있게 되었고, 이는 병렬적으로 많은 HTTP 메세지들이 송수신될 수 있음을 의미한다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbd41j/btsH08aaJIx/TRLMpdKaQUajIMant0DmY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbd41j/btsH08aaJIx/TRLMpdKaQUajIMant0DmY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbd41j/btsH08aaJIx/TRLMpdKaQUajIMant0DmY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbbd41j%2FbtsH08aaJIx%2FTRLMpdKaQUajIMant0DmY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;678&quot; height=&quot;374&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;이렇게 멀티플렉싱을 통해 여러 HTTP 메세지를 병렬적으로 송수신하기 때문에 앞서 설명한 HOL 차단이 발생하지 않는다. 다만 TCP 레벨에서는 HOL 차단이 발생할 수 있는데, 오류가 발생하거나 패킷이 손실되는 경우, 결국 하나의 패킷 안에는 여러 스트림의 정보가 포함되어 있기 때문에 스트림 간의 격리성을 보장받을 수 없다. 우선은 HTTP 레벨에서의 HOL 차단이 없어졌음을 이해하자.&lt;br&gt;&amp;nbsp;&lt;br&gt;또한, 하나의 TCP 커넥션을 사용하는 구조이기 때문에 리소스 측면에서도 이점을 가진다. 서버나 클라이언트는 더 이상 HTTP를 위한 TCP 커넥션을 파이프라이닝해서 사용할 필요도 없어졌으며, 메모리, 프로세싱 비용도 줄어들게 되었다. 이외에도 암호화 연결을 위한 키 교환 과정이 한 번만 이루어지면 되기 때문에, 보안 연결 측면에서도 이점이 있다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Fat Message Headers (방대한 메세지 헤더)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 소개할 문제점은 메세지 헤더와 관련된 것이다. HTTP 메세지의 경우 바디를 설명하기 위한 메타데이터와 같은 정보들로 헤더가 상당량을 차지하게 된다. 다만 HTTP/1.1에서는 이러한 헤더를 매번 반복적으로 전송했고, 이는 네트워크를 대역폭을 낭비하는 원인으로 자리잡게 된다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;HTTP/2에서는 이를 &lt;b&gt;HPACK&lt;/b&gt; 이라는 압축 방식으로 해결하는데, 쉽게 말해 이전에 보낸 적이 있었던 헤더거나, 서로 알고 있는 헤더인 경우 헤더 그 자체를 보내는 것이 아닌, 인덱스 (번호)만 보내 수신측에서 테이블을 참고하여 헤더 값을 알아낼 수 있도록 하고 보낸 적이 없던 헤더에 대해서는 Huffman 부호화 방식을 적용하는 방식이다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTTP/2 개선안: HPACK&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP/2에서는 HPACK 알고리즘을 통해 헤더를 압축한다. HPACK은 앞서 말했듯 &lt;b&gt;이미 알고 있는 헤더에 대해서는 인덱스만 보내고, 그렇지 않은 헤더에 대해서는 Huffman 부호화를 적용하는 방식&lt;/b&gt;이다. 조금 더 구체적으로 어떻게 압축이 이루어지는지를 알아보자.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;517&quot; data-origin-height=&quot;425&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bskk3E/btsH121wxom/hpuwd6nsKxWgAMRkZaLkkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bskk3E/btsH121wxom/hpuwd6nsKxWgAMRkZaLkkK/img.png&quot; data-alt=&quot;https://developers.google.com/web/fundamentals/performance/http2?hl=ko&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bskk3E/btsH121wxom/hpuwd6nsKxWgAMRkZaLkkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbskk3E%2FbtsH121wxom%2Fhpuwd6nsKxWgAMRkZaLkkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;517&quot; height=&quot;425&quot; data-origin-width=&quot;517&quot; data-origin-height=&quot;425&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://developers.google.com/web/fundamentals/performance/http2?hl=ko&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;Request #1을 통해 전송한 적이 있었던 헤더들에 대해서는 이후 반복적으로 전송하지 않고, 새로운 헤더에 대해서만 전송하게 된다. 이런 작업이 가능한 이유는 Indexed List 방식 때문인데, 클라이언트와 서버는 헤더 정보를 위한 static table과 dynamic table을 유지하기 때문에 서로 알고 있는 헤더에 대해서는 인덱스만 전송하는게 가능하다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;static table&lt;/b&gt;: 일반적인 HTTP 헤더 필드들에 대한 정보를 기록하는 테이블. 대부분의 HTTP 메세지에서 사용하는 헤더들이 여기에 기록되어 있음&lt;/li&gt;&lt;li&gt;&lt;b&gt;dynamic table&lt;/b&gt;: 처음에는 빈 테이블로 초기화되고, 주고받는 메세지 헤더에 따라 동적으로 헤더가 채워지는 테이블&lt;/li&gt;&lt;/ul&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rQiyD/btsH1oYbj0H/rLkgfaTB3EquxlkvHRICV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rQiyD/btsH1oYbj0H/rLkgfaTB3EquxlkvHRICV0/img.png&quot; data-alt=&quot;https://zhuanlan.zhihu.com/p/34108036/voters&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rQiyD/btsH1oYbj0H/rLkgfaTB3EquxlkvHRICV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrQiyD%2FbtsH1oYbj0H%2FrLkgfaTB3EquxlkvHRICV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;234&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;234&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://zhuanlan.zhihu.com/p/34108036/voters&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;또한, 서로 알고 있지 못하는 헤더들(테이블에 없는 헤더)에 대해서는 Huffman 부호화 방식을 적용한다. Huffman coding은 무손실 압축 알고리즘인데, 정보의 빈도수를 기반으로 높은 출현율을 가지는 정보는 짧은 부호를, 낮은 출현율을 가지는 정보는 긴 부호를 할당하여 압축하는 방식을 의미한다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Limited Priorities (우선순위의 부재)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt; 어떠한 정보를 서버에 요청할 때, 특정 정보는 더 우선순위를 가질 수 있다. 가령 뉴스 웹 사이트를 방문한다고 해보자. 페이지가 로드됨에 따라 어떠한 정보가 먼저 사용자에게 도달하는 것이 좋을까? 광고나 기타 부수적인 정보들보단, 헤드라인의 제목 등을 먼저 가시화하는 편이 나을 것이다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;이처럼 HTTP로 제공하는 것이 결국은 비즈니스기에 우선순위를 지정할 수 있어야 된다는 니즈가 생기게 되었지만, HTTP/1.1에서는 관련한 수단이 없었다. HTTP/2에서는 &lt;b&gt;스트림 우선순위 지정&lt;/b&gt;이라는 방식을 통해 이 문제를 해결한다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTTP/2의 개선안: 스트림 우선순위 지정&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP/2에서는 스트림마다 우선순위를 지정할 수 있다. 각 스트림은 &lt;b&gt;가중치와 종속성&lt;/b&gt;을 가질 수 있는데, 가중치는 1~256 사이의 정수로 할당되며 이러한 가중치는 송수신 시 비중을 나타내게 된다. 종속성은 말 그대로  스트림간의 종속성을 나타내는 것이며, 상위 스트림이 먼저 처리되어야 하위 스트림이 처리될 수 있다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;521&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X2dSv/btsH03fLHkz/FXzxKAxKPKsBzct8tQuug1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X2dSv/btsH03fLHkz/FXzxKAxKPKsBzct8tQuug1/img.jpg&quot; data-alt=&quot;https://web.dev/articles/performance-http2?hl=ko#streams_messages_and_frames&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X2dSv/btsH03fLHkz/FXzxKAxKPKsBzct8tQuug1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX2dSv%2FbtsH03fLHkz%2FFXzxKAxKPKsBzct8tQuug1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;521&quot; height=&quot;220&quot; data-origin-width=&quot;521&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://web.dev/articles/performance-http2?hl=ko#streams_messages_and_frames&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;스트림 간의 가중치와 종속성이라는 개념은 결국 트리 구조로 표현될 수 있고, HTTP/2에서는 클라이언트가 &lt;b&gt;우선순위 지정 트리&lt;/b&gt;를 구성하여 서버에게 요청할 수 있다. 가령 클라이언트는 '스트림 A는 대역폭의 3/4를 부여하고, 스트림 B는 1/4를 부여해라. 그리고 스트림 C는 A와 B 이전에 처리되어야 한다.' 라는 요구사항을 전달할 수 있는 것이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;다만 중요한 점은, 이는 어디까지나 요구사항이기 때문에 서버에서 우선순위 지정 트리에 대한 작업을 반드시 보장하진 않는다. 위와 같이 우선순위를 클라이언트에서 전적으로 관리하게 되면 starvation과 같은 문제가 발생할 수도 있고, 서버 측의 안정성을 보장할 수 없기 때문이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;참고로, 스트림의 경우 우선순위를 지정할 수 있을 뿐 아니라 &lt;b&gt;개별적으로 흐름 제어도 가능&lt;/b&gt;하다. 별도의 스트림마다 윈도우 크기를 지정할 수 있기 때문에, 유튜브와 같은 서비스에서의 일시 정지 기능을 굳이 TCP 커넥션 단에서 구현하는게 아니라 스트림 레벨에서 구현할 수 있다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Client-Driven Transmission (클라이언트 주도 전송)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP/1.1은 클라이언트가 요청을 했을 시에만 응답을 제공한다. 이는 클라이언트와 서버의 관계를 고려하면 일반적으로는 적절한 시나리오이다. 하지만 서버가 송신해야 하는 리소스가 상당히 많고, 서버는 굳이 클라이언트가 요청하지 않아도 무엇을 제공해야 할지 아는 경우 전통적인 클라이언트 - 서버 관계는 비효율적이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;HTTP/2에서는 &lt;b&gt;Server Push(&lt;/b&gt;&lt;b&gt;서버 푸쉬)&lt;/b&gt;&amp;nbsp;를 통해 이러한 문제를 해결한다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTTP/2의 개선안: Server Push (서버 푸쉬)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;개념을 설명하기 이전에 중요한 점을 짚고 넘어가자면, HTTP/2의 서버 푸쉬는 대부분의 웹 브라우저에서 지원을 중단하게 되었다. 이는 생각보다 성능상의 이점을 실현하기가 어려운 경우가 많았고, 전체 HTTP/2 웹사이트의 1.25% 정도만 사용했기 때문이다. (참고: &lt;a href=&quot;https://developer.chrome.com/blog/removing-push?hl=ko&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;Chrome에서 HTTP/2 서버 푸시 삭제&lt;/span&gt;&lt;/a&gt;)&lt;br&gt;&amp;nbsp;&lt;br&gt;따라서 개념은 간략하게만 설명할 예정인데, 아래 그림과 같이 PUSH_PROMISE 프레임을 통해 클라이언트에게 푸쉬할 예정임을 알리고 이후 전송하는 방식이다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;321&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AVNMr/btsH0oLDHZB/Mc9twmcRBFYFWAS11wnDP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AVNMr/btsH0oLDHZB/Mc9twmcRBFYFWAS11wnDP1/img.png&quot; data-alt=&quot;https://web.dev/articles/performance-http2?hl=ko#streams_messages_and_frames&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AVNMr/btsH0oLDHZB/Mc9twmcRBFYFWAS11wnDP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAVNMr%2FbtsH0oLDHZB%2FMc9twmcRBFYFWAS11wnDP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;699&quot; height=&quot;277&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;321&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://web.dev/articles/performance-http2?hl=ko#streams_messages_and_frames&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;구조의 변화: Binary Framing Layer의 도입&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP/1.1과 HTTP/2의 차이점을 알아보았고, 마지막으로 구조적 차이점을 알아보고자 한다. HTTP/2는 바이너리 프레이밍 레이어를 통해 앞서 설명했던 HTTP 메세지를 프레임 단위로 나누는 작업, 압축하는 작업을 수행한다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;728&quot; data-origin-height=&quot;369&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KP77s/btsH0iSkszd/dX8ATd6TURFhpJ63bTdJnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KP77s/btsH0iSkszd/dX8ATd6TURFhpJ63bTdJnK/img.png&quot; data-alt=&quot;https://web.dev/articles/performance-http2?hl=ko#streams_messages_and_frames&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KP77s/btsH0iSkszd/dX8ATd6TURFhpJ63bTdJnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKP77s%2FbtsH0iSkszd%2FdX8ATd6TURFhpJ63bTdJnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;728&quot; height=&quot;369&quot; data-origin-width=&quot;728&quot; data-origin-height=&quot;369&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://web.dev/articles/performance-http2?hl=ko#streams_messages_and_frames&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;본문 제일 앞쪽에서 언급했듯이, HTTP의 인터페이스 자체는 변화하지 않았지만 바이너리 프레이밍 계층이 추가되면서 클라이언트와 서버가 HTTP/1.1의 의미 체계로는 HTTP/2 메세지를 이해할 수 없게 되었다. (그래서 버저닝도 1.x가 아닌 2가 되었다)&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP/1.1과 비교하며 HTTP/2의 기능, 구조를 알아보았다. 추후 HTTP/3와 QUIC에 대해서도 정리하도록 하겠다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Head-of-line_blocking&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://en.wikipedia.org/wiki/Head-of-line_blocking&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://web.dev/articles/performance-http2?hl=ko#streams_messages_and_frames&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://web.dev/articles/performance-http2?hl=ko#streams_messages_and_frames&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://developer.chrome.com/blog/removing-push?hl=ko&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://developer.chrome.com/blog/removing-push?hl=ko&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://freecontent.manning.com/mental-model-graphic-how-is-http-1-1-different-from-http-2/&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://freecontent.manning.com/mental-model-graphic-how-is-http-1-1-different-from-http-2/&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://www.cloudflare.com/ko-kr/learning/performance/http2-vs-http1.1/&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://www.cloudflare.com/ko-kr/learning/performance/http2-vs-http1.1/&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>네트워크</category>
      <category>HTTP/1.1</category>
      <category>HTTP/2</category>
      <author>teo_99</author>
      <guid isPermaLink="true">https://dev-ws.tistory.com/124</guid>
      <comments>https://dev-ws.tistory.com/124#entry124comment</comments>
      <pubDate>Sun, 16 Jun 2024 16:50:11 +0900</pubDate>
    </item>
    <item>
      <title>gRPC basic concepts</title>
      <link>https://dev-ws.tistory.com/123</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Remote Procedure Call&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gRPC는 구글에서 개발한 프레임워크다. 따라서 앞에 'g'가 붙은 것임을 유추할 수 있을 것이다. 그렇다면 RPC는 무엇인가? gRPC라는 개념을 알아보기에 앞서 RPC(Remote Procedure Call)에 대해 이해해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RPC는 직역하면 원격 프로시저 호출로, &lt;b&gt;다른 주소 공간에서 함수나 프로시저를 실행할 수 있게 하는 프로세스 간 통신 기술&lt;/b&gt;이다. 쉽게 말해, 다른 컴퓨터나 프로세스에 있는 함수를 마치 내 것인것 마냥 원격으로 호출할 수 있다는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 RPC는 네트워크 기반으로 동작하도록 설계되었다. 즉, 원격으로 내 컴퓨터에 있는 다른 프로세스의 함수를 호출하는 것이 1순위 목적이 아니라, 네트워크를 타고 다른 노드에 있는 함수를 호출하는 것이 목적이라는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mz5z9/btsHwwbeERB/w9yMKZJjeAYu3wXGWXCWpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mz5z9/btsHwwbeERB/w9yMKZJjeAYu3wXGWXCWpK/img.png&quot; data-alt=&quot;https://jindongpu.wordpress.com/2013/01/30/remote-procedure-call-sequence-of-events/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mz5z9/btsHwwbeERB/w9yMKZJjeAYu3wXGWXCWpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMz5z9%2FbtsHwwbeERB%2Fw9yMKZJjeAYu3wXGWXCWpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;566&quot; height=&quot;299&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;338&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://jindongpu.wordpress.com/2013/01/30/remote-procedure-call-sequence-of-events/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Why RPC?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로, gRPC가 최신 기술인 것에 반해 RPC는 1980년대에 처음으로 제안되었다. (논문 참고: &lt;a href=&quot;https://web.eecs.umich.edu/~mosharaf/Readings/RPC.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;) Xerox라는 연구소에서 해당 아이디어를 제안했는데, Xerox는 GUI, Ethernet을 처음으로 고안한 회사이기도 하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 왜 RPC가 등장했는가? 바로 &lt;b&gt;분산 환경에 대한 니즈&lt;/b&gt; 때문이다. 이전에는 메인프레임이라는 컴퓨터를 통해 연산을 처리했는데, 기업 입장에서는 값비싼 메인프레임보다는 비교적 저가의 워크스테이션 서버 여러 대로 서비스를 제공하고자 하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 &lt;b&gt;'워크스테이션 간 어떻게 통신할 것인가'&lt;/b&gt;가 자연스레 화두에 올랐고, 네트워크의 중요성도 대두된다. 다만 네트워크는 상당히 저수준(low-level)이었기 때문에, 소켓 프로그래밍, 메시지 직렬화/역직렬화, 오류 처리 등과 같은 번거로운 작업들을 처리해야 했다. 그리고 이러한 문제를 해결하기 위해 RPC가 등장한 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 RPC의 논문에서 발췌한 내용을 번역한 것인데, 참고하면 좋을 듯 하다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이 아이디어에는 여러 가지 매력적인 측면이 있습니다. 하나는 깔끔하고 단순한 의미론입니다. 이를 통해 분산 계산을 더 쉽게 구축하고 올바르게 수행할 수 있습니다. 또 하나는 효율성입니다. 프로시저 호출이 간단해 보이므로 통신이 매우 빨라집니다. 세 번째는 일반성입니다. 싱글 머신 계산에서 프로시저는 알고리즘의 각 부분 간의 통신을 위한 가장 중요한 메커니즘인 경우가 많습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 이렇게 오래 된 기술인 RPC가, &lt;b&gt;왜 최근 들어 gRPC등과 같은 프레임워크들이 등장했는지&lt;/b&gt;에 대해 의문이 생길 수 밖에 없다. 이는 여러 기술적 발전이 있었기 때문이다. 네트워크 분야에서는 대역폭이 크게 증가했고, 마이크로서비스와 같은 아키텍쳐가 등장하면서 이전 기술인 RPC에 다시 이목이 집중되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 같은 RPC에는 여러 구현 모델이 존재한다. Facebook이 만든 Thrift, Twitter이 만든 Finagle 등이 대표적인 구현체이다. 그 중 우리는 Google이 만든, gRPC에 대해 살펴보도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Protocol Buffer and gRPC&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 앞서 RPC란 '원격지의 함수나 프로시저를 네트워크를 통해 호출하는 기술' 이라고 설명했다. 그렇다면 RPC의 구현체들에는 이러한 기능을 가능하게 하기 위한 방법론들이 존재할 것임을 자연스레 유추해볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gRPC에서는 &lt;b&gt;Protocol Buffer를 IDL(Interface Definition Language)로, 그리고 기본 메세지 교환 형식으로 사용함으로서&lt;/b&gt; 이러한 기능을 가능하게 한다. IDL이란, 인터페이스 정의 언어로 일종의 인터페이스를 묘사하기 위한 언어라고 생각하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원격지의 함수를 마치 내 것인마냥 호출하기 위해선, 타겟 시스템의 환경이나 운영체제, 메세지 형식 등에 구애받지 않아야 한다. 즉, '언어'를 통일할 필요가 있는 것이다. 그리고 Protocol Buffer가 IDL로서 이러한 역할을 한다. 우선 Protocol Buffer를 조금 알아보고, gRPC에서 어떻게 이를 사용해 통신하는지 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Protocol Buffer&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;425&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0jDNI/btsHv8iO1QS/zNzTs9DhOOWIuI5kdCpQ5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0jDNI/btsHv8iO1QS/zNzTs9DhOOWIuI5kdCpQ5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0jDNI/btsHv8iO1QS/zNzTs9DhOOWIuI5kdCpQ5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0jDNI%2FbtsHv8iO1QS%2FzNzTs9DhOOWIuI5kdCpQ5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;630&quot; height=&quot;236&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;425&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Protocol Buffer 역시도 구글이 만들었는데, gRPC보다 약 8년 빠르게 공개된 기술이다. 공식 문서에 따르면 Protocol Buffer는 다음과 같은 역할을 한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Protocol Buffers are a language-neutral, platform-neutral extensible mechanism for serializing structured data.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;언어나 플랫폼과는 상관 없이 구조화된 데이터를 직렬화할 수 있는 확장 가능한 방식&lt;/b&gt;이라는 의미다. 쉽게 말해 타겟 서버의 프로세스가 사용하는 언어나, OS 등과는 상관 없이 메세지를 주고받을 수 있는 방식이다. JSON과 비슷한 형태라고 이해해도 좋을 듯 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 구체적으로는 아래와 같이 메세지 형태를 '.proto' 파일에 정의하는 방식이다. 이렇게 메세지 형식을 정의하면, Protocol Buffer 컴파일러를 통해 특정 언어를 지정하고 컴파일할 수 있다. 그러면 각 언어에 맞는 코드가 자동으로 생성되고, 이후 해당 코드를 사용하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1716305257106&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;message Person {
  optional string name = 1;
  optional int32 id = 2;
  optional string email = 3;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1716305685105&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Java로 Compile하는 예시
protoc --java_out=. person.proto&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 Protocol Buffer는 중요한 특성이 있는데, 바이너리 형태로 데이터를 전송한다는 점이다. 이는 JSON과는 확실히 대조적이다. 가령 아래와 같은 JSON이 있다고 가정해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;367&quot; data-origin-height=&quot;114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QyBTy/btsHw5ZNJl3/x2c5vBW2ub6WwCaLurCZi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QyBTy/btsHw5ZNJl3/x2c5vBW2ub6WwCaLurCZi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QyBTy/btsHw5ZNJl3/x2c5vBW2ub6WwCaLurCZi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQyBTy%2FbtsHw5ZNJl3%2Fx2c5vBW2ub6WwCaLurCZi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;367&quot; height=&quot;114&quot; data-origin-width=&quot;367&quot; data-origin-height=&quot;114&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON은 바이너리 형태가 아닌 문자 그 자체로 직렬화되어 전송된다. 즉, 각각의 정보를 Human Readable한 문자열 그 자체로 전송하기 때문에 전송하는 메세지의 용량이 커질 수 밖에 없다. 위 JSON의 경우, whitespace를 제외하고 82바이트를 차지한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3RN2a/btsHxKN8QVm/olr9RQKH285Ech1DGayiR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3RN2a/btsHxKN8QVm/olr9RQKH285Ech1DGayiR0/img.png&quot; data-alt=&quot;binary encoding in Protocol Buffer&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3RN2a/btsHxKN8QVm/olr9RQKH285Ech1DGayiR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3RN2a%2FbtsHxKN8QVm%2Folr9RQKH285Ech1DGayiR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;230&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;230&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;binary encoding in Protocol Buffer&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 Protocol Buffer의 경우, 바이너리 인코딩을 지원하기 때문에, 더 적은 용량으로 메세지를 전송할 수 있다. JSON으로는 82 바이트로 정보 표현을 했었는데, Protocol Buffer를 사용하면 33바이트만 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 이해하기는 불가능하지만, 전송 측면에서의 이점을 누리는 것이다. 또한, 이렇게 인코딩 및 디코딩 과정을 매번 거치게 되면 프로세싱 비용이 늘어나게 되는데, &lt;b&gt;네트워크 대역폭을 줄이고 CPU 소모량을 늘렸다&lt;/b&gt;고 이해해도 좋을 듯 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 지금까지 설명한 Protocol Buffer의 Worflow를 한 눈에 표현한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;561&quot; data-origin-height=&quot;160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GlLKk/btsHx0pGLMk/kNJaLgSpdqInZtOCAHL5KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GlLKk/btsHx0pGLMk/kNJaLgSpdqInZtOCAHL5KK/img.png&quot; data-alt=&quot;PB Workflow&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GlLKk/btsHx0pGLMk/kNJaLgSpdqInZtOCAHL5KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGlLKk%2FbtsHx0pGLMk%2FkNJaLgSpdqInZtOCAHL5KK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;561&quot; height=&quot;160&quot; data-origin-width=&quot;561&quot; data-origin-height=&quot;160&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;PB Workflow&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;gRPC&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gRPC의 핵심 개념은 크게 어렵지 않다. gRPC는 Stub이라는 개념을 통해 원격지의 주소 호출을 가능하게 하고, 메세지를 직렬화 및 역직렬화를 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이러한 stub은 Protocol Buffer 컴파일러로 '.proto' 파일을 grpc 플래그와 함께 컴파일하면 자동으로 생성된다. Stub은 어떠한 대리 기능을 수행해주는 주체라고 생각하면 된다. Stub을 사용하는 이유는 RPC의 개념을 생각해보면 떠올릴 수 있는데, 원격지의 주소 공간이 다르기 때문에, 프로시저나 함수를 호출하기 위해선 &lt;b&gt;'주소공간과 관련 없이 호출해줄 수 있는 대리인'&lt;/b&gt;이&amp;nbsp;필요한 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;552&quot; data-origin-height=&quot;327&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEeibS/btsHxI3SVbi/ZhWROx14t8PZ090cmdcTBK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEeibS/btsHxI3SVbi/ZhWROx14t8PZ090cmdcTBK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEeibS/btsHxI3SVbi/ZhWROx14t8PZ090cmdcTBK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEeibS%2FbtsHxI3SVbi%2FZhWROx14t8PZ090cmdcTBK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;552&quot; height=&quot;327&quot; data-origin-width=&quot;552&quot; data-origin-height=&quot;327&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 컴파일러로 생성된 Stub을 통해 서버에서, 그리고 클라이언트에서는 이를 Import하여 쉽게 소스 코드를 작성할 수 있게 된다. 즉, Python, Java 등으로 작성된 프로덕션 코드가 Stub에 프로시저 호출 요청을 보내면, Stub이 이를 적절히 바이너리 형태로 직렬화하여 전송하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 응답이나 요청 형식을 스트림으로 지정할 수도 있고, 동기나 비동기 방식을 모두 사용할 수 있다. 혹은 데드라인을 지정할 수도 있는데, 이러한 부분들은 구체적인 사용과 관련된 측면이므로 이번 글에서는 다루지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 gRPC의 동작 과정을 간단히 요약한 그림이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;325&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4L4PL/btsHxsf7cnj/ox7eiyc05wk6fucmKQKcMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4L4PL/btsHxsf7cnj/ox7eiyc05wk6fucmKQKcMK/img.png&quot; data-alt=&quot;https://medium.com/@lchang1994/deep-dive-grpc-protobuf-http-2-0-74e6295f1d38&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4L4PL/btsHxsf7cnj/ox7eiyc05wk6fucmKQKcMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4L4PL%2FbtsHxsf7cnj%2Fox7eiyc05wk6fucmKQKcMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;668&quot; height=&quot;310&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;325&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://medium.com/@lchang1994/deep-dive-grpc-protobuf-http-2-0-74e6295f1d38&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;gRPC의 특징&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 gRPC는 아래와 같은 몇가지 특징을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. HTTP/2 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP/1.1이 아닌 헤더 압축, 멀티플렉싱 등의 기능이 추가된 HTTP/2를 기반으로 동작한다. 따라서 HTTP/2가 가지는 성능 측면에서의 이점도 활용할 수 있다. HTTP/2에 대한 자세한 내용은 추후 따로 정리할 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 특정 분야에서는 REST를 대체함&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바이너리 형태로 전송하고, 또한 엄격한 Rule을 가지고 있기 때문에 JSON + HTTP/1.1 기반으로 동작하던 기존의 Server to Server 연결을 대체하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 브라우저 지원이 빈약함&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 마이크로서비스에서 활용되는 등 Server to Server를 기반으로 설계되었기 때문에, 브라우저에서 gRPC를 사용할 수 있는 방법은 없다. 다만 JavaScript 라이브러리 등을 사용하면 우회적으로 사용할 순 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. Human Readable하지 않음&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gRPC 메세지들은 Protocol Buffer에 의해 인코딩되기 때문에, 사람이 읽을 수 없다. 따라서 gRPC Payload를 중간 노드 등에서 해석하기 위해선 추가적인 처리가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Remote_procedure_call&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://en.wikipedia.org/wiki/Remote_procedure_call&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://web.eecs.umich.edu/~mosharaf/Readings/RPC.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://web.eecs.umich.edu/~mosharaf/Readings/RPC.pdf&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://martin.kleppmann.com/2012/12/05/schema-evolution-in-avro-protocol-buffers-thrift.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://martin.kleppmann.com/2012/12/05/schema-evolution-in-avro-protocol-buffers-thrift.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://protobuf.dev/overview/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://protobuf.dev/overview/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://grpc.io/docs/what-is-grpc/core-concepts/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://grpc.io/docs/what-is-grpc/core-concepts/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/naver-cloud-platform/nbp-%EA%B8%B0%EC%88%A0-%EA%B2%BD%ED%97%98-%EC%8B%9C%EB%8C%80%EC%9D%98-%ED%9D%90%EB%A6%84-grpc-%EA%B9%8A%EA%B2%8C-%ED%8C%8C%EA%B3%A0%EB%93%A4%EA%B8%B0-1-39e97cb3460&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/naver-cloud-platform/nbp-%EA%B8%B0%EC%88%A0-%EA%B2%BD%ED%97%98-%EC%8B%9C%EB%8C%80%EC%9D%98-%ED%9D%90%EB%A6%84-grpc-%EA%B9%8A%EA%B2%8C-%ED%8C%8C%EA%B3%A0%EB%93%A4%EA%B8%B0-1-39e97cb3460&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>네트워크</category>
      <author>teo_99</author>
      <guid isPermaLink="true">https://dev-ws.tistory.com/123</guid>
      <comments>https://dev-ws.tistory.com/123#entry123comment</comments>
      <pubDate>Wed, 22 May 2024 01:37:36 +0900</pubDate>
    </item>
    <item>
      <title>ZeroMQ &amp;amp; basic ZeroMQ Patterns</title>
      <link>https://dev-ws.tistory.com/122</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;437&quot; data-origin-height=&quot;169&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9gQfg/btsGB2Yg60S/9AH0CVkyuLzuBksXQ7zCQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9gQfg/btsGB2Yg60S/9AH0CVkyuLzuBksXQ7zCQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9gQfg/btsGB2Yg60S/9AH0CVkyuLzuBksXQ7zCQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9gQfg%2FbtsGB2Yg60S%2F9AH0CVkyuLzuBksXQ7zCQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;339&quot; height=&quot;131&quot; data-origin-width=&quot;437&quot; data-origin-height=&quot;169&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선조의 개발자들은 클라이언트나 서버를 구현하기 위해 Berkeley Sockets와 같은 Socket API를 사용해 프로그램을 작성했을 것이다. 다만 Socket API의 경우 운영체제와 바로 맞닿아 있는 부분이기에, 저수준의 지엽적인 내용이 많고 사용하기가 복잡하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;자주 발생하는 부분은 패턴으로 정의하고, 추상화하고자 하는 니즈가 자연스레 생겨났다.&lt;/b&gt; 그 결과 전송 계층 위에서 네트워크를 제어할 수 있는 소프트웨어들이 등장하게 되었고, ZeroMQ는 그러한 소프트웨어 중 하나이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;111&quot; data-origin-height=&quot;141&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QAvwq/btsGCuzWx3O/HRJUDskVu6DMlHHbQsDVw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QAvwq/btsGCuzWx3O/HRJUDskVu6DMlHHbQsDVw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QAvwq/btsGCuzWx3O/HRJUDskVu6DMlHHbQsDVw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQAvwq%2FbtsGCuzWx3O%2FHRJUDskVu6DMlHHbQsDVw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;100&quot; height=&quot;127&quot; data-origin-width=&quot;111&quot; data-origin-height=&quot;141&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ZeroMQ 소개&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ZeroMQ는 &lt;b&gt;분산 어플리케이션이나 동시처리 어플리케이션에서 활용할 수 있는&lt;/b&gt; &lt;b&gt;고성능 비동기 메세징 라이브러리&lt;/b&gt;이다. 다만 이 말의 뜻을 개인적으로는 이해하기 어려워서 추가적으로 조사해봤다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;분산 어플리케이션(distributed application)&lt;/b&gt;이란, 네트워크로 연결된 여러 컴퓨터에서 실행되는 프로그램을 의미한다. 가장 이해하기 쉬운 예시로는 프론트엔드, 백엔드가 있는데, 프론트엔드는 웹 브라우저에서 실행되고 백엔드는 별도의 엔드포인트에서 구축되기 때문에 다른 어플리케이션이다. 그렇지만 이들은 하나의 목적(회원가입, 쇼핑 등)을 처리하기 위해 네트워크로 협력하기 때문에 분산 어플리케이션이라고 볼 수 있는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동시처리 어플리케이션(concurrent application)&lt;/b&gt;이란, 말 그대로 동시에 여러 작업을 처리할 수 있는 어플리케이션을 의미한다. 멀티스레드와 같은 기법을 사용해 동시성 제어를 수행하거나, 비동기 처리를 진행한다면 동시처리 어플리케이션이라고 볼 수 있겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼, ZeroMQ가 분산 어플리케이션이나 동시처리 어플리케이션에서 사용된다는 사실을 이해했으니, 1-depth 더 들아가보도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;'Zero'의 의미&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 ZeroMQ의 설계 철학을 알아보기 위해 &lt;b&gt;'Zero'가 의미하는 바&lt;/b&gt;가 무엇인지부터 이해해보자. 공식 문서에 따르면, 'Zero'는 zero broker, zero latency, zero cost(open source), zero administration을 의미한다고 한다. 이 중 중요한 부분은&lt;b&gt; 'zero broker'&lt;/b&gt;라는 사실인데, 이는 메세지 브로커가 없이 동작할 수 있음을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메세지 브로커&lt;/b&gt;란 애플리케이션이나 시스템 및 서비스가 서로 간에 통신하고 정보를 교환할 수 있도록 해주는 소프트웨어인데, 쉽게 말해 sender와 receiver 사이에서 메세지를 큐에 쌓거나, 라우팅해주거나, 어떠한 처리를 해주는 역할이라고 보면 된다. 메세지 브로커에 대한 내용이 궁금하다면 &lt;a href=&quot;https://binux.tistory.com/74&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;를 참고하면 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ZeroMQ는 이러한 메세지 브로커가 존재하지 않는다. 따라서 메세지 브로커가 존재하는 상황과 대비해서, 보다 단순한 환경에서 사용될 수 있다. 브로커를 추가한다는 것은 장애 지점, 관리 비용이 늘어난다는 것을 의미하는데, ZMQ 공식 Reference에서는 이러한 이유를 들며 ZMQ의 활용성을 제시한다. 물론 관리 비용을 감당할 수 있는 상황이고, 대규모 분산처리 환경에서 메세지 브로커의 도입으로 인한 리턴이 확실한 상황이라면 ZMQ보다는 Apache Kafka와 같은 기술을 선택할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ZeroMQ가 필요한 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 왜 ZeroMQ가 필요한 것일까? Berkeley Sockets와 같은 소켓 프로그래밍으로도 충분히 네트워크에 접속해 메세지를 보내는 프로그램을 작성할 수 있었는데 말이다. 다만 이렇게 &lt;b&gt;Raw한 4계층 프로토콜을 사용하게 되면 여러 문제가 발생할 수 있는데&lt;/b&gt;, 주로 다음과 같은 것들이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매 요청을 포어그라운드에서 처리할지, 백그라운드로 처리할지에 대한 고려가 필요하다.&lt;/li&gt;
&lt;li&gt;클라이언트와 서버의 역할이 명확히 나뉘어져 있기 때문에, 서버는 클라이언트가 요청할 때 항상 존재해야 한다는 책임이 있다. 또한, 서버와 서버가 통신할 때에는 어떤 노드가 reconnect를 할지 기준이 불명확하다.&lt;/li&gt;
&lt;li&gt;메세지를 어떻게 표현해야 할지 결정하기 어렵다. 크거나 작은 여러 종류의 데이터(비디오, 텍스트 등)에 대해 적절한 메세지 형태를 고려해야 한다.&lt;/li&gt;
&lt;li&gt;메세지 큐를 관리하기 위한 방법이 별도로 마련되어야 한다. 또한, 메세지 큐가 꽉 찼을 때의 방법론도 별도로 마련되어야 한다.&lt;/li&gt;
&lt;li&gt;3, 4계층에 대한 변경에 쉽게 대응하기 어렵다. 가령 IPv6을 사용한다면, 많은 코드를 바꿔야 할 것이다.&lt;/li&gt;
&lt;li&gt;네트워크 에러에 대한 후속 처리는 어떻게 할 것인지 결정해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 것들은&amp;nbsp;&lt;b&gt;Messaging layer가 없던 시절 문제가 되는 부분들&lt;/b&gt;이었다. 이는 네트워크를 사용해 메세지를 보내는 개발자들이 매번 고려해야 하는 부분이 많았음을 의미한다. 그리고 이는 ZeroMQ가 등장한 이유이기도 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ZeroMQ Patterns&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 ZeroMQ에서 지원하는 몇가지 패턴들에 대해 알아보도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Request Reply Pattern&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;162&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4Tier/btsGMEop5P3/JbY7wCBdC3obtMdbGxKds0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4Tier/btsGMEop5P3/JbY7wCBdC3obtMdbGxKds0/img.png&quot; data-alt=&quot;https://zguide.zeromq.org/docs/chapter1/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4Tier/btsGMEop5P3/JbY7wCBdC3obtMdbGxKds0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4Tier%2FbtsGMEop5P3%2FJbY7wCBdC3obtMdbGxKds0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;159&quot; height=&quot;251&quot; data-origin-width=&quot;162&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://zguide.zeromq.org/docs/chapter1/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Request-reply, which connects a set of clients to a set of services. This is a remote procedure call and task distribution pattern. - ZMQ Reference&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 처음으로 소개할 패턴은 Request Reply다. 직역하면 알 수 있듯이, 요청을 보내고 응답을 받는 패턴이다. 가장 기본적인 패턴이며, 위 그림에서는 Client가 Hello를 보내면 Server가 World로 응답하는 모습을 보여준다. Client는 Synchronous하게 요청을 보내고 기다리며, Server는 Asynchronous하게 요청을 처리한다는 점에 주의하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, Request - Reply 패턴을 사용하는 경우 단순히 1:1 통신만 지원하는게 아니라 Autonomous하게 1:N 연결도 지원되기에 서버 측에서는 어떠한 소스 코드의 수정도 필요가 없다. 이는 Socket 프로그래밍을 하는 경우, 일일이 비동기 처리, 멀티스레딩을 고려해야 했던 것과 비교하면 대조적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Publish/Subscribe Pattern&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;414&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxz5c4/btsGMSfHoRv/dPpVBnAf0BkpvokJeGjli0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxz5c4/btsGMSfHoRv/dPpVBnAf0BkpvokJeGjli0/img.png&quot; data-alt=&quot;https://zguide.zeromq.org/docs/chapter1/#Getting-the-Message-Out&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxz5c4/btsGMSfHoRv/dPpVBnAf0BkpvokJeGjli0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcxz5c4%2FbtsGMSfHoRv%2FdPpVBnAf0BkpvokJeGjli0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;413&quot; height=&quot;367&quot; data-origin-width=&quot;414&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://zguide.zeromq.org/docs/chapter1/#Getting-the-Message-Out&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Pub-sub, which connects a set of publishers to a set of subscribers. This is a data distribution pattern. - ZMQ Reference&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음으로는 Publish/Subscribe 패턴인데, 직역하면 '발행/구독' 패턴이다. 말 그대로 메세지를 발행하는 주체가 있고, 이는 구독하는 대상들에게 전송된다. 여기서 중요한 점은, 단방향 메세지 전송으로 Subscriber는 Publisher에게 메세지를 전송할 수 없다. 그리고 이러한 특징으로 인해 날씨 정보 앱 등에서 활용될 수 있다. 날씨 정보에 대한 업데이트가 있을 때마다 Subscriber들에게 메세지가 전송되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 Subscriber들의 경우, 필요로 하지 않는 메세지도 받을 수 있는데, 이는 ZMQ 라이브러리에 내장된 setsockopt라는 메소드를 통해 ZMQ_SUBSCRIBE 옵션을 지정함으로서 필터를 설정할 수 있다. 아무런 옵션을 지정하지 않으면 모든 메세지를 수신한다. 그리고 Subscriber는 다수의 Publisher와 connect될 수 있음도 참고하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 ZeroMQ의 Pub/Sub 구조에서는 'slow joiner' 현상이 발생할 수 있다. Publisher가 Subscriber의 가입 여부 등을 신경쓰지 않고 메세지를 발행하기 때문에 소실될 수 있다는 것이다. 이러한 문제를 해결하기 위해서는 Subscriber가 가입할 때까지 Publisher를 기다리게 하거나, 일정 시간을 기다리게 할 수 있다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Pipeline pattern&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;360&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kw4xF/btsGNtmn8a9/6YXHGm7j4iKWUUg3oSAl60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kw4xF/btsGNtmn8a9/6YXHGm7j4iKWUUg3oSAl60/img.png&quot; data-alt=&quot;https://zguide.zeromq.org/docs/chapter1/#Divide-and-Conquer&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kw4xF/btsGNtmn8a9/6YXHGm7j4iKWUUg3oSAl60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fkw4xF%2FbtsGNtmn8a9%2F6YXHGm7j4iKWUUg3oSAl60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;359&quot; height=&quot;479&quot; data-origin-width=&quot;360&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://zguide.zeromq.org/docs/chapter1/#Divide-and-Conquer&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Pipeline, which connects nodes in a fan-out/fan-in pattern that can have multiple steps and loops. This is a parallel task distribution and collection pattern. - ZMQ Reference&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 소개할 패턴은 Pipeline 패턴이다. 이는 병렬처리와 같은 작업에서 사용할 수 있는 패턴인데, Ventilator가 작업을 분배하고 Worker가 처리하며, Sink가 결과를 수집한다. 해당 패턴에서는 Ventilator와 Sink가 Stable한 부분이고, Worker는 동적으로 변할 수 있는 부분임을 참고하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 패턴은 multi-node multi-gpu와 같이, 여러 노드에서 각각의 GPU를 사용해서 연산을 처리하는 등의 작업을 진행할 때 사용될 수 있다. 다만 주의해야 할 점은, Pub/Sub과 비슷하게 'slow joiner' 문제가 있어서 Worker가 connected 되기 전에 task를 분배한다거나 하는 경우에는 병렬처리가 제대로 되지 않을 수 있다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 Push/Pull이 Pub/Sub과 다른 점은, Push/Pull의 경우 연결된 Pull 소켓들을 대상으로 모두 동일한 메세지를 보내는게 아니라, 라운드 로빈 형식으로 메세지를 보낸다는 점이다. 따라서 각 Worker가 수신하는 메세지가 다르므로 병렬처리에 활용될 수 있는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Dealer-Router pattern&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/diKTEv/btsGMgPbB2v/ukDkSpQkQ4nbiVG8yN9MXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/diKTEv/btsGMgPbB2v/ukDkSpQkQ4nbiVG8yN9MXK/img.png&quot; data-alt=&quot;https://zguide.zeromq.org/docs/chapter3/#The-Asynchronous-Client-Server-Pattern&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/diKTEv/btsGMgPbB2v/ukDkSpQkQ4nbiVG8yN9MXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdiKTEv%2FbtsGMgPbB2v%2FukDkSpQkQ4nbiVG8yN9MXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;403&quot; height=&quot;509&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://zguide.zeromq.org/docs/chapter3/#The-Asynchronous-Client-Server-Pattern&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 살펴볼 패턴은 Dealer-Router다. Dealer-Router 패턴은 Asynchronous Request/Reply 패턴이라고도 불린다. 클라이언트와 서버 모두 비동기적으로 메세지를 전송하기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dealer란 비동기적으로 다수의 클라이언트와 양방향 통신하는 역할을 의미하며, Router는 이러한 Dealer에게 메세지를 전달해주는 역할을 한다. 위 그림을 보면 알 수 있듯이, 로드밸런싱과 상당히 닮아 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ZeroMQ의 개념, 특징을 살펴보았고 몇 가지 대표적인 패턴을 알아봤다. 조사해보니 이외에도 수많은 패턴이 존재하는 것을 확인할 수 있었는데, 서비스 요구사항에 맞게 사용하면 될 듯 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://zeromq.org/get-started/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://zeromq.org/get-started/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://zguide.zeromq.org/docs/chapter1/#Divide-and-Conquer&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://zguide.zeromq.org/docs/chapter1/#Divide-and-Conquer&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://zguide.zeromq.org/docs/chapter2/#Missing-Message-Problem-Solver&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://zguide.zeromq.org/docs/chapter2/#Missing-Message-Problem-Solver&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://api.zeromq.org/4-2:zmq-setsockopt&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;http://api.zeromq.org/4-2:zmq-setsockopt&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/17814436/difference-between-pub-sub-and-push-pull-pattern-in-zeromq&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://stackoverflow.com/questions/17814436/difference-between-pub-sub-and-push-pull-pattern-in-zeromq&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.couchbase.com/blog/distributed-applications/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.couchbase.com/blog/distributed-applications/&lt;/a&gt;&lt;/p&gt;</description>
      <category>네트워크</category>
      <category>zmq</category>
      <author>teo_99</author>
      <guid isPermaLink="true">https://dev-ws.tistory.com/122</guid>
      <comments>https://dev-ws.tistory.com/122#entry122comment</comments>
      <pubDate>Sat, 20 Apr 2024 00:08:19 +0900</pubDate>
    </item>
    <item>
      <title>TCP Error Control</title>
      <link>https://dev-ws.tistory.com/121</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 TCP의 흐름 제어, 혼잡 제어를 살펴봤다. 이외에도 TCP는 오류 제어라는 기법을 사용하는데, 이번 글에서 어떻게 TCP가 오류에 대한 제어를 수행하는지 살펴본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 오류 제어가 무엇인지, 왜 필요한지 당위성부터 알 필요가 있다. TCP는 Survivalability가 중요한 프로토콜이다. 신뢰성 있는 통신을 지향하며, 가능한 효율적으로 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통신은 오류가 발생할 수 있다. 잡음이 발생해 패킷이 손상되거나, 순서가 잘못되었거나, 버려지는 등 항상 의도한대로 동작할 순 없다. 따라서 이러한 오류가 발생했을 때 적절히 대처하는 방법이 필요한데, 그것이 &lt;b&gt;오류 제어&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP 오류 제어는 여러 기법들을 사용해서 진행된다. 크게는 총 3가지 기법을 사용한다고 말할 수도 있는데, &lt;b&gt;체크섬, ACK, 재전송&lt;/b&gt;이다. 이제 각각에 대해서 알아보도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Checksum&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP 헤더에는 Checksum 필드가 존재한다. 이를 통해 패킷이 손상되었는지 여부를 판단할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;432&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xFJ39/btsGtUzgtN1/r6SPJRBC4y4V5Vg46yncl0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xFJ39/btsGtUzgtN1/r6SPJRBC4y4V5Vg46yncl0/img.jpg&quot; data-alt=&quot;http://www.ktword.co.kr/test/view/view.php?no=1796&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xFJ39/btsGtUzgtN1/r6SPJRBC4y4V5Vg46yncl0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxFJ39%2FbtsGtUzgtN1%2Fr6SPJRBC4y4V5Vg46yncl0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;432&quot; height=&quot;212&quot; data-origin-width=&quot;432&quot; data-origin-height=&quot;212&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;http://www.ktword.co.kr/test/view/view.php?no=1796&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;체크섬은 16비트의 값을 가진 필드인데, 위 그림과 같이 여러 요소들을 복합적으로 고려해 체크섬이 계산된다. 체크섬이란, 간단하게 에러를 검출할 수 있는 일종의 간단한 메커니즘이라고 이해하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림을 보면 알겠지만, &lt;b&gt;TCP 체크섬은 TCP 헤더 부분만 고려하는 것은 아니다.&lt;/b&gt; 가상 헤더라고 표현된 부분과, 실제 데이터, 그리고 Padding까지 고려해 체크섬이 계산된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP 헤더, 데이터 부분만 고려하는게 아니라 가상 헤더까지 고려하는 이유는 목적지라는 개념이 IP 주소와 포트 번호까지 함께 고려되어야 하기 때문이다. 그렇기 때문에 IP 헤더의 일부분을 가상 헤더로 활용한다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;가상 헤더(Pseudo Header)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 헤더는 사실 TCP를 만들면서 '가상 헤더가 필요하겠군!' 해서 바로 넣어진게 아니라, 어떠한 역사적 배경과 관련이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 TCP가 처음 등장했을 때에는 TCP/IP 처럼 구분되어 등장한게 아니라, IP와 결합된 형태로 등장했다. 즉, TCP가 라우팅과 트래픽 자체를 모두 통제하는 상황이었다. IP라는 개념이 TCP 속에 녹아들어 있던 상태라고 봐도 무방하다. 그리고 다음은 초기 TCP의 헤더를 그림으로 표현한 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;501&quot; data-origin-height=&quot;348&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUDyXk/btsGw6YNr2R/1WJ3LJm2vmQGbVXHpOWGm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUDyXk/btsGw6YNr2R/1WJ3LJm2vmQGbVXHpOWGm1/img.png&quot; data-alt=&quot;Early TCP Header&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUDyXk/btsGw6YNr2R/1WJ3LJm2vmQGbVXHpOWGm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUDyXk%2FbtsGw6YNr2R%2F1WJ3LJm2vmQGbVXHpOWGm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;443&quot; height=&quot;308&quot; data-origin-width=&quot;501&quot; data-origin-height=&quot;348&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Early TCP Header&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 우리가 알고 있는 TCP의 헤더와는 조금 다르다. Destination TCP Address, Source TCP Address 부분이 현재의 IP에 해당하는, 즉 라우팅을 수행하는 부분이다. 다만 시간이 지나고, 네트워크가 더욱 복잡해지면서 라우팅만을 위한 계층이 필요하게 되었고, TCP/IP로의 전환도 이루어진다. 즉, 우리가 알고 있는 TCP 헤더의 모습으로 변화한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;501&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UqOys/btsGwfCkCJC/tKaODEqmtAe1qKBMgqksX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UqOys/btsGwfCkCJC/tKaODEqmtAe1qKBMgqksX0/img.png&quot; data-alt=&quot;Recent TCP Header&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UqOys/btsGwfCkCJC/tKaODEqmtAe1qKBMgqksX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUqOys%2FbtsGwfCkCJC%2FtKaODEqmtAe1qKBMgqksX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;490&quot; height=&quot;223&quot; data-origin-width=&quot;501&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Recent TCP Header&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 TCP의 가상 헤더도 생겨났다. 초기 TCP는 'End-to-End' 프로토콜로 정의되었다. 하지만 TCP/IP 모델로 변화하면서 TCP 헤더만으로는 'End-to-End' 프로토콜임을 보장할 수 없다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 TCP/IP 모델에서는 가상 헤더를 활용하여 IP 부분의 주소까지 고려하게 된다. 이를 통해 TCP 자체적으로도 엔드포인트 주소가 손상되지 않았음을 보장할 수 있게 된다. 즉, TCP의 개념 정의를 면밀히 따르다보니 생긴 결과다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼, TCP는 이러한 가상 헤더를 포함해서 체크섬을 생성한다. 송신 측에서는 가상 헤더를 포함한 체크섬을 계산해 전송하고, 수신 측에서도 마찬가지로 가상 헤더를 생성해 체크섬이 올바른 값인지를 확인한다. 체크섬을 구하는 방법은 의외로 간단한데, 단순히 값들을 모두 더한 뒤 1의 보수를 취하는 형식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ACK 및 재전송(Retransmission)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;체크섬은 오류가 발생했다고 판단되면 패킷을 폐기하는 방식이다. 이외에도 TCP는 재전송 기반의 오류 제어 기법도 가지고 있다. 이는 흐름 제어와 어느정도 겹치는 내용이 많은데, 이전 내용을 간단히 복기한 뒤 추가적인 내용을 설명하도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP는 ACK 기반으로 동작한다는 사실은 알 것이다. TCP는 재전송 타이머를 가지고 있어서, 타이머가 울리기 전에 ACK가 도착하지 않으면 재전송을 진행한다. 혹은 타이머가 울리기 전이라도 ACK가 n번 (통상 3번) 이상 발생하면 오류라고 판단하고 곧바로 재전송한다는 사실도 혼잡 제어에서 배웠다. (Fast Retransmission)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외에도 TCP는 이전에 설명하지 않았던 오류 제어를 위한 추가적인 기능들을 가지고 있는데, 하나씩 알아보도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;NACK or NAK&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP에는 ACK 응답만 있는 것이 아니다. Negative ACK라고 해서, '부정 확인응답'이라는 방법도 존재한다. 직관적으로 이해할 수 있듯이, 정상적으로 수신되지 않았음을 알리는 메세지이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;243&quot; data-origin-height=&quot;327&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dDqRwy/btsGupFRbYO/5uM40k56S4Jzlj2nYOKhB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dDqRwy/btsGupFRbYO/5uM40k56S4Jzlj2nYOKhB1/img.png&quot; data-alt=&quot;https://ianfinlayson.net/class/cpsc414/notes/09-transport2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dDqRwy/btsGupFRbYO/5uM40k56S4Jzlj2nYOKhB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdDqRwy%2FbtsGupFRbYO%2F5uM40k56S4Jzlj2nYOKhB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;243&quot; height=&quot;327&quot; data-origin-width=&quot;243&quot; data-origin-height=&quot;327&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://ianfinlayson.net/class/cpsc414/notes/09-transport2&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 NACK는 실제로는 잘 사용되지 않고, 위성 통신과 같은 특수한 상황에 주로 사용되는 편이다. 일반적인 경우라면 ACK 응답 혹은 재전송 타이머를 통해 오류 제어가 충분히 가능하기 때문에 굳이 추가적인 비용을 들여서 NACK를 보내지는 않는 것 같다. 반면 '오류가 발생한 이유' 등을 통지해야만 하는 critical한 상황이라면 NACK가 사용될 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Go Back N ARQ&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외에도 ARQ의 한 종류인 Go Back N ARQ 기법을 사용하여 오류 제어를 수행하기도 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Go Back N ARQ 방식에서 ACK 응답이 타이머가 울리기 전에 도착하지 않았거나, NACK 응답이 도착했다면 오류라고 판단하고 재전송을 진행한다. 다만, 여기서 중요한 점은 오류가 발생한 패킷 지점부터 전부 전송한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQj27q/btsGwKPpJqE/DzULeyjg3AVunrPceuywi0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQj27q/btsGwKPpJqE/DzULeyjg3AVunrPceuywi0/img.jpg&quot; data-alt=&quot;https://www.geeksforgeeks.org/sliding-window-protocol-set-2-receiver-side/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQj27q/btsGwKPpJqE/DzULeyjg3AVunrPceuywi0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQj27q%2FbtsGwKPpJqE%2FDzULeyjg3AVunrPceuywi0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;724&quot; height=&quot;418&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.geeksforgeeks.org/sliding-window-protocol-set-2-receiver-side/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림을 보면 알 수 있듯이, 2번 패킷에 대해 Packet Loss가 발생한다면 수신측은 이후에 받는 모든 패킷을 폐기한다. 또한 송신 측에서는 타이머가 울리기 전에 ACK 응답을 받지 못했으므로, 2번 패킷부터 모든 패킷을 재전송한다. 이처럼 오류가 발생하면 되돌아가서 다시 전송하고, 이것이 이름이&lt;b&gt; 'Go Back'&lt;/b&gt; N ARQ라고 지어진 이유이기도 하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 '오류가 발생했다면 해당 부분만 재전송하면 되지 않을까?' 라는 생각이 들 수도 있다. 그리고 해당 방식을 구현한게 &lt;b&gt;Selective Repeat ARQ 방식&lt;/b&gt;이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Selective Repeat ARQ&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름에서부터 알 수 있듯이, Selective Repeat는 선택적으로 '어떠한 부분을 받지 못했음'이라고 알리는 프로토콜이다. 그림으로 우선 살펴보면 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oq3tV/btsGw8WHMKL/LwmTs3Z3K8N4HVwsFc5m21/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oq3tV/btsGw8WHMKL/LwmTs3Z3K8N4HVwsFc5m21/img.jpg&quot; data-alt=&quot;https://www.youtube.com/watch?app=desktop&amp;amp;amp;v=zBBlV_iQ9YM&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oq3tV/btsGw8WHMKL/LwmTs3Z3K8N4HVwsFc5m21/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Foq3tV%2FbtsGw8WHMKL%2FLwmTs3Z3K8N4HVwsFc5m21%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;536&quot; height=&quot;402&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.youtube.com/watch?app=desktop&amp;amp;v=zBBlV_iQ9YM&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재전송 타임아웃이 발생했을 때, 2번 패킷에 대해서는 ACK 응답을 받지 못했으므로 '선택적으로' 2번 패킷만 재전송한다. 만약 Go Back N ARQ 프로토콜을 사용했더라면 2번 패킷을 포함해 이후의 모든 패킷(3, 4, 5)를 모두 재전송했을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Go back N ARQ 방식의 경우, 수신받는 패킷은 순서를 항상 보장받았다. 즉, ACK 응답을 보내는 순서가 항상 역행하지 않는다는 것이다. 가령 4번 ACK를 보냈다면, 1번부터 3번 패킷은 수신이 잘 되었음을 보장할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 Selective Repeat ARQ 방식의 경우라면, ACK 응답이 중간에 비어도 큰 문제가 되지 않는다. 송신측에서 타이머가 종료되면 재전송을 할 것이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이는 다른 말로는, 연산량이 크다는 것을 의미하기도 한다. 순서와 상관없이 도착한 패킷들에 대해 항상 재정렬을 해야 하기 때문이다. 따라서 Go Back N ARQ 프로토콜에 비해 계산량이 크다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재의 TCP는 Go Back N ARQ가 아닌, Selective Repeat 방식을 기본으로 사용한다. 다만 이는 운영체제의 구현에 따라 달라질 수 있으나 최근에는 컴퓨팅 능력의 향상으로 재정렬로 인한 오버헤드를 그렇게 크게 따지지 않는 것 같다. 관련해서 전공 교수님께 여쭤본 결과, 요즘에는 기기나 네트워크의 상황보다는 '전송하고자 하는 패킷의 특성'에 따라 결정되는 경우가 많다고 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상으로 TCP의 흐름, 오류, 혼잡제어를 모두 살펴봤다. 생각보다 쉽지 않은 내용이었고, 추후 추가적인 내용을 학습하게 된다면 글을 보강하도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://www.ktword.co.kr/test/view/view.php?nav=2&amp;amp;no=1299&amp;amp;sh=TCP+%EC%98%A4%EB%A5%98%EC%A0%9C%EC%96%B4&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;http://www.ktword.co.kr/test/view/view.php?nav=2&amp;amp;no=1299&amp;amp;sh=TCP+%EC%98%A4%EB%A5%98%EC%A0%9C%EC%96%B4&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://www.ktword.co.kr/test/view/view.php?no=1796&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;http://www.ktword.co.kr/test/view/view.php?no=1796&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.baeldung.com/cs/pseudo-header-tcp&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.baeldung.com/cs/pseudo-header-tcp&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://evan-moon.github.io/2019/11/22/tcp-flow-control-error-control/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://evan-moon.github.io/2019/11/22/tcp-flow-control-error-control/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ddongwon.tistory.com/81&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ddongwon.tistory.com/81&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.javatpoint.com/go-back-n-arq&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.javatpoint.com/go-back-n-arq&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Sliding_window_protocol#Examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://en.wikipedia.org/wiki/Sliding_window_protocol#Examples&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/sliding-window-protocol-set-2-receiver-side/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.geeksforgeeks.org/sliding-window-protocol-set-2-receiver-side/&lt;/a&gt;&lt;/p&gt;</description>
      <category>네트워크</category>
      <category>TCP 오류 제어</category>
      <author>teo_99</author>
      <guid isPermaLink="true">https://dev-ws.tistory.com/121</guid>
      <comments>https://dev-ws.tistory.com/121#entry121comment</comments>
      <pubDate>Thu, 11 Apr 2024 00:01:12 +0900</pubDate>
    </item>
    <item>
      <title>TCP Congestion Control</title>
      <link>https://dev-ws.tistory.com/120</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;혼잡 제어(Congestion Control)은 안정적인 네트워크 구축을 위해 필요한 기능이다. 인터넷을 비롯한 모든 네트워크는 하드웨어적 한계가 존재하므로 감당할 수 있는 용량이 정해져 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 용량을 초과하는 경우, 높은 대기 시간, 연결 시간 초과 및 패킷 손실과 같은 현상들이 발생하게 된다. 네트워크를 사용하는 입장에서는 이러한 정체가 발생하는 것을 좋아하진 않을 것이다. 또한 이러한 상황이 계속 누적되어 네트워크 정체가 점점 악화될 수도 있는데, 이를 &lt;b&gt;혼잡 붕괴(Congestion Collapse)&lt;/b&gt;라고 부르기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 정체가 발생했다면 전송하는 패킷의 수를 줄이는 등의 작업이 필요하다. 그리고 이러한 관리 기법을 &lt;b&gt;혼잡 제어(Congestion Control)&lt;/b&gt;이라고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP의 경우도 혼잡 제어를 통해 최대한 네트워크의 혼잡을 줄일 수 있도록 노력한다. 다만 TCP가 그렇다는 것이고, UDP 위에서 구현된 다른 프로토콜은 혼잡 제어를 수행하지 않을 수도 있다(즉, 네트워크를 배려하지 않을 수도 있다는 이야기다). 이후 알게 되겠지만, Slow Start와 같은 기법이 결국엔 성능을 저하시키기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Congestion Window&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flow control을 정리할 때, 윈도우라는 개념이 등장했었다. 잠깐 복기해보자면 TCP의 경우 Selective Repeat(Sliding Window 구현의 일종) 방식을 사용해 윈도우 크기를 동적으로 조절하면서 수신측의 버퍼가 넘치지 않게 흐름제어를 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비슷한 개념으로 TCP는 혼잡 제어에서 Congestion Window를 사용한다. Congestion Window란 &lt;b&gt;송신측이 ACK를 받기 전에 보낼 수 있는 패킷의 최대 개수&lt;/b&gt;를 의미한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;730&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5Ea8I/btsGqlvu6mm/WUy6OcaK07RFsH8bSypOn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5Ea8I/btsGqlvu6mm/WUy6OcaK07RFsH8bSypOn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5Ea8I/btsGqlvu6mm/WUy6OcaK07RFsH8bSypOn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5Ea8I%2FbtsGqlvu6mm%2FWUy6OcaK07RFsH8bSypOn1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;573&quot; height=&quot;391&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;730&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 말로만 이해하면 어려우니 그림을 통해 이해해보자. 송신측은 cwnd(Congestion Window Size) 값에 따라 보내는 패킷 수를 제한하고 있다. 가령 cwnd가 4라면 4개의 패킷을 전송한 뒤 더 이상 보내지 않고 ACK 패킷을 기다린다. 이처럼, flow control의 window와 congestion control의 window는 동일한 의미를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;'rwnd와 cwnd의 역할이 중복되지 않나'&lt;/b&gt; 라는 생각이 들 수 있겠으나, TCP의 경우&lt;b&gt;&amp;nbsp;rnwd 값과 cwnd 값 중 더 작은 값을 송신 윈도우 크기&lt;/b&gt;로 두기 때문에, 결국은 두 값 모두를 사용하는 것이라 볼 수 있겠다. 수식으로 표현하면 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;sender window size = min(rwnd, cwnd)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 TCP는 cwnd 값을 조절하면서 송신 측의 윈도우 사이즈를 조절하고, 이를 통해  혼잡제어를 수행한다. 그럼 어떻게 cwnd 값을 결정할까?&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 질문에 대한 답을 하기 전에, 미리 알아두어야 할 점이 있다. TCP의&lt;b&gt; 혼잡 제어 구현 방식은 하나가 아니라는 것&lt;/b&gt;이다. 관련 표준도 여러 개가 있으며 그에 따른 혼잡 제어 방식도 여러개가 존재한다. 따라서 단순히 'TCP는 이러이러한 혼잡제어 방식을 사용한다' 라고 단언해서는 안될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 혼잡제어 구현 방식은 &lt;b&gt;TCP Tahoe와 TCP Reno&lt;/b&gt;인데, 각각 1988년, 1990년에 릴리즈된 방식이다. 각각의 혼잡 제어 구현 방식이 사용하는 구체적인 기법들은 다음과 같은데, 이제 차례대로 하나씩 알아보도록 하겠다.&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;TCP Tahoe = Slow Start + AIMD + Fast Retransmit&lt;/blockquote&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;TCP Reno = Slow Start + AIMD + Fast Retransmit + Fast Recovery&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AIMD (Additive Increase / Multiplicative Decrease)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 AIMD 기법에 대해 살펴본다. AIMD는 Additive Increase, Mulitiplicative Decrease의 약자로, cwnd를 1씩 증가시키다가 절반으로 감소시키는 기법을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 cwnd를 통해 송신측의 패킷 전송량을 조절할 수 있다고 하였다. 즉,&lt;b&gt; AIMD는 송신측의 전송량을 차근차근 늘리다가, Packet Loss가 발생하면 전송량을 절반으로 줄여 혼잡을 회피하는 방법&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 구체적으로는, 요청을 보내고 응답이 오기까지의 시간인 &lt;b&gt;RTT(Round Trip Time)&lt;/b&gt; 당 한 개의 패킷을 더 보낼 수 있도록 윈도우 크기를 증가시킨다. 그리고 혼잡이 감지되는 상황이 발생하면 cwnd 값을 절반으로 줄인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보다 이해하기 쉽게 그림으로 나타내면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;521&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmV31E/btsGt6LXJ8W/4xQNtHRli4kNkq5Dcc7Nw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmV31E/btsGt6LXJ8W/4xQNtHRli4kNkq5Dcc7Nw0/img.png&quot; data-alt=&quot;https://www.learnsteps.com/do-you-know-about-the-aimd-method-in-tcp-congestion-control/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmV31E/btsGt6LXJ8W/4xQNtHRli4kNkq5Dcc7Nw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmV31E%2FbtsGt6LXJ8W%2F4xQNtHRli4kNkq5Dcc7Nw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;743&quot; height=&quot;461&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;521&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.learnsteps.com/do-you-know-about-the-aimd-method-in-tcp-congestion-control/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전송하는 패킷의 개수가 선형적으로 1씩 증가하다가(Linear Increase), 혼잡이라고 판단되는 순간에 절반으로 감소(Exponential Decrease)를 수행한다. 그리고 여기서 '혼잡이라고 판단되는 순간'이란, &lt;b&gt;Packet Loss나 Time out이 발생하는 상황 등&lt;/b&gt;을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 AIMD 기법은 공평함을 보장한다는 특징이 있다. 네트워크에 가장 처음 접속한 디바이스든, 가장 나중에 접속한 디바이스든 윈도우 크기가 동적으로 계속 커졌다 작아졌다를 반복하며 균등한 기회를 보장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 AIMD의 경우 직관적으로 예측할 수 있듯, &lt;b&gt;'선형적으로 증가한다는 특징'&lt;/b&gt; 때문에 빠르게 패킷을 전송하기는 어렵다. TCP를 기준으로 생각해보면 소켓 연결 이후는 항상 패킷을 하나만 보낼 수 있게 되는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Slow Start&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 비슷하지만 살짝 다른, Slow Start 혼잡 제어 기법이라는게 존재한다. AIMD의 경우 선형적으로 증가하지만, Slow Start의 경우는 &lt;b&gt;지수 차원으로 증가한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, cwnd값이 1이었다면 Slow Start 기법을 사용하는 경우 RTT마다 1, 2, 4, 8, 16 ... 과 같은 형태로 증가하게 된다. 따라서 AIMD 방식에 비해 빠르게 패킷 수를 늘려나갈 수 있어서 효율적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;847&quot; data-origin-height=&quot;497&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vc9yL/btsGu2a2tca/G4tqKeye9iuScin0jwuyoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vc9yL/btsGu2a2tca/G4tqKeye9iuScin0jwuyoK/img.png&quot; data-alt=&quot;Data Communications and Networking, 5th Edition&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vc9yL/btsGu2a2tca/G4tqKeye9iuScin0jwuyoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvc9yL%2FbtsGu2a2tca%2FG4tqKeye9iuScin0jwuyoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;745&quot; height=&quot;437&quot; data-origin-width=&quot;847&quot; data-origin-height=&quot;497&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Data Communications and Networking, 5th Edition&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slow Start의 경우, &lt;b&gt;혼잡이 감지된다면 윈도우 크기를 1로 줄이게 된다.&lt;/b&gt; AIMD의 경우에는 윈도우 크기를 절반으로 줄인 것과 대비하면 큰 낙폭이나, 지수적으로 증가한다는 점을 감안하면 어느정도 납득이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Fast Retransmit&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 Fast Retransmit 과정을 살펴보도록 하겠다. TCP Tahoe와 Reno의 혼잡 제어 기법들을 다시 복기하자면 다음과 같았다.&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;TCP Tahoe = Slow Start + AIMD + Fast Retransmit&lt;/blockquote&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;TCP Reno = Slow Start + AIMD + Fast Retransmit + Fast Recovery&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Fast Retransmit은 Tahoe, Reno 둘 다 지원하는 기능이며, Fast Recovery는 Reno에서만 지원한다.&lt;/b&gt; 용어가 비슷하니 해당 내용을 잊어버리지 말고 넘어가도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fast Retranbmit은 직역하면 '빠른 재전송'이다. 이는 즉, 오류가 발생한 상황이라면 빠르게 재전송을 하겠다는 의미이다. TCP의 경우 내부적으로 타이머가 존재해서 타이머가 작동하는 시간 내에 응답이 왔는지 오지 안왔는지로 패킷의 재전송 여부를 판단한다. (Retransmission Timeout)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 여기서 문제점은, 패킷의 전송 속도에 비해 time-out 시간이 너무 길 수 있다는 의미다. 즉, 패킷 손실이 발생했다고 하더라도 타이머가 종료되지 않으면 재전송을 하지 않는데, Fast Retransmit은 이러한 문제점을 해결하기 위한 기법이다. 타이머가 울리기도 전에 재전송을 시도한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 구현하기 위한 방법은 여러 가지가 있지만, 주로 &lt;b&gt;Duplicate ACK&lt;/b&gt; 기법을 사용한다. 이는 말 그대로 &lt;b&gt;중복된 ACK를 여러번 보내서 해당 패킷에 대한 소실 여부를 알려주겠다는 의미&lt;/b&gt;다. 송신측에서는 여러 번 ACK 응답을 받으면 Fast Retrasmit을 해야 한다고 판단하고, 타이머가 울리기도 전에 패킷을 재전송한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통상 Duplicate ACK의 기준이 되는 ACK 개수는 3개이며, 3개 이상의 ACK가 오면 &lt;b&gt;손실이 아닌 지연이라고 판단한다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5xqYB/btsGvvjKBeQ/wmHdPW75da7OxgGcXoG1O1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5xqYB/btsGvvjKBeQ/wmHdPW75da7OxgGcXoG1O1/img.png&quot; data-alt=&quot;Data Communications and Networking, 5th Edition&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5xqYB/btsGvvjKBeQ/wmHdPW75da7OxgGcXoG1O1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5xqYB%2FbtsGvvjKBeQ%2FwmHdPW75da7OxgGcXoG1O1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;498&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;516&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Data Communications and Networking, 5th Edition&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Fast Recovery&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fast Recovery는 '빠른 회복' 이라는 뜻이다. Slow Start 기법에 대해 조금 생각해보면, 다음과 같은 의문점이 들 수 있을 것이다. &lt;b&gt;'매번 혼잡이 감지될 때마다 cwnd 값을 1로 줄이면, 비효율적이지 않나?'&lt;/b&gt; 라는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 실제 TCP Reno에서는 이러한 단점을 보완하기 위해 Fast Recovery라는 기법을 사용한다. &lt;b&gt;이는 Duplicate ACK가 탐지되었을 때 cwnd 값을 1로 줄이는게 아니라, 일정 비율만 줄이는 방식을 의미한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 혼잡을 탐지하는 대표적인 방법으로 &lt;b&gt;time-out과 Duplicate ACK&lt;/b&gt;가 있다고 했다. Fast Recovery의 경우 time-out은 신경쓰지 않고, &lt;b&gt;Duplicate ACK에 대해서만 cwnd 값을 일정한 비율로 줄인다&lt;/b&gt;. 반대로 time-out에 대해서는 그대로 cwnd 값을 1로 줄인다. &amp;nbsp;이는 Dupliate ACK가 time-out에 비해 혼잡의 정도가 떨어진다고 가정을 하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;All in together&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 각각의 기법을 살펴봤으므로, TCP Tahoe와 Reno가 어떤 방식으로 혼잡을 제어하는지 종합적으로 살펴본다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TCP Tahoe&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Tahoe의 경우를 살펴보자. 앞서 Tahoe는 AIMD + Slow Start + Fast Retransmit 방식을 사용한다고 했다(Fast Recovery는 사용 X). &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그림으로 나타내면 아래와 같은데, 하나씩 차례대로 설명하도록 하겠다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;713&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xeQJq/btsGuM0uM8y/MToK16mjJzjX8xIdC21cP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xeQJq/btsGuM0uM8y/MToK16mjJzjX8xIdC21cP0/img.png&quot; data-alt=&quot;Data Communications and Networking, 5th Edition&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xeQJq/btsGuM0uM8y/MToK16mjJzjX8xIdC21cP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxeQJq%2FbtsGuM0uM8y%2FMToK16mjJzjX8xIdC21cP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;692&quot; height=&quot;464&quot; data-origin-width=&quot;713&quot; data-origin-height=&quot;478&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Data Communications and Networking, 5th Edition&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x축이 의미하는 바는 RTT(Round Trip Time)이고, y축이 의미하는 바는 cnwd다. 앞서 RTT 하나 당 cwnd 하나를 증가시키는게 AIMD, 2배씩 증가시키는게 Slow Start라고 했으므로 참고하면 좋을 듯 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼, Tahoe의 경우 그래프를 살펴보면 다음과 같은 특징이 있음을 알 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;송신을 시작한 뒤, cwnd는 1이 되고, Slow Start 방식으로 동작한다.&lt;/li&gt;
&lt;li&gt;Time-out이 발생하면 cwnd는 1이 되고, Slow Start 방식으로 동작하다가 ssthresh 값을 넘어서면 AIMD로 동작한다.&lt;/li&gt;
&lt;li&gt;Duplicate ACK가 발생하면 cwnd는 1이 되고, &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;Slow Start 방식으로 동작하다가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;ssthresh 값을 넘어서면 AIMD로 동작한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 ssthresh 값은 Slow Start Threshold라는 의미로, Slow Start를 끝낼 임계점을 의미한다. 매번 Slow Start로 (지수적으로) 증가하게 되면 cwnd 값이 큰 경우는 너무 빠르게 증가하기 때문에, 오히려 비효율적이기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벌써 눈치 챈 분도 있겠지만, Tahoe의 경우 ssthresh를 &lt;b&gt;'혼잡이 발생한 cwnd 크기의 절반'&lt;/b&gt; 으로 설정한다. Time-out이 발생했던 지점은 cwnd가 8일 때이므로, 이후 ssthresh는 절반인 4로 설정된다. Duplicate ACK가 발생하는 순간에는 cwnd가 12이므로, 6으로 설정되는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 이런 Tahoe의 경우 문제점이 존재하는데, &lt;b&gt;혼잡이 발생하면 매번 cwnd가 1로 초기화된다는 것&lt;/b&gt;이다. 이러한 문제점을 개선하고자 한 방식이 바로 TCP Reno이며, 앞서 설명한 Fast Recovery로 해결한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TCP Reno&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP Reno의 경우는 거두절미하고 그래프부터 살펴보도록 하겠다. 동작 방식을 빠르게 이해할 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;470&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Kwfda/btsGvs1DomU/LIBFP5Zureoo8ozyiMDsA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Kwfda/btsGvs1DomU/LIBFP5Zureoo8ozyiMDsA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Kwfda/btsGvs1DomU/LIBFP5Zureoo8ozyiMDsA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKwfda%2FbtsGvs1DomU%2FLIBFP5Zureoo8ozyiMDsA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;695&quot; height=&quot;457&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;470&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래프를 살펴보면 Tahoe의 경우와는 조금 다른데, Fast Recovery 알고리즘이 추가되었기 때문이다. 알고리즘이 조금 복잡한데, 하나씩 설명해보도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 말했듯 Fast Recovery는 Time-out에 관여하지 않으므로, 우선 Duplicate ACK 부분만 살펴보자. 3 Duplicate ACK가 발생하는 순간, 혼잡이 발생한 것이므로 어떠한 조치를 취해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tahoe의 경우라면 cwnd를 바로 1로 내려버리면 그만이었지만, Reno의 경우는 Fast Recovery 알고리즘에 의해 1이 아닌 9로 설정한다. 여기서 9로 설정한 이유는 Fast Recovery에 대한 알고리즘을 규정하는 RFC 2001에서 확인할 수 있는데, 원칙적으로는 cwnd = ssthresh + 3으로 설정하기 때문이다. 어떤 자료들은 Fast Recovery에서 cwnd 값을 절반으로 설정한다고 주장하기도 하는데, 표준에 따르면 ssthresh + 3으로 설정이 되는게 맞다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ssthresh는 혼잡이 발생한 cwnd의 절반으로 설정된다 하였으므로 6이 되고, 그에 따라 Fast Recovery의 시작점도 6 + 3 = 9가 된다. Fast Recovery 시작 이후에도 지수적으로 증가하다가, &lt;b&gt;'new ACK'를 만나는 순간 cwnd를 ssthreshold 값까지 내린다. &lt;/b&gt;그리고 Fast Recovery를 종료하고, AIMD로 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말이 조금 어려운데, 순서대로 정리하면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Duplicate ACK를 마주하면 Fast Recovery 단계에 돌입한다.&lt;/li&gt;
&lt;li&gt;ssthresh 값을 혼잡이 발생한 cwnd의 절반으로 설정, cwnd는 ssthresh + 3으로 설정한다.&lt;/li&gt;
&lt;li&gt;이후 지수적으로 증가시키다가, 오류가 발생한 패킷들을 모두 재전송해 새로운 ACK 응답을 받는다면 Fast Recovery를 종료한다.&lt;/li&gt;
&lt;li&gt;Fast Recovery가 종료된 이후에는 cwnd를 ssthresh로 설정하고 AIMD로 동작한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP의 혼잡 제어 기법을 살펴봤다. 생각보다 내용이 더 어려웠고, 교차 검증을 하면서 글을 쓰는 데에도 상당한 시간이 걸렸다.  이로써 TCP의 흐름 제어, 혼잡 제어 기법을 살펴봤는데, 오류 제어에 대한 글도 조만간 작성하고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://www.ktword.co.kr/test/view/view.php?m_temp1=1469&amp;amp;id=746&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;http://www.ktword.co.kr/test/view/view.php?m_temp1=1469&amp;amp;id=746&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://www.ktword.co.kr/test/view/view.php?no=5249&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;http://www.ktword.co.kr/test/view/view.php?no=5249&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://www.ktword.co.kr/test/view/view.php?no=5536&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;http://www.ktword.co.kr/test/view/view.php?no=5536&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://www.ktword.co.kr/test/view/view.php?m_temp1=5249&amp;amp;id=1102&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;http://www.ktword.co.kr/test/view/view.php?m_temp1=5249&amp;amp;id=1102&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/what-is-network-congestion-common-causes-and-how-to-fix-them/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.geeksforgeeks.org/what-is-network-congestion-common-causes-and-how-to-fix-them/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/tcp-tahoe-and-tcp-reno/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.geeksforgeeks.org/tcp-tahoe-and-tcp-reno/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/what-is-rttround-trip-time/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.geeksforgeeks.org/what-is-rttround-trip-time/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.isi.edu/nsnam/DIRECTED_RESEARCH/DR_WANIDA/DR/JavisInActionFastRecoveryFrame.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.isi.edu/nsnam/DIRECTED_RESEARCH/DR_WANIDA/DR/JavisInActionFastRecoveryFrame.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://evan-moon.github.io/2019/11/26/tcp-congestion-control/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://evan-moon.github.io/2019/11/26/tcp-congestion-control/&lt;/a&gt;&lt;/p&gt;</description>
      <category>네트워크</category>
      <category>TCP Congestion control</category>
      <category>TCP 혼잡 제어</category>
      <author>teo_99</author>
      <guid isPermaLink="true">https://dev-ws.tistory.com/120</guid>
      <comments>https://dev-ws.tistory.com/120#entry120comment</comments>
      <pubDate>Mon, 8 Apr 2024 23:50:41 +0900</pubDate>
    </item>
  </channel>
</rss>