<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dev_angie.log</title>
        <link>https://velog.io/</link>
        <description>열심히 달리는 개발자</description>
        <lastBuildDate>Fri, 03 Oct 2025 04:29:56 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dev_angie.log</title>
            <url>https://velog.velcdn.com/images/dev_angie/profile/6229d4b1-6a77-45a8-ae0a-54468bf5e098/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dev_angie.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev_angie" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Green Developers 프로젝트 후기]]]></title>
            <link>https://velog.io/@dev_angie/Green-Developers-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@dev_angie/Green-Developers-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Fri, 03 Oct 2025 04:29:56 GMT</pubDate>
            <description><![CDATA[<h3 id="프로젝트-소개">프로젝트 소개</h3>
<p>이 프로젝트는 사용자가 자신의 취향과 여행 목적에 맞게 자유롭게 여행 계획을 구성할 수 있도록 돕는 것을 목표로 합니다. 단순히 정해진 일정표를 제공하는 것이 아니라, 여행지를 선택하고 동선을 짜는 과정에서 개인의 선호와 상황을 충분히 반영할 수 있도록 설계되었습니다. 또한 여행 준비 단계뿐만 아니라 실제 여행을 진행하는 중에도, 새로운 장소를 추가하거나 불필요한 일정을 삭제하는 등 계획을 유연하게 수정할 수 있습니다. 이를 통해 사용자는 언제든지 자신에게 최적화된 일정을 손쉽게 관리할 수 있으며, 여행 도중 발생할 수 있는 돌발 상황에도 빠르게 대응할 수 있습니다. 결과적으로 이 플랫폼은 ‘사용자 중심의 여행 계획 서비스’라는 가치에 초점을 두고, 보다 편리하고 실용적인 여행 경험을 제공하고자 했습니다.</p>
<p>👉 결과물 링크: <a href="https://github.com/SWYP-Roadream">https://github.com/SWYP-Roadream</a></p>
<h3 id="ncloud에서-활용한-서비스">Ncloud에서 활용한 서비스</h3>
<p>프로젝트에서는 <strong>Naver Cloud Platform(NCP)</strong>의 다양한 서비스를 활용했습니다.</p>
<ul>
<li>Compute &amp; Network: VPC, Subnet, Server, Load Balancer</li>
<li>Storage: Object Storage</li>
<li>Security: SSL 인증서</li>
</ul>
<h3 id="ncloud-적용-과정">Ncloud 적용 과정</h3>
<p>이번 PoC/시연 단계에서는 비용과 일정 제약에 따라 단일 서버에 애플리케이션과 DB를 함께 구동했습니다.</p>
<p>외부 트래픽은 Load Balancer를 통해 유입되며, SSL 인증서를 LB에서 종단시켜 보안과 도메인 관리를 단순화했습니다.</p>
<p>이미지와 파일은 Object Storage에 저장하여 서버 디스크 의존도를 낮추고, 추후 CDN 연계까지 고려 가능한 구조로 설계했습니다.</p>
<p>이 구성은 빠른 구축·낮은 비용과 운영 단순성을 확보하면서, 필요 시 LB 뒤에 서버만 수평 확장해 무중단 확장이 가능하도록 여지를 남겼습니다.</p>
<p>실제 시연에서도 단일 서버 기준으로 요청 처리에 무리가 없었고, 안정적으로 동작함을 확인했습니다.</p>
<p>(선택·후속 계획 한 줄)
→ 차기 버전에서는 Dev/Prod 분리, DB의 Private Subnet/Cloud DB 분리, 모니터링/알림을 단계적으로 도입할 예정입니다.</p>
<h3 id="만족했던-점과-아쉬웠던-점">만족했던 점과 아쉬웠던 점</h3>
<p>*<em>만족스러웠던 점
*</em>클라우드 자원 관리의 유연성입니다. Terraform과 콘솔을 병행해 인프라를 빠르게 구성하고, 필요 시 스펙 변경·확장/축소를 수월하게 수행했습니다. 코드 기반 관리로 변경 이력 추적과 재현성도 확보할 수 있었습니다.</p>
<p>*<em>아쉬웠던 점
*</em>모든 서비스를 Terraform만으로 완전히 제어하기는 어려웠습니다. 일부 서비스/옵션은 프로바이더 지원이 제한적이어서 콘솔 수동 설정을 병행해야 했고, 이로 인해 상태 드리프트(state drift) 가능성이 존재했습니다. 또한 인증서·모니터링 등 일부 영역은 선언적 자동화 범위가 제한되어 운영 자동화에 제약이 있었습니다.</p>
<h3 id="green-developers-프로그램-참여-소감">Green Developers 프로그램 참여 소감</h3>
<p>이번 프로그램을 통해 단순히 서비스를 만드는 것을 넘어, 클라우드 인프라와 DevOps에 대한 실제 경험을 쌓을 수 있었습니다. 교육과 실습이 함께 진행되면서 프로젝트 결과물이 더 풍부해졌고, 다른 참가자분들의 아이디어도 큰 자극이 되었습니다.</p>
<h3 id="마지막-한-말씀">마지막 한 말씀</h3>
<p>프로젝트를 진행하면서 아키텍처 설계와 클라우드 자원 운용에 대해 많이 배울 수 있었고, 팀 협업 과정에서도 큰 성장을 느꼈습니다. 앞으로도 개인 프로젝트나 회사 업무에서 Ncloud를 적극적으로 활용할 계획입니다. Green Developers 프로그램이 계속 이어진다면, 더 많은 개발자들이 실전 경험을 쌓을 수 있을 거라 생각합니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C] C언어 문법 정리하기]]></title>
            <link>https://velog.io/@dev_angie/C-%EA%B3%B5</link>
            <guid>https://velog.io/@dev_angie/C-%EA%B3%B5</guid>
            <pubDate>Wed, 18 Jun 2025 06:50:57 GMT</pubDate>
            <description><![CDATA[<h2 id="1-문자열-비교strcmp---string-compare">1. 문자열 비교(<code>strcmp</code>) - string compare</h2>
<pre><code class="language-c">strcmp(ineq, &quot;&gt;&quot;) == 0 //string compare</code></pre>
<ul>
<li>if(ineq == “&gt;”) 안됨!!</li>
<li><strong>C에서는 문자열을 == 로 비교하면 주소 비교라서 안됨. 내용 비교는 반드시 strcmp() 사용</strong></li>
</ul>
<h2 id="2-문자열-붙이기strcat---string-catenate">2. 문자열 붙이기(<code>strcat</code>) - string catenate</h2>
<ul>
<li><code>strcat()</code> 사용<pre><code class="language-c">#include &lt;string.h&gt;
</code></pre>
</li>
</ul>
<p>char str[100] = &quot;Hello &quot;;
strcat(str, &quot;world!&quot;);</p>
<pre><code>
## 3. int ↔ char (`sprintf`) / char ↔ int 변환 (`atoi`)

```c
&#39;0&#39; + num // 숫자 하나를 문자
sprintf(str, &quot;%d&quot;, num); // 정수 -&gt; 문자열
sprintf(str, &quot;%d%d&quot;, a, b) // 이런식으로도 사용 가능!
atoi(str) // 문자열 -&gt; 정수</code></pre><h2 id="4-문자열-복사---strdup">4. 문자열 복사 - <code>strdup()</code></h2>
<ul>
<li><code>strdup()</code> 은 문자열을 복사해서 새로운 메모리 공간에 저장하는 함수</li>
</ul>
<pre><code class="language-c">char* strdup(const char* s);</code></pre>
<ul>
<li><p>인자로 받은 문자열 s를 복사해서, malloc으로 새 메모리에 저장한 뒤, 그 복사된 문자열의 포인터를 리턴</p>
</li>
<li><p>⚠️ strdup()은 malloc() 기반이라 반드시 free() 해야해!</p>
<pre><code class="language-c">  char *s = strdup(&quot;banana&quot;);
  // 사용 후 반드시!!
  free(s);</code></pre>
</li>
<li><p><strong>strcpy(dest, src), strncpy(dest, src, n) 과의 차이가 뭘까</strong></p>
</li>
</ul>
<pre><code>| 함수 | 의미 | 특징 |
| --- | --- | --- |
| `strcpy(dest, src)` | 문자열 복사 | 끝까지 복사 (`\0` 포함), **위험할 수 있음** |
| `strncpy(dest, src, n)` | 문자열 n글자 복사 | 길이 제한 있음, **`\0` 자동 보장 X** |
| `strdup(src)` | 문자열 복사해서 **새 메모리에 저장** | `malloc` + `strcpy` 한 번에 처리 |
| `memcpy(dest, src, n)` | 메모리 복사 | 문자열이 아니라 **바이트 단위** 복사, **널 종료 없음** |
| `strlcpy(dest, src, size)` *(비표준)* | 안전한 복사 | 항상 `\0` 보장, **BSD 계열만** (Linux엔 없음) |</code></pre><h2 id="5-qsort-란">5. <code>qsort()</code> 란?</h2>
<ul>
<li>C에서 제공하는 범용 정렬 함수.</li>
</ul>
<pre><code class="language-c">void qsort(void* base, size_t nitems, int (*compar)(const void*, const void*));</code></pre>
<ul>
<li>매개변수 하나씩 설명<ul>
<li><code>base</code> : 정렬할 배열의 시작 주소</li>
<li><code>nitems</code> : 배열의 요소 개수</li>
<li><code>size</code> : 배열 하나당 요소 크기 (바이트)</li>
<li><code>compar</code> : 정렬 기준이 담긴 함수 포인터 (비교 함수)</li>
</ul>
</li>
<li>예제 1: 숫자 배열 오름차순 정렬</li>
</ul>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;

int cmp(const void* a, const void* b) {
    return (*(int*)a - *(int*)b);
}

int main() {
    int arr[] = {5, 3, 2, 4, 1};
    int len = sizeof(arr) / sizeof(arr[0]);

    qsort(arr, len, sizeof(int), cmp);

    for (int i = 0; i &lt; len; i++) {
        printf(&quot;%d &quot;, arr[i]);  // 👉 1 2 3 4 5
    }
}</code></pre>
<ul>
<li>예제 2: 문자열 배열 정렬</li>
</ul>
<pre><code class="language-c">int cmp_str(const void* a, const void* b) {
    return strcmp(*(char**)a, *(char**)b);
}

char* words[] = {&quot;banana&quot;, &quot;apple&quot;, &quot;cherry&quot;};

qsort(words, 3, sizeof(char*), cmp_str);</code></pre>
<ul>
<li>비교 함수(cmp) 규칙</li>
</ul>
<pre><code class="language-c">int cmp(const void* a, const void* b) {
    // 비교 결과를 리턴:
    // 음수 → a &lt; b
    // 0    → a == b
    // 양수 → a &gt; b
}</code></pre>
<h2 id="6-포인터-vs-배열">6. 포인터 VS 배열</h2>
<h2 id="🔁-정리-비교표">🔁 정리 비교표</h2>
<table>
<thead>
<tr>
<th>표현</th>
<th>의미</th>
<th>변경 가능?</th>
<th>메모리 위치</th>
</tr>
</thead>
<tbody><tr>
<td><code>&quot;abc&quot;</code></td>
<td>문자열 리터럴</td>
<td>❌ 읽기 전용</td>
<td>상수 영역 (Read-Only)</td>
</tr>
<tr>
<td><code>char* str = &quot;abc&quot;;</code></td>
<td>문자열 리터럴을 가리키는 포인터</td>
<td>❌</td>
<td>읽기 전용 영역</td>
</tr>
<tr>
<td><code>char str[] = &quot;abc&quot;;</code></td>
<td>문자열을 복사한 배열</td>
<td>✅</td>
<td>스택 영역 (또는 데이터 영역)</td>
</tr>
</tbody></table>
<h3 id="문자열-리터럴을-수정하고-싶을-때">문자열 리터럴을 수정하고 싶을 때?</h3>
<pre><code class="language-c">char* src = &quot;abc&quot;;
char str[10];
strcpy(str, src);
srt[0] = &#39;x&#39;;  //수정 가능

/*
동적 메모리 할당 방법
*/
char* src = &quot;abc&quot;;
char* str = (char*)malloc(strlen(src)+1);
strcpy(str, src);
str[0] = &#39;x&#39; // 가</code></pre>
<h2 id="7-c-문자열포인터배열-정리-헷갈렸던-포인트-중심">7. C 문자열/포인터/배열 정리 (헷갈렸던 포인트 중심)</h2>
<pre><code class="language-c">char* a = &quot;abc&quot;;
printf(&quot;%c&quot;, a[0]); // -&gt; &#39;a&#39;</code></pre>
<ul>
<li>포인터도 배열처럼 a[i] 사용 가능 (포인터 산술 *(a+i))</li>
</ul>
<pre><code class="language-c">// 포인터 배열: 문자열 리터럴을 가리킴 -&gt; 수정 불가
// char* 3개를 담는 배열 
char* arr[3] = {&quot;a&quot;, &quot;b&quot;, &quot;c&quot;}
printf(&quot;%s&quot;, arr[0]);  // 출력: a
printf(&quot;%c&quot;, arr[1][0]);  // 출력: b</code></pre>
<ul>
<li>%s : 널 문자(\0)로 끝나는 문자열의 시작 주소를 받아서, 그 주소부터 하나씩 문자 출력</li>
</ul>
<h3 id="malloc은-바이트-단위-⇒-최소화"><code>malloc</code>은 바이트 단위 ⇒ 최소화!!!!!!</h3>
<ul>
<li>malloc? memory(메모리) + allocation(할당) ⇒ 필요한 만큼 메모리를 동적으로 빌려주는 함</li>
<li>할당 후에는 꼭 free()로 해제 → 안하면 메모리 누수 생김!</li>
<li>동적으로 사이즈 조절도 가넝</li>
</ul>
<pre><code class="language-c">char* str = malloc(10);           // ✅ 가능 (1바이트 * 10)
int* arr = malloc(sizeof(int) * N);        // ✅ 40바이트 -&gt; int 10개 저장 가능  
int* arr = malloc(10);            // ❌ 버그 (10바이트 -&gt; int는 4바이트라서 2~3개만 들어감)</code></pre>
<ul>
<li>malloc(N)은 N 바이트 → int 배열이라면 <code>sizeof(int) * N</code> 필요</li>
</ul>
<hr>
<h2 id="왜-c는-포인터를-많이-쓸까">왜 C는 포인터를 많이 쓸까??</h2>
<ul>
<li>C는 하드웨어에 가까운 언어라서, 메모리를 직접 다루는 게 설계 철학이고 그걸 가능하게 해주는 도구가 바로 <code>포인터</code> 이다.</li>
</ul>
<aside>
💡

<h3 id="1-c는-메모리를-주소로-다루는-언어">1. C는 메모리를 “주소”로 다루는 언어</h3>
<ul>
<li>모든 변수는 메모리에 저장되고, 그 메모리는 주소로 접근</li>
<li>포인터는 그 주소를 직접 조작하거나 전달할 수 있는 도구</li>
</ul>
<pre><code class="language-c">int x = 10;
int* p - &amp;x; // x의 주소를 p에 저장</code></pre>
<h3 id="2-값을-복사하지-않고-직접-참조할-수-있음">2. 값을 복사하지 않고, 직접 “참조”할 수 있음</h3>
<ul>
<li>예: 배열 넘기기</li>
</ul>
<pre><code class="language-c">void modify (int* arr) {
    arr[0] = 100;
}</code></pre>
<ul>
<li>배열 자체를 복사하면 느리고 비효율적이니까 주소를 넘겨서 원본을 찹조하게 만든다. → 성능, 메모리 효율, 직접 조작 모두 가능!!</li>
</ul>
<h3 id="3-c에는-문자열-타입이-없음-→-포인터로-다룸">3. C에는 “문자열 타입”이 없음 → 포인터로 다룸</h3>
<ul>
<li>char str[] = “hello”; 는 사실상 char*로 동작함</li>
<li>문자열은 그냥 문자들의 배열이고, C는 배열을 넘기면 포인터로 취급</li>
</ul>
<pre><code class="language-c">void print(const char* str) {
    printf(&quot;%s\n&quot;, str);
}</code></pre>
<h3 id="4-동적-메모리-할당이-전부-포인터-기반">4. 동적 메모리 할당이 전부 포인터 기반</h3>
<ul>
<li><code>malloc()</code> 은 메모리를 런타임에 직접 확보</li>
<li>확보된 메모리는 주소값(포인터)로만 접근 가능</li>
</ul>
<pre><code class="language-c">int* p = malloc(sizeof(int) * 10);</code></pre>
<h3 id="5-c에서-함수는-리턴값을-하나만-줄-수-있다">5. C에서 함수는 리턴값을 하나만 줄 수 있다.</h3>
<pre><code class="language-c">void getSize(int* width, int* height) {
    *width = 1920;
    *height = 1080;
}

int main() {
    int w, h;
    getSize(&amp;w, &amp;h);
    printf(&quot;%d x %d\n&quot;, w, h);  // 👉 1920 x 1080
}</code></pre>
</aside>

<ul>
<li><strong>포인터는 ‘주소’를 담는 변수다!!!!!!!!!!</strong></li>
</ul>
<p><strong>[ 값의 복사 ]</strong></p>
<table>
<thead>
<tr>
<th>개념</th>
<th>C</th>
<th>Java</th>
</tr>
</thead>
<tbody><tr>
<td>기본형 전달</td>
<td>값 복사</td>
<td>값 복사</td>
</tr>
<tr>
<td>배열/객체 전달</td>
<td>포인터(주소)를 직접 전달</td>
<td>참조값을 복사해서 전달</td>
</tr>
<tr>
<td>원본 변경 가능?</td>
<td>포인터 쓰면 가능</td>
<td>참조값을 통해 내부는 변경 가능</td>
</tr>
<tr>
<td>원본 자체 교체 가능?</td>
<td>가능 (<code>*x = ...</code>)</td>
<td>불가능 (참조 자체는 복사본)</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 크루스칼 알고리즘(Kruskal Algorithm)]]></title>
            <link>https://velog.io/@dev_angie/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%81%AC%EB%A3%A8%EC%8A%A4%EC%B9%BC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98Kruskal-Algorithm</link>
            <guid>https://velog.io/@dev_angie/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%81%AC%EB%A3%A8%EC%8A%A4%EC%B9%BC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98Kruskal-Algorithm</guid>
            <pubDate>Mon, 25 Nov 2024 06:36:08 GMT</pubDate>
            <description><![CDATA[<h2 id="크루스칼-알고리즘kruskal-algorithm">크루스칼 알고리즘(Kruskal Algorithm)</h2>
<blockquote>
<p>크루스칼 알고리즘은 그래프에서 최소 비용 신장 부분 트리(최소 신장 트리: Minimum Spanning Tree)를 찾는 알고리즘이다.</p>
</blockquote>
<h3 id="매커니즘">매커니즘</h3>
<ul>
<li>크루스칼 알고리즘은 기본적으로 그리디한 선택을 바탕으로 알고리즘을 진행한다.</li>
</ul>
<ol>
<li>주어진 그래프의 모든 간선에 대해, 간선의 연결비용을 낮은 순으로 오름차순 정렬한다.</li>
<li>정렬된 간선 순서대로 선택하면서, 간선의 양 끝 정정을 Union 한다. 단, 이때 선택된 두 정점이 같은 집합에 속해있다면 사이클(cycle)이 있다고 판단하여 포함시키지 않는다)</li>
</ol>
<ul>
<li>이러한 매커니즘을 바탕으로 최종 선택된 간선을 연결하는 것이 최소 비용 신장트리이다.  </li>
</ul>
<h3 id="과정">과정</h3>
<p><img src="https://velog.velcdn.com/images/dev_angie/post/419900f6-68cf-4dc3-bae2-7d58caa705f1/image.png" alt=""></p>
<ul>
<li>비용이 적은 간선의 양 끝 노드를 선택하고 <strong>사이클이 존재하지 않는다면 Union 및 해당 간선 선택, 존재한다면 스킵</strong>하는 형태로 모든 간선에 대하여 탐색이 끝날 때까지 동작을 반복한다. </li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_angie/post/d05a9ea3-9362-4965-9a07-0ce0afa03fb0/image.png" alt=""></p>
<ul>
<li>먼저 첫번째 간선을 선택한다. 두 노드 2,3은 서로 독립된 집합이기 떄문에 Union을 진행하고 간선도 선택한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_angie/post/deedd038-aa56-4c50-82d6-cb2a2eaff469/image.png" alt=""></p>
<ul>
<li>마찬가지 방법으로 두번쨰 간선을 선택한다. 두 노드 1,6은 서로 독립된 집합이므로 Union 및 간선을 선택한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_angie/post/cec5e98d-7e25-4f2d-8289-93b13a465a0a/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev_angie/post/65a5e2e1-8c44-4759-9ead-79523adf2d03/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev_angie/post/fb22b0b6-60e5-4658-bb8a-2c0014bdf9b6/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev_angie/post/b9812d94-115f-4228-891b-9865ad94c548/image.png" alt=""></p>
<ul>
<li>5, 6번 노드를 간선에 대해서는 각 노드 5번의 최종 부모는 1, 6번의 최종 부모는 1로 두 집합은 이미 같은 집합에 속해있음을 알 수 있다. 그림에서 확인해보면 알겠지만, 두 노드를 연결하는 순간 사이클이 발생한다. 따라서, 이 경우는 스킵한다.  </li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_angie/post/2c53bc40-7491-4337-9abe-2f4c5926ee1a/image.png" alt=""></p>
<ul>
<li>나머지 노드에 대해서도 마찬가지로 진행해주면, 최종적으로 선택된 간선(빨간색)이 최소 신장트리(MST)가 됨을 확인할 수 있다.</li>
</ul>
<h3 id="구현java">구현(JAVA)</h3>
<pre><code class="language-java">import java.util.Arrays;
import java.util.Scanner;

/*
sample input(첫 줄의 첫 숫자는 정점의 개수, 두 번째 숫자는 간선의 개수).
6 9
1 6 5
2 4 6
1 2 7
3 5 15
5 6 9
3 4 10
1 3 11
2 3 3
4 5 7
 */

public class Kruskal {
    static int V, E;
    static int[][] graph;
    // 각 노드의 부모
    static int[] parent;
    // 최종적으로 연결된 최소 신장 트리 연결 비용.
    static int final_cost;

    public static void main(String[] args) {
        // 그래프의 연결상태(노드1, 노드2, 비용)를 초기화.
        Scanner sc = new Scanner(System.in);
        V = sc.nextInt();
        E = sc.nextInt();
        graph = new int[E][3];
        for (int i = 0; i &lt; E; i++) {
            graph[i][0] = sc.nextInt();
            graph[i][1] = sc.nextInt();
            graph[i][2] = sc.nextInt();
        }
        parent = new int[V];
        final_cost = 0;

        // 간선 비용 순으로 오름차순 정렬
        Arrays.sort(graph, (o1, o2) -&gt; Integer.compare(o1[2], o2[2]));

        // makeSet
        for (int i = 0; i &lt; V; i++) {
            parent[i] = i;
        }
        // 낮은 비용부터 크루스칼 알고리즘 진행
        for (int i = 0; i &lt; E; i++) {
            // 사이클이 존재하지 않는 경우에만 간선을 선택한다(여기서는 최종 비용만 고려하도록 하겠다).
            if (find(graph[i][0] - 1) != find(graph[i][1] - 1)) {
                System.out.println(&quot;&lt;선택된 간선&gt;&quot;);
                System.out.println(Arrays.toString(graph[i]));
                union(graph[i][0] - 1, graph[i][1] - 1);
                final_cost += graph[i][2];
                System.out.println(&quot;&lt;각 노드가 가리키고 있는 부모&gt;&quot;);
                System.out.println(Arrays.toString(parent) + &quot;\n&quot;);
                continue;
            }
        }

        System.out.println(&quot;최종 비용 : &quot; + final_cost);
        sc.close();
    }

    private static void union(int a, int b) {
        a = find(a);
        b = find(b);
        if (a &gt; b) {
            parent[a] = b;
        } else {
            parent[b] = a;
        }
    }

    private static int find(int x) {
        if (parent[x] == x)
            return x;
        else
            return find(parent[x]);
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자료구조] HashMap 함수]]></title>
            <link>https://velog.io/@dev_angie/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-HashMap-%ED%95%A8%EC%88%98</link>
            <guid>https://velog.io/@dev_angie/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-HashMap-%ED%95%A8%EC%88%98</guid>
            <pubDate>Fri, 08 Nov 2024 01:54:57 GMT</pubDate>
            <description><![CDATA[<h3 id="hashmap이란">HashMap이란?</h3>
<p>Map 인터페이스의 한 종류로써 key와 Value 값으로 데이터를 저장하는 형태이다. </p>
<p>또한, 해싱(Hashing)이란 검색방법을 사용하여 많은 양의 데이터를 검색하는데 있어 뛰어난 성능을 보인다.</p>
<p>HashMap에서 주의할점이 있다면 map 데이터를 등록할 떄, key 값을 중복되지 않고 value는 중복이 허용된다는 점이다. </p>
<h3 id="hashmapmap-사용하기">HashMap(Map) 사용하기</h3>
<h4 id="put">put</h4>
<pre><code class="language-java">HashMap&lt;String, String&gt; map = new HashMap&lt;String, String&gt;(); // HashMap 생성
map.put(&quot;people&quot;, &quot;사람&quot;);
map.put(&quot;baseball&quot;, &quot;야구&quot;);</code></pre>
<h4 id="putifabsent">putIfAbsent</h4>
<p>지정된 키가 맵에 없을 경우에만 해당 키-값 쌍을 추가한다. 만약 키가 이미 존재하면 값을 덮어쓰지 않는다. </p>
<pre><code class="language-java">Map&lt;String, Integer&gt; map = new HashMap&lt;&gt;();
map.putIfAbsent(&quot;pop&quot;, 150); // 장르가 없으므로 추가
map.putIfAbsent(&quot;pop&quot;, 200); // &quot;pop&quot; 장르는 이미 존재하므로 값이 바뀌지 않음</code></pre>
<h4 id="get">get</h4>
<p>key에 해당되는 값을 얻기 위해서 사용한다.</p>
<pre><code class="language-java">map.get(&quot;people&quot;);</code></pre>
<p>value값인 &quot;사람&quot;이라는 문자열을 출력한다. </p>
<h4 id="getordefault">getOrDefault</h4>
<p>맵에서 지정한 키에 대한 값을 반환한다. 그러나 키가 존재하지 않으면 지정된 기본값을 반환한다.</p>
<pre><code class="language-java">Map&lt;String, String&gt; map = new HashMap&lt;&gt;();
int value = map.getOrDefault(&quot;pop&quot;, 0) // &quot;pop&quot; 장르가 없으므로 0을 반환한다.
</code></pre>
<h4 id="containskey">containsKey</h4>
<p>continsKey 메소드는 맵(Map)에 해당 키(key)가 있는지를 조사하여 그 결과값을 boolean으로 리턴한다.</p>
<pre><code class="language-java">map.containsKey(&quot;people&quot;);</code></pre>
<p>true가 리턴된다.</p>
<h4 id="remove">remove</h4>
<p>remove 메소드는 맵(Map)의 항목을 삭제하는 메소드로 key값에 해당되는 아이템(key,value)을 삭제한 후 그 value값을 리턴한다. </p>
<pre><code class="language-java">System.out.println(map.remove(&quot;people));
</code></pre>
<p>&quot;people&quot;에 해당하는 아이템이 삭제된 후 &quot;사람&quot;이 출력된다.</p>
<h4 id="size">size</h4>
<p>size 메소드는 Map의 개수를 리턴한다.</p>
<pre><code class="language-java">map.size()</code></pre>
<h4 id="entryset을-사용한-순회">entrySet()을 사용한 순회</h4>
<pre><code class="language-java">    Map&lt;String, String&gt; map = new HashMap&lt;&gt;();
    map.put(&quot;people&quot;, &quot;사람&quot;);
    map.put(&quot;baseball&quot;, &quot;야구&quot;);

    for(Map.Entry&lt;String, String&gt; entry : map.entrySet()){

    String key = entry.getKey();
    String value = entry.getValue();
    System.out.println(key + &quot;: &quot; + value);
    }</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kafka] Django와 Kafka를 활용한 메시지 전송 및 수신]]></title>
            <link>https://velog.io/@dev_angie/Kafka-Python%EA%B3%BC-Kafka%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%A9%94%EC%8B%9C%EC%A7%80-%EC%A0%84%EC%86%A1-%EB%B0%8F-%EC%88%98%EC%8B%A0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev_angie/Kafka-Python%EA%B3%BC-Kafka%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%A9%94%EC%8B%9C%EC%A7%80-%EC%A0%84%EC%86%A1-%EB%B0%8F-%EC%88%98%EC%8B%A0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 21 Oct 2024 06:47:41 GMT</pubDate>
            <description><![CDATA[<h2 id="1-docker로-kafka-설치하기">1. Docker로 Kafka 설치하기</h2>
<h4 id="1docker-composeyml-파일을-생성하고-작성">1)docker-compose.yml 파일을 생성하고 작성</h4>
<pre><code class="language-yaml">version: &#39;2&#39;
services:
  zookeeper:
    image: wurstmeister/zookeeper
    container_name: zookeeper
    ports:
      - &quot;2181:2181&quot;

  kafka:
    image: wurstmeister/kafka:2.12-2.5.0
    container_name: kafka
    ports:
      - &quot;9092:9092&quot;
    environment:
      KAFKA_ADVERTISED_HOST_NAME: 127.0.0.1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
</code></pre>
<ul>
<li>카프카는 설치를 할 때 2개의 이미지를 이용</li>
<li>카프카 와 주키퍼(카프카 코디네이터)를 같이 설치</li>
</ul>
<h4 id="2터미널에서-명령을-수행---클러스터-1개를-가진-카프카-서버를-실행">2)터미널에서 명령을 수행 - 클러스터 1개를 가진 카프카 서버를 실행</h4>
<pre><code class="language-bash">docker-compose up -d</code></pre>
<h4 id="3외부에서-사용할-수-있도록-설정-변경">3)외부에서 사용할 수 있도록 설정 변경</h4>
<ul>
<li>터미널에서 도커 컨테이너 안으로 접속<pre><code class="language-bash">docker exec -it kafka /bin/bash</code></pre>
</li>
<li>설정 파일을 수정 - 내용을 추가<pre><code class="language-bash">cd /opt/kafka/config
 vi server.properties

</code></pre>
</li>
</ul>
<p>   listeners=PLAINTEXT://:9092<br>   delete.topic.enable=true<br>   auto.create.topics.enable=true  </p>
<pre><code>#### 4)토픽 생성과 조회 및 삭제
- 명령어를 사용하기 위해서 프롬프트 이동: 
```bash
bash# cd /opt/kafka/bin</code></pre><ul>
<li>첫번째 카프카 서버의 첫번째 영역에 토픽(exam-topic) 생성: <pre><code class="language-bash">bash# kafka-topics.sh --bootstrap-server localhost:9092 --create --topic exam-topic --partitions 1 --replication-factor 1</code></pre>
</li>
<li>생성된 토픽 목록 확인<pre><code class="language-bash">bash# kafka-topics.sh --bootstrap-server localhost:9092 --list</code></pre>
</li>
<li>토픽 삭제: <pre><code class="language-bash">bash# kafka-topics.sh --delete --zookeeper zookeeper:2181 --topic exam-topic</code></pre>
</li>
</ul>
<h4 id="5메세지-전송-및-받기">5)메세지 전송 및 받기</h4>
<ul>
<li><p>메시지 전송
터미널에서 <code>docker exec -it kafka /bin/bash</code></p>
<pre><code class="language-bash">bash# cd /opt/kafka/bin
bash# kafka-console-producer.sh --topic exam-topic --broker-list localhost:9092
&gt;메세지를 작성</code></pre>
</li>
<li><p>메시지 받기
새로운 터미널에서 <code>docker exec -it kafka /bin/bash</code></p>
<pre><code class="language-bash">bash# cd /opt/kafka/bin
bash# ./kafka-console-consumer.sh --topic exam-topic --from-beginning --bootstrap-server localhost:9092
</code></pre>
</li>
</ul>
<pre><code>
docker로 kafka를 만들었을 때 메시지 전송과 수신이 잘 수행되는 것을 알 수 있다. 

## 2. Django에서 카프카 메세지 전송 및 수신 (Window)

![](https://velog.velcdn.com/images/dev_angie/post/f769cb56-ce38-4ebf-84df-97be7035f5c8/image.png)

- 가상 환경을 생성(Window)</code></pre><p>python -m venv kafka_env</p>
<pre><code>- 가상환경 활성화</code></pre><p>kafka_env\Scripts\activate</p>
<pre><code>- 패키지 설치</code></pre><p>pip install kafka-python
pip install six==1.6.0</p>
<pre><code>
- 메시지 전송하는 코드를 작성하고 실행 한 후 터미널을 확인

`producer.py`
```python
import sys
import six
if sys.version_info &gt;= (3, 12, 0):
   sys.modules[&#39;kafka.vendor.six.moves&#39;] = six.moves

from kafka import KafkaProducer
import json


class MessageProducer:
   def __init__(self, broker, topic):
       self.broker = broker
       self.topic = topic
       #key_serializer=str.encode 를 추가하면 key 와 함께 전송
       #그렇지 않으면 value 만 전송
       self.producer = KafkaProducer(
           bootstrap_servers=self.broker,
           value_serializer=lambda x: json.dumps(x).encode(&quot;utf-8&quot;),
           acks=0,
           api_version=(2, 5, 0),
           key_serializer=str.encode,
           retries=3,
       )
   def send_message(self, msg, auto_close=True):
       try:
           print(self.producer)
           future = self.producer.send(self.topic, value=msg, key=&quot;key&quot;)
           self.producer.flush()  # 비우는 작업
           if auto_close:
               self.producer.close()
           future.get(timeout=2)
           return {&quot;status_code&quot;: 200, &quot;error&quot;: None}
       except Exception as exc:
           raise exc

# 브로커와 토픽명을 지정
broker = [&quot;localhost:9092&quot;]
topic = &quot;exam-topic&quot;
pd = MessageProducer(broker, topic)
#전송할 메시지 생성
msg = {&quot;name&quot;: &quot;John&quot;, &quot;age&quot;: 30}
res = pd.send_message(msg)
print(res)</code></pre><pre><code>python producer.py</code></pre><ul>
<li>파이썬 카프카 컨슈머를 작성</li>
</ul>
<p><code>consumer.py</code></p>
<pre><code class="language-python">import sys
import six
if sys.version_info &gt;= (3, 12, 0):
   sys.modules[&#39;kafka.vendor.six.moves&#39;] = six.moves

from kafka import KafkaConsumer
import json
class MessageConsumer:
   def __init__(self, broker, topic):
       self.broker = broker
       self.consumer = KafkaConsumer(
           topic,  # Topic to consume
           bootstrap_servers=self.broker,
           value_deserializer=lambda x: x.decode(
               &quot;utf-8&quot;
           ),  # Decode message value as utf-8
           group_id=&quot;my-group&quot;,  # Consumer group ID
           auto_offset_reset=&quot;earliest&quot;,  # Start consuming from earliest available message
           enable_auto_commit=True,  # Commit offsets automatically
       )
   def receive_message(self):
       try:
           for message in self.consumer:
               #print(message.value)
               result = json.loads(message.value)
               for k, v in result.items():
                   print(k, &quot;:&quot;, result[k])
               print(result[&quot;name&quot;])
               print(result[&quot;age&quot;])
       except Exception as exc:
           raise exc

# 브로커와 토픽명을 지정한다.
broker = [&quot;localhost:9092&quot;]
topic = &quot;exam-topic&quot;
cs = MessageConsumer(broker, topic)
cs.receive_message()
</code></pre>
<pre><code>python consumer.py</code></pre><p><img src="https://velog.velcdn.com/images/dev_angie/post/d0695b52-7358-4d69-b7bb-321303d3a561/image.png" alt=""></p>
<blockquote>
<ul>
<li>작성한 파일을 실행하면 에러가 발생: 이전에 JSON 형식이 아닌 데이터를 전송했는데 그 데이터를 파싱할려고 해서 에러가 발생</li>
</ul>
</blockquote>
<ul>
<li>토픽을 삭제하고 다시 전송한 후 실행<pre><code>#  docker exec -it kafka /bin/bash
bash-5.1# cd /opt/kafka/bin
bash-5.1# kafka-topics.sh --delete --zookeeper zookeeper:2181 --topic exam-topic </code></pre></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[네트워크]]]></title>
            <link>https://velog.io/@dev_angie/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC</link>
            <guid>https://velog.io/@dev_angie/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC</guid>
            <pubDate>Thu, 26 Sep 2024 08:25:49 GMT</pubDate>
            <description><![CDATA[<h2 id="1-네트워크">1. 네트워크</h2>
<h3 id="1-네트워크-구성도">1) 네트워크 구성도</h3>
<ul>
<li><p>홈 네트워크
인터넷 &lt;-&gt; 모뎀 &lt;-&gt; 공유기 &lt;-&gt; 컴퓨터, 테블릿, 스마트폰</p>
</li>
<li><p>데이터 센터 네트워크</p>
<ul>
<li>안정적이고 빠른 대용량 서비스 제공을 목표로 함</li>
<li>구성 방법<ul>
<li>3계층 디자인 - 예전에 많이 사용하던 방식<ul>
<li>CORE &lt;-&gt; Aggregation(흐름제어 - 속도, 트래픽) &lt;-&gt; Access</li>
</ul>
</li>
<li>2계층 디자인 - 최근에 네트워크 속도가 빨라지면서 사용, Spine-Leaf 구조</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="2-프로토콜">2) 프로토콜</h3>
<ul>
<li>개요<ul>
<li>통신을 하기 위한 규칙 규약</li>
<li>물리적 측면: 데이터 전송 매체(케이블), 신호 규약, 회선 규격 등을 정의하는 것인데 최근에는 거의 이더넷만 사용</li>
<li>논리적 측면: 프로토콜 규격 등을 정의하는 것인데 최근에는 TCP/IP 만 사용</li>
<li>프로토콜을 정의할 떄 예전에는 비트 기반으로 많이 만들었는데 최근에는 문자 기반으로 많이 사용한다. 
비트 기반의 프로토콜은 효율적이기는 하지만 사람이 읽기가 어렵다. </li>
<li>TCP/IP는 엄밀한 의미에서는 프로토콜이 아니고 프로토콜 스택. TCP와 IP는 별도의 계층에서 동작하는 프로토콜인데 이를 합쳐서 부른다. 이렇게 여러 개를 합쳐서 부를 때는 Stack 이라는 단어를 추가. TCP/IP 프로토콜 스택에는 TCP와 IP 이외에도 UDP, ICMP, ARP, HTTP, SMTP, FTP, HTTPS 등이 포함되어있다. </li>
</ul>
</li>
</ul>
<h3 id="3-osi-7계층과-tcpip">3) OSI 7계층과 TCP/IP</h3>
<ul>
<li>OSI 7계층 <ul>
<li>전에는 프로토콜을 각 벤더들이 별도로 개발했기 때문에 호환되지 않는 시스템이나 애플리케이션이 많았음</li>
<li>하나의 프로토콜로 통합하고자 했던 노력으로 만들어진 것이 OSI 7계층</li>
<li>7개의 계층으로 모듈화 한 이유는 재사용 문제 때문: 하나로 만들면 재사용성이 떨어지고 이해하기 어려워진다. </li>
</ul>
</li>
</ul>
<table>
<thead>
<tr>
<th>이름</th>
<th>데이터</th>
</tr>
</thead>
<tbody><tr>
<td>Application</td>
<td>실제 데이터: 우리가 이해할 수 있는 데이터</td>
</tr>
<tr>
<td>Presentation</td>
<td>실제 데이터</td>
</tr>
<tr>
<td>Session</td>
<td>실제 데이터</td>
</tr>
<tr>
<td>Transport</td>
<td>Segments</td>
</tr>
<tr>
<td>Network</td>
<td>Packet</td>
</tr>
<tr>
<td>DataLink</td>
<td>Frames</td>
</tr>
<tr>
<td>Physical</td>
<td>Bits</td>
</tr>
</tbody></table>
<ul>
<li><p>2개 부분으로 분할
5-7계층: 상위 계층 - 애플리케이션 계층 : 인간, 애플리케이션 개발자의 영역
1-4계층: 하위 계층 - Data Flow 계층 : 기계, 네트워크 엔지니어의 영역</p>
</li>
<li><p>TCP/IP 프로토콜 스택</p>
<ul>
<li>4개 계층으로 프로토콜을 정의
  Application: OSI 의 5-7계층
Transport
Internet: OSI 3계층
Network Access: OSI의 1,2 계층</li>
</ul>
</li>
<li><p>계층 별 기능을 설명할 떄 OSI 7계층을 가지고 설명을 많이 한다. </p>
</li>
</ul>
<h3 id="4-osi-7계층-별-이해">4) OSI 7계층 별 이해</h3>
<ul>
<li><p>1계층(물리 계층)</p>
<ul>
<li>물리적 연결과 관련된 정보</li>
<li>장비로는 Hub, Repeater, Cable, Connector, Transiver(랜 카드와 케이블을 연결해주는 장비), TAP(네트워크 모니터링 이나 패킷 분석을 하기 위해서 전기 신호를 다른 장비로 복제해주는 장비) 등</li>
</ul>
</li>
<li><p>2계층(데이터 링크 계층)</p>
<ul>
<li>전기 신호를 데이터 형태로 변환하는 계층</li>
<li>주소 정보를 정의하고 정확한 주소로 통신이 되도록 하는 역할을 수행</li>
<li>1계층에서는 주소라는 개념이 존재하지 않지만 출발지 주소와 목적지 주소를 확인하고 자신에게 전송된 것인지 확인</li>
<li>에러 탐지도 수행</li>
<li>2계층 장비는 NIC(Network Interface Card)와 Switch</li>
<li>사용하는 주소 체계는 MAC Address</li>
<li>2계층 장비들은 MAC Address를 이해할 수 있는 장비</li>
<li>NIC 동작 방식
  전기 신호를 데이터 형태로 생성
목적지 MAC Address와 출발지 MAC Address를 확인
자신의 MAC Address를 확인
목적지 MAC Address와 자신의 MAC Address가 일치하면 데이터를 처리하고 다르면 데이터를 폐기</li>
<li>Switch
허브와 유사하게 여러 개의 포트로 구성
MAC Address Table을 가지고 있어서 통신을 하면 MAC Address를 학습해서 테이블에 저장할 수 있다. 
단말들이 통신할 때 포트를 적절히 필터링하고 정확한 포트로 포워딩한다.
허브는 어느 순간 하나의 장비만 전송이 가능하지만 Switch는 동시에 데이터 전송이 가능</li>
</ul>
</li>
<li><p>3계층 (네트워크 계층)
 논리적인 주소가 정의
 사용자가 환경에 맞게 변경할 수 있고 주소를 네트워크 주소 부분과 호스트 주소 부분으로 나누어서 할당할 수 있다. 
 3계층 장비가 라우터이다. 
 IP를 이해해서 다른 네트워크를 찾아가는 장비이다. </p>
</li>
<li><p>4계층(전송 계층)</p>
<ul>
<li><p>데이터들이 정상적으로 잘 전송이 되는지 확인하는 역할</p>
</li>
<li><p>데이터는 하나의 묶음으로 전송되지 않고 일정한 크기(Packet) 단위로 분할해서 보내게 되는데 중간에 패킷이 유실되거나 순서가 바뀔 수 있는데 이러한 문제를 해결하는 계층</p>
</li>
<li><p>패킷을 만들 떄 Sequence Number를 붙이고 받는 쪽에서는 ACK(Acknowlegement) Number를 전송한다.</p>
</li>
<li><p>컴퓨터 내에서 애플리케이션을 구분하기 위해서 port를 부여</p>
</li>
<li><p>4계층에서 동작하는 장비는 Load Balancer와 Firewall: 장비라고 하기도 하고 Software라고 하기도 한다. </p>
<ul>
<li>Load Balancer: 부하분산</li>
</ul>
</li>
<li><p>5계층(세션 계층)</p>
<ul>
<li>연결 해제와 연결 유지하는 역할</li>
<li>에러 복구와 중단된 통신에 재전송 수행 수행</li>
<li>연결 해제와 유지를 위해서 특별한 키를 발급해서 키를 가지고 실제 연결하는 컴퓨터를 구분</li>
</ul>
</li>
<li><p>6계층(프레젠테이션)</p>
<ul>
<li>구분 변환, 데이터 압축, 인코딩과 디코딩을 수행</li>
<li>데이터의 형식을 맞추는 계층</li>
</ul>
</li>
<li><p>7계층(애플리케이션 계층)</p>
<ul>
<li>애플리케이션 프로세스 정의하는 계층</li>
<li>OSI 전 계층을 수행하는 장비는 Gateway(Router를 Gateway로 보는 경우가 많음)</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="5-통신-과정">5) 통신 과정</h3>
<ul>
<li><p>데이터를 전송을 하게 되면 상위 계층에서 하위 계층으로 데이터를 전송
전송을 받는 쪽에서는 하위 계층에서 상위 계층으로 데이터를 전송</p>
</li>
<li><p>데이터를 보내는 과정을 Encasulation 그리고 받는 과정을 Dcapsulation 이라고 하기도 한다. </p>
</li>
<li><p>보내는 쪽
Data -&gt; 4계층(4계층 헤더 + Data: Segment) -&gt; 3계층(3계층 헤더 + 4계층 헤더 + 데이터: Packet) -&gt; 2계층(2계층 헤더 + 3계층 헤더 + 4계층 헤더 + 데이터 + FCS) -&gt; 1계층 (전기적 신호로 변환)</p>
</li>
<li><p>받는 쪽은 반대 과정을 거침</p>
</li>
<li><p>MSS와 MTU</p>
<ul>
<li>MSS(Maximum Segment Size): 4계층에서 사용할 수 있는 데이터의 최대 크기</li>
<li>MTU(Maximum Transmission Unit): 네트워크 계층에서 한 번에 보낼 수 있는 데이터의 최대 크기, 이데넛에서는 1500 바이트</li>
</ul>
</li>
</ul>
<h2 id="2-네트워크-구분과-구성-요소">2. 네트워크 구분과 구성 요소</h2>
<h3 id="1-네트워크-연결-구분">1) 네트워크 연결 구분</h3>
<ul>
<li>규모에 따른 구분<ul>
<li>LAN: Local Area Network
사무실이나 건물 하나 정도의 소규모의 네트워크
도시 단위의 네트워크를 만들 때 통신사의 인프라를 이용하면 WAN이라고 하고 자체 인프라를 이용하면 MAN 이라고 하기도 한다. </li>
<li>MAN: Metropolitan Area Network
하나의 도시 정도를 네트워크로 연결</li>
<li>WAN: Wide Area Network
먼 거리를 연결한 네트워크</li>
<li>PAN: Personal Area Network
개인의 작업 공간을 중심으로 장치들을 서로 연결하기 위한 네트워크</li>
</ul>
</li>
</ul>
<h3 id="2-회선에-따른-분류">2) 회선에 따른 분류</h3>
<ul>
<li>인터넷 회선: 인터넷 접속을 위해 통신 사업자와 연결하는 회선
가입자와 통신 사업자 간에 직접 연결되는 구조가 아니라 전송 선로 공유 기술을 이용</li>
<li>전용 회선: 가입자와 통신 사업자 간에 대역폭을 보장해주는 서비스</li>
</ul>
<p>컴퓨터 - 스위치 - 라우터 - ISP - 라우터 - 스위치 - 컴퓨터
본사와 지사 연결이나 지사와 지사 연결등에 사용
종류는 저속인 음성 전송 기술 기반이 있고 고속인 메트로 이더넷이 있다. </p>
<p>LLCF(Link Loss Carry Forward)
한쪽 링크가 다운되면 이를 감지해 반대쪽 링크도 다운시키는 기능
전용 회선을 이더넷으로 구성할 때 통신 사업자가 LLCF를 설정하지 않으면 한 사이트에 다운되더라도 반대쪽 사이트에서 회선이 살아있는 것처럼 보이기 때문에 회선 개통 한 후 회선사에서 LLCF 설정이 되어있는지 확인</p>
<ul>
<li>인터넷 전용 회선: 인터넷 연결 회선에 대한 통신 대역폭을 보장해주는 서비스</li>
</ul>
<h3 id="3-vpnvirtual-private-network">3) VPN(Virtual Private Network)</h3>
<ul>
<li><p>전용 회선이 아니지만 가상으로 직접 연결한 것 같은 효과가 나도록 만들어주는 네트워크 기술</p>
</li>
<li><p>통신 사업자 VPN</p>
<ul>
<li>통신 사업자가 VPN을 구성해주는 것</li>
<li>하나의 건물에서만 VPN을 구성한다면 직접 구성하면 되지만 먼거리에 떨어져있는 곳 과도 통신이 가능한 VPN을 만들려면 회선 연결 비용이 증가하기 때문에 통신 사업자의 회선을 이용해서 구성한다. </li>
<li>MPLS VPN: 통신 사업자는 전용선인 것처럼 보이게 하지만 실제로는 가입자를 구분하는 기술을 적용해서 시분할을 이용해서 서비스를 제공</li>
</ul>
</li>
<li><p>가입자 VPN: 일반 사용자가 가상 네트워크를 직접 구성하는 것</p>
</li>
</ul>
<h3 id="4-dwdmdense-wavelenth-divistion-multiplex-파장-분할-다중화">4) DWDM(Dense Wavelenth Divistion Multiplex: 파장 분할 다중화)</h3>
<ul>
<li>광케이블로 데이터를 전송할 채널을 분할해서 데이터를 전송하는 것</li>
</ul>
<h3 id="5-네트워크-구성-요소">5) 네트워크 구성 요소</h3>
<ul>
<li>NIC(Network Interface Card)
직렬화, MAC Address, 흐름 제어(Flow Control - 데이터를 안정적으로 주고받기 위해서 통신 중일 때 다른 네트워크에서 데이터를 보내는 것을 중지하라고 요청)</li>
</ul>
<p>일반적으로 사용하는 NIC 외에도 스토리지와 서버를 연결하는 NIC도 존재
고성능 클러스터링을 구현하기 위한 HPC(HIgh Performance COmputing)에서도 우리가 일반적으로 사용하는 NIC를 사용하지 않는다.</p>
<ul>
<li><p>케이블과 커넥터
1000(속도)BASE(채널)-T(케이블 타입)
케이블 종류는 트위스트페어(유선 인터넷), 동축 케이블(TV 회선), 광케이블 등이 있음</p>
</li>
<li><p>스위치
여러 장비를 연결하고 케이블을 한 곳으로 모아주는 역할을 수행
스위치와 허브의 기능을 같이하면 스위칭 허브라고 부르기도 하고 4계층이나 3계층의 역할까지 수행하는 스위치를 L3 또는 L4 스위치라고 한다. </p>
</li>
<li><p>라우터
외부 네트워크와 통신하기 위한 장비
정확한 방향으로 데이터가 전송되도록 하고 최적의 경로로 데이터를 포워딩하는 역할을 수행</p>
</li>
<li><p>로드밸런서</p>
<ul>
<li>4계층 장비</li>
<li>애플리케이션 계층에서 애플리케이션 프로토콜의 특징을 이해하고 동작하는 로드밸런서를 ADC(Application Delivery Controller)라고 부른다. </li>
<li>포트 주소를 확인하는 동시에 IP 변환도 수행</li>
<li>웹 서버를 구축할 때 많이 사용하는데 동일한 역할을 수행하는 웹 서버를 여러 대 만들고자 하는 경우 로드 밸런서를 웹 서버 앞에 두고 웹 서버를 여러 대 만든 다음 대표 IP는 로드밸런서에게 부여하고 로드밸런서가 요청이 오면 웹 서버를 지정해서 전송하도록 한다.</li>
<li>이런 원리로 동작하는 소프트웨어 중 하나가 nginx 이다. </li>
<li>로드밸런서를 이용하게 되면 서비스 헬스 체크나 대용량 세션 처리 기능을 사용할 수 있다. </li>
</ul>
</li>
<li><p>보안 장비</p>
<ul>
<li>방화벽이나 IPS 등의 장비 또는 소프트웨어</li>
<li>방화벽: OSI 7계층 중 4계층에서 동작해서 패킷의 3, 4 계층 정보를 확인하고 패킷을 정책과 비교해서 버리거나 포워딩한다. </li>
</ul>
<ul>
<li>공유기<ul>
<li>2계층 스위치, 3계층 라우터, 4계층 NAT와 간단한 방화벽 기능을 모아놓은 장비</li>
<li>내부는 스위치와 무선 AP 그리고 라우터 부분으로 구성되어 있음</li>
</ul>
</li>
</ul>
</li>
<li><p>모뎀</p>
<ul>
<li>공유기의 LAN 포트와 WAN 포트는 일반 이더넷이어서 100m 이상 먼 거리로 데이터를 보내지 못하기 때문에 먼 거리 통신을 위해서 모뎀이 필요</li>
</ul>
</li>
</ul>
<h2 id="3-네트워크-통신">3. 네트워크 통신</h2>
<h3 id="1-네트워크에서-출발지에서-목적지로-데이터를-전송할-떄-사용하는-통신-방식">1) 네트워크에서 출발지에서 목적지로 데이터를 전송할 떄 사용하는 통신 방식</h3>
<ul>
<li>unicast<ul>
<li>1:1 통신</li>
<li>출발지와 목적지가 명확히 하나로 정해져있는 통신 방식</li>
<li>대다수의 통신은 unicast</li>
</ul>
</li>
<li>broadcast<ul>
<li>목적지 주소가 모든으로 표기되어 있는 통신 방식</li>
<li>유니캐스트로 통신하기 전에 상대방의 정확한 위치를 알기 위해서 사용</li>
<li>로컬 네트워크에서 모든 호스트에 패킷을 전달해야 할 떄 사용</li>
</ul>
</li>
<li>multicast<ul>
<li>멀티캐스트 그룹 주소를 이용해서 해당 그룹에 속한 다수의 호스트로 패킷을 전송하기 위한 통신 방식</li>
<li>IPTV 와 같은 실시간 방송을 볼 때 사용하고 사내 방송이나 증권 시세 전송과 같이 단방향으로 다수에게 같은 내용을 전송할 때 사용</li>
</ul>
</li>
<li>anycast<ul>
<li>주소가 같은 호스트 중에서 가장 가깝거나 가장 효율적으로 서비스할 수 있는 호스트와 통신하는 방식</li>
<li>도메인을 이용해서 인터넷을 할 때 가장 가까운 DNS 서버를 찾을 떄 사용</li>
</ul>
</li>
<li>IPv6에서는 브로드캐스트라는 용어 대신에 링크 로컬 멀티캐스트라고 한다.</li>
</ul>
<h3 id="2-mac-address">2) MAC Address</h3>
<ul>
<li>주소 체계<ul>
<li>변경할 수 없도록 하드웨어 고정되어 출하</li>
<li>모든 네트워크 장비에는 MAC 주소를 할당해야 한다.</li>
<li>한 제조업체 하나 이상의 주소 풀을 주고 그 안에서 자체적으로 할당</li>
<li>48비트로 구성되는데 앞의 24비트가 제조회사 코드(OUI)이고 뒤의 24비트(UAA)는 제조사에서 자체적으로 할당</li>
<li>고유하지 않을 수 있는데 이유는 제조사의 실수나 코드의 부족 때문인데 동일 네트워크 안에서만 같지 않으면 통신하는데 문제가 없다. </li>
<li>ROM 고정되서 변경할 수 없도록 출하가 되지만 이 주소도 메모리에 적재되서 사용되므로 MAC 주소를 변경해서 사용하는 것이 가능</li>
</ul>
</li>
</ul>
<h3 id="3-ip-address">3) IP Address</h3>
<ul>
<li><p>IPv4</p>
<ul>
<li>32비트로 만들고 표기할 떄는 4개의 Octet(8비트)으로 나누고 각 OCtet은 .으록 구분</li>
<li>각 Octet은 10진수로 표기</li>
</ul>
<p>192.168.0.1과 같이 표기</p>
<ul>
<li><p>2개 부분으로 구성</p>
<ul>
<li>네트워크 부분: 호스트들을 모은 네트워크를 지칭하는 주소, 네트워크 주소가 같으면 같은 로컬 네트워크라고 한다.</li>
<li>호스트 부분: 하나의 네트워크 내에 존재하는 호스트를 구분하기 위한 주소</li>
</ul>
</li>
<li><p>네트워크 부분과 호스트 부분을 구분하는 경계점이 고정이 아니다. 따라서, 호스트의 IP 개수에 따라 네트워크 크기를 다르게 할당하는 것이 가능한데 이를 위해서 클래스라는 개념을 도입 
A클래스는 약 1600만 개의 IP 주소를 가질 수 있다. 첫번째 점이 경계
B클래스는 65536 개의 IP 주소를 가질 수 있다.두번째 점이 경계
C클래스는 256개의 IP 주소를 가질 수 있다. 세번째 점이 경계</p>
</li>
<li><p>최근에는 조금 더 세밀하고 네트워크와 호스트 부분을 설정할 수 있다. </p>
</li>
<li><p>사용 가능한 IP 범위를 할 때도 이를 이용
처음 IPv4를 만들 떄 각 클래스 별로 별도의 주소 블록을 할당
A Class는 맨 앞 비트가 0으로 시작: 0.0.0.0 ~ 127.255.255.255
B Class는 맨 앞 두 비트가 10으로 시작: 128.0.0.0 ~ 191.255.255.255
C Class는 맨 앞 세 비트가 110으로 시작: 192.0.0.0 ~ 223.255.255.255</p>
</li>
</ul>
<p>만일 IP 주소가 192.168.202.207 이라면 기본적으로 C 클래스
네트워크 주소: 192.168.202.0 
브로드캐스트 주소: 192.168.202.255
실제 사용 가능한 IP 범위: 192.168.202.1 ~ 192.168.202.254가 된다.
이를 조금 더 세분화시켜서 비트 단위로 분할 할 수 있는데 이를 서브네팅이라고 한다. </p>
</li>
<li><p>IP 주소 부족 문제 해결</p>
<ul>
<li>인터넷을 사용하는 호스트 숫자가 폭발적으로 증가해서 IP 주소가 부족해지는 문제가 발생</li>
<li>이를 해결하기 위해서 가장 먼저 도입한 것은 클래스 리스 방식 즉 CIDR 
두번째 도입한 것은 NAT와 사설 IP 주소
세번쨰 도입한 것이 IPv6</li>
</ul>
</li>
<li><p>클래스 리스 </p>
<ul>
<li>기존 클래스의 개념을 없앤 것으로 네트워크를 원하는 단위로 분할 할 수 있도록 한 것</li>
<li>이를 위해서 subnet mask 라는 개념을 도입</li>
<li>subnet mask 는 동일한 네트워크 크기를 표현하기 위한 주소
subnet mask를 표현하는 방법은 비트 수를 기재하는 방법과 10진수로 표현하는 방법 2가지이다. 
IP Address/비트 수 또는 IP Address 10진수로 표기</li>
<li>비트 수로 표시를 하면 비스 수 만큼 같으면 동일한 네트워크로 간주하는 것이 10진수로 표현하면 이진수로 변환해서 1이 있는 부분까지가 동일하면 동일한 네트워크로 간주<ul>
<li>라우터나 컴퓨터에 IP를 설정할 때 이 방식을 사용</li>
<li>비트 수로 설정하지 않고 10진수로 설정</li>
<li>네트워크를 설정할 떄는 네트워크 대역안에 있는 어떤 IP 주소를 할당해도 동일하게 적용</li>
<li>컴퓨터의 IP를 설정할 때는 동일 네트워크 대역안에서 정확하게 IP를 할당해야 한다. 
현재 컴퓨터의 IP는 192.168.202.207이다.
서브넷 마스크는 255.255.240.0(11111111.11111111.11110000.0000)</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>192.168.202.207/20 로 표기한다. </p>
<p>라우터의 네트워크 설정을 한다면 192.168.200.0 255.255.240.0 으로 해야 하지만 이 주소 풀이 192.168.200.0 ~ 192.168.215.255 이다. 
192.168.201.1 255.255.240.0 으로 설정해도 된다.</p>
<ul>
<li>필요한 크기대로 분할하는 작업 subnetting이라고 한다.</li>
<li>네트워크 사용자 입장에서 서브네팅이 중요한 것은 서브네팅 된 주소를 보고 사용할 수 있는 IP를 할당해야 한다. </li>
<li>네트워크 설계자 입장에서는 호스트의 개수를 파악해서 적절한 서브네팅 작업을 해주어야 한다. </li>
<li>방화벽을 설정할 떄도 서브네팅이 필요하다. </li>
</ul>
<p>모든 컴퓨터를 지칭 | 0.0.0./0  | 웹 애플리케이션 서버가 보통 이렇게 설정
컴퓨터 1대를 지칭 | IP/32 | 데이터베이스 서버의 경우 이렇게 설정하는 경우가 많음
일정 그룹의 컴퓨터를 지칭 | 네트워크주소/크기 | 애플리케이션 서버가 여러 대인 경우 데이터베이스는 이런식으로 설정</p>
<ul>
<li><p>퍼블릭 클라우드의 대부분 동일 네트워크 내에서의 통신은 비용을 지불하지 않는다. 
퍼블릭 클라우드에서는 직접 설정하기 보다는 동일한 VPC 로 묶으면 이 작업을 대신 수행해 준다. </p>
</li>
<li><p>Public IP와 Private IP</p>
<ul>
<li>인터넷에 접속을 할려면 IP주소가 있어야 한다.
인터넷을 할 때 사용할 수 있는 IP주소를 Public IP라고 한다.
인터넷을 연결하지 않고 네트워클 구성하기 위해서 사용하는 IP 주소를 Private IP라고 한다.</li>
<li>인터넷에 접속할려면 통신 사업자로부터 IP 주소를 할당받거나 IP 할당 기관(KISA)에서 인터넷 독립기관 주소를 할당받은 후 할 수 있는데 직접 할당 받는 것은 절차가 복잡하기 때문에 대부분은 통신 사업자로 부터 할당을 받는다. </li>
<li>인터넷에 접속하지 않거나 NAT(Network Address Translation) 기술을 사용할 경우 Private IP를 이용
Private IP를 가지고는 인터넷을 할 수 없지만 NAT 장비에서 Public IP로 변경해서 인터넷에 접속이 가능</li>
<li>Private IP 대역</li>
</ul>
<p>10.0.0.0/8  | 10.0.0.0 ~ 10.255.255.255
172.16.0.0/12 | 172.16.9.9 ~ 172.31.255.255
192.168.0.0/16 | 192.168.0.0 ~ 192.168.255.255</p>
<ul>
<li>회사 내부에서 Private Network을 구축할 떄 보안 상의 문제나 IP공유를 위해서 NAT를 사용하는데 이 떄 다른 사용자와 중복된 IP를 사용하면 안되는데 다른 사용자가 사용하고 있는 Public IP를 내부 네트워크의 IP로 할당을 하면 인터넷은 문제가 없지만 그 IP를 가진 컴퓨터와 통신은 안된다.  </li>
</ul>
</li>
<li><p>Bogon IP
사용할 수 없는 IP 주소로 IANA(IP 주소를 할당하는 최상위 기관)가 여러가지 목적으로 예약해 놓아서 public IP로 사용할 수 없는 IP.
이 주소를 이용한 네트워크 접속 시도가 있는 떄는 IP Spoofing(주소를 변조해서 공격)일 가능성이 높으므로 필터링 해야 한다. </p>
</li>
</ul>
<p>0.0.0.0/8: This Network
10.0.0.0/8: Private IP
100.64.0.0/10: 캐리어 그레이드 NAT(통신 사업자가 Private IP를 할당하기 위해서 사용)
127.0.0.0/8: Loop Back Address(자기 자신)
169.254.0.0/16: 링크 로컬
172.16.0.0/12: Private IP
192.0.0.0/24: IETF 프로토콜 할당
192.0.2.0/24: 테스트 용
192.168.0.0/16: Private IP
224.0.0.0/4: 멀티캐스트 예약
255.255.255.255/32: 브로드캐스트</p>
<ul>
<li>IP Addresds 발신자 확인 가능
우리나라에서는 <a href="https://whois.kisa.or.kr">https://whois.kisa.or.kr</a> 에서 제공</li>
</ul>
<h3 id="4-tcp와-udp">4) TCP와 UDP</h3>
<ul>
<li><p>4계층 프로토콜</p>
</li>
<li><p>포트가 이 계층에서 제공하는 주소</p>
</li>
<li><p>애플리케이션에서 사용하는 프로세스를 정확히 찾아가고 데이터를 분할한 패킷을 잘 쪼개고 조립하는 역할을 수행</p>
</li>
<li><p>포트번호</p>
<ul>
<li>HTTP TCP는 80, HTTPS TCP는 443, SMTP TCP는 25 이런 식으로 알려진 포트를 Well Know 포트라고 하고 IANA에서 등록해 놓았고 1023 이하의 포트번호를 사용</li>
<li>애플리케이션에서도 IANA에 포트 번호를 등록할 수 있는데 이때는 1024~49151 번 포트를 사용한다. 
MySQL이나 MaraiDB는 3306
Oracle은 1521,8080
Mongo DB는 27017
Django는 8000
Tomcat 8080
자주 사용하는 Application은 이 포트번호를 기억해 두는 것이 좋다. </li>
<li>49152~65535 까지는 자동으로 할당되거나 Private 용도로 클라이언트의 임시 포트 번호로 사용</li>
</ul>
</li>
<li><p>TCP</p>
<ul>
<li><p>신뢰할 수 없는 공용망에서 정보 유실없는 통신을 보장하기 위해 세션을 안전하게 연결하고 데이터를 분할하고 분할된 패킷이 잘 전송되었는지 확인하는 기능을 가지 프로토콜</p>
</li>
<li><p>송신자와 수신자가 3개의 패킷을 주고 받아야 한다면
송신자 측에서 1번 패킷을 전송하면 수신자 측에서 1번을 잘 받으면 2번을 요청하고 송신자 측에서 2번을 전송하고 수신자 측에서 2번을 전송받았다면 3번을 요청을 하고 송신자에서 3번을 전송하고 수신 측에서 3번을 받으면 데이터를 전부 받았다고 알려주고 통신을 종료</p>
</li>
<li><p>유실없는 안전한 통신을 위해서 통신 시작 전에 사전 연결 작업을 수행
클라이언트가 서버에게 Syn 신호를 전송하고 서버는 Syn과 Ack를 전송하고 다시 클라이언트는 Ack를 전송해서 연결을 수행</p>
</li>
<li><p>네트워크 프로그램을 직접 만든다면 중요한 데이터 전송에는 반드시 TCP 방식을 사용해야 한다. </p>
</li>
</ul>
</li>
<li><p>UDP</p>
<ul>
<li>사전 연결 작업을 수행하지 않고 전송하는 프로토콜</li>
<li>음성 데이터나 실시간 스트리밍 과 같이 시간에 민감한 프로토콜이나 애플리케이션을 만들 때 사용하고 사내 방송이나 증권 시세 데이터 전송에 사용하는 멀티캐스트 환경에서 사용</li>
<li>중요하지 않은 데이터 전송이나 단방향 전송을 하는 애플리케이션을 만들 때 사용</li>
</ul>
</li>
</ul>
<h2 id="4-switch">4. Switch</h2>
<h3 id="1-개요">1) 개요</h3>
<ul>
<li>2계층 주소인 MAC Address 기반으로 동작하는 장비</li>
<li>아무 설정없이 네트워크에 연결해도 MAC Address를 기반으로 패킷을 전달하는 기본 동작을 수행할 수 있다. </li>
<li>기본 동작이외에도 한 대의 장비에서 논리적으로 네트워크를 분리할 수 있는 VLAN 기능과 네트워크 루프를 방지하는 스패닝 트리 프로토콜(STP) 같은 기능을 기본적으로 가지고 있음</li>
<li>MAC Address Tble을 만들어서 어떤 포트에 어떤 MAC Address를 가진 호스트가 연결되어 있는지 학습을 할 수 있다. 
Flooding -&gt; Address Learning -&gt; Forwarding/Filtering의 3가지 동작으로 설명이 가능하다. <ul>
<li>플러딩
스위치는 부팅되면 네트워크 관련 정보가 하나도 없다.
이 상태에서는 스위치는 네트워크 통신을 중재하는 자신의 역할을 하지 못하고 허브처럼 동작
스위치가 허브처럼 모든 포트로 패킷을 흘리는 동작을 플러딩이라고 한다. 
새로 부팅하지 않았는데도 자꾸 플러딩이 일어나면 스위치가 정상적으로 동작하지 않거나 주변에서 공격이 수행되는 상황</li>
<li>Address Learning
모르는 MAC Address로 데이터를 전송하라고 하면 일단 플러딩을 수행하고 데이터를 전송하는 장비의 MAC Address를 주소 테이블에 기록한다.</li>
<li>Forwarding/Filtering
패킷이 스위치에 들어오면 도착지 MAC Address를 확인하고 자신이 가진 주소 테이블과 비교해서 맞는 정보가 있는 해당 포트로 데이터를 전송하고 다른 포트로는 데이터를 전송하지 않음 </li>
</ul>
</li>
</ul>
<h3 id="2-vlan">2) VLAN</h3>
<ul>
<li>하나의 물리적인 스위치에서 여러 개의 네트워크를 나누어 사용할 수 있는 기능: Virtual Local Area Network</li>
<li>VLAN을 나누게 되면 유니캐스트나 브로드캐스트로 다른 VLAN과 통신할 수 없다.
VLAN을 나누게 되면 VALN 간에는 3계층 장비를 이용해서 통신을 해야 한다.</li>
<li>종류<ul>
<li>포트 기반의 VLAN: 포트 단위로 VLAN을 생성</li>
<li>MAC Address 기반의 VLAN: LAN을 이동하면서 사용하는 경우 MAC Address 기반으로 VLAN 생성</li>
</ul>
</li>
</ul>
<h3 id="3-stp">3) STP</h3>
<ul>
<li><p>SPoF(Single Point of Failure: 단일 장애점)
하나의 요소 때문에 장애가 발생한 경우 전체 시스템에 장애가 발생하는 것</p>
</li>
<li><p>네트워크에서도 하나의 장비 고장으로 전체 네트워크가 마비되는 것을 막기 위해서 이중화 또는 다중화된 네트워크를 디자인하고 구성</p>
</li>
<li><p>여러 개의 switch로 네트워크를 디자인하게 되면 패킷이 잘못되면 네트워크를 따라서 계속 전송되는 일이 발생할 수 있다. </p>
</li>
<li><p>루프 구조의 switch 연결이 있으면 계속해서 데이터를 전송할 수 있다.</p>
</li>
<li><p>브로드캐스트 스톰</p>
<ul>
<li>루프구조를 가진 네트워크에서 브로드캐스트 전송을 하는 경우 각 스위치는 데이터가 들어온 포트를 제외하고 모든 포트에 데이터를 전송한다. 
이 경우 케이블을 제거하기 전까지 데이터를 계속 전송한다. </li>
</ul>
</li>
<li><p>MAC 러닝 중복 문제</p>
<ul>
<li>Switch는 출발지의 MAC Address를 가지고 테이블을 구성하는데 여기서 출발지와 실제 데이터를 전송받은 곳의 MAC Address가 다른 경우 잘못된 학습을 할 수 있다. </li>
</ul>
</li>
<li><p>Spanning Tree Protocol</p>
<ul>
<li>스위치가 루프를 확인하고 적절히 포트를 사용하지 못하도록 하는 것</li>
</ul>
</li>
</ul>
<blockquote>
<p>다운받을거</p>
</blockquote>
<ul>
<li>우분투 이미지</li>
<li>irtual box</li>
<li>GNS3(라우터나 스위치 시뮬레이션 할 때 사용하는 프로그램): <a href="https://www.gns3.com/">https://www.gns3.com/</a></li>
<li>cisco router image:<a href="https://networkrare.com/free-download-cisco-ios-images-for-gns3-and-eve-ng/">https://networkrare.com/free-download-cisco-ios-images-for-gns3-and-eve-ng/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Django] RESP API]]></title>
            <link>https://velog.io/@dev_angie/Django-RESP-API</link>
            <guid>https://velog.io/@dev_angie/Django-RESP-API</guid>
            <pubDate>Tue, 24 Sep 2024 08:49:16 GMT</pubDate>
            <description><![CDATA[<h3 id="1-apiapplication-programming-interface">1) API(Application Programming interface)</h3>
<ul>
<li>프로그램과 프로그램을 연결시켜주는 매개체</li>
<li>프로그램과 통신을 위해서 제공</li>
<li>프레임워크 형태로 제공하기도 하고 데이터 형태로 제공하기도 함</li>
<li>비슷한 말로 Software Developer Kit이라고 하기도 한다. </li>
<li>Open API라고 부르면 API를 누구나 사용할 수 있도록 해준것 입니다. </li>
</ul>
<h3 id="2-csrclient-side-rendering">2) CSR(Client Side Rendering)</h3>
<ul>
<li><p>API Server를 만들어서 데이터를 제공하도록 만들고 클라이언트에서 이 데이터를 제공받아서 출력하는 방식</p>
</li>
<li><p>Server Side Rendering은 서버가 처리를 한 후 그 데이터를 가지고 템플릿 엔진을 이용해서 HTML을 만들어서 클라이언트에게 제공하는 방식</p>
</li>
<li><p>CSR 방식은 클라이언트에서 데이터를 가공해서 화면에 출력을 하기 때문에 데이터를 어떤 형식으로 주고 받을지 결정을 해야하는데 가장 많이 사용된 방식은 XML과 JSON 이다.</p>
</li>
<li><p>XML(eXtensible Markup Language)</p>
<ul>
<li>HTML은 브라우저가 해석하지만 XML은 개발자나 프레임워크가 해석</li>
<li>XML은 구조적</li>
<li>JSON보다 인간이 읽기가 쉽지만 용량이 크다. <pre><code>&lt;Person&gt;
&lt;name&gt;이름&lt;/name&gt;
&lt;age&gt;30&lt;/age&gt;
&lt;hobby&gt;
    &lt;element&gt;축구&lt;/element&gt;
    &lt;element&gt;야구&lt;/element&gt;
&lt;/hobby&gt;
&lt;/Person&gt;</code></pre></li>
</ul>
</li>
<li><p>JSON(JavaScript Object Notation)</p>
<ul>
<li>자바스크립트의 객체 표현 방식을 이용</li>
<li>XML보다 경량이지만 인간이 읽기가 어려움</li>
<li>인간이 읽기 어렵기 떄문에 설정 파일에 사용하기는 어렵고 데이터 전송에 많이 사용한다. </li>
<li>스마트폰 운영체제의 데이터 전송 방식은 전부 JSON 이다. <pre><code>{name:&quot;이름&quot;, age:30, hobby:[&quot;축구&quot;,&quot;야구&quot;]}</code></pre></li>
</ul>
</li>
</ul>
<h3 id="3-restrepresentational-state-transfer">3) REST(REpresentational State Transfer)</h3>
<ul>
<li>분산 하이퍼미디어 시스템을 위한 소프트웨어 아키텍쳐의 형식</li>
<li>자원을 정의하고 자원에 대한 주소를 지정하는 방법 전반을 일컫는 말로 웹 상의 자료를 HTTP 위에서 SOAP(Simple Object Access Protocol)이나 쿠키를 통한 세션 트랙킹 같은 별도의 전송 계층없이 전송하기 위한 아주 간단한 인터페이스</li>
<li>REST 원리를 따르는 시스템을 RESTful 하다라고 한다. </li>
<li>6가지 제약조건<ul>
<li>Client - Server 구조</li>
<li>Stateless(무상태): 클라이언트가 서버에 요청을 보낼 때 이전 요청의 영향을 받지 않아야 한다. </li>
<li>Cacheable Data: 서버에서 리소스를 리턴할 떄 캐시가 가능한지 아닌지를 명시해야 하는 것인데 HTTP에서 cache-control이라는 헤더에 리소스의 캐시 여부를 명시할 수 있다.</li>
<li>Layered System: 여러 개의 레이어로 된 서버를 사용할 수 있음, 클라이언트는 이 사실을 알 필요가 없음
하나의 시스템을 여러 개의 애플리케이션으로 분리해서 구현하기도 하고 인증 서버, 캐싱 서버, 로드 밸런서를 거쳐서 응답을 하기도 한다. </li>
<li>A Uniform Interface: 일관성 있는 인터페이스를 가져야 한다.
HTML과 JSON 두 가지 형태의 데이터를 사용하는 것은 안된다. 
동일한 콘텐츠를 사용하는 경우 동일한 URL을 사용하는 것을 권장
<code>http://www.example.com/todo</code> Get 방식이면 가져오는 것 POST 방식이면 삽입 PUT이면 수정 DELETE이면 삭제 이렇게 HTTP 메서드로 작업을 구분</li>
<li>Code-On-Demand
선택 사항으로 클라이언트는 서버에 코드를 요청할 수 있고 서버가 리턴한 코드를 실행할 수 있음 </li>
</ul>
</li>
</ul>
<h2 id="3-django-rest-framework">3. Django REST Framework</h2>
<h3 id="1-개요">1) 개요</h3>
<ul>
<li><p>REST API를 만들 수 있는 프레임워크</p>
</li>
<li><p>설치: pip install djangorestframework</p>
<h3 id="2-프로젝트-생성">2) 프로젝트 생성</h3>
</li>
<li><p>django-admin startproject 프로젝트이름 경로</p>
<h3 id="3-애플리케이션-생성---프로젝트의-managepy-파일이-있는-디렉토리에서-수행">3) 애플리케이션 생성 - 프로젝트의 manage.py 파일이 있는 디렉토리에서 수행</h3>
</li>
<li><p>python manage.py startapp 애플리케이션이름
<img src="https://velog.velcdn.com/images/dev_angie/post/0bbe5a41-3892-436a-b17d-52527b3d198c/image.png" alt=""></p>
</li>
</ul>
<h3 id="4-settingpy-파일-수정">4) setting.py 파일 수정</h3>
<ul>
<li><p>INSTALLED_APPS 부분에 rest_framework 와 자신의 애플리케이션을 등록</p>
<pre><code class="language-python">INSTALLED_APPS = [
  &#39;django.contrib.admin&#39;,
  &#39;django.contrib.auth&#39;,
  &#39;django.contrib.contenttypes&#39;,
  &#39;django.contrib.sessions&#39;,
  &#39;django.contrib.messages&#39;,
  &#39;django.contrib.staticfiles&#39;,
  &#39;myweb&#39;,
  &#39;rest_framework&#39;,
  &#39;apiapp&#39;
]</code></pre>
</li>
<li><p>DATABASES 부분을 사용할 수 있는 데이터베이스로 수정</p>
<pre><code class="language-python">DATABASES = {
  &#39;default&#39;: {
      &#39;ENGINE&#39;: &#39;django.db.backends.mysql&#39;,
      &#39;NAME&#39;: &#39;rest&#39;,
      &#39;USER&#39;:&#39;root&#39;,
      &#39;PASSWORD&#39;:&#39;1234&#39;,
      &#39;HOST&#39;:&#39;127.0.0.1&#39;,
      &#39;PORT&#39;:&#39;3306&#39;
  }
}</code></pre>
</li>
<li><p>TIME_ZONE 수정</p>
<pre><code>TIME_ZONE = &#39;Asia/Seoul&#39;</code></pre></li>
</ul>
<h3 id="5-urlspy-파일을-수정해서-example로-시작하는-url에-대한-처리는-apiapp의-urlspy에서-처리하도록-수정">5) urls.py 파일을 수정해서 example로 시작하는 url에 대한 처리는 apiapp의 urls.py에서 처리하도록 수정</h3>
<ul>
<li>프로젝트에 여러 개의 애플리케이션 존재하는 경우 프로젝트에서 모든 요청을 처리하도록 하면 프로젝트의 urls.py가 너무 복잡해지므로 요청을 처리하는 로직이나 url 설정을 애플리케이션 단위로 분할하는 것이 좋다. <pre><code class="language-python">from django.contrib import admin
from django.urls import path, include
</code></pre>
</li>
</ul>
<p>urlpatterns = [
    path(&#39;admin/&#39;, admin.site.urls),
    path(&#39;example/&#39;, include(&#39;apiapp.urls&#39;))
]</p>
<pre><code>
### 6) 요청을 만들고 JSON을 리턴하는 코드를 작성
- apiapp 디렉토리에 urls.py 파일을 추가하고 작성
```python
from django.urls import path
from .views import helloAPI

urlpatterns = [
    path(&quot;hello/&quot;, helloAPI)
]</code></pre><ul>
<li>apiapp 디렉토리의 views.py 파일에 helloAPI 함수를 추가<pre><code class="language-python">from django.shortcuts import render #템플릿 엔진을 이용해서 HTML 출력에 사용
from rest_framework.decorators import api_view
from rest_framework.response import Response #json 데이터 생성에 사용
</code></pre>
</li>
</ul>
<h1 id="create-your-views-here">Create your views here.</h1>
<p>@api_view([&#39;GET&#39;])
def helloAPI(request):
    return Response(&quot;Hello REST API&quot;)</p>
<pre><code>- 관리자용 테이블을 생성</code></pre><p>python manage.py makemigrations
python manage.py migrate</p>
<pre><code>
- 확인
  - 웹서버를 실행 
  `python manage.py runserver 127.0.0.1:80`
  - 브라우저에서 확인
  `localhost/example/hello`
 ![](https://velog.velcdn.com/images/dev_angie/post/ccb91691-ef88-488c-b3c5-a185aea007cb/image.png)

### 7) Response
- 응답 결과를 만들기 위한 클래스
- 인스턴스를 생성할 떄 첫번쨰 클래스 매개변수가 클라이언트에게 전송될 데이터
status는 상태 코드
- 주요 상태 코드
200: OK
201: Created - 요청은 처리되어서 새로운 리소스를 생성
202: Accepted - 요청은 접수되었지만 처리가 완료되지 않음
3xx: 리다이렉션 중
400: 잘못된 요청
401: 권한이 없음
403: 권한 처리 이외의 사유로 리소스에 대한 엑세스가 금지
404: 지정한 리소스를 찾을 수 없음. 서버에서 처리하는 로직을 찾을 수 없음
500: 내부 서버 오류 

- 응답 결과를 전송할 떄 상태 코드를 같이 전송해서 정상 처리 여부를 알려주는 것이 좋다. 

### 8) Django와 다른점
- 실행 부분에 HTTP의 헤더가 보임
- 템플릿의 형태가 아닌 JSON과 같은 형태의 응답을 제공
- Serializer(직렬화)
  - 인스턴스 단위로 파일이나 네트워크를 통해 전소하는 것
  - Djangorestframework 에서는 Model을 JSON 데이터로 변환하는 작업

### 9) Model 생성
- 애플리케이션의 models.py 파일에 Moedel 클래스로 부터 상속받는
</code></pre><h1 id="create-your-models-here">Create your models here.</h1>
<p>class Book(models.Model):
    bid = models.IntegerField(primary_key=True)
    title = models.CharField(max_length=50)
    author = models.CharField(max_length=50)
    category = models.CharField(max_length=50)
    pages = models.IntegerField()
    price = models.IntegerField()
    published_date = models.DateField()
    descripton = models.TextField()</p>
<pre><code>
 - 변경 내용을 데이터베이스에 반영
```python 
python manage.py makemigrations
python manage.py migrate</code></pre><h3 id="10-모델의-데이터를-json-문자열로-변환해서-출력해주는-serializer를-생성">10) 모델의 데이터를 json 문자열로 변환해서 출력해주는 Serializer를 생성</h3>
<ul>
<li>apiapp 디렉토리에 시리얼라이저를 구현할 serializers.py 파일을 생성하고 작성</li>
</ul>
<h3 id="11-apiapp-디렉토리에-요청을-처리하는-함수를-작성---전체-가져오기-데이터-삽입-데이터-1개-가져오기">11) apiapp 디렉토리에 요청을 처리하는 함수를 작성 - 전체 가져오기, 데이터 삽입, 데이터 1개 가져오기</h3>
<pre><code class="language-python">from django.shortcuts import render #템플릿 엔진을 이용해서 HTML 출력에 사용
from rest_framework.decorators import api_view
from rest_framework.response import Response #json 데이터 생성에 사용

from rest_framework import status
from rest_framework.generics import get_object_or_404
from .models import Book
from .serializers import BookSerializer

#하나의 함수를 가지고 GET과 POST를 구분해서 처리
@api_view([&#39;GET&#39;, &#39;POST&#39;])
def booksAPI(request):
    if request.method == &#39;GET&#39;:
        # 테이블의 전체 데이터 가져오기
        books = Book.objects.all()
        #가져온 데이터를 JSON 문자열로 변환
        serializer = BookSerializer(books, many=True)
        #JSON으로 출력
        return Response(serializer.data, status = status.HTTP_200_OK)
    elif request.method == &#39;POST&#39;:
        # 클라이언트에게 전송된 문자열을 모델로 변환
        serializer = BookSerializer(data=request.data)
        # 데이터가 유효성 검사를 통과하면 삽입하고 그렇지 않으면 에러 코드를 전송
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status = status.HTTP_201_CREATED)
        return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)

#기본 키 값을 받아서 하나의 데이터를 찾아서 출력하는 함수
@api_view([&#39;GET&#39;])
def bookAPI(request, bid):
    book = get_object_or_404(Book, bid=bid)
    serializer = BookSerializer(book)
    return Response(serializer.data, status=status.HTTP_200_OK)</code></pre>
<h3 id="12-apiapp-디렉터리의-urlspy-파일에-url과-요청을-처리하는-함수를-연결">12) apiapp 디렉터리의 urls.py 파일에 url과 요청을 처리하는 함수를 연결</h3>
<pre><code>from django.urls import path
from .views import helloAPI, booksAPI, bookAPI

urlpatterns = [
    path(&quot;hello/&quot;, helloAPI),
    path(&quot;fbv/books/&quot;, booksAPI),
    path(&quot;fbv/book/&quot;, bookAPI)
]</code></pre><pre><code>{
&quot;bid&quot;:1,
&quot;title&quot;:&quot;REST API&quot;,
&quot;author&quot;:&quot;adam&quot;,
&quot;category&quot;:&quot;development&quot;,
&quot;pages&quot;:3000,
&quot;price&quot;:25000,
&quot;published_date&quot;:&quot;1970-05-30&quot;,
&quot;description&quot;:&quot;안녕하세요&quot;
}</code></pre><p><img src="https://velog.velcdn.com/images/dev_angie/post/4a19f5cd-93d8-4071-8984-27713fa66ca7/image.png" alt="">
<img src="https://velog.velcdn.com/images/dev_angie/post/21730329-ffc0-40c3-8e68-1dec59d7ab0e/image.png" alt=""></p>
<h3 id="13-브라우저에서-확인">13) 브라우저에서 확인</h3>
<pre><code>127.0.0.1/example/fbv/bookd와 fbv/book/&lt;bid&gt;로 확인</code></pre><h2 id="4-웹-클라이언트-애플리케이션에서-웹-서버의-데이터-가져오기">4. 웹 클라이언트 애플리케이션에서 웹 서버의 데이터 가져오기</h2>
<ul>
<li><p>템플릿 엔진을 이용하는 방식은 서버를 만들 때 사용한 언어를 가지고 클라이언트에게 출력물을 만들어서 제공하는 방식</p>
</li>
<li><p>서버의 데이터를 가져와서 출력하는 방식은 서버는 JSON이나 XML 데이터를 만드는 것 까지만 하고 클라이언트가 데이터를 파싱해서 출력하는 방식</p>
<h3 id="1-방법">1) 방법</h3>
</li>
<li><p>가장 전통적인 방법: ajax(Asynchronous + XML)</p>
<ul>
<li><p>Javascript를 이용해서 XML 데이터를 비동기적으로 받아서 처리하는 기술</p>
</li>
<li><p>최근에는 XML 뿐 아니라 JSON 데이터를 가져와도 ajax라고 합니다.</p>
</li>
<li><p>비동기적으로 동작을 하기 떄문에 콜백을 이용해서 처리해야 하고 동작이 수행중이더라도 다른 작업을 수행하는 것이 가능하다.</p>
</li>
<li><p>웹 페이지가 사용자가 하고 있는 것을 방해하지 않으면서 서버에서 데이터를 받아서 일부분을 수정하고자 할 때 사용</p>
</li>
<li><p>Fetch API</p>
<ul>
<li>ajax와 동일한 기능을 수행하는데 콜백이 아닌 다른 방식으로 요청을 처리</li>
<li>전역 fetch 함수를 이용</li>
</ul>
</li>
</ul>
</li>
<li><p>axios와 같은 라이브러리 이용</p>
</li>
<li><p>서버에서 일방적으로 데이터를 전송하고자 하는 경우는 EventSource를 이용한 SSE(Server Side Event)를 이용
메시지 구독과 게시 방식으로 서버가 클라이언트에게 데이터를 전송</p>
</li>
<li><p>완전 양방향 통신(채팅)을 원하는 경우에는 Web Socket을 사용</p>
</li>
</ul>
<h3 id="2-ajaxxmlhttprequest-인스턴스-이용-구현">2) ajax(XMLHttpRequest 인스턴스 이용) 구현</h3>
<ul>
<li><p>작업 순서</p>
<ul>
<li>XMLHttpRequest 인스턴스를 생성</li>
<li>이벤트별 리스너(콜백 함수)</li>
<li>서버로 보낼 데이터를 생성</li>
<li>연결 요청을 준비: open 메서드 사용</li>
<li>요청을 전송: send 메서드 이용</li>
<li>응답을 처리: status를 확인</li>
<li>결과를 가지고 다음 작업을 수행: 화면에 출력할 것인지 아니면 다른 페이지로 이동할지 결정</li>
</ul>
</li>
<li><p>프로젝트의 urls.py 파일에 ajax 요청을 추가</p>
<pre><code class="language-python">from django.contrib import admin
from django.urls import path, include
</code></pre>
</li>
</ul>
<p>from apiapp import views</p>
<p>urlpatterns = [
    path(&#39;admin/&#39;, admin.site.urls),
    path(&#39;example/&#39;, include(&#39;apiapp.urls&#39;)),
    path(&#39;ajax/&#39;, views.ajax)</p>
<p>]</p>
<pre><code>- apiapp 디렉토리의 views.py에 ajax 함수를 추가
```python
def ajax(request):
    return render(request, &quot;ajax.html&quot;)</code></pre><ul>
<li>apiapp 디렉토리에 templates 폴더를 추가한 후 ajax.html 파일 추가<pre><code class="language-ajax">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
  &lt;meta charset=&quot;UTF-8&quot;&gt;
  &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
  &lt;title&gt;ajax&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
</code></pre>
</li>
</ul>
</body>
<script>
    //ajax 객체를 생성
    let request = new XMLHttpRequest();
    //전체 데이터를 가져오는 요청을 생성
    request.open('GET', '../example/fbv/books/');
    //요청 전송
    request.send('')
    //응답이오면 호출될 함수를 등록
    request.addEventListener("load", () => {
        alert(request.responseText)
    })
</script>
</html>
```
- 서버를 다시 실행하고 브라우저에서 `http://127.0.0.1:8000/ajax/`를 요청해서 브라우저의 대화상자에 문자열이 출력되는지 확인
![](https://velog.velcdn.com/images/dev_angie/post/82a5892d-fac8-4f46-a7e0-932c40abfd4d/image.png)

<ul>
<li><p>JSON Parsing</p>
<ul>
<li>JSON은 문자열</li>
<li>프로그래밍 언어에서 사용하기 위해서는 이 문자열을 원하는 인스턴스로 변환을 해야 하는데 이 작업을 Parsing이라고 한다. <ul>
<li>JSON.parse(JSON 문자열): 문자열을 자바스크립트 객체로 변환</li>
<li>JSON.stringify(JavaScript 객체): 자바스크립트 객체를 JSON 문자열로 변환</li>
</ul>
</li>
</ul>
</li>
<li><p>ajax.html 파일의 스크립트 코드를 수정해서 파싱한 후 출력</p>
<pre><code class="language-python">//ajax 객체를 생성
  let request = new XMLHttpRequest();
  //전체 데이터를 가져오는 요청을 생성
  request.open(&#39;POST&#39;, &#39;../example/fbv/books/&#39;);

  //전송할 데이터를 생성
  let formdata = new FormData();
  formdata.append(&#39;bid&#39;, 3);
  formdata.append(&#39;title&#39;, &#39;파이썬&#39;);
  formdata.append(&#39;authoer&#39;, &quot;귀도반로썸&quot;);
  formdata.append(&#39;category&#39;,&#39;programming&#39;)
  formdata.append(&#39;pages&#39;, 123)
  formdata.append(&#39;price&#39;, 20900)
  formdata.append(&#39;published_data&#39;, &#39;2024-09-24&#39;)
  formdata.append(&#39;description&#39;, &#39;설명&#39;)

  //데이터를 헤더에 포함시켜서 전송하기
  request.setRequestHeader(&quot;Content-type&quot;, &quot;application/x-www-form-urlencoded&quot;)
  let param = &quot;&quot;
  for(let pair of formdata.entries()){
      param += pair[0] + &#39;=&#39; + pair[1] + &#39;&amp;&#39;
  } </code></pre>
</li>
<li><p>Fetch API</p>
<ul>
<li>콜백을 이용하는 것이 아니고 then을 이용해서 비동기 요청을 수행</li>
<li>대다수의 응답이 JSON으로 오기 때문에 파싱을 별도의 <pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
&lt;title&gt;ajax&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
</code></pre>
</li>
</ul>
</li>
</ul>
</body>
<script>
    const data = {
        bid:6,
        title:'자바',
        author:'고슬링',
        category:'프로그래밍',
        pages:245,
        price:38000,
        published_date:'2024-09-24',
        description:'OOP'
    }

<pre><code>fetch(&#39;../example/fbv/books/&#39;,{
    method:&#39;POST&#39;,
    headers:{
        &#39;Content-Type&#39;:&#39;application/json&#39;
    },
    body: JSON.stringify(data)
})
.then((response) =&gt; response.json())
.then((data) =&gt; {
    for(idx in data){
        alert(data[idx].bid)
    }
})</code></pre><p></script></p>
</html>
 ```

<ul>
<li>axios 라이브러리 이용<ul>
<li>코드를 단순화 시켜서 비동기 통신을 수행<pre><code class="language-python">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
&lt;title&gt;ajax&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
</code></pre>
</li>
</ul>
</li>
</ul>
</body>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js">
    axios.get("../example/fbv/books/")
    .then((response)=>{
        alert(response.data)
    })
    .catch((error)=>{
        //실패했을 떄 처리
        alert(error)
    })
    .then(()=>{
        //무조건 수행할 구문
        alert("무조건 수행")
    })
</script>
</html>
```
- ????안되는듯??]]></description>
        </item>
        <item>
            <title><![CDATA[[Web] Web Programming]]></title>
            <link>https://velog.io/@dev_angie/Web-Programming</link>
            <guid>https://velog.io/@dev_angie/Web-Programming</guid>
            <pubDate>Fri, 20 Sep 2024 08:47:39 GMT</pubDate>
            <description><![CDATA[<h2 id="1-개요">1. 개요</h2>
<ul>
<li><p>웹 애플리케이션은 수행되는 위치에 따라 FrontEnd(Client)와 BackEnd(Server)로 분류</p>
</li>
<li><p>FrontEnd</p>
<ul>
<li>BackEnd와 사용자 사이에서 보여지는 부분</li>
<li>Web에서는 HTML, CSS, JavaScript 언어를 이용해서 구현을 하는데 최근에는 프레임워크 형태로 제공되는 jQuery(사용을 금기시), Angular(업데이트 더 이상 진행되지 않음), react, vue, bootstrap(react나 vue 안에서 사용되는 경우가 많음) 등을 많이 이용</li>
<li>Java, Kotlin, Objective-C, Swift, JavaScript, Python 등 도 FrontEnd 프로그래밍이 가능한 언어</li>
</ul>
</li>
<li><p>BackEnd</p>
<ul>
<li>애플리케이션을 동적이고 대화형으로 만들기 위한 숨은 부분</li>
<li>클라이언트의 요청을 받아서 처리하기 위한 부분</li>
<li>서버를 제작하기 위한 프로그래밍 언어(Java, Python, PHP, C#, JavaScript, GO 등) 과 데이터 저장 기술(RDBMS, NoSQL, File등) 과 SW 공학 등의 기술을 가지고 제작</li>
<li>최근에는 프레임워크를 사용하는 경우가 많음: Spring, Django, Express.js, FastApI 등</li>
</ul>
</li>
<li><p>일반적인 Web Application의 동작
클라이언트에서 최초 요청(request)을 서버에 전달하고 서버는 요청을 받아서 처리한 후 그 응답(response)을 HTML 형태로 클라이언트에게 전송하고 이 후 추가적인 자원이 필요하면 다시 요청한 후 서버가 응답을 하는 구조
최근에는 서버가 클라이언트에게 HTML을 전송하지 않고 문자열 형식으로 만들어진 데이터를 전달하고 클라이언트에서 해석을 해서 출력하는 방식을 많이 사용
<del>서버가 HTML을 전송하게 되면 클라이언트가 다양한 형태로 존재하는 경우 서버를 여러 개 준비를 해야 할 가능성이 생긴다.</del></p>
</li>
<li><p>Web 3.0</p>
<ul>
<li>시맨틱 웹의 개념: 컴퓨터가 정보 자원의 뜻을 이해하고 논리적 추론까지 가능한 것</li>
<li>속도와 플랫폼 변화</li>
<li>인공지능</li>
<li>애플리케이션의 진화: open API, SOA(Service Oriented Architecture) 등의 새로운 플랫폼 등장과 Mesh Up</li>
</ul>
</li>
<li><p>WOA(Web Oriented Architecture)</p>
<ul>
<li>웹 기반 서비스 구조</li>
<li>Restful : 동일한 요청에 대해서 동일한 URL을 사용할 수 있돌고 하는 것</li>
</ul>
</li>
<li><p>프레임워크를 이용한 개발을 권장</p>
<ul>
<li>지금의 프로그램은 너무 복잡</li>
<li>복잡한 프로그램을 만들기 위해서 알아야 할 기술이 너무 많음</li>
</ul>
</li>
</ul>
<h3 id="1-urluniform-resource-locator">1) URL(Uniform Resource Locator)</h3>
<ul>
<li>클라이언트가 서버에 존재하는 자원을 요청하는데 필요한 네트워크 서비스의 표현</li>
<li>기본 형식
<code>[프로토콜]://[호스트][:포트]/[경로]/[QueryString - Parameter]#Fragment</code></li>
<li>프로토콜 : 이 부분이 생략되고 //로 시작하면 상황에 따라 http나 https로 프로토콜을 추가</li>
<li>호스트 : 컴퓨터를 구분하기 위한 이름으로 IP나 도메인</li>
<li>포트 : 컴퓨터 내에서 애플리케이션을 구분하기 위한 번호인데 프로토콜의 기본 포트인 경우는 생략이 가능</li>
<li>경로 : 서버에게 요청을 하는 구분하기 위한 문자열로 설정에 따라 생략이 가능</li>
<li>Query String: 클라이언트가 서버에게 전송하는 데이터</li>
<li>Fragment : 문서 내에서 이동할 이름</li>
</ul>
<h3 id="2-url을-바라보는-측명">2) URL을 바라보는 측명</h3>
<ul>
<li><p>웹 클라이언트에서 웹 서버에 존재하는 Application에 대한 API(Application Programming interface)</p>
</li>
<li><p>URL을 RPC(Remote Procedure Call)로 바라보는 시각이 있고 다른 하나는 REST(Representational State Transfer)로 바라보는 시각이 있음</p>
</li>
<li><p>RPC - 원격 프로시저 호출
클라이언트가 네트워크 상에 있는 서버가 제공하는 API 함수를 호출하는 것
URL의 경로를 API 함수 이름으로 하고 쿼리 스트링을 함수의 매개변수로 간주해서 웹 클라이언트에서 URL을 전송하는 것은 웹 서버의 API 함수를 호출하는 것으로 인식하는 것
이 경우는 함수의 이름이 대부분 동사
search라는 API 함수에 test라는 데이터를 전송
<a href="http://blog.example.com/search?q=test&amp;debug=true">http://blog.example.com/search?q=test&amp;debug=true</a></p>
</li>
<li><p>REST
웹 서버에 존재하는 요소들을 모두 리소스로 정의하고 URL을 통해 웹 서버의 특정 리소스를 표현한다고 하는 개념
리소스는 시간이 지남에 따라 상태가 변할 수 있기 때문에 클라이언트와 서버 간의 데이터 교환을 리소스 상태의 교환으로 간주
리소스에 대한 조작을 HTTP 메서드로 구분: GET, POST, PUT, DELETE, PATCH 등
웹 클라이언트에서 URL을 전송하는 것이 웹 서버에 있는 리소스 상태에 대한 데이터를 주고 받는 것으로 간주
<a href="http://blog.example.com/search/test">http://blog.example.com/search/test</a></p>
</li>
<li><p>단축 URL
URL을 간단하게 줄여서 사용하는 방식
<a href="http://example.com/index.php?page=foo">http://example.com/index.php?page=foo</a> 
=&gt; <a href="http://eample.com/foo">http://eample.com/foo</a>
<a href="http://example.com/index.php?page=consulting/marketing">http://example.com/index.php?page=consulting/marketing</a> =&gt; <a href="http://example.com/consulting/marketing">http://example.com/consulting/marketing</a>
<a href="http://example.com/porducts?category=2&amp;pid=25">http://example.com/porducts?category=2&amp;pid=25</a> =&gt; <a href="http://example.com/proudcts/2/25">http://example.com/proudcts/2/25</a></p>
</li>
</ul>
<h3 id="3-http-메서드">3) HTTP 메서드</h3>
<ul>
<li><p>get</p>
<ul>
<li>데이터를 요청할 때 사용하는 것으로 default</li>
<li>클라이언트에서 서버에게 데이터를 전달할 떄 URL 뒤에 데이터를 같이 전송</li>
<li>단점
보안이 취약
데이터 길이에 제한</li>
<li>장점
자동 재전송</li>
<li>폼이 있다면 폼에 file, textarea, password가 있는 경우 get 방식으로 전송하면 안된다. </li>
</ul>
</li>
<li><p>post </p>
<ul>
<li>데이터를 삽입할 때 사용하는 것으로 클라이언트에서 서버에게 데이터를 전송할 떄 요청의 body에 숨겨서 전송하는 방식</li>
<li>장점
보안이 우수
데이터 길이에 제한 없음</li>
<li>단점
자동 재전송 기능이 없음</li>
</ul>
</li>
<li><p>put : 리소스 전체 수정</p>
</li>
<li><p>patch : 리소스 일부 변경 - 사용을 권장하지 않음</p>
</li>
<li><p>delete : 리소스 삭제</p>
</li>
<li><p>head : 리소스 헤더 취득</p>
</li>
<li><p>options : 리소스가 지원하는 메서드 취득</p>
</li>
<li><p>trace : loopback 시험에 이용</p>
</li>
<li><p>connect : 프록시 동작의 터널 접속으로 변경</p>
</li>
<li><p>예전에는 get과 post만 사용했는데 최근에는 put과 delete를 같이 사용</p>
</li>
</ul>
<h3 id="4-상태-코드">4) 상태 코드</h3>
<ul>
<li>웹 서버와 통신에서는 항상 클라이언트에게 상태 코드를 전송
1xx : 정보 제공 중
2xx : 정상 응답
3xx : 리다이렉션 중 - 완전한 동작을 위해서 추가적인 동작을 수행
4xx : 클라이언트 오류
5xx : 서버 오류</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Linux] Shell 프로그래밍]]></title>
            <link>https://velog.io/@dev_angie/Linux-Shell-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</link>
            <guid>https://velog.io/@dev_angie/Linux-Shell-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</guid>
            <pubDate>Mon, 09 Sep 2024 10:22:39 GMT</pubDate>
            <description><![CDATA[<h2 id="shell-programming">Shell Programming</h2>
<hr>
<blockquote>
<p><strong>개요</strong></p>
</blockquote>
<ul>
<li>리눅스의 쉘 스크립트는 C 언어와 유사한 방법으로 프로그래밍 할 수 있다. </li>
<li>쉘 스크립트 파일의 확장자는 sh로 한다. </li>
<li>최상단에는 <code>#!/bin/sh</code> 를 추가한다. 이 줄은 서뱅이라고 불리며, bash 쉘을 사용하겠다는 의미이다. 일반적으로 <code>#</code>으로 시작하면 주석으로 처리되지만, 서뱅은 주석이 아니며, 스크립트 실행 환경을 설정하는 중요한 명령어이다. </li>
</ul>
<blockquote>
<p><strong>스크립트 파일 실행 방법</strong></p>
</blockquote>
<ul>
<li><code>sh 스크립트파일경로</code> - 이 경우는 읽기 권한만 있으면 수행된다. </li>
<li><code>스크립트파일경로</code> - 현재 디렉토리에 있는 경우 ./를 추가해서 경로를 작성, 실행 권한이 있어야 한다. </li>
<li>스크립트 파일의 경로를 입력했는데 실행되지 않으면 현재 디렉토리의 경우 ./를 추가하지 않았는지 또는 실행 권한이 없는 것이 아닌지 확인해야 한다. </li>
</ul>
<blockquote>
<p><strong>변수</strong></p>
</blockquote>
<ul>
<li><strong>이름에 값을 보관하기 위한 것</strong></li>
<li>특징<ul>
<li>기본적으로 변수에 넣는 값은 전부 문자열로 취급</li>
<li>변수 이름은 대소문자 구분</li>
<li>변수에 데이터를 대입할 때 = 를 이용하는데 좌우에 공백이 없어야 한다. </li>
<li>값에 공백이 있는 경우는 &quot;&quot;로 묶어 주어야 한다. </li>
<li>$가 들어간 내용을 출력할 때는 &quot;로 묶어주거나 \를 붙여서 출력해야 한다.</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>계산식 사용</strong></p>
</blockquote>
<ul>
<li><strong>계산식을 사용하고자 하는 경우는 백틱 안에 expr로 시작해서 작성</strong>하면 된다. </li>
<li>수식에 괄호를 사용하는 경우 곱하기 인 *을 사용할 때는 \를 붙여야 한다.</li>
<li>곱하기(<em>)는 특수 문자로 사용되기 때문에, `\</em>`로 작성해야 곱셈으로 인식된다.</li>
<li>괄호도 수식에서 사용할 때는 <code>\(</code> <code>\)</code>를 붙여 사용해야 합니다.</li>
</ul>
<blockquote>
<p><strong>파라미터 설정</strong></p>
</blockquote>
<ul>
<li><strong>실행을 할 때 같이 넘겨주는 데이터</strong></li>
<li>파라미터를 사용할 때는 <code>$파라미터위치</code> 형태로 작성하면 된다. 
위치는 0부터 시작한다. </li>
<li>데이터가 없으면 명령이나 프로그램은 고정적인 일만 가능하다.</li>
<li>명령어<ul>
<li>Shell 내장 명령<ul>
<li>Shell에 따라 다를 수 있음 </li>
</ul>
</li>
<li>실행 파일 경로<pre><code class="language-bash">sh 스크립트파일이름 a b c d
</code></pre>
</li>
</ul>
</li>
</ul>
<p>$ echo &quot;$0&quot; -&gt; a
$ ehco &quot;$2&quot; -&gt; c</p>
<pre><code>&gt; **제어문**

**if**
``` bash
if [표현식] 
then 
      참일 때 수행할 내용
else
      거짓일 때 수행할 내용
fi</code></pre><p><strong>파일 관련 조건</strong></p>
<ul>
<li><p>파일을 읽어야하면 파일이 있는지 없는지 확인해야 한다</p>
</li>
<li><p><code>-d 파일 경로</code> : 디렉터리이면 참
<code>-e 파일 경로</code> : 존재하면 참
<code>-f 파일 경로</code> : 일반 파일이면 참
<code>-g 파일 경로</code> : setGID가 설정되면 참
<code>-r 파일 경로</code> : 읽기 가능이면 참
<code>-s 파일 경로</code> : 크기가 0이 아니면 참
<code>-u 파일 경로</code> : setUID가 설정되면 참 
<code>-w 파일 경로</code> : 쓰기 가능이면 참
<code>-x 파일 경로</code> : 실행 가능이면 참</p>
</li>
<li><p><strong>|</strong> /lib/systemd/system/cron_service 라는 파일의 존재 여부를 확인해서 존재하면 존재한다고 그렇지 않으면 없다고 메시지를 출력</p>
<pre><code class="language-bash">if [ -e /lib/systemd/system/cron_service ]
then
  echo &quot;파일이 존재합니다.&quot;
else
  echo &quot;파일이 존재하지 않습니다.&quot;
fi</code></pre>
</li>
<li><p><em>case~esac문*</em></p>
</li>
<li><p>값으로 분기</p>
<pre><code class="language-bash">case 데이터 in
  값)
      값일 떄 수행할 내용;;
  값)
      값일 떄 수행할 내용;;
  값)
      값일 떄 수행할 내용;;
  *)
       나머지 경우에 수행할 내용;;
esac</code></pre>
</li>
<li><p>case 구문에 각 값 안에서 여러 개의 실행문을 작성할 수 있기 때문에 내용을 작성할 때 마지막에 <code>;;</code>을 추가해주어야 한다. </p>
</li>
<li><p>여러개의 값에 동일한 내용을 수행하고자 하는 경우는 <code>값 | 값</code>의 형태로 작성하면 된다. </p>
</li>
</ul>
<p><strong>and와 or</strong></p>
<ul>
<li>and : <code>&amp;&amp;</code>, <code>-a</code></li>
<li>or : <code>||</code>, <code>-o</code></li>
</ul>
<p><code>if [조건] &amp;&amp; [조건]</code> 의 형태로 입력</p>
<ul>
<li><p><strong>|</strong>  /lib/systemd/system/cron.service 가 있고 홈 디렉토리에 if.sh 파일이 있다면 성공 그렇지 않다면 실패라고 출력</p>
<pre><code class="language-bash">if [-f /lib/systemd/system/cron.service] &amp;&amp; [-f ~/if.sh]
then 
   echo &quot;success&quot;
else
   echo &quot;fail&quot;
fi</code></pre>
<p><strong>for~in</strong></p>
</li>
<li><p>형식</p>
<pre><code class="language-bash">for 임시변수 in 데이터나열
do 
  데이터를 임시변수에 하나씩 대입하고 수행할 문장
done</code></pre>
</li>
<li><p><strong>|</strong> 1부터 5까지 합계</p>
<pre><code class="language-bash">for i in 1 2 3 4 5
do 
  hap=`expr $hap + $i`
done
echo $hap</code></pre>
</li>
<li><p><strong>|</strong> 현재 디렉토리의 모든 txt 파일을 읽어서 내용을 출력</p>
<pre><code class="language-bash">for fname in $(ls *.txt)
do
  cat $fname
done</code></pre>
<blockquote>
<p><strong>while</strong></p>
</blockquote>
</li>
<li><p>표현식이 거짓이 될 때까지 반복</p>
</li>
<li><p>형식</p>
<pre><code class="language-bash">while [표현식]
do 
  표현식이 거짓이 아니면 수행할 내용
done</code></pre>
</li>
<li><p><strong>|</strong> 1부터 5까지 합계</p>
<pre><code class="language-bash">hap=0
i=1
while [ $i -le 6 ]
do
  hap=`expr $hap + $i`
  i=`expr $i + 1`
done
echo &quot;Totla: &quot;$hap&quot;</code></pre>
</li>
</ul>
<blockquote>
<p><strong>기타 제어문</strong></p>
</blockquote>
<blockquote>
<p><strong>break, continue, exit, return</strong></p>
</blockquote>
<ul>
<li><strong>break</strong> : for나 while을 강제로 중단하고자 할 때 사용. case 구문에서 수행할 내용이 없을 떄 사용하기도 한다.</li>
<li><strong>until</strong> : 반복문</li>
<li><strong>continue</strong> : for나 while의 시작 부분으로 이동</li>
<li><strong>exit</strong> : 프로그램이 완전히 종료되는 제어문으로 상위 프로세스에게 넘겨줄 정수를 같이 사용된다. exti 0</li>
<li><strong>return</strong> : 함수를 호출한 곳으로 돌아가는 제어 명령어</li>
</ul>
<blockquote>
<p><strong>함수</strong></p>
</blockquote>
<ul>
<li>자주 사용하는 구문을 묶어서 하나의 이름으로 사용하는 것</li>
<li>메모리를 별도로 할당 받아서 사용한다</li>
</ul>
<p><strong>생성</strong></p>
<pre><code class="language-bash">이름(매개변수 나열){
    함수 내용
    return
}</code></pre>
<p><strong>호출</strong></p>
<ul>
<li>함수이름(매개변수)</li>
<li>매개변수가 없으면 이름만으로 호출 가능</li>
</ul>
<pre><code class="language-bash">myfunc0 (){
    echo &quot;My function&quot;
    return
}

myfunc
exit 0 </code></pre>
<p><strong>eval</strong></p>
<ul>
<li><strong>문자열을 명령으로 수행</strong></li>
<li>파이썬이나 자바스크립트에서는 이 함수가 문자열을 데이터로 치환한다. </li>
</ul>
<p><code>eval&quot;ls -l&quot;</code> -&gt; <code>ls -l</code> 명령어가 실행되어 현재 디렉터리의 파일 목록이 출력됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Linux] Ubuntu 사용자 관리]]></title>
            <link>https://velog.io/@dev_angie/Linux-Ubuntu-%EC%82%AC%EC%9A%A9%EC%9E%90-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@dev_angie/Linux-Ubuntu-%EC%82%AC%EC%9A%A9%EC%9E%90-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Mon, 09 Sep 2024 10:13:58 GMT</pubDate>
            <description><![CDATA[<h2 id="1-사용자-계정-관련-파일">1. 사용자 계정 관련 파일</h2>
<h3 id="1-etcpasswd">1) /etc/passwd</h3>
<p><strong>개요</strong></p>
<ul>
<li><strong>사용자 계정 정보가 저장된 기본 파일</strong></li>
<li>초창기에는 암호도 저장되었지만 지금은 암호는 /etc/shadow에 저장</li>
</ul>
<p><strong>구조</strong>
<code>로그인ID:x:UID:GID:설명:홈디렉터리:로그인쉘</code></p>
<ul>
<li>7개의 항목으로 구성되면 각 항목은 콜론으로 구분한다. </li>
<li><code>x</code> : 예전에 비밀번호를 저장하던 영역으로 호환성 문제 때문에 남아있다. </li>
<li><code>UID</code> : 사용자를 구분하기 위한 번호. 일반적으로 0~9999번과 65534는 시스템 사용자를 위한 UID로 예약되어 있고, 일반 사용자는 1000번 부터 할당한다. root가 0번, 시스템 데몬이 1번, 명령어를 위한 관리 계정이 2번</li>
<li><code>GID</code> : 사용자가 속한 그룹의 ID로 사용자를 등록할 때 정해지고 특별히 소속 그룹이 지정하지 않는 경우 로그인 ID가 그룹으로 등록. 그룹에 대한 정보는 /tex/group에 저장</li>
<li><code>설명</code> : 사용자의 실제 이름이나 부서명 같은 것을 적을 수 있는 부분</li>
<li><code>홈디렉터리</code> : 로그인을 했을 때 자동으로 로그인 되는 디렉토리의 절대 경로</li>
<li><code>로그인 쉘</code> : 로그인했을 때 처음 보여지는 쉘</li>
</ul>
<h3 id="2-etcshadow">2) /etc/shadow</h3>
<ul>
<li>사용자 암호에 대한 정보를 저장한 파일</li>
<li>9개 영역으로 구성<ol>
<li>ID</li>
<li>비밀번호, 단방향 암호화를 이용해서 저장. 시스템 계정은 *<ul>
<li>단방향 암호화: 암호화한 내용과 평문을 비교해서 일치여부는 알 수 있지만 암호화된 내용을 이용해서 원래 내용으로 만드는 것은 안되는 방식</li>
<li>양방향 암호화: 암호화한 내용을 기반으로 원래 내용을 만들 수 있는 방식</li>
</ul>
</li>
<li>비밀번호 변경한 날짜
&quot;1970년 1월 1일을 기준으로 지나온 날짜&quot;</li>
<li>비밀번호를 변경하고 사용해야 하는 최소 날짜<ol start="5">
<li>비밀번호를 변경하고 사용해야 하는 최대 날짜</li>
<li>경고를 발생할 날짜</li>
<li>비밀번호가 유효한 마지막 날짜</li>
<li>미래를 위해서 만든 항목</li>
</ol>
</li>
</ol>
</li>
</ul>
<h3 id="3-etclogindefs">3) /etc/login.defs</h3>
<ul>
<li>로그인과 관련된 기본 설정 파일</li>
<li>주요 설정 값
<code>MAIL_DIR</code> -&gt; /var/mail : 기본 메일 디렉터리
<code>PASS_MAX_DAYS</code> -&gt; 99999
<code>PASS_MIN_DAYS</code> -&gt; 0
<code>PASS_WARN_AGE</code> -&gt; 7
<code>UID_MIN</code>, <code>UID_MAX</code> -&gt; 1000<del>60000 : 사용자 계정의 UID 범위
<code>SYS_UID_MIN</code>, <code>SYS_UID_MAX</code> -&gt; 100</del>699 : 시스템 계정의 UID 범위
<code>GID_MIN</code>, <code>GID_MAX</code> -&gt; 1000<del>60000 : 사용자 계정의 GID 범위
<code>SYS_GID_MIN</code>, <code>SYS_GID_MAX</code> -&gt; 100</del>699 : 시스템 계정의 GID 범위
<code>DEFAULT_HOME</code> -&gt; yes : 사용자 홈 디렉터리 생성 여부
<code>UMASK</code> -&gt; 0002 : umask 값 설정
<code>USERGROUP_ENAB</code> -&gt; yes: 계정 삭제 시 그룹 삭제 여부
<code>ENCRYPT_METHOD</code> -&gt; SHA512: 암호화 기법</li>
</ul>
<h3 id="4-etcgroup">4) /etc/group</h3>
<ul>
<li>그룹의 정보가 저장된 파일</li>
<li>리눅스에서 사용자는 하나 이상의 그룹에 속해야 한다.</li>
<li>기본 그룹은 /etc/passwd에 작성되어 있고 2차 그룹에 대한 내용을 /etc/group에 작성
<code>그룹이름:x:GID:그룹멤버</code></li>
</ul>
<h3 id="5-etcgshadow">5) /etc/gshadow</h3>
<ul>
<li>그룹 암호가 저장된 파일</li>
<li>유닉스에는 없는 파일</li>
</ul>
<h2 id="2-계정-관리-명령">2. 계정 관리 명령</h2>
<h3 id="1-사용자-계정-생성">1) 사용자 계정 생성</h3>
<ul>
<li>형식
<code>useradd [옵션] [로그인ID]</code></li>
<li>옵션
<code>u</code>: UID 지정
<code>o</code>: UID 중복 허용
<code>g</code>: GID 지정
<code>G</code>: 2차 그룹 지정
<code>d</code>: 홈 디렉터리 지정
<code>s</code>: 기본 쉘 
<code>c</code>: 부가적인 설명
<code>D</code>: 기본값을 설정하거나 출력
<code>e</code>: EXPIRE 항목을 설정
<code>f</code>: 비활성 설정
<code>k</code>: 계정 생성할 때 사용할 초기화 파일을 저장한 디렉토리 설정</li>
<li>사용자 계정을 생성할 때는 passwd 명령을 이용해서 비밀번호를 설정을 해야한다. </li>
<li>사용자 계정이 만들어질 때의 옵션 확인: <code>useradd -D</code></li>
<li>-1 =&gt; 1의 2의 보수 / 모든 자리가 다 1, 즉 제일 큰 수 의미한다 / 양수만 있는건지 음수도 있는건지 같이 확인해야한다. 양수만 존재하면 -1은 가장 큰 수</li>
</ul>
<pre><code>$ useradd -D
GROUP=100       -- 그룹 ID
HOME=/home      -- 홈 디렉터리의 생성 위치
INACTIVE=-1     -- 0으로 설정하면 암호가 만료되자마자 바로 계정이 잠김
EXPIRE=          -- 계정 종료일
SHELL=/bin/sh   -- 기본 로그인 쉘을 설정
SKEL=/etc/skel  -- 홈 디렉터리의 복사할 기본 환경 파일의 경로
CREATE_MAIL_SPOOL=no  -- 메일 디렉터리 생성 여부  </code></pre><ul>
<li><p>사용자 계정 시 설정된 옵션은 vi를 이용해서 /etc/default/user 파일을 수정해서 변경할 수 있지만 되도록이면 -b, -e, -f, -g, -s 와 같은 옵션을 수정하는 것을 권장</p>
</li>
<li><p>옵션을 이용한 사용자 계정 생성
<code>sudo useradd -s /bin/bash -m -d /home/user3 -u 2000 -g 1000 -G 3 user3</code></p>
</li>
<li><p>adduser 명령</p>
<ul>
<li>사용자 계정을 생성하는 명령</li>
<li>형식
<code>adduser [옵션] 로그인ID</code><ul>
<li>옵션
<code>--uid</code>
<code>--gid</code> 
<code>--home</code> 
<code>--shell</code> 
<code>--gecos</code> : 설명을 붙여주는 옵션</li>
</ul>
</li>
<li>생성할 때 옵션 적용 내용이 출련된다. </li>
<li>sudo adduser user4</li>
<li>adduser로 유저를 생성할 때는 /etc/adduser.conf 파일을 기반으로 한다. </li>
</ul>
</li>
</ul>
<h3 id="2-계정-수정">2) 계정 수정</h3>
<ul>
<li><p>명령 형식
<code>usermod [옵션] [로그인ID]</code></p>
<ul>
<li><p>옵션
<code>u</code> : UID 수정
<code>o</code> : UID 중복 허용
<code>g</code> : 기본 그룹 수정
<code>G</code> : 2차 그룹 수정
<code>d</code> : 홈 디렉토리 수정
<code>s</code> : 기본 쉘 수정
<code>c</code> : 보충 설명 수정
<code>f</code> : 계정 비활성화 날짜를 수정
<code>e</code> : 계정 만료날짜 수정
<code>i</code> : 계정 이름 변경</p>
</li>
<li><p>UID를 수정해서 중복을 허용하게 되면 사용자 아이디가 달라도 동일한 계정으로 로그인한 것으로 간주</p>
</li>
</ul>
</li>
</ul>
<h3 id="3-패스워드-에이징">3) 패스워드 에이징</h3>
<ul>
<li><p>aging : 시간이 지남에 따라 우선 순위가 높아지는 것</p>
</li>
<li><p>비밀번호의 유효 기간을 설정하는 것을 패스워드 에이징이라고 한다. </p>
</li>
<li><p>관련 명령</p>
<table>
<thead>
<tr>
<th></th>
<th>useradd, usermod, passwd 명령 수정</th>
<th>chage 명령으로 수정</th>
</tr>
</thead>
<tbody><tr>
<td><strong>MIN</strong></td>
<td>passwd -n 날짜</td>
<td>change -m</td>
</tr>
<tr>
<td><strong>MAX</strong></td>
<td>passwd -x 날짜</td>
<td>change -M</td>
</tr>
<tr>
<td><strong>WARNING</strong></td>
<td>passwd -w 날짜</td>
<td>change -W</td>
</tr>
<tr>
<td><strong>INACTIVE</strong></td>
<td>useradd -f 날수, usermod -f 날수</td>
<td>change -I</td>
</tr>
<tr>
<td><strong>EXPIRE</strong></td>
<td>useradd -e 날짜, usermod -e 날짜</td>
<td>change -E</td>
</tr>
</tbody></table>
</li>
</ul>
<ul>
<li>변경<ul>
<li>기존의 값을 확인 : <code>sudo cat /etc/shadow | grep user3</code></li>
<li>변경: <code>sudo usermod -f 10 -e 2-24-12-31 user3</code></li>
<li>기존의 값을 확인: <code>sudo cat /etc/shadow | grep user3</code></li>
</ul>
</li>
</ul>
<h3 id="4-계정-삭제">4) 계정 삭제</h3>
<ul>
<li>형식
<code>userdel [옵션] [로그인ID]</code><ul>
<li>옵션
r : 홈 디렉터리까지 삭제
f : 로그인 중이어도 강제 삭제</li>
</ul>
</li>
</ul>
<h3 id="5-사용자-계정-명령-실습">5) 사용자 계정 명령 실습</h3>
<ul>
<li><p>useradd 명령으로 test01, test02 계정을 생성 : 비밀번호 설정 없이 계정을 생성(원격 접속 불가) 하고 계정이 생성될 때 수행하는 기본 작업을 보여주지 않는다. </p>
<table>
<thead>
<tr>
<th>로그인ID</th>
<th>로그인쉘</th>
<th>UID</th>
<th>2차그룹</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>test01</td>
<td>sh(본쉘)</td>
<td>2100</td>
<td>3</td>
<td>test01.user</td>
</tr>
<tr>
<td>test02</td>
<td>bash</td>
<td>2200</td>
<td>4</td>
<td>test02.user</td>
</tr>
</tbody></table>
<ul>
<li>sudo useradd -m -u 2100 -G 3 -s /bin/sh -c &quot;test01 user&quot; test01 </li>
<li>sudo useradd -m -u 2200 -G 4 -s /bin/sh -c &quot;test02 user&quot; test02</li>
</ul>
</li>
<li><p>adduser 명령으로 test03 계정을 생성 : 초기 설정 과정이 화면에 출력되고 비밀번호 설정 메시지가 제공된다. </p>
<table>
<thead>
<tr>
<th>로그인ID</th>
<th>로그인쉘</th>
<th>UID</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>test03</td>
<td>bash</td>
<td>2300</td>
<td>test03.user</td>
</tr>
</tbody></table>
<ul>
<li>sudo adduser --uid 2300 --shell /bin/bash --gecos &quot;test03 user&quot; test03</li>
</ul>
</li>
</ul>
<ul>
<li><p>패스워드 에이징(유효 기간 수정)</p>
<table>
<thead>
<tr>
<th>MIN(m)</th>
<th>MAX(M)</th>
<th>WARNING(W)</th>
<th>INACTIVE(I)</th>
<th>EXPIRE(E)</th>
</tr>
</thead>
<tbody><tr>
<td>4</td>
<td>200</td>
<td>10</td>
<td>5</td>
<td>2024-12-09</td>
</tr>
<tr>
<td>chage 명령으로 수정</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>- sudo chage -m 4 -M 200 -W 10 -l 5 -E 2024-12-09 test01</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
</li>
<li><p>test03 계정의 UID를 2010으로 계정의 이름을 test33으로 수정. usermode 명령을 이용. uid를 u 옵션을 이용. 계정의 이름 변경은 l 옵션 사용. </p>
<ul>
<li>sudo usermod -u 2010 -l test33 test03</li>
</ul>
</li>
<li><p>test02 계정을 삭제 - 사용자의 홈디렉토리까지 삭제</p>
<ul>
<li>sudo userdel -r test02</li>
</ul>
</li>
<li><p>public cloud 에서 IaaS 서비스로 머신을 제공할 떄 계정을 추가할 수 있도록 하는데 이 경우 계정만 추가하도록 해주는 경우도 있고 /home 디렉토리를 만들어주는 경우도 있다. </p>
</li>
</ul>
<h2 id="3-그룹-관리-명령">3. 그룹 관리 명령</h2>
<h3 id="1-개요">1) 개요</h3>
<ul>
<li>리눅스는 모든 유저가 하나 이상의 그룹에 속하도록 한다. </li>
<li>시스템을 사용하는 사용자가 많아지면 업무나 기능에 따라 사용자들을 적절히 나누고 권한을 조정해야 합니다. </li>
<li>시스템 관리자가 수행하는 작업</li>
<li>관련 명령은 groupadd, addgroup, groupmod, groupdel</li>
</ul>
<h3 id="2-그룹-생성groupadd">2) 그룹 생성(groupadd)</h3>
<ul>
<li><code>groupadd [옵션] [그룹이름]</code><ul>
<li>옵션 
<code>g</code> : 그룹 아이디
<code>o</code> : 그룹 아이디 중복 허용<ul>
<li>옵션 없이 생성하면 마지막에 생성된 그룹 아이디 다음 번호로 그룹 아이디를 설정해서 생성</li>
</ul>
</li>
</ul>
</li>
<li>옵션 없이 그룹 생성<ul>
<li>sudo groupadd gtest01</li>
<li>sudo cat /etc/group | grep gtest01</li>
</ul>
</li>
<li>그룹 아이디를 직접 설정<ul>
<li>sudo groupadd -g 3000 gtest02</li>
</ul>
</li>
<li>addgroup 명령으로 생성가능한데 이 때는 옵션이 --gid<ul>
<li>sudo addgroup --gid 3001 gtest04</li>
</ul>
</li>
</ul>
<h3 id="3-그룹-수정groupmod">3) 그룹 수정(groupmod)</h3>
<ul>
<li><code>groupmod [옵션] [그룹아이디]</code><ul>
<li>옵션
<code>g</code> : 그룹 아이디를 변경 
<code>n</code> : 그룹 이름 변경</li>
</ul>
</li>
<li>sudo groupmod -g 2500 -n gtest05 gtest04</li>
</ul>
<h3 id="4-그룹-삭제groupdel">4) 그룹 삭제(groupdel)</h3>
<ul>
<li><code>groupdel</code></li>
</ul>
<h3 id="5-그룹-암호-설정-및-사용">5) 그룹 암호 설정 및 사용</h3>
<ul>
<li><code>gpasswd [옵션] [그룹이름]</code><ul>
<li>옵션
<code>a</code> : 사용자 계정을 그룹에 추가
<code>d</code> : 사용자 계정을 그룹에서 삭제
<code>r</code> : 그룹 암호를 삭제
옵션이 없으면 암호 설정</li>
</ul>
</li>
<li>그룹에 멤버 추가<ul>
<li>sudo gpasswd -a test01 gtest01</li>
</ul>
</li>
<li>그룹에서 멤버 삭제<ul>
<li>sudo gpasswd -d test01 gtest01</li>
</ul>
</li>
<li>그룹 암호 설정<ul>
<li>sudo gpasswd gtest01</li>
</ul>
</li>
<li>암호를 설정하는 이유는 한명의 유저가 서로 다른 2개 이상의 그룹에 소속된 경우그룹을 변경하고자 할 때 사용한다. 이 때 사용하는 명령은 <code>newgrp [그룹이름]</code></li>
</ul>
<h2 id="4-사용자-정보-관리-명령">4. 사용자 정보 관리 명령</h2>
<h3 id="1-uid와-euid">1) UID와 EUID</h3>
<ul>
<li>UID(RUID): 사용자가 로그인할 떄 사용한 계정의 UID</li>
<li>EUID: 현재 명령을 수행하는 주체의 UID</li>
<li>대부분의 경우는 UID와 EUID가 일치하지만 달라지는 경우<ul>
<li>실행 파일에 setuid가 설정된 경우</li>
<li>su 명령을 이용해서 계정을 변경한 경우</li>
</ul>
</li>
</ul>
<h3 id="2-who와-w">2) who와 w</h3>
<ul>
<li>who : 현재 로그인한 사용자 정보를 출력
옵션을 설정하면 일부 정보만 확인 가능</li>
<li>w 명령은 현재 시스템에 로그인 한 사용자의 정보 외에 사용자가 현재 실행 중인 작업에 대한 정보를 출력. 옵션에 사용자 이름을 설정하면 사용자 이름에 해당하는 작업만 출력</li>
<li>last 명령을 이용하면 시스템에 로그인 하고 로그아웃 정보를 출력</li>
</ul>
<h3 id="3-uid와-euid-확인">3) UID와 EUID 확인</h3>
<ul>
<li>현재 사용 중인 유저 확인(EUID 출력) : whoami</li>
<li>로그인 한 유저 확인(RUID - UID) : who am i</li>
<li>아이디 확인 : id</li>
<li>소속 그룹 확인 : groups [유저아이디]
유저 아이디를 생략하면 현재 사용자 계정이 속한 그룹</li>
</ul>
<h3 id="4-권한-수정">4) 권한 수정</h3>
<ul>
<li><p>다른 계정으로 전환해서 권한을 사용 </p>
<ul>
<li>su 명령으로 root 계정으로 전환해서 기능 사용</li>
<li>root 계정으로 전환해서 권한을 사용하는 것을 위험한데 이는 시스템 관리 권한을 갖기 떄문</li>
</ul>
</li>
<li><p>계정 별로 특정한 작업을 수행할 수 있도록 권한을 부여</p>
<ul>
<li>sudo 명령으로 root 권한을 실행하려면 특정 권한을 부여받아야 하는데 이 권한은  /etc/sudoers 파일에 설정. 
사용자 계정 호스트 = 명령어 형태로 설정</li>
<li>sudoers 파일은 기본적으로 읽기 전용이므로 수정을 하고자 하면 sudo chmod 명령으로 w 권한을 설정하고 해야 한다. </li>
</ul>
<p>root ALL=(ALL)  ALL</p>
<ul>
<li>user2에게 유저를 생성하고 유저를 수정하는 권한을 갖도록 수정
user2 ALL=/usr/sbin/useradd, /usr/sbin/usermod</li>
</ul>
</li>
<li><p>처음 로그인 한 유저가 관리자 명령 실행 권한이 없어서 명령을 수행을 못하는 경우 위처럼 sudoers 파일을 수정해서 해도 되고 su를 이용해서 관리자로 로그인 하고 usermod -aG sudo 계정으로 계정에 모든 관리자 명령을 수행할 수 있도록 설정하기도 한다. </p>
</li>
</ul>
<h3 id="5-파일-및-디렉터리-소유자-외-소유-그룹-변경">5) 파일 및 디렉터리 소유자 외 소유 그룹 변경</h3>
<ul>
<li><p>파일이나 디렉터리를 생성하고 생성한 사용자의 계정과 그룹이 소유자와 소유 그룹으로 설정</p>
</li>
<li><p>파일이나 디렉터리의 소유자를 변경할 필요가 있을 때 사용하는 명령</p>
</li>
<li><p>파일 소유자를 변경하는 <code>chown</code></p>
<ul>
<li><p>형식
<code>chown [옵션][사용자 계정][파일 or 디렉터리 경로]</code>
옵션
<code>R</code> : 서브 디렉토리까지 적용</p>
</li>
<li><p>예시
<code>chown user2 file</code>   -&gt; file1의 소유자를 user2로 변경
<code>chown user2:grp01 file1</code>  -&gt; file1의 소유자를 grp01의 user2로 변경
<code>chown -R user2 file1</code>  -&gt; file1의 소유자를 user2로 변경하는데 하위 디렉터리가 있으면 같이 변경</p>
</li>
<li><p>소유자 변경보다는 권한 변경을 하는 경우가 많음</p>
</li>
<li><p>실습</p>
<ol>
<li>홈 디렉터리에 linux_ex 디렉터리를 생성
linux_ex 디렉터리 안에 autoever라는 디렉터리를 생성
autoever 디렉터리 안에 temp 라는 디렉터리를 생성
-&gt; <code>mkdir -p ~/linux_ex/autoever/temp</code>
(웹 서버를 만들어서 파일 업로드 기능을 만드는 경우나 DataLake를 구성하는 경우, 날짜를 가지고 디렉터리를 만들어서 데이터를 보관하는 경우가 있는데 이 경우 디렉터리 생성할 때 날짜를 계층 별로 이용하는 디렉터리를 생성하는 경우가 있다)
Datawarehouse: 업무에 맞는,  DataLake: 모든 데이터 </li>
<li>temp 디렉터리 안에 /etc/hosts 디렉터리의 모든 내용을 복사</li>
</ol>
<p>-&gt; <code>cp /etc/hosts ~/linux_ex/autoever/temp</code></p>
<ol start="3">
<li>temp 디렉터리 안에 /etc/services 디렉터리의 모든 내용을 복사</li>
</ol>
<p>-&gt; <code>cp /etc/services ~/linux_ex/autoever/temp</code></p>
<ol start="4">
<li>소유자와 그룹 확인(소유자와 그룹이 파일을 복사할 때 로그인 중인 유저가 된다)</li>
</ol>
<p>-&gt; <code>ls -l</code></p>
<ul>
<li>hosts 파일의 소유자를 owner1으로 수정 
-&gt; <code>sudo chown owner1 hosts</code></li>
<li>services 파일의 소유자를 owner1으로 소유 그룹도 owner1으로 변경
-&gt; <code>sudo chown owner1.owner1 services</code></li>
</ul>
</li>
</ul>
</li>
<li><p>파일의 소유 그룹을 변경하는 <code>chgrp</code></p>
<ul>
<li>형식
<code>chgrp [옵션][사용자 계정][파일 or 디렉터리 경로]</code></li>
<li>chown 명령 사용시 주의사항<ul>
<li>스크립트를 이용해서 자동화하는 경우 권한 문제가 발생할 수 있음
chown 명령을 사용하는 스크립트의 경우는 앞에 sudo를 추가해서 필요한 권한을 전부 취득하고 하는 것이 좋다. </li>
<li>디렉터리 관련 작업을 할 때 -R 옵션을 추가하면 하위 디렉터리까지 같이 작업을 수행한 후 할 수 있다면 find 명령을 수행하고 하는 것이 바람직: 그렇지 않으면 성능 이슈가 발생할 수 있다. </li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="5-디스크-사용량-설정">5. 디스크 사용량 설정</h2>
<h3 id="1-개요-1">1) 개요</h3>
<ul>
<li>리눅스는 기본적으로 여러 사용자가 한꺼번에 사용하는 시스템</li>
<li>디스크 사용량을 제한하는 기능을 제공. 이를 디스크 쿼터라고 한다. </li>
<li>설정 방식<ul>
<li>사용자가 사용할 수 있는 파일의 전체 용량을 설정</li>
<li>파일의 개수를 제한</li>
</ul>
</li>
<li>쿼터 설정시 한계치 설정<ul>
<li>hard limit: 절대로 넘어설 수 없는 용량을 설정</li>
<li>soft limit: 일정 시간 동안은 용량을 넘어설 수 있는 방식</li>
</ul>
</li>
</ul>
<h3 id="2-사용하는-패키지-quota">2) 사용하는 패키지: quota</h3>
<p><code>sudo apt update</code>
<code>sudo apt install -y quota</code></p>
<h3 id="3-준비">3) 준비</h3>
<ul>
<li>파일 시스템의 마운트 옵션에 쿼터 속성을 설정<ul>
<li>usrquota : 개별 사용자의 쿼터를 제한할 수 있는 속성</li>
<li>grpquota : 개별 그룹의 쿼터를 제한할 수 있는 속성</li>
</ul>
</li>
<li>쿼터 속성 설정은 /etc/fstab 파일에 설정</li>
</ul>
<h3 id="4-루트에-실습을-위한-디렉토리와-계정-설정">4) 루트에 실습을 위한 디렉토리와 계정 설정</h3>
<p><code>sudo mkdir /home2</code></p>
<p><code>sudo mount /dev/sda2 /home2</code> 
 마운트 - 디렉토리를 어떤 디스크에 생성할 것인가 설정</p>
<p><code>sudo useradd -m -d /home2/qtest1 qtest1</code>
<code>sudo useradd -m -d /home2/qtest2 qtest2</code></p>
<p><code>ls /home2</code>
<code>tail -2 /etc/passwd</code></p>
<p>-- 현재 파일 시스템 확인
<code>df -TH</code></p>
<ul>
<li>쿼터 속성 설정 - /etc/fstab 파일
<code>파일시스템이름 디렉토리경로 파일시스템종류  defaults,usrquota 1 1</code>
<code>/dev/sda2       /home2        ext4    default,usrquota     1  1</code>
<img src="https://velog.velcdn.com/images/dev_angie/post/38c044c0-320c-4954-a76a-41fc2594aeb2/image.png" alt=""></li>
<li>remount
<code>sudo mount -o remount /home2</code></li>
<li>mount 정보 확인
<code>mount</code></li>
</ul>
<h3 id="5-쿼터-데이터베이스-파일-생성">5) 쿼터 데이터베이스 파일 생성</h3>
<ul>
<li><p>명령 - quotacheck</p>
<ul>
<li>쿼터 파일을 생성 및 확인 그리고 수정하기 위해서 파일 시스템을 스캔하는 명령</li>
<li>형식: <code>quotacheck [옵션] [파일 시스템]</code><ul>
<li>옵션
<code>a</code> : 전체 파일 시스템을 체크
<code>u</code> : 사용자 쿼터를 확인
<code>g</code> : 그룹 쿼터를 확인
<code>m</code> : 파일 시스템을 리마운트하지 않음
<code>v</code> : 명령 진행 상황을 상세하게 출력</li>
</ul>
</li>
</ul>
</li>
<li><p>명령 실행
<code>quotacheck -ugvm /home</code></p>
<ul>
<li>2개의 데이터베이스 파일이 생성
<code>aquota.user</code> : 사용자 쿼터 데이터베이스 파일
<code>aquota.group</code> : 그룹 쿼터 데이터베이스 파일</li>
</ul>
</li>
<li><p>쿼터 사용 활성화 : quotaon
<code>quotaon [옵션][파일시스템]</code></p>
<ul>
<li>옵션
<code>a</code> : 전체 파일 시스템에 적용
<code>u</code> : 사용자 쿼터를 활성화
<code>g</code> : 그룹 쿼터 활성화
<code>v</code> : 명령 진행 상황을 상세하게 출력</li>
<li>명령 수행
<code>sudo quotaon -uv /home2</code></li>
</ul>
</li>
<li><p>쿼터 설정 및 확인</p>
<ul>
<li>명령: edquota</li>
<li>형식
<code>edquota [옵션][사용자 계정 or 그룹]</code><ul>
<li>옵션
<code>u</code> : 사용자 쿼터 설정
<code>g</code> : 그룹 쿼터 설정 
<code>p</code> : 쿼터 설정을 복사</li>
<li>-u 옵션을 이용하면 쿼터 편집 창이 나오게 되는데 원하는 크기를 soft와 hard에 설정하고 저장한 후 빠져나오면 된다. <ul>
<li>주의할 점은 단위가 KB라는 것이다. </li>
<li>설정을 할 때 값을 0으로 설정하면 무한대</li>
<li>쿼터 확인
<code>quota [옵션][사용자 계정 및 그룹]</code>
옵션은 u와 g</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] AOP 활용하여 로깅 구현하기]]></title>
            <link>https://velog.io/@dev_angie/Spring-AOP%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-AOP%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%98%EC%97%AC-%EB%A1%9C%EA%B9%85-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev_angie/Spring-AOP%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-AOP%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%98%EC%97%AC-%EB%A1%9C%EA%B9%85-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 11 Aug 2024 08:15:08 GMT</pubDate>
            <description><![CDATA[<h2 id="스프링-aop-aspect-oriented-programming">스프링 AOP (Aspect Oriented Programming)</h2>
<hr>
<p>AOP는 관점 지향 프로그래밍이다. 이는 <strong>어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화</strong>하겠다는 것이다. 여기서 모듈화란 공통된 로직이나 기능을 하나의 단위로 묶는 것을 말한다. </p>
<p>예로들어 핵심적인 관점은 결국 우리가 적용하고자 하는 <em>핵심 비즈니스 로직</em> 이 된다. 또한 부가적인 관점은 핵심 로직을 실행하기 위해서 행해지는 <em>데이터베이스 연결, 로깅, 파일 입출력</em> 등을 예로 들 수 있다.</p>
<p>AOP에서는 각 관점을 기준으로 로직을 모듈화한다는 것은 코드들을 부분적으로 나누어서 모듈화하겠다는 의미다. 이때, 소스 코드상에서 다른 부분에 계속 반복해서 쓰는 코드들을 발견할 수 있는 데 이것을 <strong>흩어진 관심사 (Crosscutting Concerns)</strong>라 부른다. </p>
<p><img src="https://velog.velcdn.com/images/dev_angie/post/09168f8d-9ca1-46fd-b33f-0be7d7578747/image.png" alt=""></p>
<p>위와 같이 <strong>흩어진 관심사를 Aspect로 모듈화하고 핵심적인 비즈니스 로직에서 분리하여 재사용하겠다는 것이 AOP의 취지</strong>다.</p>
<h2 id="aop-주요-개념">AOP 주요 개념</h2>
<hr>
<ul>
<li><strong>Aspect</strong> : 위에서 설명한 흩어진 관심사를 모듈화 한 것. 주로 부가기능을 모듈화함.</li>
<li><strong>Target</strong> : Aspect를 적용하는 곳 (클래스, 메서드 .. )</li>
<li><strong>Advice</strong> : 실질적으로 어떤 일을 해야할 지에 대한 것, 실질적인 부가기능을 담은 구현체</li>
<li><strong>JointPoint</strong> : Advice가 적용될 위치, 끼어들 수 있는 지점. 메서드 진입 지점, 생성자 호출 시점, 필드에서 값을 꺼내올 때 등 다양한 시점에 적용가능</li>
<li><strong>PointCut</strong> : JointPoint의 상세한 스펙을 정의한 것. &#39;A란 메서드의 진입 시점에 호출할 것&#39;과 같이 더욱 구체적으로 Advice가 실행될 지점을 정할 수 있음</li>
</ul>
<h2 id="스프링-aop-특징">스프링 AOP 특징</h2>
<hr>
<ul>
<li>프록시 패턴 기반의 AOP 구현체이며, 프록시 객체를 쓰는 이유는 접근 제어 및 부가기능을 추가하기 위해이다.</li>
<li>스프링 빈에만 AOP를 적용 가능하다.</li>
<li>모든 AOP 기능을 제공하는 것이 아닌 스프링 IoC와 연동하여 엔터프라이즈 애플리케이션에서 가장 흔한 문제(중복코드, 프록시 클래스 작성의 번거로움, 객체들 간 관계 복잡도 증가  등)에 대한 해결책을 지원하는 것이 목적이다. </li>
</ul>
<h2 id="spring-aop로-로깅-작성하기">Spring AOP로 로깅 작성하기</h2>
<hr>
<p>기존에 일일이 작성했던 로그 대신, Spring AOP를 사용하여 로그를 남겨볼 것이다. </p>
<h3 id="1-buildgradle-추가하기">1. build.gradle 추가하기</h3>
<pre><code class="language-gradle">implementation &#39;org.springframework.boot:spring-boot-starter-aop&#39;</code></pre>
<h3 id="2-loggingaspect-파일-작성">2. LoggingAspect 파일 작성</h3>
<pre><code class="language-java">@Slf4j
@Aspect
@Component
public class LoggingAspect {

    // 공통 포인트컷 정의
    @Pointcut(&quot;execution(* com.shopping.book..controller.*.*(..)) || &quot; +
            &quot;execution(* com.shopping.book..service.*.*(..)) || &quot; +
            &quot;execution(* com.shopping.book..repository.*.*(..))&quot;)
    public void applicationPackagePointcut() {
        // 포인트컷 메서드 본문은 비워둠
    }

    @Before(&quot;applicationPackagePointcut()&quot;)
    public void logBeforeMethods(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        log.info(&quot;Entering method: {} with arguments: {}&quot;, methodName, Arrays.toString(args));
    }

    @AfterReturning(pointcut = &quot;applicationPackagePointcut()&quot;, returning = &quot;result&quot;)
    public void logAfterMethods(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        log.info(&quot;Exiting method: {} with result: {}&quot;, methodName, result);
    }

    @AfterThrowing(pointcut = &quot;applicationPackagePointcut()&quot;, throwing = &quot;exception&quot;)
    public void logException(JoinPoint joinPoint, Throwable exception) {
        String methodName = joinPoint.getSignature().getName();
        log.error(&quot;Exception in method: {} with message: {}&quot;, methodName, exception.getMessage(), exception);
    }
}
</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_angie/post/a2a0d751-fb92-493a-9833-4a7ea729acb5/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Test] Testcontainers를 사용하여 테스트 환경 구축하기]]></title>
            <link>https://velog.io/@dev_angie/%EC%87%BC%ED%95%91%EB%AA%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%A5%BC-%EC%9C%84%ED%95%9C-testcontainer-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev_angie/%EC%87%BC%ED%95%91%EB%AA%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%A5%BC-%EC%9C%84%ED%95%9C-testcontainer-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 07 Jun 2024 06:36:04 GMT</pubDate>
            <description><![CDATA[<p>Repository 테스트 코드 작성 중, 데이터베이스와 연동된 테스트를 실행하면서 예상치 못한 상황을 마주했다. 최종적으로 Testcontainers를 도입한 경험을 공유하고자 한다.</p>
<h2 id="1-기존-접근-방식의-한계">1. 기존 접근 방식의 한계</h2>
<p>테스트 환경을 구축할 때, 주로 아래와 같은 방법들이 사용됩니다:</p>
<blockquote>
<ul>
<li>Local: 로컬에 데이터베이스를 설치하고, 환경을 구축하여 테스트를 수행.</li>
</ul>
</blockquote>
<ul>
<li>In-Memory: 인메모리 데이터베이스를 활용하여 테스트 실행 시 사용.</li>
<li>Embedded Library: 임베디드 라이브러리를 이용하여 테스트 구동 시 사용</li>
</ul>
<p><strong>문제점</strong>
GitHub Actions를 사용해 CI 파이프라인을 설정하면서 테스트를 자동화했는데, 이로 인해 로컬에서 직접 데이터베이스를 설정하여 테스트하는 것은 불가능했다. 또한, CI 환경에서 서버에 데이터베이스를 올리는 방법도 고려했지만, 이 경우 비용 부담이 있었다.</p>
<p>처음에는 인메모리 데이터베이스를 사용하려 했으나, Spring Boot에서 인메모리 데이터베이스를 사용할 경우 테이블 이름을 변경해야 했다. 운영 환경에서는 MySQL을 사용하지만, 테스트 환경에서 H2를 사용하는 경우 이와 같은 차이로 인해 테스트 결과의 신뢰성이 저하되고, 오류가 발생할 가능성이 있다고 생각했다. 이러한 이유로, 더 나은 해결책을 찾고자 했다.</p>
<h2 id="2-docker를-활용한-테스트-환경의-고려">2. Docker를 활용한 테스트 환경의 고려</h2>
<p>그 후, Docker를 사용해 로컬에서 데이터베이스를 구동하는 방법도 고려했다. 그러나 Dockerfile과 docker-compose 파일을 관리해야 하고, 컨테이너를 수동으로 시작하고 중지하는 등의 작업이 추가되는 귀찮은 작업을 진행해야 했다. </p>
<h2 id="3-testcontainers의-도입">3. Testcontainers의 도입</h2>
<h3 id="testcontainer란">TestContainer란?</h3>
<p>Testcontainers란 Docker를 기반으로 Junit 수행 시 테스트를 도와주는 Java Library 이다. </p>
<h3 id="testcontainers-특징">Testcontainers 특징</h3>
<ul>
<li>Java로 Container를 동작시킬 수 있다.</li>
<li>Dockerfile, docker-compose, docker hub로 Container를 동작시킬 수 있다.</li>
<li>테스트 실행 전/후로 Container를 start, stop 할 수 있다.</li>
<li>Parallel Test를 지원한다.</li>
<li>다양한 module을 제공하고 있다.<h3 id="코드">코드</h3>
</li>
<li><em>build.gradle*</em><pre><code class="language-gradle">testImplementation &#39;org.testcontainers:testcontainers:1.19.7&#39;</code></pre>
</li>
</ul>
<pre><code class="language-java">
@Testcontainers
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@DataJpaTest
class ProductRepositoryTest {

   @Container
    static MySQLContainer mySQLContainer = new MySQLContainer&lt;&gt;(DockerImageName.parse(&quot;mysql:8.0-debian&quot;));

    @DynamicPropertySource
    static void kafkaProperties(DynamicPropertyRegistry registry) {
        registry.add(&quot;spring.datasource.url&quot;, () -&gt; mySQLContainer.getJdbcUrl());
        registry.add(&quot;spring.datasource.driverClassName&quot;, () -&gt; mySQLContainer.getDriverClassName());
        registry.add(&quot;spring.datasource.username&quot;, () -&gt; mySQLContainer.getUsername());
        registry.add(&quot;spring.datasource.password&quot;, () -&gt; mySQLContainer.getPassword());
        registry.add(&quot;spring.jpa.hibernate.ddl-auto&quot;, () -&gt; &quot;update&quot;);
    }

(생략)

}</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_angie/post/63162cbf-fc65-4022-a618-3b1a17edebde/image.png" alt=""></p>
<h3 id="결론">결론</h3>
<p>Testcontainers를 도입한 이후, 테스트 환경 설정이 훨씬 편해졌다. GitHub Actions와 같은 CI 환경에서도 손쉽게 테스트를 자동화할 수 있었으며, 실제 운영 환경과 유사한 조건에서 테스트를 수행할 수 있었다.
단점으로는 테스트하는데 시간이 좀 걸린다.
<br></p>
<hr>
<h1 id="참고">참고</h1>
<p><a href="https://dev.gmarket.com/76">https://dev.gmarket.com/76</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 17일차 TIL + 그리디]]></title>
            <link>https://velog.io/@dev_angie/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-17%EC%9D%BC%EC%B0%A8-TIL-%EA%B7%B8%EB%A6%AC%EB%94%94</link>
            <guid>https://velog.io/@dev_angie/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-17%EC%9D%BC%EC%B0%A8-TIL-%EA%B7%B8%EB%A6%AC%EB%94%94</guid>
            <pubDate>Wed, 05 Jun 2024 14:58:22 GMT</pubDate>
            <description><![CDATA[<h1 id="학습-키워드">학습 키워드</h1>
<ul>
<li>그리디</li>
</ul>
<h1 id="문제-및-해결방법">문제 및 해결방법</h1>
<blockquote>
<p><strong>문제</strong>
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/42885">구명보트
</a></p>
</blockquote>
<p><strong>해결 방법</strong></p>
<ul>
<li><code>구명보트는 작아서 한 번에 최대 2명씩 밖에 탈 수 없고, 무게 제한도 있습니다.</code> </li>
<li><code>모든 사람을 구출하기 위해 필요한 구명보트 개수의 최솟값</code></li>
<li>그리디 알고리즘을  사용해 최댓값과 최솟값을 더해 <code>limit</code>이 넘는지 넘지 않는지를 판단한다.  </li>
</ul>
<p><strong>최종 코드</strong></p>
<pre><code class="language-java">import java.util.*;

class Solution {
    public int solution(int[] people, int limit) {

        Arrays.sort(people);

        int light = 0;
        int heavy = people.length-1;
        int boat = 0;

        while(light &lt;= heavy){

            if(limit &gt;= people[light] + people[heavy]){
                light++;
            }

            heavy--;

            boat++;
        }
        return boat;
    }
}</code></pre>
<blockquote>
<p><strong>문제</strong>
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/42884">단속카메라
</a></p>
</blockquote>
<p><strong>해결 방법</strong></p>
<ul>
<li>우선 도착점인 <code>routes[1]</code>을 기준으로 정렬한다. 도착지점을 카메라 지점으로 삼을 것이다. 먼저, 다음 시작점인<code>routes[0]</code>과 현재 카메라 지점을 비교한다. 만약 카메라 지점이 작다면 카메라를 늘려야한다. 왜냐하면, 다음 시작지점보다 카메라 지점이 이전에 있기 때문이다.  </li>
</ul>
<p><strong>최종 코드</strong></p>
<pre><code class="language-java">import java.util.*;

class Solution {

    public int solution(int[][] routes) {

        Arrays.sort(routes, Comparator.comparingInt(a-&gt;a[1]));

        int cameras = 0;

        for(int[] route: routes){
            if(lastCameraPosition &lt; route[0]){
                lastCameraPosition = route[1];
                cameras++;
            }
        }
        return cameras;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 015일차 TIL + BFS/DFS]]></title>
            <link>https://velog.io/@dev_angie/aa</link>
            <guid>https://velog.io/@dev_angie/aa</guid>
            <pubDate>Mon, 03 Jun 2024 09:20:19 GMT</pubDate>
            <description><![CDATA[<h1 id="학습-키워드">학습 키워드</h1>
<ul>
<li>BFS</li>
<li>DFS</li>
</ul>
<h1 id="문제-및-해결-방법">문제 및 해결 방법</h1>
<blockquote>
<p><strong>문제</strong>
<a href="https://leetcode.com/problems/reverse-odd-levels-of-binary-tree/">2415. 이진 트리의 홀수 레벨 반전</a></p>
</blockquote>
<p><strong>해결 방법</strong></p>
<ul>
<li><p>이진 트리의 한 레벨을 살펴봐야하기 때문에 BFS를 사용한다.</p>
</li>
<li><p>이진 트리 한 level을 살펴보는 방법이다. <code>queue.size()</code>는 현재 살펴볼 level의 노드 수를 나타낸다. <code>currentLevelNodes</code>은 값을 교환할 때 사용하기 위해 필요하다. 현재 노드를 살펴보며 각 노드의 자식 노드를 큐에 집어 넣는다. </p>
<pre><code class="language-java">             int size = queue.size();
          List&lt;TreeNode&gt; currentLevelNodes = new ArrayList&lt;&gt;();

          for (int i = 0; i &lt; size; i++) {
              TreeNode node = queue.poll();
              currentLevelNodes.add(node);
              if (node.left != null) {
                  queue.offer(node.left);
              }
              if (node.right != null) {
                  queue.offer(node.right);
              }
          }</code></pre>
</li>
<li><p>한 level의 노드를 다 살펴본 후 level이 홀수인지 살펴봐야한다. 홀수라면 <code>val</code>을 교환한다. 마지막으로 level을 증가시켜 다음 레벨로 넘어간다.  </p>
</li>
</ul>
<p><strong>전체 코드</strong></p>
<pre><code class="language-java">public class TreeNode {
      public int val;
      public int level;
      TreeNode left;
      TreeNode right;
      TreeNode() {}
      TreeNode(int val) { this.val = val; }
      TreeNode(int val, TreeNode left, TreeNode right) {
          this.val = val;
          this.left = left;
          this.right = right;
    }
}

class Solution {

     public TreeNode bfs(TreeNode root) {
        if (root == null) {
            return null;
        }

        Queue&lt;TreeNode&gt; queue = new LinkedList&lt;&gt;();
        queue.offer(root);
        int level = 0;

        while (!queue.isEmpty()) {
            int size = queue.size();
            List&lt;TreeNode&gt; currentLevelNodes = new ArrayList&lt;&gt;();

            for (int i = 0; i &lt; size; i++) {
                TreeNode node = queue.poll();
                currentLevelNodes.add(node);
                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }

            if (level % 2 == 1) {
                int left = 0, right = currentLevelNodes.size() - 1;
                while (left &lt; right) {
                    int temp = currentLevelNodes.get(left).val;
                    currentLevelNodes.get(left).val = currentLevelNodes.get(right).val;
                    currentLevelNodes.get(right).val = temp;
                    left++;
                    right--;
                }
            }
            level++;
        }

        return root;
    }
    public TreeNode reverseOddLevels(TreeNode root) {
        bfs(root);
        return root;
    }
}</code></pre>
<br>

<blockquote>
<p><strong>문제</strong>
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/43162">네트워크</a></p>
</blockquote>
<p><strong>해결 방법</strong></p>
<ul>
<li><p>방문하지 않는 노드에 dfs를 진행한다. </p>
<pre><code>class Solution {

  public static boolean[] visited;

  public static void dfs(int start, int[][] computers){
      visited[start] = true;

      for(int i = 0; i&lt;computers[start].length; i++){

          if(start == i) continue;                                

          if(computers[start][i] == 1 &amp;&amp; visited[i] == false){
              dfs(i, computers);
          }
      }
  }

  public int solution(int n, int[][] computers) {

      visited = new boolean[n];
      int answer = 0;

      for(int i = 0; i&lt;n; i++){
          if(visited[i] == false){
              dfs(i, computers);
              answer++;
          }
      }

      return answer;
  }
} </code></pre></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 14일차 TIL + DFS]]></title>
            <link>https://velog.io/@dev_angie/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-14%EC%9D%BC%EC%B0%A8-TIL-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%ED%82%A4%EC%9B%8C%EB%93%9C</link>
            <guid>https://velog.io/@dev_angie/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-14%EC%9D%BC%EC%B0%A8-TIL-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%ED%82%A4%EC%9B%8C%EB%93%9C</guid>
            <pubDate>Sun, 02 Jun 2024 11:49:33 GMT</pubDate>
            <description><![CDATA[<h2 id="오늘의-학습-키워드">오늘의 학습 키워드</h2>
<ul>
<li>DFS<h2 id="문제-및-해결-방법">문제 및 해결 방법</h2>
</li>
</ul>
<blockquote>
<p>** 문제 **
<a href="https://leetcode.com/problems/all-paths-from-source-to-target/">797. All Paths From Source to Target</a></p>
</blockquote>
<ul>
<li>주어진 그래프에서 0번째 노드부터 n-1번째 노드까지의 모든 경로를 찾는 문제</li>
</ul>
<p><strong>해결 방법</strong></p>
<ul>
<li>이전에 버릇처럼 visited 배열을 넣었지만, 문제 조건에서 무한 루프는 없다고 했기 때문에 visited 배열을 빼주었다.</li>
<li>탐색한 경로를 저장할 수 있게 dfs 함수에 ArrayList<Integer> path를 넣었다. 여기서 주의할 점은 path 객체는 공유되는 객체임으로 path.remove(path.size()-1)을 해주어야 한다.</li>
</ul>
<p><strong>최종 코드</strong>   </p>
<pre><code class="language-java">class Solution {
    public static List&lt;List&lt;Integer&gt;&gt; pathResult = new ArrayList&lt;&gt;();
    public static int goal;

    public static void dfs(int x, int[][] graph, ArrayList&lt;Integer&gt; path){
        path.add(x);

        if(x == goal){
           pathResult.add(new ArrayList&lt;&gt;(path)); 
           path.remove(path.size() - 1);
           return;
        }

        for(int i = 0; i &lt; graph[x].length; i++){
            int y = graph[x][i];
            dfs(y, graph, new ArrayList&lt;&gt;(path)); 
        }

        path.remove(path.size() - 1); 
    }

    public List&lt;List&lt;Integer&gt;&gt; allPathsSourceTarget(int[][] graph) {
        pathResult.clear(); 
        visited = new boolean[graph.length]; 

        goal = graph.length - 1; 
        ArrayList&lt;Integer&gt; path = new ArrayList&lt;&gt;();
        dfs(0, graph, path);

        return pathResult;
    }
}  </code></pre>
<hr>
<blockquote>
<p>** 문제 **
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/43163">단어 변환</a></p>
</blockquote>
<ul>
<li>단어를 변환할 수 있는 최소단계 구하기</li>
</ul>
<p><strong>해결 방법</strong></p>
<ul>
<li>dfs 사용</li>
<li>우선 단어를 비교하는 알고리즘을 아래와 같이 짤 수 있다. 한 알파벳씩 비교하는 것으로, diff는 문제 조건에 따라 1이어야한다. </li>
</ul>
<pre><code class="language-java">      int diff = 0;
     for (int j = 0; j &lt; begin.length(); j++) {
          if (begin.charAt(j) != words[i].charAt(j)) diff++;
     }</code></pre>
<ul>
<li>만약 diff가 1이면 변환이 가능하다는 뜻이기 때문에, visited = true로 만들고 다음 단어 탐색을 위해 dfs한다. 또한, 백트래킹을 위해 visited = false로 만들어야 한다. 다른 경로 탐색을 위한 것이다.   </li>
</ul>
<p><strong>최종 코드</strong>                                                            </p>
<pre><code class="language-java">  class Solution {

    public static int min = Integer.MAX_VALUE;

    public static void dfs(String begin, String target, String[] words, boolean[] visited, int count){

        if(begin.equals(target)){
            if(min &gt; count) min = count;
            return;
        }

        for(int i = 0; i&lt;words.length; i++){

            if(!visited[i]){
            int diff = 0;
            for(int j = 0; j&lt;begin.length(); j++){
                if(begin.charAt(j) != words[i].charAt(j)) diff++;
            }

            if(diff == 1){
                visited[i] = true;
                dfs(words[i], target, words, visited, count+1);
                visited[i] = false;
            }
            }
        }
    } 


    public int solution(String begin, String target, String[] words) {

        boolean exists = false;
        for (String word : words) {
            if (word.equals(target)) {
                exists = true;
                break;
            }
        }
        if (!exists) return 0;

        boolean[] visited = new boolean[words.length];
        dfs(begin, target, words, visited, 0);

        if(min == Integer.MAX_VALUE) return 0;
        return min;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[MSA 프로젝트] github action으로 CI 구현하기]]></title>
            <link>https://velog.io/@dev_angie/MSA-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-github-action%EC%9C%BC%EB%A1%9C-CI-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev_angie/MSA-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-github-action%EC%9C%BC%EB%A1%9C-CI-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 02 Jun 2024 03:51:14 GMT</pubDate>
            <description><![CDATA[<p>도커 이미지를 지속적으로 만드는 번거로움을 해결하기 위해, GitHub Actions을 사용하여 도커 이미지를 자동으로 빌드하는 CI(Continuous Integration) 코드를 구현했다. </p>
<h2 id="1-github에-환경변수-등록하기">1. github에 환경변수 등록하기</h2>
<p>빌드에 필요한 환경 변수는 github에 등록해주어야 한다. 각 시크릿 변수와 환경 변수는 
<code>Setting -&gt; Secrets and variables -&gt; Actions</code>로 가서 추가할 수 있다. </p>
<ul>
<li><code>Secrets</code> : 민감한 정보 저장</li>
<li><code>Variables</code> : 시크릿이 필요하지 않은 일반 환경 설정 정보 저장<br></li>
<li><code>Repository</code>: 해당 리포지토리에서만 사용할 값 저장</li>
<li><code>Organization</code> : 모든 리포지토리에서 사용할 값을 저장. </li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_angie/post/2a99c7ae-15a5-44a8-a0ef-66a278ceb418/image.png" alt=""></p>
<h2 id="2-github-action-워크플로-파일-만들기">2. github action 워크플로 파일 만들기</h2>
<p>GitHub Actions를 설정하기 위해 먼저 루트 폴더에 .github/workflows/notice-deploy.yaml 파일을 만들어준다. 이 파일이 생성되면 GitHub Actions가 자동으로 동작한다.</p>
<pre><code class="language-yaml">name: Build and Push Docker Image
on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  build-and-push-image:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Set up JDK 17
        uses: actions/setup-java@v2
        with:
          distribution: &#39;temurin&#39;
          java-version: &#39;17&#39;

      - name: Build with Gradle
        run: |
          cd msa-notice
          chmod +x ./gradlew
          ./gradlew build
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
          DATABASE_USERNAME: ${{ secrets.DATABASE_USERNAME }}
          DATABASE_PASSWORD: ${{ secrets.DATABASE_PASSWORD }}
          RABBITMQ_URL: ${{ env.RABBITMQ_URL }}
          RABBITMQ_PORT: ${{ env.RABBITMQ_PORT }}
          RABBITMQ_USERNAME: ${{ secrets.RABBITMQ_USERNAME }}
          RABBITMQ_PASSWORD: ${{ secrets.RABBITMQ_PASSWORD }}
          UNIV_RECOMMENDATION_URL: ${{ env.UNIV_RECOMMENDATION_URL }}
          EXTERNAL_RECOMMENDATION_URL: ${{ env.EXTERNAL_RECOMMENDATION_URL }}
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}

      - name: Login to DockerHub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_NAME }}
          password: ${{ secrets.DOCKERHUB_PASSWORD }}

      - name: Build and push Docker image
        run: |
          cd msa-notice
          docker build -t ${{ secrets.DOCKERHUB_NAME }}/msa-notice:latest .
          docker push ${{ secrets.DOCKERHUB_NAME }}/msa-notice:latest</code></pre>
<h3 id="name"><strong>name</strong>:</h3>
<p>워크플로우의 이름. 여기서는 &quot;Build and Push Docker Image&quot;이 이름이다.</p>
<h3 id="on"><strong>on</strong>:</h3>
<p>워크플로우를 트리거하는 이벤트. main 브랜치로의 푸시나 풀 리퀘스트가 발생할 때 워크플로우가 실행된다.</p>
<h3 id="jobs"><strong>jobs</strong>:</h3>
<p>워크플로우 내에서 수행할 작업을 정의한다. 여기서는 하나의 작업인 build-and-push-image가 있다.</p>
<ul>
<li><strong>runs-on</strong>: 작업을 실행할 환경을 지정한다. 여기서는 최신 우분투 환경인 ubuntu-latest를 사용한다.</li>
<li><strong>steps</strong>: 각 단계별로 수행할 작업을 정의한다.</li>
</ul>
<blockquote>
<p><strong>name</strong> : &quot;Checkout&quot;
<strong>uses</strong> : actions/checkout 액션을 사용하여 현재 저장소를 체크아웃한다.</p>
</blockquote>
<blockquote>
<p><strong>name</strong>: &quot;Set up JDK 17&quot;
<strong>uses</strong>: actions/setup-java 액션을 사용하여 JDK 17을 설정
<strong>with</strong>: JDK의 배포판과 버전을 지정한다. 여기서는 Temurin 배포판의 JDK 17을 사용합니다.</p>
</blockquote>
<blockquote>
<p><strong>name</strong>: &quot;Build with Gradle&quot;
<strong>run</strong>: 셸 명령어를 실행합니다.</p>
</blockquote>
<ul>
<li>cd msa-notice: msa-notice 디렉토리로 이동</li>
<li>chmod +x ./gradlew: Gradle 래퍼 스크립트에 실행 권한을 부여</li>
<li>./gradlew build: Gradle을 사용하여 프로젝트를 빌드
env: 빌드 과정에서 사용할 환경 변수를 설정한다. GitHub에 환경 변수를 커밋할 수는 없기에, 빌드 시 필요한 민감한 정보는 github 환경변수 등록한 후 시크릿은 <code>secrets.</code> 으로 환경변수는 <code>env.</code>으로 가져와 사용한다.</li>
</ul>
<blockquote>
<p><strong>name</strong>: &quot;Login to DockerHub&quot;
<strong>uses</strong>: docker/login-action 액션을 사용하여 DockerHub에 로그인한다.
<strong>with</strong>: DockerHub 로그인 정보로 사용할 시크릿 변수를 지정한다.</p>
</blockquote>
<blockquote>
<p><strong>name</strong>: &quot;Build and push Docker image&quot;
<strong>run</strong>: 셸 명령어를 실행합니다.</p>
</blockquote>
<ul>
<li>cd msa-notice: msa-notice 디렉토리로 이동합니다.</li>
<li>docker build -t ${{ secrets.DOCKERHUB_NAME }}/msa-notice:latest .: 도커 이미지를 빌드한다.</li>
<li>docker push ${{ secrets.DOCKERHUB_NAME }}/msa-notice:latest: 빌드한 도커 이미지를 DockerHub에 푸시한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_angie/post/a5d8e06c-d8d4-4ec0-a4ce-ba18fa7b6790/image.png" alt=""></p>
<p>성공 메시지가 표시된 후, Docker Hub에 가보면 이미지가 잘 생성된 것을 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/dev_angie/post/6e8e5f0f-2567-4a72-9da8-023c403b9962/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[MSA 프로젝트] minikube 설치 및 사용하기]]></title>
            <link>https://velog.io/@dev_angie/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-minikube-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev_angie/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4-minikube-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 28 May 2024 10:49:06 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dev_angie/post/09b73636-be2d-43f2-a246-c3efe576c746/image.png" alt=""></p>
<p>이전에 마이크로서비스를 테스트를 하기위해 GKE를 활용하였다. 그러나 클라우드에서 쿠버네티스를 사용하는 건 번거롭고 크레딧이 생길 수 있기 때문에 minikube를 사용할 것이다.</p>
<h1 id="minikube-설치하기">minikube 설치하기</h1>
<hr>
<p><strong>1. 설치 하기</strong>
<a href="https://minikube.sigs.k8s.io/docs/start/">https://minikube.sigs.k8s.io/docs/start/ </a>에서 본인의 환경에 맞는 minikube를 설치한다. 
Windows를 선택했고 latest release를 클릭한다.</p>
<p><img src="https://velog.velcdn.com/images/dev_angie/post/d4f73771-5000-45b4-aec8-898cd29287da/image.png" alt=""></p>
<p><strong>2. 설치 확인</strong>
설치가 완료되면 cmd를 열어 minikube version 명령어로 설치를 확인할 수 있다. </p>
<pre><code>&gt; minikube version
minikube version: v1.33.1
commit: 5883c09216182566a63dff4c326a6fc9ed2982ff</code></pre><br>

<h1 id="minikube-사용하기">minikube 사용하기</h1>
<hr>
<h3 id="1-cluster-시작하기"><strong>1. Cluster 시작하기</strong></h3>
<pre><code>minikube start</code></pre><blockquote>
<ul>
<li>아래 명령어로 클러스터 생성을 위한 이미지</li>
</ul>
</blockquote>
<pre><code># minikube 시작 시 download 되는 docker image
$ docker images
REPOSITORY                      TAG                   IMAGE ID       CREATED         SIZE
gcr.io/k8s-minikube/kicbase     v0.0.44               5a6e59a9bdc0   2 weeks ago     1.26GB</code></pre><blockquote>
</blockquote>
<ul>
<li>클러스터 컨테이너<pre><code># 다운받은 docker image로 minikube container를 실행하여 kubernetes cluster 생성
$ docker ps
CONTAINER ID   IMAGE                                 COMMAND                  CREATED         STATUS         PORTS                                                                                                                                  NAMES
0afaff80de0b   gcr.io/k8s-minikube/kicbase:v0.0.44   &quot;/usr/local/bin/entr…&quot;   3 minutes ago   Up 3 minutes   127.0.0.1:57389-&gt;22/tcp, 127.0.0.1:57390-&gt;2376/tcp, 127.0.0.1:57392-&gt;5000/tcp, 127.0.0.1:57393-&gt;8443/tcp, 127.0.0.1:57391-&gt;32443/tcp   minikube</code></pre><blockquote>
</blockquote>
</li>
<li>kubernetes cluster와 kubectl의 연결을 위한 정보가 담긴 config 파일 <pre><code>$ cat ~/.kube/config</code></pre><blockquote>
</blockquote>
</li>
<li>클러스터 정보<pre><code>$ kubectl cluster-info
Kubernetes control plane is running at https://127.0.0.1:57393
CoreDNS is running at https://127.0.0.1:57393/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy</code></pre></li>
</ul>
<h3 id="2-cluster-일시정지재가동"><strong>2. cluster 일시정지/재가동</strong></h3>
<pre><code>$ minikube pause</code></pre><p>일시정지 명령어로, kubectl을 통한 cluster의 접근이나 추가적인 명령어는 timeout이 발생하며 사용이 불가능하다. 단, 이미 배포된 application에는 영향을 미치지않고 계속 동작한다.</p>
<ul>
<li>클러스터 재기동 명령어<pre><code>$ minikube unpause</code></pre></li>
</ul>
<h3 id="3-cluster-종료"><strong>3. cluster 종료</strong></h3>
<pre><code>$ minikube stop</code></pre><ul>
<li>kubernetes cluster를 위해 구동되었던 minikube docker container가 종료되며 cluster가 종료되게 된다. 또한 cluster와 kubectl의 연결을 위해 사용되었던 ~/.kube/config 파일의 내용도 모두 사라진다.</li>
<li>단, 따로 저장되어있는 cluster 내의 생성했던 리소스들의 정보는 삭제되지 않기 때문에 다시 minikube start 명령어를 통해 cluster를 생성하면 기존에 생성해놨던 application 같은 리소스들이 동일하게 생성되어 구동된다.</li>
</ul>
<h3 id="4-cluster-삭제"><strong>4. cluster 삭제</strong></h3>
<pre><code>$ minikube delete</code></pre><ul>
<li>delete를 통해 cluster를 종료한다면 이러한 정보들까지 모두 삭제한다. 따라서 delete 후 다음번 minikube를 시작시 완벽히 초기상태로 시작이 된다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 4일차 TIL + 스택/큐]]></title>
            <link>https://velog.io/@dev_angie/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-4%EC%9D%BC%EC%B0%A8-TIL-%EC%8A%A4%ED%83%9D%ED%81%90</link>
            <guid>https://velog.io/@dev_angie/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-4%EC%9D%BC%EC%B0%A8-TIL-%EC%8A%A4%ED%83%9D%ED%81%90</guid>
            <pubDate>Thu, 23 May 2024 14:13:05 GMT</pubDate>
            <description><![CDATA[<h2 id="오늘의-학습-키워드">오늘의 학습 키워드</h2>
<ul>
<li>스택/큐</li>
</ul>
<h2 id="해결-방법">해결 방법</h2>
<blockquote>
<p>문제: <a href="https://school.programmers.co.kr/learn/courses/30/lessons/12909/">올바른 괄호</a></p>
</blockquote>
<pre><code class="language-java">import java.util.*;

class Solution {
    boolean solution(String s) {
        boolean answer = true;

        Stack&lt;Character&gt; stack = new Stack&lt;&gt;();

        for(int i = 0; i&lt;s.length(); i++){
            char currentChar = s.charAt(i);

            if (currentChar == &#39;(&#39;) { 
                stack.push(currentChar);
            }else{
                if(stack.isEmpty()){
                    return false;
                }else if(stack.peek() == &#39;(&#39;) stack.pop();
            }

        }

        if(stack.isEmpty()) return true;

        return false;
   }
}</code></pre>
<h2 id="무엇을-새롭게-알았는지">무엇을 새롭게 알았는지</h2>
<ul>
<li>Stack의 메서드
<img src="https://velog.velcdn.com/images/dev_angie/post/562975fd-2903-4d1d-9e2e-53303f3c1dd7/image.png" alt=""></li>
<li>Queue의 메서드
<img src="https://velog.velcdn.com/images/dev_angie/post/892b2483-7a7b-4b06-9c89-c47d4e86a2fc/image.png" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[MSA 프로젝트] GKE에서 Kubernetes 매니페스트 파일 배포하기]]></title>
            <link>https://velog.io/@dev_angie/MSA-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-GKE%EC%97%90%EC%84%9C-Kubernetes-%EB%A7%A4%EB%8B%88%ED%8E%98%EC%8A%A4%ED%8A%B8-%ED%8C%8C%EC%9D%BC-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev_angie/MSA-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-GKE%EC%97%90%EC%84%9C-Kubernetes-%EB%A7%A4%EB%8B%88%ED%8E%98%EC%8A%A4%ED%8A%B8-%ED%8C%8C%EC%9D%BC-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 23 May 2024 03:48:21 GMT</pubDate>
            <description><![CDATA[<p>실제 작성한 매니페스트 파일이 쿠버네티스 상에서 잘 동작하는지 알아보기 위해 GKE를 사용해보았다.</p>
<p>GKE란 Google Kubernetes Engine으로, GCP(Google Cloud Platform)에서 제공하는 관리형 Kubernetes 서비스이다. 클러스터 관리 및 애플리케이션 배포를 쉽게 할 수 있게 해준다. </p>
<h2 id="1-gke-클러스터-생성"><strong>1. GKE 클러스터 생성</strong></h2>
<p><img src="https://velog.velcdn.com/images/dev_angie/post/f1d22222-8d8c-403f-9e82-21454cd11f51/image.png" alt=""></p>
<blockquote>
<p>테스트 용이기 때문에 기본값으로 생성하였다.  </p>
</blockquote>
<p><strong>클러스터 기본사항</strong></p>
<ul>
<li>이름: k8s</li>
<li>영역: asia-northease3-a</li>
<li>출시 채널<ul>
<li>출시 채널: 채널 없음</li>
<li>버전: 기본값<blockquote>
</blockquote>
</li>
</ul>
</li>
<li><em>노드 설정 구성*</em></li>
<li>이미지 유형: containerd를 포함한 Ubuntu</li>
<li>머신 구성: 범용/N1</li>
<li>머신 유형<ul>
<li>코어: 1</li>
<li>메모리: 1</li>
<li>부팅 디스크 크기: 10</li>
</ul>
</li>
</ul>
<p>다음과 같이 클러스터 생성되었다. 노드 수가 3으로 설정되어 있어, VM 인스턴스를 확인해 보면 3개의 인스턴스가 생성된 것을 볼 수 있다. 각 노드는 클러스터 내에서 작업을 수행하는 독립적인 컴퓨팅 자원인 것을 알 수 있다. </p>
<p><img src="https://velog.velcdn.com/images/dev_angie/post/0d1d93bd-d329-4c42-a7b1-c8ad6f00a20e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev_angie/post/3d2b4192-0fcc-4ad8-87a5-e169286f38da/image.png" alt=""></p>
<h2 id="2-k8s-클러스터-연결"><strong>2. K8s 클러스터 연결</strong></h2>
<blockquote>
</blockquote>
<p><strong>kubectl 설정</strong>
PC에서 클러스터에 연결하기 위해 명령어을 넣으면 kubectl이 없다는 오류가 발생한다. 아래 링크로 들어가 운영환경에 맞게 설치하면 된다. </p>
<blockquote>
</blockquote>
<p><a href="https://kubernetes.io/ko/docs/tasks/tools/">https://kubernetes.io/ko/docs/tasks/tools/</a>
<img src="https://velog.velcdn.com/images/dev_angie/post/1128a4ea-8696-4de3-82fe-8935c7ca5173/image.png" alt=""></p>
<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev_angie/post/1111ac54-5bb8-4815-9b4c-abce01ace0fd/image.png" alt=""></p>
<h3 id="k8s-클러스터-연결"><strong>K8s 클러스터 연결</strong></h3>
<p><img src="https://velog.velcdn.com/images/dev_angie/post/33c7c379-68cc-432e-b4fc-6927928074ae/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev_angie/post/616e2657-78a1-494e-a69f-be4ae0a7fe13/image.png" alt=""></p>
<p>cmd 창에 <code>명령줄 액세스</code> 부분을 실행하면 클러스터에 연결할 수 있다. </p>
<ul>
<li>잘 연결되었는지 확인해보자<pre><code>kubectl config get-contexts  // GKE K8s 클러스터의 컨텍스트 확인
</code></pre></li>
</ul>
<p>kubectl get node // 노드 목록 보기</p>
<pre><code>
## **3. 매니페스트 파일 준비**

이번 프로젝트에서는 Secret, ConfigMap, Deployment, Service 오브젝트를 정의하는 매니페스트 파일을 만들었다.

매니페스트 파일을 각각 분리하여 사용할 수도 있지만, &#39;---&#39; 을 사용하여 하나의 파일에 여러 오브젝트를 정의할 수도 있다. 실제 만든 서비스가 잘 동작하는지 테스트하는 용도이기 때문에 하나로 합쳐서 진행해볼 것이다. 

```combined-manifest.yaml```
```yml
#secret
apiVersion: v1
kind: Secret
metadata:
    name: my-secret
type: Opaque
data:
  DATABASE_USERNAME: &lt;base64로_인코딩된_값&gt;
  DATABASE_PASSWORD: &lt;base64로_인코딩된_값&gt;

---

#configmap
apiVersion: v1
kind: ConfigMap
metadata:
  name: my-config
data:
  DATABASE_URL: &lt;값&gt;

---

# deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment
  replicas: 1
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: myapp
          image: &lt;dockerhub-username&gt;/my-app
          ports:
            - containerPort: 8080
          env:
          - name: DATABASE_URL
            valueFrom:
              configMapKeyRef:
                name: notice-config
                key: DATABASE_URL
          - name: DATABASE_USERNAME
            valueFrom:
              secretKeyRef:
                name: notice-secret
                key: DATABASE_USERNAME
          - name: DATABASE_PASSWORD
            valueFrom:
              secretKeyRef:
                name: notice-secret
                key: DATABASE_PASSWORD      

---

# service
apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  selector:
    app: myapp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
  type: LoadBalancer</code></pre><h2 id="4-매니페스트-파일-배포"><strong>4. 매니페스트 파일 배포</strong></h2>
<p>매니페스트 파일이 있는 경로로 가서 아래 명령어를 입력한다.</p>
<pre><code>kubectl apply -f &lt;매니페스트_파일_이름&gt; 

kubectl apply -f combined-manifest.yaml</code></pre><ul>
<li><p>이때 오류가 발생했는데, 이는 docker hub 리포지토리를 private으로 설정해두었기 때문이었다. private으로 두면 여러 설정 작업이 필요하여 public으로 변경하였다. 환경 변수를 .env 파일로 관리하고 있기 때문에, public으로 변경할 수 있었다. </p>
</li>
<li><p>오류가 발생한다면, <code>kubectl describe</code> 또는 <code>kubectl logs</code> 를 통해 확인할 수 있다.</p>
</li>
</ul>
<blockquote>
<p><strong>매니페스트 파일에서 배포한 리소스 삭제</strong>
매니페스트 파일에서 배포한 리소스를 삭제하고 싶다면 아래 명령어를 사용할 수 있다. </p>
</blockquote>
<pre><code>kubectl delete -f &lt;매니페스트_파일_이름&gt; </code></pre><h3 id="5-배포-확인"><strong>5. 배포 확인</strong></h3>
<pre><code># 전체 확인
kubectl get all

# 파드 상태 확인
kubectl get po

# 서비스 상태 확인
kubectl get svc
</code></pre><ul>
<li>Kubernetes 대시보드이다. 컨테이너가 잘 동작하는 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/dev_angie/post/09c57534-e639-4b8e-817a-f6ff33c228de/image.png" alt=""></li>
</ul>
<blockquote>
<p>💡<strong>사용하지 않는 리소스는 과금방지를 위해 꼭 삭제하자!</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[99클럽 코테 스터디 3일차 TIL + 오늘의 학습 키워드]]></title>
            <link>https://velog.io/@dev_angie/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-3%EC%9D%BC%EC%B0%A8-TIL-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%ED%82%A4%EC%9B%8C%EB%93%9C</link>
            <guid>https://velog.io/@dev_angie/99%ED%81%B4%EB%9F%BD-%EC%BD%94%ED%85%8C-%EC%8A%A4%ED%84%B0%EB%94%94-3%EC%9D%BC%EC%B0%A8-TIL-%EC%98%A4%EB%8A%98%EC%9D%98-%ED%95%99%EC%8A%B5-%ED%82%A4%EC%9B%8C%EB%93%9C</guid>
            <pubDate>Wed, 22 May 2024 14:34:31 GMT</pubDate>
            <description><![CDATA[<h2 id="해결-방법">해결 방법</h2>
<blockquote>
<p>문제: <a href="https://school.programmers.co.kr/learn/courses/30/lessons/425860">기능개발</a></p>
</blockquote>
<p>현재 배포 주기의 기준일보다 더 빨리 또는 동시에 완료되는 작업인 경우, cnt를 증가시킨다. 현재 배포 주기의 기준일보다 늦게 완료되는 작업인 경우, 현재까지의 작업 수를 answerList에 추가하고, 새로운 배포 주기를 시작한다.</p>
<pre><code>import java.util.*;

class Solution {
    public int[] solution(int[] progresses, int[] speeds) {
        ArrayList&lt;Integer&gt; answerList = new ArrayList&lt;&gt;();

        int period = 0;
        int cnt = 1;
        for (int i = 0; i &lt; progresses.length; i++) {
            int speed = (int) Math.ceil((100.0 - progresses[i]) / speeds[i]); 
            if (period &gt;= speed) {
                cnt += 1;
            } else {
                if (i != 0) { 
                    answerList.add(cnt);
                }
                period = speed;
                cnt = 1;
            }
        }
        answerList.add(cnt); 

        int[] answer = new int[answerList.size()];
        for (int i = 0; i &lt; answerList.size(); i++) {
            answer[i] = answerList.get(i);
        }

        return answer;
    }
}</code></pre>]]></description>
        </item>
    </channel>
</rss>