<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>profile-exe.log</title>
        <link>https://velog.io/</link>
        <description>컴퓨터공학과 학부생</description>
        <lastBuildDate>Sat, 02 Mar 2024 16:20:49 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>profile-exe.log</title>
            <url>https://images.velog.io/images/profile_exe/profile/bee21df0-8acb-4d09-b246-af282ddd475c/social.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. profile-exe.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/profile_exe" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[JPA] 프록시, 원본 엔티티의 영속성 컨텍스트 관리 여부 확인]]></title>
            <link>https://velog.io/@profile_exe/JPA-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8-%EA%B4%80%EB%A6%AC-%EC%97%AC%EB%B6%80</link>
            <guid>https://velog.io/@profile_exe/JPA-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8-%EA%B4%80%EB%A6%AC-%EC%97%AC%EB%B6%80</guid>
            <pubDate>Sat, 02 Mar 2024 16:20:49 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.inflearn.com/course/ORM-JPA-Basic/">JPA 강의</a>를 수강하면서 궁금한 점이 생겼다.</p>
<p>JPA는 <u><strong>동일성 보장</strong></u>을 위해 프록시 객체를 먼저 조회하면, 나중에 <code>find()</code>를 호출하더라도 프록시 객체인 동일한 인스턴스를 반환한다는 사실을 알게되었다. </p>
<ul>
<li>예제 코드<pre><code class="language-java">Member m1 = em.getReference(Member.class, 1L);
Member m2 = em.find(Member.class, 1L);
</code></pre>
</li>
</ul>
<p>System.out.println(&quot;m1.getClass() = &quot; + m1.getClass());
System.out.println(&quot;m2.getClass() = &quot; + m2.getClass());
System.out.println(&quot;(m1 == m2) = &quot; + (m1 == m2));</p>
<pre><code>- 출력 결과
```bash
m1.getClass() = class hellojpa.Member$HibernateProxy$FobBtKKE
m2.getClass() = class hellojpa.Member$HibernateProxy$FobBtKKE
(m1 == m2) = true</code></pre><p><code>em.find()</code> 시점에서 프록시 객체가 초기화되며 원본 엔티티의 인스턴스가 메모리에 올라갈텐데, 이 인스턴스는 어디에 있는걸까?</p>
<h3 id="원본-엔티티는-어디에-있는거지">원본 엔티티는 어디에 있는거지??</h3>
<p>1차 캐시에 원본 엔티티와 프록시 객체 둘 중 하나가 존재하는 것일까?
아니면 둘 다 존재하는 것일까?</p>
<hr>
<h2 id="가설-세우기">가설 세우기</h2>
<p>나는 후술할 메커니즘을 근거로 프록시와 원본 모두 1차 캐시에 존재하여 영속성 컨텍스트가 관리할 것이라 생각했다.</p>
<h3 id="변경-감지dirty-checking">변경 감지(Dirty-Checking)</h3>
<p>1차 캐시에 저장되는 내용</p>
<ul>
<li>영속화된 엔티티</li>
<li><strong>스냅샷</strong>: 엔티티가 영속화된 시점의 상태</li>
</ul>
<blockquote>
<p>JPA는 트랜잭션 <code>commit()</code> 등으로 <code>flush()</code>가 일어나면 영속성 컨텍스트의 1차 캐시에서 엔티티와 스냅샷을 비교한다.</p>
<p>비교를 통해 다른 부분이 있다면 <code>UPDATE</code> 쿼리를 <code>쓰기 지연 SQL 저장소</code>에 넣고 데이터베이스로 쿼리를 보낸다.</p>
</blockquote>
<p>프록시 객체를 통해 메서드를 호출하여 원본 엔티티의 내용이 바뀌면, 변경 감지를 통해 데이터베이스에 쿼리를 보내야 한다.</p>
<p>-&gt; <u><strong>변경 감지를 위해서는 1차 캐시에 원본 엔티티가 존재해야 한다!!</strong></u></p>
<br>

<h3 id="동일성-보장">동일성 보장</h3>
<blockquote>
<p><code>EntityManager</code>의 <code>getReference()</code>, <code>find()</code> 등으로 조회 시 <strong>1차 캐시를 우선 확인</strong>하여 해당 인스턴스가 있다면 반환한다.</p>
<p>이를 통해 <code>JPA</code>는 어플리케이션에서 <code>REPEATABLE READ</code> 수준의 트랜잭션 격리 수준을 제공한다.</p>
</blockquote>
<p><code>getReference()</code>로 프록시 객체를 조회한 후 <code>find()</code>를 호출하면 1차 캐시를 확인하여 프록시 객체가 반환된다.</p>
<p>마찬가지로 <code>find()</code>로 원본 엔티티를 조회한 후 <code>getReference()</code>를 호출하면 1차 캐시를 확인해 원본 엔티티가 반환된다.</p>
<p>-&gt; <u><strong>동일성 보장이 이루어지려면 1차 캐시에 프록시 객체가 존재해야 한다!!</strong></u></p>
<hr>
<h2 id="필요한-유틸-함수들">필요한 유틸 함수들</h2>
<h3 id="hibernateunproxy---원본-엔티티-꺼내기"><code>Hibernate.unproxy()</code> - 원본 엔티티 꺼내기</h3>
<p><strong><a href="https://docs.jboss.org/hibernate/orm/6.1/javadocs/org/hibernate/Hibernate.html#unproxy(java.lang.Object)">Hibernate.unproxy()</a> - Hibernate JavaDoc</strong></p>
<blockquote>
<h3 id="unproxy">unproxy</h3>
</blockquote>
<p><code>public static Object unproxy​(Object proxy)</code></p>
<blockquote>
</blockquote>
<p>If the given object is not a proxy, return it. But, if it is a proxy, ensure that the proxy is initialized, and return a direct reference to its proxied entity object.</p>
<p>주어진 객체가 <u><strong>프록시 객체일 경우 해당 프록시를 초기화</strong></u>하고, 프록시가 가리키는 <u><strong>원본 엔티티 객체에 대한 직접 참조를 반환</strong></u>한다. </p>
<p>프록시 객체가 아닌 경우에는 객체 자체를 그대로 반환한다.</p>
<p>프록시가 가리키는 원본 엔티티 인스턴스는 <code>$$_hibernate_interceptor</code> 내부 <code>target</code> 필드에 저장되어 있다.</p>
<h4 id="원본-엔티티에-this를-반환하는-메서드를-두면-안될까">원본 엔티티에 <code>this</code>를 반환하는 메서드를 두면 안될까?</h4>
<p>프록시는 원본 엔티티 자신을 리턴하는 경우 프록시 객체를 리턴하도록 구현되어 <code>return this</code> 로 원본 엔티티의 인스턴스를 얻을 수 없다.</p>
<br>


<h3 id="emcontains---영속성-컨텍스트-관리1차-캐시에-존재하는지-확인"><code>em.contains()</code> - 영속성 컨텍스트 관리(1차 캐시에 존재하는지) 확인</h3>
<p><strong><a href="https://javaee.github.io/javaee-spec/javadocs/javax/persistence/EntityManager.html#contains-java.lang.Object-">EntityManager.contains()</a> - JAKARTA EE SPECIFICATIONS</strong></p>
<blockquote>
<h3 id="contains">contains</h3>
</blockquote>
<p><code>boolean contains(Object entity)</code></p>
<blockquote>
</blockquote>
<p>Check if the instance is a managed entity instance belonging to the current persistence context.</p>
<p>주어진 인스턴스가 <u><strong>현재 영속성 컨텍스트에 속하는 관리하는 인스턴스인지 확인</strong></u>한다.</p>
<br>


<h3 id="systemidentityhashcode---동일한-인스턴스인지-확인"><code>System.identityHashCode()</code> - 동일한 인스턴스인지 확인</h3>
<p><strong><a href="https://docs.oracle.com/javase/8/docs/api/java/lang/System.html#identityHashCode-java.lang.Object-">System.identityHashCode()</a> - JavaSE 8 document</strong></p>
<blockquote>
<h3 id="identityhashcode">identityHashCode</h3>
</blockquote>
<p><code>public static int identityHashCode(Object x)</code></p>
<blockquote>
</blockquote>
<p>Returns the same hash code for the given object as would be returned by the default method <code>hashCode()</code>, whether or not the given object&#39;s class overrides <code>hashCode()</code>. The hash code for the null reference is zero.</p>
<p>해당 객체의 <code>hashCode()</code> 재정의 유무와 무관하게 <u><strong>객체의 고유한 <code>hashCode()</code> 메서드 값(재정의 되기 전 <code>hashCode()</code> 함수 값)을 반환</strong></u>한다.</p>
<p>이 함수를 통해 프록시와 원본이 동일한 인스턴스인지 확인할 수 있다.</p>
<hr>
<h2 id="결과-확인">결과 확인</h2>
<p>위 함수들을 이용하여 결과를 확인해보자.</p>
<ul>
<li>예제 코드<pre><code class="language-java">// 프록시 얻기
Member memberRef = em.getReference(Member.class, 1L);
System.out.println(&quot;Proxy  isManaged = &quot; + em.contains(memberRef));
System.out.println(&quot;Proxy  hashCode  = &quot; + System.identityHashCode(memberRef));
</code></pre>
</li>
</ul>
<p>// 원본 얻기
Member entity = (Member) Hibernate.unproxy(memberRef);
System.out.println(&quot;Entity isManaged = &quot; + em.contains(entity));
System.out.println(&quot;Entity hashCode  = &quot; + System.identityHashCode(entity));</p>
<pre><code>- 출력 결과
```bash
Proxy  isManaged = true
Proxy  hashCode  = 270313690
Hibernate: 
    select
        m1_0.id,
        m1_0.name,
        m1_0.team_id 
    from
        Member m1_0 
    where
        m1_0.id=?
Entity isManaged = true
Entity hashCode  = 5987161</code></pre><p><code>isManaged</code> 가 모두 <code>true</code> 이며, <code>hashCode</code> 값이 서로 다르다.</p>
<p><code>unproxy()</code>가 호출되며 SQL 쿼리가 나가고 프록시 객체가 초기화된 것을 볼 수 있다.</p>
<p>-&gt; <u><strong>프록시와 원본 모두 서로 다른 인스턴스이며 1차 캐시에 존재한다(영속성 컨텍스트에서 관리된다)!!</strong></u></p>
<hr>
<h2 id="번외1---emcontains-동작-방식">번외1 - <code>em.contains()</code> 동작 방식</h2>
<p>프록시와 원본 엔티티는 서로 다른 특성을 갖고있기 때문에 <code>EntityManager</code>의 <code>contains()</code> 함수도 동작이 다를 것이라 생각해 코드를 확인해보았다.</p>
<p>특히 의문이 들었던 점은 <u><strong>초기화가 되지 않은 프록시는 무엇을 근거로 영속성 엔티티에 관리된다고 판단할 수 있는가?</strong></u> 였다.</p>
<ul>
<li><p><code>contains()</code> 함수</p>
<pre><code class="language-java">public boolean contains(Object object) {
  ...
  if (object == null) {
      return false;
  } else {
      try {
          // HibernateProxy.extractLazyInitializer() : proxy 객체가 아니면 null을 반환한다!!!!!
          LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer(object);
          if (lazyInitializer != null) {                       // proxy 객체인 경우
              if (lazyInitializer.isUninitialized()) {         // proxy 객체가 초기화되지 않은 경우
                  return lazyInitializer.getSession() == this; // proxy 객체가 현재 세션에 속해있는지 확인
              }

              object = lazyInitializer.getImplementation();     // proxy 객체가 초기화된 경우, proxy 객체의 원본 엔티티를 가져온다.
          }

          // EntityEntry 객체는 엔티티의 생명주기 상태, 식별자, 버전 정보 등 엔티티와 관련된 다양한 메타데이터를 포함한다.
          EntityEntry entry = this.persistenceContext.getEntry(object);
          this.delayedAfterCompletion();
          if (entry == null) { // null이 반환된 경우 해당 엔티티가 영속성 컨텍스트에서 관리되지 않는다고 판단할 수 있다.
              ... 
              return false;
          } else {             // null이 아닌 경우 해당 엔티티가 영속성 컨텍스트에서 관리되고 있는 상태이다.
              // 엔티티의 상태 중 DELETE(삭제), GONE(데이터베이스에서 이미 삭제되었거나 더이상 접근이 불가능한 상태)가 아니라면 true를 반환한다.
              return !entry.getStatus().isDeletedOrGone();
          }
      } 
      ... catch 예외처리 ...
  }
}</code></pre>
</li>
</ul>
<p>예상대로 프록시인지, 원본 엔티티인지 분기를 두어 확인 후 각각 다르게 <code>boolean</code> 값을 반환했다.</p>
<hr>
<h3 id="프록시인-경우">프록시인 경우</h3>
<p><strong><a href="https://docs.jboss.org/hibernate/orm//6.1/javadocs/org/hibernate/proxy/HibernateProxy.html#extractLazyInitializer(java.lang.Object)">HibernateProxy.extractLazyInitializer</a> - Hibernate JavaDoc</strong></p>
<blockquote>
<p>Extract the <code>LazyInitializer</code> from the object, if and only if the object is actually an <code>HibernateProxy</code>. If not, null is returned.</p>
</blockquote>
<p>프록시인 경우 <code>LazyInitializer</code>를 추출하고 프록시가 아니면 <code>null</code>을 반환한다.</p>
<br>

<p><strong><a href="https://docs.jboss.org/hibernate/orm/6.4/javadocs/org/hibernate/proxy/LazyInitializer.html">LazyInitializer</a> - Hibernate JavaDoc</strong></p>
<blockquote>
<p>Handles fetching of the underlying entity for a proxy.</p>
</blockquote>
<p>프록시에 대한 원본 엔티티를 가져오는 것을 처리한다.</p>
<br>

<p>어떤 객체던 <code>LazyInitializer</code> 를 얻어서 <u><strong>1) 프록시인지 원본 엔티티인지를 확인</strong></u>하고, 프록시인 경우 <code>lazyInitializer.isUninitialized()</code> 함수를 통해 <u><strong>2) 초기화 유무를 확인</strong></u>한다.</p>
<h4 id="초기화되지-않은-프록시의-경우">초기화되지 않은 프록시의 경우</h4>
<pre><code class="language-java">return lazyInitializer.getSession() == this</code></pre>
<p><code>getSession()</code>은 프록시가 연결된 세션을 가져오거나, 연결되지 않은 경우 <code>null</code>을 반환하며 이 세션을 <code>this</code>와 비교한다.</p>
<p>-&gt; <u><strong>현재 세션에 존재하는 프록시인지 여부를 반환</strong></u>한다.</p>
<h4 id="초기화된-프록시의-경우">초기화된 프록시의 경우</h4>
<pre><code class="language-java">object = lazyInitializer.getImplementation();</code></pre>
<p><code>getImplementation()</code>은 프록시의 <code>target</code>인 원본 엔티티 인스턴스를 반환한다.</p>
<p>-&gt; <u><strong>원본 엔티티를 가져온 후 원본 엔티티를 넘겼을 때와 동일하게 진행</strong></u>한다.</p>
<br>

<hr>
<h3 id="원본인-경우">원본인 경우</h3>
<p><strong><a href="https://docs.jboss.org/hibernate/orm/6.4/javadocs/org/hibernate/engine/spi/PersistenceContext.html#getEntry(java.lang.Object)">PersistenceContext.getEntry()</a> - Hibernate Javadoc</strong></p>
<blockquote>
<p>Retrieve the EntityEntry representation of the given entity.</p>
</blockquote>
<p>초기화된 프록시, 원본 엔티티를 <code>contains()</code>에 넘기면 프록시는 원본 엔티티를 꺼내오고 원본 엔티티에서 <code>EntityEntry</code>를 가져온다.</p>
<br>

<p><strong><a href="https://docs.jboss.org/hibernate/orm/6.4/javadocs/org/hibernate/engine/spi/EntityEntry.html">EntityEntry</a> - Hibernate Javadoc</strong></p>
<blockquote>
<p>Information about the current state of a managed entity instance with respect to its persistent state.</p>
</blockquote>
<p><code>EntityEntry</code>는 영속성 컨텍스트에서 관리되는 엔티티 인스턴스의 현재 상태(<code>Status</code>)와 상태에 대한 정보다.</p>
<br>

<p><strong><a href="https://docs.jboss.org/hibernate/orm/6.4/javadocs/org/hibernate/engine/spi/Status.html">Status</a> - Hibernate Javadoc</strong></p>
<blockquote>
<p>Represents the status of an entity with respect to this session.</p>
</blockquote>
<p>엔티티의 세션에 대한 상태를 나타내는 데 사용되며 애플리케이션 수준에서 직접적으로 사용되거나 표시되는 개념이 아니다.</p>
<p><code>isDeletedOrGone</code> 함수를 사용하므로 아래 두 상태만 소개했다.</p>
<p><code>DELETED</code>: 엔티티가 삭제되어 데이터베이스에서 제거되는 상태
<code>GONE</code>: 엔티티가 더 이상 존재하지 않거나 접근할 수 없는 상태</p>
<pre><code class="language-java">// EntityEntry 객체는 엔티티의 생명주기 상태, 식별자, 버전 정보 등 엔티티와 관련된 다양한 메타데이터를 포함한다.
EntityEntry entry = this.persistenceContext.getEntry(object);
this.delayedAfterCompletion();
if (entry == null) { // null이 반환된 경우 해당 엔티티가 영속성 컨텍스트에서 관리되지 않는다고 판단할 수 있다.
    ... 
    return false;
} else {             // null이 아닌 경우 해당 엔티티가 영속성 컨텍스트에서 관리되고 있는 상태이다.
    // 엔티티의 상태 중 DELETE(삭제), GONE(데이터베이스에서 이미 삭제되었거나 더이상 접근이 불가능한 상태)가 아니라면 true를 반환한다.
    return !entry.getStatus().isDeletedOrGone();
}</code></pre>
<ol>
<li><p><code>EntityEntry</code>를 얻는다. 만약 <code>null</code>이면 해당 엔티티가 영속성 컨텍스트에서 관리되지 않는다는 것이다.</p>
</li>
<li><p><code>null</code>이 아닌 경우, 해당 세션에서 관리되는 엔티티이며 <code>EntityEntry</code>는 <code>Hibernate</code> 내부 로직을 위한 엔티티의 상태인 <code>Status</code>를 지닌다.</p>
</li>
<li><p><code>Status</code>가 <code>DELETED</code> 또는 <code>GONE</code>이 아니면 <code>true</code>를 반환한다.</p>
</li>
</ol>
<p>-&gt; <u><strong>해당 엔티티가 영속성 컨텍스트에서 관리되는지를 확인하고 상태가 <code>DELETE</code> 또는 <code>GONE</code>이 아니라면 <code>true</code>를 반환한다.</strong></u></p>
<hr>
<h3 id="contains-정리"><code>contains()</code> 정리</h3>
<ul>
<li><p><strong>초기화되지 않은 프록시</strong>
현재 세션과 동일한 세션에 있는지를 반환</p>
</li>
<li><p><strong>초기화된 프록시</strong>
원본 엔티티를 가져오고 이후 원본 엔티티의 경우와 동일하게 진행</p>
</li>
<li><p><strong>원본 엔티티</strong>
<code>EntityEntry</code>를 얻는 시도를 통해 영속성 컨텍스트에 관리되는지(관리 안되면 <code>null</code> 얻음)와 엔티티의 대한 정보를 확인하고 <code>DELETE</code>나 <code>GONE</code> 상태가 아니면 <code>true</code> 반환</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DB] SQL Developer와 docker 컨테이너 연결 - 데이터베이스 접속]]></title>
            <link>https://velog.io/@profile_exe/DB-SQL-Developer%EC%99%80-docker-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EC%97%B0%EA%B2%B0-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%A0%91%EC%86%8D</link>
            <guid>https://velog.io/@profile_exe/DB-SQL-Developer%EC%99%80-docker-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EC%97%B0%EA%B2%B0-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%A0%91%EC%86%8D</guid>
            <pubDate>Fri, 29 Sep 2023 12:36:00 GMT</pubDate>
            <description><![CDATA[<p> 앞서 <a href="https://velog.io/@profile_exe/DB-oracle-database-19c-with-docker-arm-%EA%B8%B0%EB%B0%98-mac-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%8B%A4%ED%96%89">arm 기반 mac OS에 Oracle Databse 19c Docker로 설치</a>하는 글을 작성했다.</p>
<p> 글이 너무 길어져 컨테이너 실행 까지만 포스팅했고, 여기서 <code>SQL Plus</code>와 <code>SQL Developer</code>로 <code>oracle database 19c</code> 컨테이너에 연결하는 내용을 적겠다.</p>
<h4 id="🚨-주의-🚨">🚨 주의 🚨</h4>
<p> 접속을 위해서는 <u><strong>Docker 컨테이너가 실행중</strong></u>이어야 한다. <code>Docker Desktop</code>으로 들어가 해당 컨테이너가 실행 중인지 확인 후 멈춰있다면 실행을 해야한다.</p>
<br>

<hr>
<h2 id="sql-plus로-접속하기"><code>SQL Plus로 접속하기</code></h2>
<p> 오라클 데이터베이스에 접속하는 방법 중 하나인 <code>SQL Plus</code> 를 이용한 방법이다. <code>docker</code> 컨테이너는 <code>docker exec</code> 명령어로 접속할 수 있다.</p>
<pre><code class="language-bash">docker exec -it test19c sqlplus</code></pre>
<p><code>-it</code> 옵션은 <code>i</code> 옵션과 <code>t</code> 옵션을 축약해 적용한 것이다.</p>
<ul>
<li><p><code>-i</code> 옵션 : <code>--interactive</code> 옵션으로 표준 입력(STDIN)을 유지한다. 이를 통해 컨테이너에 bash 명령을 입력할 수 있게 한다.</p>
</li>
<li><p><code>-t</code> 옵션 : <code>--tty</code> 옵션으로 컨테이너 내에서 명령을 실행할 때 터미널에서 직접 실행하는 것처럼 출력이 시각화된다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/e7280e02-3111-456c-ac8c-1522d444023c/image.png" alt="sqlplus로 접속한 화면"></p>
<p> 맥 터미널에서 위 명령어를 실행하면 첨부한 사진처럼 컨테이너에 접속이 된다. <code>sqlplus</code>를 실행하고 사용자 이름과 비밀번호를 입력하면 <code>CLI</code> 방식으로 데이터베이스와 상호작용이 가능하다.</p>
<p> 여기서 비밀번호는 앞서 컨테이너 생성 시 환경변수(<code>-e</code> 옵션) <code>ORACLE_PASSWORD</code>에 설정한 값이다.</p>
<br>

<hr>
<h2 id="sql-developer로-접속하기"><code>SQL Developer</code>로 접속하기</h2>
<p><a href="https://www.oracle.com/database/sqldeveloper/technologies/download/">SQL Developer 다운로드</a>
위 링크로 들어가 자신에게 알맞은 <code>OS</code>를 선택해 설치한다.</p>
<p>나는 <code>M2</code> 맥북에어라서 <code>ARM64</code> 버전을 설치했다.</p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/68c5c30d-f733-488f-8cf6-900d7127a499/image.png" alt="SQL Developer 버전 목록"></p>
<p>설치 후 실행하면 아래와 같은 화면이 뜬다.</p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/fa2e6b28-1afe-451d-a47f-fcd1b604e70e/image.png" alt="SQL Developer 화면"></p>
<p>여기서 <strong>수동으로 접속 생성</strong> 버튼을 누른다.</p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/b784b65e-f8e6-4ca0-ad4f-91f312bb4c68/image.png" alt="접속 생성 화면"></p>
<p>이 접속 생성 화면에서 입력해야 할 내용은 다음과 같다.</p>
<ul>
<li><code>Name</code> : 접속은 여러 접속을 만들 수 있어 각 접속마다 임의로 정하면 된다. 한글도 가능하다.</li>
<li><code>사용자 이름</code> : 데이터베이스에 등록되어있는 사용자 이름</li>
<li><code>비밀번호</code> : 해당 사용자의 비밀번호<ul>
<li><code>비밀번호 저장</code> : 접속 목록에서 접속할 때 비밀번호를 저장해놓아 재입력을 안해도 된다. 옵션을 체크하는게 좋다.</li>
</ul>
</li>
<li><code>SID</code> : 데이터베이스 인스턴스의 아이디</li>
<li><code>서비스 이름</code> : 여러 인스턴스를 묶어 하나의 서비스로 구성된 것의 이름</li>
</ul>
<p>데이터베이스 접속 방법은 <code>SID</code> 와 <code>서비스 이름(Service Name)</code> 이 있는데, 아직 하나의 인스턴스밖에 존재하지 않아 둘 중 아무거나 사용해도 무방하다.</p>
<br>

<h3 id="sid-service-name-확인"><code>SID, Service Name</code> 확인</h3>
<p>접속을 위해 실행중인 데이터베이스의 <code>SID</code>와 <code>Service Name</code>을 확인해보겠다.</p>
<p>우선 데이터베이스에 접속해야 하니 <code>sql plus</code>로 먼저 접속해보겠다.</p>
<p>맥 터미널에서 다음과 같이 입력해 <code>sql plus</code>로 접속한다.</p>
<pre><code class="language-bash">docker exec -it test19c sqlplus</code></pre>
<p>이후 아래 명령어로 <code>SID</code> 와 <code>Service Name</code> 을 확인한다.</p>
<pre><code class="language-sql">show parameter service_name;           -- 서비스 이름 조회
select instance_name from v$instance; -- SID 조회</code></pre>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/f29e81e2-a10f-403a-b1b3-c91c9b76d7b7/image.png" alt="Mac 터미널 sqlplus 접속 후 sid, service name 확인 이미지"></p>
<p><code>SID</code> 와 <code>서비스 이름</code> 모두 <code>FREE</code> 인 것을 알 수 있다. 그러므로 아래와 같이 입력을 해서 접속을 생성할 수 있다.</p>
<p> 아직 사용자를 생성하지 않았으니 <code>sys</code> 관리자 계정으로 접속하며, <code>SQL plus</code>에서 <code>sys as sysdba</code>로 접속했던 것을 여기서는 <code>롤</code>로 <code>SYSDBA</code>를 설정해주면 된다.</p>
<p> 비밀번호 역시 앞서 설정해두었던 것과 같다. 접속을 선택할 때마다 다시 입력하기 귀찮으니 비밀번호 저장을 체크했다.</p>
<p> 포트는 컨테이너 실행 시 <code>-p</code> 옵션을 통해 <code>1521</code> 포트로 외부에서 컨테이너의 <code>1521</code> 포트와 연결되도록 포트포워딩을 했으니 그대로 입력하면 된다.</p>
<p> <code>SID</code> 나 <code>서비스 이름</code> 둘 중 하나를 선택해 <code>FREE</code> 를 입력하고 <code>접속</code>을 누른다.</p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/5fa8385a-3132-49c0-9a1a-24e0ea11666b/image.png" alt="SQL Developer 접속 생성 입력란 작성 이미지"></p>
<p>위 처럼 양식을 채우면 아래와 같이 접속이 완료된 것을 볼 수 있다. <code>SHOW USER;</code>로 현재 사용자를 볼 수 있다.</p>
<p> <img src="https://velog.velcdn.com/images/profile_exe/post/36acd106-8e18-4f2f-8430-60e21c3655d5/image.png" alt="SQL Developer connection 완료 화면"></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DB] oracle database 19c with docker (arm 기반 mac) 설치 및 실행]]></title>
            <link>https://velog.io/@profile_exe/DB-oracle-database-19c-with-docker-arm-%EA%B8%B0%EB%B0%98-mac-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%8B%A4%ED%96%89</link>
            <guid>https://velog.io/@profile_exe/DB-oracle-database-19c-with-docker-arm-%EA%B8%B0%EB%B0%98-mac-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%8B%A4%ED%96%89</guid>
            <pubDate>Fri, 29 Sep 2023 10:43:47 GMT</pubDate>
            <description><![CDATA[<p> 이번 학기에 칼복학을 하게 되며 맥북 에어 구매와 함께 데이터베이스 강의를 듣게되었다. </p>
<p> 데이터베이스 강의는 <code>oracle database 19c</code>를 설치해 실습을 진행하는 방식이고,   오라클 데이터베이스는 <code>arm</code> 기반의 <code>mac OS</code>에 대한 지원이 없어서 <code>docker</code>를 이용해 설치를 해보려 한다.</p>
<p> 구글링을 해보니 10일 전에 올라온 따끈따끈한 글이 있어 이를 보고 설치를 해보았다. 영어로 되어있기 때문에 출처를 밝히고 한글로 내용을 요약해 정리한다. 또한, 내가 추가로 수정하거나 다르게 진행한 부분도 추가했으니 이 점 참고하길 바란다.</p>
<p> 출처 : <a href="https://itnext.io/oracle-on-arm-mac-m1-m2-docker-images-99ed67ed6ba6">Oracle on ARM, Mac M1/M2 Docker images</a></p>
<p> 이 글을 통해 <code>arm 기반 Mac OS</code>에서 <code>oracle database 19c</code>를 설치 및 실행해본다.</p>
<p>이 포스트를 끝까지 읽고 컨테이너 실행까지 완료했다면 <a href="https://velog.io/@profile_exe/DB-SQL-Developer%EC%99%80-docker-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EC%97%B0%EA%B2%B0-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%A0%91%EC%86%8D">SQL Developer와 docker 컨테이너 연결 - 데이터베이스 접속</a> 포스트를 통해 DB에 쿼리를 날려보자.</p>
<hr>
<h2 id="1-linux-arm-기반-oracle-database-19c-이미지-빌드"><code>1. Linux ARM 기반 Oracle Database 19c 이미지 빌드</code></h2>
<br>

<h3 id="1-docker-desktop-설치"><code>1) Docker Desktop 설치</code></h3>
<p><code>Docker</code>를 설치하는 여러 방법이 있는데 나는 <code>homebrew</code>를 이용한 설치로 진행했다.</p>
<ul>
<li><p>방법 1 : 홈페이지에서 설치 </p>
<p><a href="https://www.docker.com/get-started/">Docker Desktop</a> 이 링크로 들어가 설치한다. <code>Mac - Apple chip</code> 을 선택해서 설치하면 되겠다.</p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/019323ec-c3f0-40a2-b0e3-505bc944cd32/image.png" alt="Docker Get Started 이미지"></p>
</li>
<li><p>방법 2 : <code>homebrew</code>를 이용해 설치</p>
<pre><code class="language-bash">brew install --cask docker</code></pre>
<p>나는 <code>iterm2</code> 터미널이 있어 여기서 진행했다.</p>
</li>
</ul>
<br>

<h3 id="2-vscode-docker-desktop-extension-설치"><code>2) VsCode Docker Desktop Extension</code> 설치</h3>
<p> 이후 <a href="https://open.docker.com/extensions/marketplace?extensionId=mochoa%2Fcoder-docker-extension&amp;_gl=1*p1pmhh*_ga*MTk1OTg1MjkyOC4xNjk0MjEyODk3*_ga_XJWPQMJYHQ*MTY5NTEyMzMzNC4xMi4xLjE2OTUxMjM2NzEuNTUuMC4w">VSCode Docker Desktop Extension</a>을 설치한다. 이 링크를 클릭해 브라우저가 <code>Docker Desktop</code>을 열게 하고 <code>VS Code for the Web</code> 확장을 설치하면 된다.</p>
<p> <img src="https://velog.velcdn.com/images/profile_exe/post/beac5f72-4f0b-4aab-9a39-a3c2d70ffc99/image.png" alt="Docker Desktop VS Code for the Web Extension 이미지"></p>
<p> 위 이미지처럼 왼쪽 하단 <strong>Extensions</strong>에 <code>VS Code Web</code>이라는 확장이 설치된다.</p>
<p> 이제 <code>VS Code for the Web</code> 확장에 <code>Docker</code> 지원을 추가하는 명령어를 입력해야한다. <strong><code>Mac 터미널</code></strong> 에서 진행하면 된다.</p>
<pre><code class="language-bash">docker exec -ti --user root coder_embedded_dd_vm /bin/sh -c &quot;curl -s https://raw.githubusercontent.com/marcelo-ochoa/coder-docker-extension/main/addDocker.sh | bash&quot;</code></pre>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/ab3c948f-421e-4519-8eba-a835ece783ce/image.png" alt="VS Code 확장에 Docker 지원 추가 명령어"></p>
<br>

<h3 id="3-oracle-19c-eeenterprise-edition-다운로드"><code>3) Oracle 19c EE(Enterprise Edition) 다운로드</code></h3>
<h4 id="🚨-주의-🚨">🚨 주의 🚨</h4>
<p> <u><strong>사파리</strong></u>를 사용하면 <u><strong>자동 압축 해제</strong></u>가 되버린다. 이러면 설치가 불가능하기 때문에 <u><strong>크롬</strong></u>을 사용하거나
 <a href="https://kimsungjin.tistory.com/642">사파리 자동 압축 해제 끄기</a>를 적용한 후 진행하자.
<br></p>
<p><a href="https://www.oracle.com/database/technologies/oracle-database-software-downloads.html#19c">Oracle Database Enterprise Edition</a> 이 링크를 타고 아래로 내리면 <code>Linux ARM</code>용 <code>Oracle 19c</code> 다운로드 링크가 존재한다.</p>
<p> 이 파일을 가져다 설치를 진행할 예정이니 우선 다운로드를 받아둔다.</p>
<h4 id="🚨-주의-🚨-1">🚨 주의 🚨</h4>
<p> 파일을 <strong>다운로드(터미널 기준 <code>~/Downloads</code>)</strong> 폴더에 다운로드 하는게 차후 파일 복사 명령어 입력 시 명령어 수정을 안해도 돼서 좋다.</p>
<p> <img src="https://velog.velcdn.com/images/profile_exe/post/2b3843cb-3935-47ea-804e-a476beed892f/image.png" alt="Oracle Database 19c for LINUX ARM (arrch64) 다운로드 이미지"></p>
<br>

<h3 id="4-vscode-docker-확장에서-oracle의-docker-이미지-저장소-복제"><code>4) VSCode Docker 확장에서 Oracle의 Docker 이미지 저장소 복제</code></h3>
<p><code>Docker Desktop</code>의 <code>VSCode</code> 확장을 들어간 뒤, 홈 디렉터리로 이동할 것이다.</p>
<ul>
<li><p>먼저 폴더 열기를 누른다.
<img src="https://velog.velcdn.com/images/profile_exe/post/7bbdaf08-cf99-4734-a862-30208dda35c2/image.png" alt="vscode 도커 확장 폴더 열기 메뉴"></p>
</li>
<li><p><code>/home/coder</code> 경로를 입력하고 <code>OK</code>를 누른다.
<img src="https://velog.velcdn.com/images/profile_exe/post/683a0478-60b8-4636-b351-84e2fb480780/image.png" alt="폴더 여는 경로"></p>
</li>
</ul>
<p>여기서 새 터미널을 열어준다. 단축키로는 윈도우에선 <code>Ctrl + j</code> 맥에서는 <code>Cmd + j</code>로 터미널을 열 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/06a349ca-10e1-4281-aa79-ef20eb88598c/image.png" alt="VSCode Docker Extension 이미지"></p>
<p> 이 블로그에서는 <code>myWorkspace</code> 라는 폴더를 만들어서 진행하기 때문에 다음과 같이 입력하도록 하자.</p>
<ul>
<li><code>myWorkspace</code> 디렉토리 생성</li>
</ul>
<pre><code class="language-bash">cd ~                     # 홈 디렉터리(~)로 이동
mkdir myWorkspace        # myWorkspace 폴더 생성
cd myWorkspace            # myWorkspace 폴더로 이동</code></pre>
<ul>
<li>이후 <code>myWorkspace</code> 폴더 내에서 <code>Oracle</code>의 <code>Docker</code> 이미지 저장소를 복제한다.</li>
</ul>
<pre><code class="language-bash">git clone https://github.com/oracle/docker-images    # 저장소 복제
cd docker-images/            # 저장소로 이동
ls -l                        # 저장소 폴더 내 목록 조회</code></pre>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/418bb359-2a47-4a2b-b65f-21b8dc24a937/image.png" alt="Oracle Docker 이미지 저장소 복제 이미지"></p>
<br>

<h3 id="5-다운로드한-oracle-파일을-vscode-폴더로-복사"><code>5) 다운로드한 oracle 파일을 VSCode 폴더로 복사</code></h3>
<h4 id="🚨-주의-🚨-2">🚨 주의 🚨</h4>
<p> 이번 명령어는 <code>VSCode</code> 확장이 아닌 <u><strong>Mac 터미널</strong></u>에서 진행한다. 앞서 다운로드 했던 <code>Oracle Database 19c</code> 압축파일을 <code>VSCode</code> 확장의 <code>Oracle</code> 도커 저장소로 옮기는(복사하는) 것이다.</p>
<pre><code class="language-bash">cd Downloads    # 다운로드 폴더로 이동
docker cp LINUX.ARM64_1919000_db_home.zip coder_embedded_dd_vm:/home/coder/myWorkspace/docker-images/OracleDatabase/SingleInstance/dockerfiles/19.3.0</code></pre>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/1c5570af-de65-43b6-bf20-dd7ecf79c5be/image.png" alt="Oracle Database 19c 압축 파일을 VSCode 디렉터리로 복사"></p>
<br>

<h3 id="6-docker-이미지-빌드"><code>6) Docker 이미지 빌드</code></h3>
<h4 id="🚨-주의-🚨-3">🚨 주의 🚨</h4>
<p> 이번 명령어는 <u><strong>VSCode 확장의 터미널</strong></u>에서 진행한다. <u><strong>도커 이미지 빌드 명령어를 입력</strong></u>하기 위한 것이다. 명령어는 블로그와 달리 <code>cd</code>로 폴더를 이동할 때 <strong>절대경로</strong>로 입력하여 어떤 위치에 있더라도 빌드가 가능한 장소로 이동하도록 수정했다.</p>
<pre><code class="language-bash"># 빌드를 위해 해당 폴더로 이동
cd ~/myWorkspace/docker-images/OracleDatabase/SingleInstance/dockerfiles/
# 빌드 명령어 실행
./buildContainerImage.sh -v 19.3.0 -e</code></pre>
<br>

<hr>
<h2 id="2-mac-arm용-oracle-database-19c-이미지-빌드"><code>2. Mac ARM용 oracle database 19c 이미지 빌드</code></h2>
<p> <code>Linux ARM</code> 기반의 <code>oracle database 19c</code> 이미지를 성공적으로 빌드했다면, 다음은 <code>Mac ARM</code>에서 실행 가능하도록 이미지를 빌드하는 것이다. 앞서 잘 따라왔다면 여기서는 명령어만 따라치면 되니 빠르게 진행해보도록 하자.</p>
<h3 id="1-oci-oracle-free-저장소-복제"><code>1) oci-oracle-free 저장소 복제</code></h3>
<h4 id="🚨-주의-🚨-4">🚨 주의 🚨</h4>
<p> 이 명령어들은 <u><strong>VSCode 확장의 터미널</strong></u>의 <u><strong>myWorkspace</strong></u> 폴더 내에서 진행한다.</p>
<pre><code class="language-bash">cd ~/myWorkspace    # myWorkspace로 이동
# oci-oracle-free 저장소 복제
git clone -b 19c-arm-slim https://github.com/marcelo-ochoa/oci-oracle-free

cd oci-oracle-free  # oci-oracle-free 폴더로 이동</code></pre>
<br>

<h3 id="2-slim-버전-이미지-빌드"><code>2) slim 버전 이미지 빌드</code></h3>
<pre><code class="language-bash">docker buildx build --build-arg BUILD_MODE=SLIM -t oracle/database:19.3.0-ee-slim -f Dockerfile.193 .</code></pre>
<br>

<h3 id="3-slim-faststart-버전-이미지-빌드"><code>3) slim-faststart 버전 이미지 빌드</code></h3>
<pre><code class="language-bash">docker buildx build --build-arg BASE_IMAGE=oracle/database:19.3.0-ee-slim -t oracle/database:19.3.0-ee-slim-faststart -f Dockerfile.faststart .</code></pre>
<br>

<hr>
<h2 id="3-oracle-19c-docker-컨테이너-생성"><code>3. Oracle 19c Docker 컨테이너 생성</code></h2>
<p> 이제 이미지를 다 만들었으니 실행 가능한 컨테이너로 만든다.</p>
<h4 id="🚨-주의-🚨-5">🚨 주의 🚨</h4>
<p> 이 명령어들은 <u><strong>Mac 터미널</strong></u>에서 진행한다.</p>
<pre><code class="language-bash"># 컨테이너 생성 및 실행
docker run -d --name test19c -e ORACLE_PASSWORD=Oracle_2023 -p 1521:1521 oracle/database:19.3.0-ee-slim-faststart

# 실행한 컨테이너 로그 확인
docker logs -f test19c</code></pre>
<p>위 명령어에 대한 옵션을 설명하겠다.</p>
<ul>
<li><p><code>d</code> : 컨테이너를 백그라운드에서 실행할 수 있게 한다. 즉, 컨테이너를 실행한 이후에도 터미널에서 다른 작업을 진행할 수 있고, 실행한 터미널을 종료하더라도 백그라운드에서 계속 실행된다.</p>
</li>
<li><p><code>name</code> : 생성될 컨테이너의 이름</p>
</li>
<li><p><code>-e</code> : 컨테이너의 환경변수 설정</p>
<ul>
<li><code>ORACLE_PASSWORD</code> : 오라클 데이터베이스 접속 시 사용하는 비밀번호</li>
</ul>
</li>
<li><p><code>-p</code> : 호스트와 컨테이너의 포트포워딩</p>
<ul>
<li><code>1521:1521</code> : <code>Oracle Database</code>는 일반적으로 <code>1521</code> 포트로 열리므로 컨테이너의 1521 포트와 호스트의 1521 포트를 연결해 <u><strong>컨테이너 외부에서 프로그램이 1521 포트를 사용해 컨테이너 내부 1521 포트로 연결되어 데이터베이스에 접속</strong></u>할 수 있게 한다.<br>
</li>
</ul>
</li>
<li><p>명령어 입력 결과
로그를 보면 정상적으로 데이터베이스가 실행된 것을 볼 수 있다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/2c0dee27-70c3-4cc7-97a1-18752fa15af2/image.png" alt="명령어 입력 결과 이미지"></p>
<ul>
<li><code>Docker Desktop</code>에서 확인
실행한 컨테이너는 <code>Docker Desktop</code>에서 관리할 수 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/404e14d6-9920-499d-9cac-f3a546516ad3/image.png" alt="Docker Desktop에서 실행되는 컨테이너 확인"></p>
<hr>
<p>여기까지 <code>Oracle Database 19c</code>를 <code>ARM 기반 Mac OS</code>에서 실행할 수 있도록 <code>Docker</code>를 이용해 컨테이너를 만들어보았다. 글이 길어져서 다음 글에 <code>SQL Plus</code>와<code>SQL Developer</code>를 이용해 데이터베이스에 접속하는 방법을 작성했다. 아래 링크로 방문하면 된다.</p>
<p><a href="https://velog.io/@profile_exe/DB-SQL-Developer%EC%99%80-docker-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EC%97%B0%EA%B2%B0-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%A0%91%EC%86%8D">SQL Developer와 docker 컨테이너 연결 - 데이터베이스 접속</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DB] UNIQUE INDEX 사용 이유]]></title>
            <link>https://velog.io/@profile_exe/DB-UNIQUE-INDEX-%EC%82%AC%EC%9A%A9-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@profile_exe/DB-UNIQUE-INDEX-%EC%82%AC%EC%9A%A9-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Wed, 19 Jul 2023 11:08:38 GMT</pubDate>
            <description><![CDATA[<p> 온라인 강의를 들으며 회원가입을 위한 컨트롤러에서 <code>email</code> 필드의 유효성을 검사해 이미 가입한 이메일인지 확인하는 부분을 구현하고 있었다.</p>
<p> 구현 방법에 대해서 <strong>인덱스</strong>라는 개념을 언급하지만 다른 방법을 사용하겠다고 했다. 언급한 내용은 자신의 <code>mongodb</code> 강의에 있다며 판촉(?) 행위를 하길래 그냥 혼자 공부해야겠다 생각하고 <code>Github Copilot</code>과 구글링을 통해 공부한 내용을 정리한다.</p>
<p> <strong>인덱스(INDEX), 고유 인덱스(UNIQUE INDEX), 고유 제약 조건(UNIQUE CONSTRAINT)</strong>에 대해 알아본다.</p>
<hr>
<h2 id="🧾-구현-방법-설명">🧾 구현 방법 설명</h2>
<blockquote>
<p>when using mongodb you could <u><strong><strong>create an index</strong></strong></u> in the mongo database <u><strong>on your email field</strong></u> and <u><strong>give that index the unique property</strong></u>.</p>
</blockquote>
<p> 방법에 대한 설명은 해주고 실제 강의에서는 주어진 이메일을 대조하는 방법을 사용했는데, 위 설명을 보면 <code>email</code> 필드에 <code>index</code>를 생성하고 <code>unique</code> 속성을 부여하면 된다.</p>
<p> 즉, <strong><code>email</code> 필드를 <code>UNIQUE INDEX</code>로 설정하면 된다.</strong></p>
<p> 이를 적용하면 무엇이 달라질지, 어떤 이점이 있을지 알아보도록 하겠다.</p>
<hr>
<h2 id="🔍-index-">🔍 <code>INDEX</code> ?</h2>
<p><a href="https://en.wikipedia.org/wiki/Database_index">Database_index - Wikipedia</a></p>
<blockquote>
<p>A <code>database index</code> is a <code>data structure</code> that <code>improves the speed of data retrieval operations</code> on a database table at the cost of <code>additional writes</code> and <code>storage space</code> to maintain the index data structure. </p>
</blockquote>
<ul>
<li><p>데이터베이스의 <u><strong>인덱스(INDEX)</strong></u>는 <u><strong>검색 작업</strong></u>의 속도를 향상시키는 <u><strong>데이터 구조</strong></u></p>
</li>
<li><p>데이터 구조는 <code>해시 테이블(Hash Table)</code>과 <code>B Tree</code>로 구현 가능하며 주로 <code>B Tree</code>에서 파생된 구조로 구현됨 - <code>B* Tree</code>, <code>B+ Tree</code></p>
</li>
<li><p>인덱스 역시 데이터 구조이므로 <u><strong>추가적인 쓰기 작업</strong></u>과 <u><strong>저장 공간</strong></u>이 필요</p>
<br>

</li>
</ul>
<hr>
<h2 id="⚖️-index-장단점">⚖️ <code>INDEX</code> 장단점</h2>
<h3 id="📌-장점">📌 장점</h3>
<ul>
<li><p>인덱스를 사용하면 <strong>데이터 조회(<code>SELECT</code>) 속도가 빨라진다.</strong></p>
</li>
<li><p><code>WHERE</code>, <code>ORDER BY</code>, <code>MIN/MAX</code> 속도가 빨라진다.</p>
<p>데이터베이스에서 <u><strong>특정 데이터를 조회</u>(<code>WHERE</code>)</strong>할 때 <u><strong>테이블 전체를 조회(full scan)</strong></u>해야 한다.</p>
<p>하지만 인덱스를 사용하는 경우 <u><strong>인덱스를 기준으로 정렬된 상태로 저장</strong></u>되어 있어 더 빠르게 찾을 수 있다.</p>
<p>정렬된 상태이므로 <code>ORDER BY</code>, <code>MIN/MAX</code> 또한 빨라진다</p>
<br>

</li>
</ul>
<h3 id="📌-단점">📌 단점</h3>
<ul>
<li><p>인덱스라는 <strong>데이터 구조</strong>를 유지하기 위한 <strong>비용</strong>이 존재한다.</p>
<p>테이블과는 별개로 <u><strong>추가적인 저장 공간이 필요</strong></u>하고 인덱스가 적용된 열(Column)에 <code>INSERT</code>, <code>UPDATE</code>, <code>DELETE</code> 작업 시 정렬, 인덱스 구조 갱신 등 <u><strong>추가적인 연산이 발생</strong></u>한다.</p>
<br>

</li>
</ul>
<h3 id="✨-인덱스를-적용하기-적절한-경우">✨ 인덱스를 적용하기 적절한 경우</h3>
<ul>
<li><p>조회(<code>SELECT</code>)를 자주 하는 경우</p>
</li>
<li><p>삽입(<code>INSERT</code>), 수정(<code>UPDATE</code>), 삭제(<code>DELETE</code>)가 자주 발생하지 않는 경우</p>
</li>
<li><p>데이터의 중복도가 낮은 경우</p>
</li>
<li><p><code>WHERE</code>, <code>ORDER BY</code>, <code>JOIN</code>을 자주 하는 경우</p>
</li>
</ul>
<hr>
<h2 id="✔️-구현">✔️ 구현</h2>
<p> 앞 내용들로 <code>INDEX</code>에 대해서는 어느정도 알았으니 <code>email</code> 필드에 <code>UNIQUE INDEX</code>를 부여해 <strong>중복 방지(unique)</strong> 및 <strong>빠른 조회(index)</strong>를 구현해보자.
 <br></p>
<ul>
<li><code>models/user.js</code><pre><code class="language-js">const userSchema = new Schema({
name: {
  type: String,
  required: true,
},
email: {
  type: String,
  required: true,
  unique: true,    // unique 옵션 설정
},
});</code></pre>
</li>
</ul>
<blockquote>
<ul>
<li><code>Github Copilot Chat</code> 답변</li>
</ul>
<p><code>email</code> 필드에 <code>unique</code> 옵션을 추가하면 해당 필드에 대한 <code>Unique Index</code>가 생성되며, 이를 통해 해당 필드를 기준으로 조회하는 작업이 더욱 빠르게 수행될 수 있습니다. <code>mongoose</code>에서는 <strong><code>unique</code> 옵션을 가진 필드에 대해 <u>자동으로 인덱스를 생성</strong></u>해주는 기능이 있으므로, <code>email</code> 필드에 <code>unique</code> 옵션을 추가하면 <code>mongoose</code>가 자동으로 <code>email</code> 필드에 대한 <code>Unique Index</code>를 생성합니다</p>
</blockquote>
<p>필드에 <code>unique</code> 옵션을 주면 <code>mongoose</code>에서 자동으로 <code>Unique Index</code>를 생성해준다. 이로서 <code>email</code> 필드에 대한 중복 방지 및 빠른 조회가 가능해졌다.
<br></p>
<ul>
<li><p><code>controllers/auth.js</code></p>
<pre><code class="language-js">postSignup: async (req, res, next) =&gt; {
const { name, email, password } = req.body;

try {
  // email만 넣으면 됌!!
  const existingUser = await User.findOne({ email });
  ...

} catch (err) {
  ...
}
},</code></pre>
<p><code>email</code>필드가 중복값이 없음이 보장되고 <code>index</code>가 적용되어있어 빠른 조회가 가능하다.</p>
</li>
</ul>
<hr>
<h2 id="🤔-unique-index-적용-이유">🤔 <code>UNIQUE INDEX</code> 적용 이유</h2>
<p>그렇다면 왜 <code>email</code> 필드에 <code>unique</code> 인덱스를 적용해야 하는 것일까?</p>
<h3 id="💬-index-적용-이유">💬 <code>INDEX</code> 적용 이유</h3>
<p><code>email</code>필드에 대해 살펴보면 앞서 정리한 인덱스를 적용하기 적절한 경우에 대부분 해당한다.
<br></p>
<ul>
<li><p>조회(<code>SELECT</code>)를 자주 하는 경우</p>
<p>이메일은 사용자의 데이터로서 <strong>게시글 작성자 확인 시 정보 표시</strong> 및 <strong>관리자 페이지에서 회원 조회</strong> 등 조회가 자주 일어날 수 있다.</p>
<br>
</li>
<li><p>삽입(<code>INSERT</code>), 수정(<code>UPDATE</code>), 삭제(<code>DELETE</code>)가 자주 발생하지 않는 경우</p>
<p>사람마다 다르겠지만 특정 사이트에 가입 및 탈퇴를 자주 하지 않으며 가입한 이메일 수정 또한 빈번하지는 않다.</p>
<br>
</li>
<li><p>데이터의 중복도가 낮은 경우</p>
<p>이메일 또한 타 사이트에서 중복되지 않은 아이디로 가입해 만들어진 것으로 다른 사용자와 이메일 주소가 같을 가능성이 매우 적다.</p>
<br>

</li>
</ul>
<h3 id="💬-unique-index인-이유">💬 <code>&quot;Unique&quot; Index</code>인 이유</h3>
<p><code>email</code>필드는 <strong>중복값을 허용하지 않는</strong> 필드로 설정하고 싶었기 때문이다. 사용자들이 각자의 <strong>고유한 이메일 주소</strong>를 가지고 회원가입 및 로그인이 가능하도록 구현하려 했다.</p>
<hr>
<h2 id="💡-unique-constraint와-unique-index">💡 <code>Unique Constraint</code>와 <code>Unique Index</code></h2>
<p><code>Unique Constraint(고유 제약 조건)</code>는 내부적으로 <code>Unique Index(고유 인덱스)</code>를 사용하여 구현된다. 고유 인덱스를 생성해 고유 제약 조건을 강제할 수 있기 때문이다.</p>
<p>두 기능을 지원하는 데이터베이스에서는 <u><strong>목적을 명시하는 목적</strong></u>으로 위 둘을 사용할 수 있다.</p>
<p>공통적으로 <u><strong>고유한 값을 가져 중복값을 허용하지 않는다</strong></u>는 조건이 적용된다고 간주한다.
<br></p>
<h3 id="🔗-인덱스-기능을-중점적으로-활용하여-고유성을-강조하고자-할-때">🔗 인덱스 기능을 중점적으로 활용하여 고유성을 강조하고자 할 때</h3>
<p>필드를 <u><strong>검색, 정렬, 필터링</strong></u>하는 데 <u><strong>빠른 속도</strong></u>가 필요한 경우, <u><strong>고유 인덱스를 명시적으로 사용</strong></u>하고 주석을 추가하여 코드에 명확하게 표시하면 된다.</p>
<p>이렇게 함으로써 나중에 <u><strong>고유성이 불필요해진 경우</strong></u> 인덱스 기능만 유지하면 되니 <u><strong>일반적인 non-unique 인덱스로 대체</strong></u>해야 함을 알 수 있다. </p>
<h3 id="🔗-비즈니스-규칙을-중점적으로-고유성을-강조하고자-할-때">🔗 비즈니스 규칙을 중점적으로 고유성을 강조하고자 할 때</h3>
<p>필드의 고유성을 강제하기 위해 <u><strong>인덱스가 필요하지 않고</strong></u>, 단순히 <u><strong>비즈니스 규칙으로 고유성을 검증</strong></u>해야 하는 경우, <u><strong>고유 제약 조건을 사용</strong></u>하는 것이 좋다.</p>
<h4 id="🚨-주의">🚨 주의</h4>
<p> 비즈니스 규칙에서 고유성을 강조할 때, <u><strong>인덱스가 필요하지 않아도 내부적으로 UNIQUE INDEX를 이용해 구현</strong></u>하기 때문에 <strong>&#39;<u>인덱스가 필요하지 않지만 생긴다</u>&#39;</strong>는 사실을 인지해야 한다</p>
<hr>
<h2 id="참고">참고</h2>
<p><a href="https://www.ibm.com/docs/en/db2/11.5?topic=indexes-unique-non-unique">IBM 문서 - Unique and non-unique indexes</a>
<a href="https://dba.stackexchange.com/questions/144/when-should-i-use-a-unique-constraint-instead-of-a-unique-index">When should I use a unique constraint instead of a unique index</a>
<a href="https://www.mssqltips.com/sqlservertip/4270/difference-between-sql-server-unique-indexes-and-unique-constraints/">Difference between unique indexes and unique constraints</a>
<a href="https://velog.io/@sweet_sumin/%ED%81%B4%EB%9F%AC%EC%8A%A4%ED%84%B0%EB%93%9C-%EC%9D%B8%EB%8D%B1%EC%8A%A4-Clustered-Index-%EB%84%8C-%ED%81%B4%EB%9F%AC%EC%8A%A4%ED%84%B0%EB%93%9C-%EC%9D%B8%EB%8D%B1%EC%8A%A4-Non-Clustered-Index">클러스터드 인덱스 (Clustered Index), 넌 클러스터드 인덱스 (Non Clustered Index)</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JavaScript] 왜 불러온 객체는 메서드 사용이 불가능할까? feat. 직렬화 & 역직렬화]]></title>
            <link>https://velog.io/@profile_exe/JS-deserialization-use-method</link>
            <guid>https://velog.io/@profile_exe/JS-deserialization-use-method</guid>
            <pubDate>Sun, 02 Jul 2023 10:08:53 GMT</pubDate>
            <description><![CDATA[<p>&quot;어?~&quot; 왜 메서드 호출 안됨?
...</p>
<p><code>Udemy</code>에서 <code>node.js</code> 강의를 듣고있는데, 파일시스템이나 데이터베이스에서 저장한 데이터를 불러온 뒤 객체로 파싱하면 <strong>메서드 사용이 불가능</strong>했다.</p>
<p>이 포스트에서 불가능했던 이유와 해결 방법을 기록했다.</p>
<hr>
<h2 id="🚨-에러-발생-예시">🚨 에러 발생 예시</h2>
<p>간단한 클래스를 정의하고 파일/DB에서 저장 및 불러오고 메서드를 사용하는 예시를 들어보겠다.
<br></p>
<h3 id="🧾-user-클래스-정의">🧾 <code>User</code> 클래스 정의</h3>
<p><code>User</code>클래스는 <code>name</code>과 <code>age</code> 속성, <code>getName()</code>과 <code>getAge()</code> 메서드를 제공하는 클래스다.</p>
<pre><code class="language-js">class User {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  getName() {
    return this.name;
  }

  getAge() {
    return this.age;
  }
}</code></pre>
<br>


<h3 id="📌-파일에서-객체-저장-및-불러오기">📌 파일에서 객체 저장 및 불러오기</h3>
<p><code>fs</code>모듈을 이용한다.
<code>JSON.stringify()</code>와 <code>JSON.parse()</code>로 문자열, 객체로 파싱 후 저장 및 불러온다.</p>
<pre><code class="language-js">const fs = require(&#39;fs&#39;);

// 객체 생성 및 파일 저장
const user = new User(&#39;John Doe&#39;, 25);
fs.writeFileSync(&#39;user.json&#39;, JSON.stringify(user));

// 파일에서 객체 불러오기
const data = fs.readFileSync(&#39;user.json&#39;, &#39;utf8&#39;);
const loadedUser = JSON.parse(data);

// 메서드 호출 - 에러발생!
console.log(loadedUser.getName()); // Error: loadedUser.getName is not a function
console.log(loadedUser.getAge());  // Error: loadedUser.getAge is not a function</code></pre>
<br>

<h3 id="📌-데이터베이스에서-객체-저장-및-불러오기">📌 데이터베이스에서 객체 저장 및 불러오기</h3>
<p><code>mongodb</code> 사용 예시로 DB 연결 관련 코드는 축약하고 논리 위주로 작성하였다.</p>
<pre><code class="language-js">const db = getDb(); // 데이터베이스 connection 얻기

// 객체 생성 및 데이터베이스에 저장
async function insertUser(name, age) {
    const user = new User(name, age);
    await db.collection(&#39;users&#39;).insertOne(user);
}

// 데이터베이스에서 객체 불러오기
async function findUser(userId) {
    const user = await db
        .collection(&#39;users&#39;)
        .findOne({ _id: userId }); 
    return user;
}

async function main() {
    insertUser(&#39;John&#39;, 25);

      const userId = &#39;임의의 유저 아이디&#39;;
    const loadedUser = await findUser(userId);

      // 에러 발생!
    console.log(loadedUser.getName()); // Error: loadedUser.getName is not a function
    console.log(loadedUser.getAge());  // Error: loadedUser.getAge is not a function
}

main();</code></pre>
<hr>
<h2 id="🔍-직렬화--역직렬화">🔍 직렬화 &amp; 역직렬화</h2>
<p>이와 같은 에러는 <strong>직렬화</strong>와 <strong>역직렬화</strong>에 대한 이해가 필요하다.
아래 코틀린 공식문서에 직렬화에 및 역직렬화에 대한 설명이 있다.
<a href="https://kotlinlang.org/docs/serialization.html">Serialization | Kotlin Documents</a></p>
<h3 id="💬-직렬화-serialization">💬 직렬화 <code>(Serialization)</code></h3>
<blockquote>
<p>응용 프로그램에서 쓰는 데이터를 네트워크를 통해 전송하거나 DB 또는 파일에 저장 가능한 형식으로 바꾸는 프로세스</p>
</blockquote>
<h3 id="💬-역직렬화-deserialization">💬 역직렬화 <code>(Deserialization)</code></h3>
<blockquote>
<p>외부 소스에서 데이터를 읽고 이를 런타임 객체로 바꾸는 반대 프로세스</p>
</blockquote>
<p>즉 <strong>파일시스템이나 데이터베이스에 데이터를 전송 및 불러오는 행위</strong>를 각각 직렬화와 역직렬화로 생각하면 된다. </p>
<hr>
<h2 id="🤔-에러-발생-원인">🤔 에러 발생 원인</h2>
<p>이번 문제를 해결하려면 <code>JavaScript</code>에서의 직렬화 / 역직렬화가 어떻게 이루어지는지 알아야한다.
<br></p>
<h3 id="💬-javascript-객체의-직렬화--역직렬화">💬 <code>JavaScript</code> 객체의 직렬화 &amp; 역직렬화</h3>
<p>우선 파일 및 데이터베이스에 저장하는 경우 <strong>객체의 속성만</strong> 저장된다.</p>
<ul>
<li>파일에 저장하는 경우<pre><code class="language-js">{&quot;name&quot;:&quot;John Doe&quot;,&quot;age&quot;:25}</code></pre>
</li>
<li>데이터베이스에 저장하는 경우<pre><code class="language-js">{&quot;_id&quot;:{&quot;$oid&quot;:&quot;64a132951d6cb2e438455023&quot;},&quot;name&quot;:&quot;John&quot;,&quot;age&quot;:{&quot;$numberInt&quot;:&quot;25&quot;}}</code></pre>
<br>

</li>
</ul>
<p><a href="https://stackoverflow.com/questions/46599275/typescript-instanceof-does-not-work-for-json/46599798#46599798">instanceof don&#39;t work for JSON</a></p>
<p>파일이나 데이터베이스에서 데이터를 불러와 <strong>객체로 파싱(역직렬화)</strong>하면 해당 객체는 <strong><code>no prototype</code></strong> 객체가 된다. 즉, 순수하게 <strong>객체의 속성만 담고있는 객체</strong>가 되어버린다.</p>
<ul>
<li><code>no prototype</code> 객체<ul>
<li><code>spread(...)</code> 문법 사용 불가능 - <code>no iterator</code> 에러</li>
<li><code>객체 instanceof 클래스 === false</code> - 해당 클래스의 인스턴스가 아닌 것으로 간주<br>

</li>
</ul>
</li>
</ul>
<h3 id="📝-원인-정리">📝 원인 정리</h3>
<p><strong>객체의 상태만을 포함</strong>한 JSON 형태의 데이터나 데이터베이스 레코드는 <strong>해당 클래스의 메서드 코드를 포함하고 있지 않기 때문</strong>에 메서드 호출이 불가능한 것이다</p>
<p>또한, <code>no prototype</code> 객체라서 <code>spread</code>문법이나 <code>instanceof</code>도 불가능하다.</p>
<hr>
<h2 id="✔️-해결-방안">✔️ 해결 방안</h2>
<p>객체의 속성을 추출해 <code>new</code> 연산자로 클래스의 인스턴스를 생성해서 이용하면 된다.
<strong>객체 구조분해할당</strong>을 통해 간결하고 이해하기 쉬운 코드를 작성할 수 있다.</p>
<pre><code class="language-js">const { name, age } = loadedUser; // 구조분해할당
const newUser = new User(name, age);

console.log(newUser.getName()); // &quot;John&quot;
console.log(newUser.getAge());  // 25
</code></pre>
<hr>
<h2 id="✨-응용하기---코드에-해결-방안-적용">✨ 응용하기 - 코드에 해결 방안 적용</h2>
<p>내가 듣는 강의에서는 프로젝트를 <code>mvc</code>패턴으로 구성했다.</p>
<p>미들웨어를 등록해 사용자를 얻어 <code>req.user</code>에 저장
이후 <code>controller</code>에서 <code>req.user</code>에 접근해 <code>user</code>를 사용할 수 있도록 하는 코드이다.</p>
<p><code>model</code>에서 클래스를 정의하는데, 주어진 <code>id</code>로 <code>user</code>를 반환하는 함수 <code>findById</code> 메서드에서 이 해결방안을 적용할 수 있었다.</p>
<p><code>findById</code>에서 <code>User</code>클래스의 인스턴스를 넘겨주므로 <code>req.user</code>로 얻은 사용자 객체는 메서드를 사용할 수 있다.
<br></p>
<h3 id="📃-해결-코드">📃 해결 코드</h3>
<ul>
<li><p><code>models/user.js</code> : <code>findById</code> 메서드에 해결 방안 적용</p>
<pre><code class="language-js">class User {
constructor(name, email, cart, id) {
  this.name = name;
  this.email = email;
  this.cart = cart; // {items: []}
  this._id = id ? new ObjectId(id) : null;
}

... (다른 메서드들)

getCart() { ... } // 사용자의 장바구니를 얻는 메서드

static async findById(userId) {
  try {
    const db = getDb();
    const user = await db
      .collection(&#39;users&#39;)
      .findOne({ _id: new ObjectId(userId) });

    // 데이터베이스나 파일에서 불러온 객체는 단순히 데이터의 상태만을 가지고 있음
    // 따라서 User의 메서드를 사용할 수 있도록
    // 객체 생성에 필요한 속성들을 가져온 후 new User로 인스턴스 반환
    const { name, email, cart, _id } = user;
    return new User(name, email, cart, _id);
  } catch (err) {
    console.log(err);
  }
}
}</code></pre>
<br>
</li>
<li><p><code>app.js</code> : <code>req.user</code>에 저장해 <code>controller</code>에서 접근할 수 있도록 하는 미들웨어 등록</p>
<pre><code class="language-js">app.use((req, res, next) =&gt; {
User.findById(&#39;사용자 아이디&#39;)
  .then((user) =&gt; {
    req.user = user; // req 객체에 담으면 다음 미들웨어에서 접근 가능
    next();            // next()를 호출해 다음 미들웨어로 요청이 이동하도록함
  })
  .catch((err) =&gt; console.log(err));
});</code></pre>
<br>
</li>
<li><p><code>controllers/shop.js</code> : <code>app.js</code>에서 등록한 <code>user</code>를 요청 객체(<code>req</code>)에서 불러와 메서드 사용 가능</p>
<pre><code class="language-js">module.exports = {

... (다른 컨트롤러들)

postCartDeleteProduct: async (req, res, next) =&gt; {
  const prodId = req.body.productId;
  try {
    const cart = await req.user.getCart(); // User 클래스의 인스턴스라 메서드 사용 가능

    ... (나머지 로직)

    res.redirect(&#39;/cart&#39;);
  } catch (err) {
    console.log(err);
  }
}, 
}</code></pre>
</li>
</ul>
<hr>
<h2 id="📝-정리">📝 정리</h2>
<ul>
<li><p><code>JavaScript</code>에서 파일 및 데이터베이스에 데이터를 <strong>저장(직렬화) / 불러오기(역직렬화)</strong>를 하면 <strong>객체의 속성</strong>만 저장되고 불러옴</p>
</li>
<li><p>불러온 객체는 메서드를 지니지 않으며 <strong><code>no prototype</code></strong> - <code>spread</code>문법과 <code>instanceof</code> 불가</p>
</li>
<li><p>객체의 속성을 받아 <code>new</code> 연산자로 새로운 인스턴스를 생성해야함</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘 풀이를 위한 CMake 환경 구성]]></title>
            <link>https://velog.io/@profile_exe/CMake-environment-configuration</link>
            <guid>https://velog.io/@profile_exe/CMake-environment-configuration</guid>
            <pubDate>Sat, 10 Jun 2023 11:02:36 GMT</pubDate>
            <description><![CDATA[<p>이전 글인 <a href="https://velog.io/@profile_exe/Github-Codespaces">Github Codespaces로 군대에서 코딩하기</a>에 이어서 알고리즘 문제 풀이를 위한 <code>CMake</code> 환경 구성을 해보도록 하겠다.</p>
<p>이 환경 설정은 <code>Github Codespaces</code> 뿐만 아니라 일반적인 <code>Linux</code> 환경에서도 적용되는 것이다. 따라서 윈도우에서도 <code>WSL</code>를 사용해 <code>VS Code</code> 환경에서 해당 내용으로 개발 환경을 구축할 수 있다.</p>
<hr>
<h2 id="1-cmake-g-설치-확인"><code>1. CMake, g++</code> 설치 확인</h2>
<p><a href="https://gist.github.com/luncliff/6e2d4eb7ca29a0afd5b592f72b80cb5c?permalink_comment_id=2857166">CMake 할 때 쪼오오금 도움이 되는 문서</a>
<code>CMake</code> 환경 구성에 어려움이 있다면 위 문서를 참고하고 아래 내 폴더 구조를 보면 되겠다.</p>
<p>아래 사진은 <code>Github Codepaces</code>에서 <strong>빈 템플릿(Blank)</strong>을 생성 후 알고리즘 문제풀이에 필요한 패키지들이 설치되었는지 터미널에서 확인한 것이다.</p>
<p><code>cmake</code>와 <code>g++</code>, <code>Python</code>이 설치되어있어 <code>C/C++</code>, <code>Python</code>을 활용해 알고리즘 문제를 풀 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/7b6fe0d6-91f4-49ce-82d5-b4798561e48b/image.png" alt="Blank 템플릿 생성 초기화면"></p>
<hr>
<h2 id="2-cmake-tools-extension-설치"><code>2. CMake Tools Extension</code> 설치</h2>
<p><code>CMake Tools</code> 확장을 설치하면 <code>CMake</code> 확장까지 같이 설치된다.
해당 확장을 이용해 <code>VS Code</code>에서 <code>CMake</code>환경을 편하게 이용할 수 있다.
<img src="https://velog.velcdn.com/images/profile_exe/post/765e5a3b-df22-41ae-be4d-abcf744b3935/image.png" alt="CMake Tools 확장 이미지"></p>
<hr>
<h2 id="3-cmake-환경-설정"><code>3. CMake</code> 환경 설정</h2>
<p>하단의 상태표시줄에 <code>CMake</code> 확장 메뉴들이 나와있는지 확인한다.
아래 사진에서는 <code>Live Share</code> 다음 메뉴들이 <code>CMake</code> 확장 버튼들이다.</p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/746eed6b-4a81-4a48-bf18-6f499627af40/image.png" alt="CMake 확장 버튼들"></p>
<p>해당 버튼들이 안보인다면 상태표시줄을 우클릭 후 <code>CMake Tools(확장)</code>을 선택해 표시한다.</p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/df8a8086-2bd3-4ff2-8e1a-e0158dfe5339/image.png" alt="VS Code 상태표시줄"></p>
<p><code>No Kit Selected</code> 버튼을 눌러 컴파일러를 선택해준다. 나는 <code>GCC 9.4.0</code>을 선택했다.
<img src="https://velog.velcdn.com/images/profile_exe/post/a269505a-9b48-453f-b19c-ff74843da6c8/image.png" alt=""></p>
<hr>
<h2 id="4폴더-구조"><code>4.</code>폴더 구조</h2>
<p>아래는 <code>CMake</code>를 이용해 개발 및 알고리즘 문제를 푼다고 가정한 폴더 구조이다.
<code>src</code> 안에 <code>acmicpc</code>, <code>car</code>라는 서브 디렉터리가 존재하고
그 디렉터리마다 각각 소스파일들이 존재한다.</p>
<pre><code>루트 디렉터리
├── CMakeLists.txt
└── src
    ├── CMakeLists.txt
    ├── acmicpc
    │   ├── CMakeLists.txt
    │   ├── 4029.cpp
    │   └── 1092.cpp
    └── car
        ├── CMakeLists.txt
        ├── main.cpp
        ├── Car.cpp
        ├── Car.h
        └── Vehicle.h</code></pre><blockquote>
</blockquote>
<ul>
<li><code>~/CMakeLists.txt</code>
가장 최상위의 <code>CMakeLists</code>로 프로젝트 관련 설정들을 넣어준다.<pre><code class="language-c">cmake_minimum_required(VERSION 3.0.0)
project(algorithm)
&gt;
add_subdirectory(src)</code></pre>
</li>
</ul>
<blockquote>
</blockquote>
<ul>
<li><code>src/CMakeLists.txt</code>
하위 디렉터리들을 프로젝트에 추가해준다.<pre><code class="language-c">add_subdirectory(acmicpc)
add_subdirectory(car)</code></pre>
</li>
</ul>
<blockquote>
</blockquote>
<ul>
<li><code>src/acmicpc/CMakeLists.txt</code>
<code>acmicpc</code>디렉터리는 백준 알고리즘 문제를 푸는 소스코드를 모아두고 실행하는 장소이다.<blockquote>
</blockquote>
소스파일 하나 하나가 독립된 실행 파일로 존재해야하므로 <code>add_executable</code>을 통해 설정해주었다.
나중에 <code>CMakeLists.txt</code>를 더 잘 다루게 되면 이러한 설정도 변수 등을 통해 자동화를 해봐야겠다.<blockquote>
</blockquote>
<code>add_executable(실행이름 [공백] 같이 빌드할 파일들 나열)</code> 이렇게 사용하면 된다.<blockquote>
</blockquote>
<pre><code class="language-c">add_executable(1092 1092.cpp)
add_executable(4029 4029.cpp)</code></pre>
</li>
</ul>
<blockquote>
</blockquote>
<ul>
<li><code>src/car/CMakeLists.txt</code>
<code>car</code>디렉터리는 여러 파일을 같이 빌드해야하는 상황이라 가정하고 만들어보았다.
<code>main.cpp</code>을 실행파일로 두고 나머지 <code>Car.cpp</code>와 <code>Car.h</code>를 같이 빌드한다.<pre><code class="language-c">add_executable(Car
  main.cpp
  Car.cpp
  Car.h
  Vehicle.h
)</code></pre>
</li>
</ul>
<hr>
<h2 id="5실행하기"><code>5.</code>실행하기</h2>
<p>상태표시줄에서 <strong>왼쪽에서부터 맨 우측(화살표 오른쪽 버튼)</strong>에서 실행할 목록을 선택할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/dc272419-04c1-4b98-8623-f8dbe4169dd2/image.png" alt="CMake에서 실행할 목록 선택"></p>
<p><code>1092.cpp</code>을 다음과 같은 내용으로 설정 후 <code>[1092]</code>로 선택하고 바로 옆 <code>화살표 버튼</code>을 누르면 잘 실행되는 것을 볼 수 있다.</p>
<pre><code class="language-cpp">#include &lt;iostream&gt;

using namespace std;

int main() {
    cout &lt;&lt; &quot;Hello World!&quot; &lt;&lt; endl;
    return 0;
}
</code></pre>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/9096d7b3-1c5f-448a-8e19-d4effa8458a3/image.png" alt="1092.cpp 실행화면"></p>
<h3 id="주의">주의!!</h3>
<p><code>CMakeLists.txt</code>를 편집한 후에는 <strong>반드시 저장(Ctrl + S)</strong>을 해주어야 한다.</p>
<p>저장과 동시에 <code>CMake</code>명령어가 실행되며 <code>출력</code>창에서 해당 결과가 나온다.
<code>Configuring done</code>, <code>Generating done</code>, <code>Build files have been written to:~</code>가 나온다면 정상적으로 설정이 된 것이다.</p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/abc71388-079d-4559-9aa4-98a38aef44f7/image.png" alt=""></p>
<p>물론 실행할 파일들 또한 저장을 한 이후 실행해야한다.</p>
<hr>
<h3 id="6디버깅debugging"><code>6.</code>디버깅(Debugging)</h3>
<p>위와 동일하게 <strong>실행팔 파일을 선택</strong> 후 
원하는 위치에 <strong>중단점을 찍고</strong> 화살표가 아닌 <code>벌레(디버깅 실행)</code>버튼을 누르면 된다.</p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/0caaaf64-f393-4bc3-990a-c5106b69fc38/image.png" alt=""></p>
<hr>
<h2 id="정리">정리</h2>
<ul>
<li><code>Cmake</code>를 이용해 <code>Github Codespaces</code>환경에서 알고리즘 등 <code>cpp</code> 실행 환경 구현</li>
<li>디버깅도 가능하며, 여러 파일을 같이 묶어 빌드 및 실행 가능</li>
<li><code>C++</code> 공부를 하면서 각 진도마다 디렉터리를 구분하고 실행하기를 원할때도 유용</li>
</ul>
<p><code>CMake</code>는 <code>JetBrains</code>사의 <code>CLion</code>에서 사용했었는데, <code>VS Code</code>에서도 이렇게 사용이 가능하다.</p>
<p>처음 사용하게 된다면 어렵게 느껴질 수 있지만, 익숙해진다면 <code>C/C++</code> 공부와 알고리즘 문제 풀이에 있어서 가장 편리한 기능이라고 생각한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Github Codespaces로 개발하기]]></title>
            <link>https://velog.io/@profile_exe/Github-Codespaces</link>
            <guid>https://velog.io/@profile_exe/Github-Codespaces</guid>
            <pubDate>Sat, 10 Jun 2023 09:20:25 GMT</pubDate>
            <description><![CDATA[<p>이 포스트에서는 <code>Github Codespaces</code>의 개념과 생성 방법에 대해 설명한다.</p>
<p><code>GCP(Google Cloud Platform)</code>를 이용하여 무료로 3개월간 사지방에서 개발 공부를 했으나, 기간이 다 지나 전역 전까지 새로운 환경을 만들어 공부해야했다.</p>
<p>아마존의 <code>EC2</code>서 컨테이너를 만들어 하는 방법이 있었지만 더 편한 환경을 원했고, 우연히 <code>github</code>에서 무료로 가상 컨테이너를 제공하는 것을 보게되었다.</p>
<p>클라우드 환경에서 제공되는 컨테이너는 <code>Ubuntu</code> 환경이라 내가 원하는 개발 공부를 모두 할 수 있었다.</p>
<hr>
<h2 id="1-github-codespaces란"><code>1. Github Codespaces</code>란?</h2>
<p><a href="https://docs.github.com/ko/codespaces/overview">Github Codespaces 설명서</a></p>
<blockquote>
<p>A codespace is a development environment that&#39;s hosted in the cloud.</p>
</blockquote>
<p><code>Github</code>에서 제공하는 <strong>클라우드 개발환경</strong>이며 기본적으로 <code>Docker</code> 컨테이너 위에 <code>Ubuntu Linux</code> 이미지로 생성된다. <code>OS</code>는 원하는 리눅스 버전으로 설정 가능하며 <code>Windows</code>나 <code>Mac OS</code> 컨테이너는 지원하지 않는다.</p>
<p>브라우저(크롬, edge 등), <code>Visual Studio Code</code>, <code>JetBrains Gateway</code>, <code>Github CLI</code>를 통해 접근할 수 있다.</p>
<p>나는 사지방에서 개발하기때문에 브라우저를 통해 접근하였으며 <a href="vscode.dev">vscode.dev</a>처럼 웹 상의 <code>VSC</code>인 <a href="github.dev">github.dev</a>를 통해 개발이 가능했다.</p>
<h3 id="사지방에서만-사용하나">사지방에서만 사용하나?</h3>
<p>전역 하고도 유용하게 사용할 것 같다. 시간과 장소에 상관없이 인터넷만 연결된 PC라면 접속 가능하기 때문에, 전공 수업때 노트북을 들고가지 않아도 작업하던 환경에 바로 접근할 수 있는 장점이 있다.</p>
<p>Private 리파지토리를 만들고 Codespaces를 생성해서 언제 어디서나 유용하게 사용 가능하다.</p>
<hr>
<h2 id="2무료인가"><code>2.</code>무료인가?</h2>
<h3 id="일반-계정">일반 계정</h3>
<p><a href="https://docs.github.com/en/get-started/learning-about-github/githubs-products#github-free-for-personal-accounts">Github Free for personal accounts</a></p>
<blockquote>
</blockquote>
<ul>
<li><strong>120 GitHub Codespaces core hours per month</strong></li>
<li><strong>15 GB GitHub Codespaces storage per month</strong></li>
</ul>
<h3 id="pro-계정">Pro 계정</h3>
<p><a href="https://docs.github.com/en/get-started/learning-about-github/githubs-products#github-pro">Github Free for organizations</a></p>
<blockquote>
</blockquote>
<ul>
<li><strong>180 GitHub Codespaces core hours per month</strong></li>
<li><strong>20 GB GitHub Codespaces storage per month</strong></li>
</ul>
<p>달마다 무료로 사용 가능한 저장공간과 이용시간이 주어진다.
일반 계정은 <strong>120시간</strong> Pro 계정은 <strong>180시간</strong>이다.</p>
<h3 id="⚠-주의-⚠">⚠ 주의 ⚠</h3>
<blockquote>
<p>The compute usage of a codespace is the length of time for which that codespace is active multiplied by the multiplier in the pricing table for the machine type of the codespace.</p>
</blockquote>
<p><strong>실제 사용 시간과 주어진 이용 시간은 다르다</strong></p>
<table>
<thead>
<tr>
<th>Component</th>
<th>Machine type</th>
<th>Unit of measure</th>
<th>Included usage multiplier</th>
<th>Price</th>
</tr>
</thead>
<tbody><tr>
<td>Codespaces compute</td>
<td>2 core</td>
<td>1 hour</td>
<td>2</td>
<td>$0.18</td>
</tr>
<tr>
<td>Codespaces compute</td>
<td>4 core</td>
<td>1 hour</td>
<td>4</td>
<td>$0.36</td>
</tr>
<tr>
<td>Codespaces compute</td>
<td>8 core</td>
<td>1 hour</td>
<td>8</td>
<td>$0.72</td>
</tr>
<tr>
<td>Codespaces compute</td>
<td>16 core</td>
<td>1 hour</td>
<td>16</td>
<td>$1.44</td>
</tr>
<tr>
<td>Codespaces compute</td>
<td>32 core</td>
<td>1 hour</td>
<td>32</td>
<td>$2.88</td>
</tr>
<tr>
<td>Codespaces storage</td>
<td>Storage</td>
<td>1 GB-month</td>
<td>Not applicable</td>
<td>$0.07</td>
</tr>
</tbody></table>
<p>나는 <code>express</code> 템플릿을 이용했는데, <code>4-core, 8GB RAM</code> 컴퓨팅 파워를 이용하고 있다.
따라서 <code>Included usage multiplier</code>를 반영하면 </p>
<ul>
<li><strong>180 / 4 = 45</strong> 따라서 <strong>45시간</strong>만 무료로 사용할 수 있는 것이다.</li>
</ul>
<p>내가 평소에 사용하는 시간만 생각하면 대략 평일 <strong>1시간 30분</strong> / 주말 <strong>7시간</strong> 이므로
총 <strong>107시간 30분</strong> 정도를 사용하게 되는데</p>
<p>내가 비용을 더 지불할 의향이 있다고 가정하고 각 컴퓨팅 성능에 따라 계산해보겠다.
<strong>환율은 1320원</strong>으로 계산하겠다.</p>
<ul>
<li><code>2core(4GB RAM) - $ 0.18</code> : <code>107시간 30분 - 무료 90시간(180/2) = 17시간 30분</code><ul>
<li><code>17.5 * 0.18</code> : <strong>$ 3.15 - 4,158 KRW</strong> </li>
</ul>
</li>
<li><code>4core(8GB RAM) - $ 0.36</code> : <code>107시간 30분 - 무료 45시간(180/4) = 62시간 30분</code><ul>
<li><code>17.5 * 0.18</code> : <strong>$ 22.5 - 29,700 KRW</strong></li>
</ul>
</li>
</ul>
<p><code>2core</code>와 <code>4core</code>의 비용 차이가 크다는 것을 알 수 있다.
따라서 <code>Blank</code> 템플릿으로 <code>2core</code> 컴퓨팅 파워를 사용해서 개발하는 것이 좋겠다.</p>
<p>한달에 4천원은 사지방에서 편한 개발을 위해 지불하기엔 충분하다 생각했다.</p>
<hr>
<h2 id="3-pro-자격-얻기"><code>3. Pro</code> 자격 얻기</h2>
<p>아래 링크 중 맨 위 <code>Github Pro를 얻어보자</code> 포스트를 참고하여 가입을 하면 된다. 나머지 두 개의 링크는 신청 방법이랑 <code>Pro</code> 자격을 얻고 받을 수 있는 혜택인 <code>Student Developer pack</code>이다.</p>
<p><a href="https://velog.io/@jaepal/GitHub-PRO%EB%A5%BC-%EC%96%BB%EC%96%B4%EB%B3%B4%EC%9E%90">Github Pro를 얻어보자</a>
<a href="https://docs.github.com/ko/education/explore-the-benefits-of-teaching-and-learning-with-github-education/github-global-campus-for-students/apply-to-github-global-campus-as-a-student">Github global campus 신청</a>
<a href="https://education.github.com/pack">Github Student Developer Pack</a></p>
<blockquote>
<p><strong>To qualify for student benefits, you must:</strong></p>
</blockquote>
<ul>
<li>Be currently enrolled in a degree or diploma granting course of study such as a high school, secondary school, college, university, homeschool, or similar educational institution.</li>
<li><strong>Have a verifiable school-issued email address or upload documents that prove your current student status.</strong></li>
<li>Have a GitHub user account.</li>
<li>Be at least 13 years old.</li>
</ul>
<p>나는 대학교를 다니고있어 웹메일을 통해 <code>Github Global Campus</code>에 가입하여 <code>Pro</code> 자격을 얻을 수 있었다.</p>
<hr>
<h2 id="4생성방법"><code>4.</code>생성방법</h2>
<h3 id="방법-1-repository에서-생성"><code>방법 1. repository</code>에서 생성</h3>
<p>이미 만들어져 있거나 새로 만든 <code>repository</code>에서 생성이 가능하다.
리파지토리 우측 상단의 <code>&lt;&gt; Code</code>버튼을 누르고 <code>Codespaces</code> 카테고리를 선택한 후 <code>Create codespace on main</code>을 클릭하면 된다.
<img src="https://velog.velcdn.com/images/profile_exe/post/a4f4d737-547d-4a2f-a2b5-cec422ebb0a1/image.png" alt="repository에서 codespace 생성하는 방법"></p>
<h3 id="방법-2-주어진-template에서-생성"><code>방법 2. 주어진 template</code>에서 생성</h3>
<p><code>Github</code>에서 템플릿을 선택해서 <code>codespace</code>를 생성할 수 있다.
<code>Github</code> 메인화면에서 상단 네비게이션에 <code>Codespaces</code>를 들어가면 현재 내가 생성한 <code>codespace</code>들을 볼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/6287f0b0-ac57-4cfb-abd0-f0fe5a1361a3/image.png" alt="Github codepaces 메인화면"></p>
<p><code>codespaces</code> 메인화면에서 좌측 <code>Templates</code>를 들어가면 다음과 같이 생성 가능한 템플릿 목록이 나온다.</p>
<p>빈 템플릿 뿐만 아니라 <code>React</code>, <code>Express</code>, <code>Django</code>, <code>Flask</code> 등등 여러 템플릿을 지원하니 빠르고 편하게 환경이 준비된 템플릿을 이용하는 것도 좋은 방법이다.
<img src="https://velog.velcdn.com/images/profile_exe/post/75d81afa-4386-4a0b-8cf3-a10715282395/image.png" alt="choose a template"></p>
<hr>
<h2 id="5-codespaces-관리"><code>5. Codespaces</code> 관리</h2>
<h3 id="컨테이너-관리">컨테이너 관리</h3>
<p><code>Codespaces</code> 메인화면 하단을 보면 생성한 <code>codespace</code> 목록이 나온다.
우측 버튼을 누르면 <code>Active</code> 상태의 컨테이너는 접속을 끊은 후 1시간이 지나면 자동으로 멈춘다.</p>
<ul>
<li><code>Stop codespace</code>를 통해 접속이 끝난 후 바로 멈춰주면 좀 더 타이트하게 무료로 제공되는 시간을 관리할 수 있다.</li>
<li><code>Change machine type</code>을 통해 컨테이너의 성능을 변경할 수 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/377dbfee-c698-4829-bb99-e9a02c4a6ec3/image.png" alt="Codespaces 메인화면 하단"></p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/49775604-fad5-4a25-9b93-71466052de39/image.png" alt="Change codespace machine type - 컨테이너 성능 선택"></p>
<h3 id="사용량-모니터링">사용량 모니터링</h3>
<p>내가 얼마만큼 사용했는지 알고싶다면 <code>Github</code> 우측 상단 프로필에서
<code>Settings -&gt; 좌측 Billing and plans -&gt; Plans and usage</code>에서 확인이 가능하다.
<img src="https://velog.velcdn.com/images/profile_exe/post/d5c102e6-1175-4037-9a78-1f5feb50bfe0/image.png" alt="사용량 모니터링 이미지"></p>
<p>쭉 내리다보면 <code>Codespaces</code> 사용량이 있다. <code>Usage hours</code>와 <code>Storage</code>는 클릭해서 구체적인 내용 확인이 가능하다.</p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/62f0830a-4069-4d48-82f7-852d7d63c854/image.png" alt="Codespaces 사용량 확인"></p>
<p>나는 <code>Pro</code>자격이 있어서 <code>GitHub Copilot</code>도 무료로 사용가능했다!</p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/f9fce055-71d0-4895-84b6-5cf12a969cff/image.png" alt="Github Copilot 사용"></p>
<hr>
<h2 id="정리">정리</h2>
<ul>
<li><code>Github</code>의 <code>Codespaces</code>를 통해 무료로 컨테이너를 제공받아 개발을 위해 사용 가능</li>
<li>학생인 경우 <code>Pro</code>자격을 무료로 얻어 달마다 <strong>180시간</strong> 무료로 사용 가능</li>
<li>기본적으로 <code>g++</code>, <code>cmake</code>, <code>python</code> 등이 설치되어있어 알고리즘 문제풀이에 용이</li>
<li><code>Stop</code> 기능을 활용해 자원을 사용량만큼 사용하기</li>
</ul>
<h3 id="cmake로-알고리즘-및-cc-환경-구성하기"><code>CMake</code>로 알고리즘 및 <code>C/C++</code> 환경 구성하기</h3>
<p>아래 링크를 통해 <code>CMake</code> 환경 설정도 알아보면 좋다.
<a href="https://velog.io/@profile_exe/CMake-environment-configuration">알고리즘을 위한 CMake 환경 구성</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Node.js] pug 문자열에 변수 넣기]]></title>
            <link>https://velog.io/@profile_exe/Node.js-pug-%EB%AC%B8%EC%9E%90%EC%97%B4%EC%97%90-%EB%B3%80%EC%88%98-%EB%84%A3%EA%B8%B0</link>
            <guid>https://velog.io/@profile_exe/Node.js-pug-%EB%AC%B8%EC%9E%90%EC%97%B4%EC%97%90-%EB%B3%80%EC%88%98-%EB%84%A3%EA%B8%B0</guid>
            <pubDate>Sun, 30 Apr 2023 08:13:54 GMT</pubDate>
            <description><![CDATA[<p><code>node.js</code>를 공부하는데, <code>pug</code>를 템플릿 엔진으로 사용중이다. 문자열 중간에 변수를 넣고 싶어서 해결법을 찾아보고 기록하게 되었다.</p>
<hr>
<h2 id="해결-방법">해결 방법</h2>
<p><code>pug</code>는 <code>javascript</code>와 유사한 문법을 가지고 있어서 백틱을 사용해 동적인 문자열을 만들 수 있다.
백틱을 사용하는 방법은 <code>javascript</code>와 동일하다.</p>
<pre><code class="language-js">a.btn(href=`/products/${product.id}`) Details</code></pre>
<p>괄호도 사용할 수 있어 괄호 안에 <code>js</code> 문법으로 변수를 사용할 수 있다.</p>
<pre><code class="language-js">a.btn(href=(&#39;/proxy/3000/products/&#39; + product.id)) Details </code></pre>
<hr>
<p>일반적으로 <code>#{}</code>나 <code>=</code>를 사용했었는데, 백틱과 소괄호를 사용하는 방법도 잘 알아두어야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Node.js] nodemon에 의한 socket hang up 에러 해결]]></title>
            <link>https://velog.io/@profile_exe/Node.js-nodemon%EC%97%90-%EC%9D%98%ED%95%9C-socket-hang-up-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@profile_exe/Node.js-nodemon%EC%97%90-%EC%9D%98%ED%95%9C-socket-hang-up-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Sun, 09 Apr 2023 08:20:06 GMT</pubDate>
            <description><![CDATA[<p><code>Node.js</code> 강의를 듣는 도중 <code>socket hang up</code> 오류가 났다. <code>nodemon</code>의 <code>ignore</code> 옵션을 통해 해결한 과정을 기록했다.</p>
<hr>
<h2 id="🚨-문제-발생">🚨 문제 발생</h2>
<p><code>admin/add-product</code>에서 <code>POST</code>로 넘긴 데이터를 <code>fs</code> 모듈을 이용해 <code>data/products.json</code>에 작성 후 <code>/</code> 경로로 <code>redirect</code>하는 과정에서 발생했다.</p>
<p>디버거를 돌려보니 파일에 정상적으로 데이터가 기록되어 <code>fs</code>모듈을 사용한 코드의 문제는 아니었다. <code>/</code>로 <code>redirect</code>순간 같은 코드가 반복되며 클라이언트에 응답을 주지 못하고 있었다. 그래서 <code>timeout</code>으로 인해 <code>socket hang up</code>이 발생한 것이었다.</p>
<hr>
<h2 id="🔍-원인-찾기">🔍 원인 찾기</h2>
<p><code>console.log</code>로 로깅을 하며 터미널을 보고 있었는데, 원인이 될만한 점을 찾게 되었다. </p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/2c414884-32c1-4987-b785-013dda35fb99/image.png" alt="터미널 로그"></p>
<pre><code class="language-bash">[nodemon] restarting due to changes...</code></pre>
<p><code>fs</code>모듈을 통해 <code>data/products.json</code>파일을 변경하는 경우 <code>nodemon</code>에서 이를 인식하여 서버를 재시작 하는 것이었다.</p>
<hr>
<h2 id="✔️-해결">✔️ 해결</h2>
<h3 id="해결방안-1-shell에서-옵션-넣기">해결방안 <code>1) shell</code>에서 옵션 넣기</h3>
<p>위 로그를 통해 <code>nodemon</code>이 <strong>로컬 파일 변경을 인식해 서버를 재시작</strong> 하는 것이 원인이라 생각되어 <code>nodemon restart due to change socket hang up</code>이라고 구글에 검색을 해보았고 <code>stackoverflow</code>에서 해당 답변을 찾았다.</p>
<blockquote>
</blockquote>
<p><a href="https://stackoverflow.com/a/63286432">답변 링크</a></p>
<blockquote>
</blockquote>
<p>Are you using nodemon, or some other file-watcher? In my case, I was generating some local files, uploading them, then sending the URL back to my user. Unfortunately nodemon would see the &quot;changes&quot; to the project, and trigger a restart before a response was sent. I ignored the build directories from my file-watcher and solved this issue.</p>
<blockquote>
</blockquote>
<p>Here is the Nodemon readme on ignoring files: <a href="https://github.com/remy/nodemon#ignoring-files">https://github.com/remy/nodemon#ignoring-files</a></p>
<p>답변 하단에 나온 링크로 들어가 <code>nodemon</code>에서 파일을 <code>ignoring</code>하는 방법을 찾았다.</p>
<pre><code class="language-bash">nodemon --ignore 경로</code></pre>
<hr>
<h3 id="해결방안-2-packagejson에-설정하기">해결방안 <code>2) package.json</code>에 설정하기</h3>
<p>나는 <code>package.json</code>에서 설정해두고 <code>npm start</code>로 편하게 실행하고 싶어서 해당 링크 상단에서 찾은 방법을 사용하려고 한다.</p>
<p><a href="https://github.com/remy/nodemon#packagejson">nodemon의 ignore를 package.json에서 설정하는 방법</a>
<code>package.json</code>에 <code>nodemonConfig</code>를 추가하여 <code>nodemon</code>에 관한 세부 설정이 가능했다.</p>
<p>아래는 해당 내용을 참고한 <code>package.json</code>의 일부다. 하단 <code>...</code>은 생략</p>
<pre><code class="language-json">{
  &quot;name&quot;: &quot;learn-node&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;Complete Node.js Guide&quot;,
  &quot;main&quot;: &quot;app.js&quot;,
  &quot;nodemonConfig&quot;: { // 추가한 부분! json은 주석이 불가능하다. 여기선 편의를 위해 추가했다.
    &quot;ignore&quot;: [
      &quot;data/*&quot;
    ]
  },
  &quot;scripts&quot;: {
    &quot;test&quot;: &quot;echo \&quot;Error: no test specified\&quot; &amp;&amp; exit 1&quot;,
    &quot;start&quot;: &quot;nodemon app.js&quot;
  },
  ... </code></pre>
<hr>
<h2 id="💡-정리">💡 정리</h2>
<ul>
<li><code>nodemon</code>은 로컬 파일이 변경되면 이를 인지하고 서버를 재시작. 이때 <code>redirect</code>를 한다면 <code>socket hang up</code> 오류 발생</li>
<li><code>ignore</code> 옵션을 통해 불필요한 재시작 방지</li>
</ul>
<p>이제 터미널에서 <code>npm start</code>를 통해 <code>nodemon</code>을 실행해도 <code>socket hang up</code>오류가 발생하지 않는다. 👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Expo Go 다크모드 인식 불가]]></title>
            <link>https://velog.io/@profile_exe/Expo-Go-%EB%8B%A4%ED%81%AC%EB%AA%A8%EB%93%9C-%EC%9D%B8%EC%8B%9D-%EB%B6%88%EA%B0%80</link>
            <guid>https://velog.io/@profile_exe/Expo-Go-%EB%8B%A4%ED%81%AC%EB%AA%A8%EB%93%9C-%EC%9D%B8%EC%8B%9D-%EB%B6%88%EA%B0%80</guid>
            <pubDate>Sun, 19 Feb 2023 04:46:52 GMT</pubDate>
            <description><![CDATA[<p><code>react-native</code>강의를 듣는 도중에, 내 폰의 다크모드가 적용되어 있음에도 불구하고, <code>Expo Go</code>를 통해 열려있는 개발중인 앱에는 다크모드 인식을 못하는 것을 보았다.</p>
<p><code>app.json</code>에서 따로 설정이 필요하다는 것을 알았고, 이 문제를 해결한 내용을 기록하려 한다.</p>
<hr>
<h2 id="appjson-설정"><code>app.json</code> 설정</h2>
<blockquote>
</blockquote>
<p><a href="https://docs.expo.dev/guides/color-schemes/">Expo docs - 다크모드 관련 문서</a></p>
<blockquote>
</blockquote>
<p><strong>Configuration</strong>
Both managed and bare projects for Android and iOS require additional configuration to support switching between light and dark modes. No additional configuration is required for the web.</p>
<p>라이트모드와 다크모드 간의 변경을 지원하기 위해서는 추가적인 설정이 필요하다고 한다.</p>
<p><code>app.json</code>에 다음 옵션을 추가한다. <code>...</code>는 생략</p>
<pre><code class="language-json">{
  &quot;expo&quot;: {
    ...
    &quot;userInterfaceStyle&quot;: &quot;automatic&quot;  // 이 옵션을 넣어야 한다.
  },
  ...
}</code></pre>
<hr>
<h2 id="expo-system-ui-설치"><code>expo-system-ui</code> 설치</h2>
<blockquote>
<p>In EAS Build and development builds you&#39;ll need to install the native module <strong>expo-system-ui</strong> otherwise the userInterfaceStyle property will be ignored.</p>
</blockquote>
<p><code>userInterfaceStyle</code> 속성이 무시될 수 있으므로 <code>expo-system-ui</code>를 설치해야한다.</p>
<pre><code class="language-bash">npm install expo-system-ui</code></pre>
<hr>
<h2 id="다크모드-인식">다크모드 인식</h2>
<p>이제 메트로서버를 재시작하면 휴대폰 설정에서 다크모드 적용 시 어플리케이션에서도 인식하는 것을 확인할 수 있다.</p>
<p>다크모드를 인식하는 방법은 두 가지가 있다.</p>
<ul>
<li><code>Appearance</code> 모듈</li>
<li><code>useColorScheme</code> 훅</li>
</ul>
<p><a href="https://docs.expo.dev/guides/color-schemes/#detecting-the-color-scheme">Expo docs - detecting the color scheme</a>
문서에 따르면 <code>useColorScheme</code> 훅을 사용하되 필요시 <code>Appearance</code> 모듈을 사용하면 될 것 같다.</p>
<pre><code class="language-jsx">import { Appearance, useColorScheme } from &#39;react-native&#39;;

// 방법 1: Appearance 모듈 사용
if (Appearance.getColorScheme() === &#39;dark&#39;) { }

// 방법 2: useColorScheme 훅 사용
if (useColorScheme() === &#39;dark&#39;) { }</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[code-server 폰트 적용]]></title>
            <link>https://velog.io/@profile_exe/code-server-%ED%8F%B0%ED%8A%B8-%EC%A0%81%EC%9A%A9</link>
            <guid>https://velog.io/@profile_exe/code-server-%ED%8F%B0%ED%8A%B8-%EC%A0%81%EC%9A%A9</guid>
            <pubDate>Sat, 18 Feb 2023 05:31:20 GMT</pubDate>
            <description><![CDATA[<p>사지방에서 <code>code-server</code>를 이용해 코딩을 하고 있었으나, 폰트가 마음에 안들어서 바꾸고 싶었다. <code>GCP</code>의 인스턴스에서 <code>ssh</code>로 접속 후 폰트를 설치해도, <code>docker container</code>에 폰트를 설치해도 적용이 되지 않았다.</p>
<p> 알고보니 <code>code-server</code>가 설치된 디렉터리에서 <code>html</code>, <code>css</code> 파일을 변경해야 사용 가능하다는 것을 알게 되었다. 하지만, 여러 블로그를 둘러보며 해당 파일들을 변경해도 바뀌지 않아서 너무 힘들었다...</p>
<p> 폰트 적용을 위해 수많은 삽질을 겪고 나서 내가 적용한 방식을 기록하려 한다.</p>
<hr>
<h2 id="1-폰트-설치"><code>1.</code> 폰트 설치</h2>
<p>  나는 웹 폰트(<code>woff</code>, <code>woff2</code>)를 적용하는 형태로 구현했다.
  자신이 원하는 폰트를 위에 나온 확장자로 선택해 다운로드 받는다.</p>
<p>  사지방에서 윈도우에 다운로드 받아도 <code>Drag &amp; Drop</code>으로 <code>code-server</code>에 업로드할 수 있으니 굳이 터미널에서 명령어를 통해 다운로드 받고 파일을 이동시킬 필요가 없다.</p>
<p>  내가 선호하는 폰트는 <code>Fira Code</code>와 <code>JetBrains Mono</code>다.</p>
<p>  <a href="https://github.com/tonsky/FiraCode/releases/download/6.2/Fira_Code_v6.2.zip">Google Fira Code</a>
  <a href="https://www.jetbrains.com/lp/mono/">JetBrains Mono</a></p>
<hr>
<h2 id="2-파일-업로드-및-수정"><code>2.</code> 파일 업로드 및 수정</h2>
<p> 가장 중요한 부분이다. 이 파트에서 삽질을 가장 많이 했으며, 이전에 썼던 글에 있는 맨 처음 <code>code-server</code>에 접속한 상태에서 직접 적용해봐도 이상이 없으므로 천천히 따라오면 되겠다.</p>
<hr>
<h3 id="2-1-폴더-열기"><code>2-1.</code> 폴더 열기</h3>
<p><code>code-server</code>에 접속 후 <code>/usr/lib/code-server/src/browser/pages</code> 해당 경로로 폴더를 열어준다.</p>
<p> <img src="https://velog.velcdn.com/images/profile_exe/post/8e0666b7-dedb-4dd7-8b84-7f727fb83284/image.png" alt="파일 - 폴더 열기"></p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/ca541f0b-e335-4f98-b3fe-6e5eb6ac1c2c/image.png" alt="경로 입력"></p>
<hr>
<h3 id="2-2-fonts-폴더-생성-및-파일-업로드drag--drop"><code>2-2. fonts</code> 폴더 생성 및 파일 업로드(<code>Drag &amp; Drop</code>)</h3>
<p>폴더를 생성하기 전 우선 <strong>파일 및 폴더 생성/삭제 권한</strong>을 얻어야한다.</p>
<pre><code class="language-bash">&gt; sudo chmod ugo+rwx -R /usr/lib/code-server/src/browser/pages</code></pre>
<p>만약 이래도 생성 및 삭제가 불가능하다면 <code>browser</code>, <code>src</code> 이렇게 점점 더 높은 폴더에서 권한을 얻어보도록 하자.</p>
<p>편하게 <code>sudo chmod ugo+rwx -R /usr/lib/code-server</code> 이래도 되지만 시간이 좀 걸리고 불필요하게 파일 권한을 더 얻어서 나중에 실수로 파일을 지우게 되는 경우가 생길 수 있으니 주의가 필요하다.</p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/d87f4676-59ab-4def-8702-375b097d1899/image.png" alt="fonts 파일 생성"></p>
<p>이제 <code>fonts</code> 폴더를 생성하고 해당 폴더에 다운로드 받았던 폰트 파일들을 넣는다. 다른 사용하고 싶은 폰트들도 이와 같은 방법으로 적용하면 되겠다.</p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/9c1446e6-21f8-4a49-a252-afade3ea9d0a/image.png" alt="복사 과정"></p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/e73d791d-b64e-4a44-a6e8-f518eca0f0ca/image.png" alt="복사 완료"></p>
<p>갑자기 테마 색상이 어둡게 바뀐 이유는 <code>JetBrains Mono</code> 폰트를 적용하고 나서 새로 업로드 했기 때문이다.</p>
<hr>
<h3 id="2-3-fontscss-생성-및-수정"><code>2-3. fonts.css</code> 생성 및 수정</h3>
<p><code>fonts.css</code> 파일을 생성 후 다음 내용을 작성한다.</p>
<p>주의할 점은 <code>fonts.css</code>는 <strong><code>fonts</code> 폴더 내부에 있는 것이 아니다</strong>
<code>fonts</code> 폴더와 <code>fonts.css</code>파일은 <code>pages</code>라는 같은 폴더 내에 위치한다.</p>
<pre><code class="language-css">@font-face {
  font-family: &#39;JetBrainsMono&#39;;
  src: url(&#39;./fonts/JetBrainsMono-Light.woff2&#39;) format(&#39;woff2&#39;);
  font-weight: 300;
  font-style: normal;
}

@font-face {
  font-family: &#39;JetBrainsMono&#39;;
  src: url(&#39;./fonts/JetBrainsMono-Regular.woff2&#39;) format(&#39;woff2&#39;);
  font-weight: 400;
  font-style: normal;
}

@font-face {
  font-family: &#39;JetBrainsMono&#39;;
  src: url(&#39;./fonts/JetBrainsMono-Medium.woff2&#39;) format(&#39;woff2&#39;);
  font-weight: 500;
  font-style: normal;
}

@font-face {
  font-family: &#39;JetBrainsMono&#39;;
  src: url(&#39;./fonts/JetBrainsMono-SemiBold.woff2&#39;) format(&#39;woff2&#39;);
  font-weight: 600;
  font-style: normal;
}

@font-face {
  font-family: &#39;JetBrainsMono&#39;;
  src: url(&#39;./fonts/JetBrainsMono-Bold.woff2&#39;) format(&#39;woff2&#39;);
  font-weight: 700;
  font-style: normal;
}</code></pre>
<p>  <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide#using_a_variable_font_font-face_changes">@font-face document</a></p>
<p><code>@font-face</code>에 대해 알아보려면 위 링크를 참고해도 좋다.</p>
<hr>
<h3 id="2-4-loginhtml-수정"><code>2-4. login.html</code> 수정</h3>
<p>편집기 좌측 파일 탐색기를 보면 <code>login.html</code> 파일이 있을 것이다. </p>
<p>파일을 열고 <code>&lt;head&gt;</code> 태그 맨 마지막에 아래 코드를 입력한다.</p>
<pre><code class="language-html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;{{CS_STATIC_BASE}}/src/browser/pages/fonts.css&quot;&gt;</code></pre>
<p>이러면 <code>login.html</code>의 모습은 다음과 같다. <code>...</code>은 생략된 내용들이다.</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    ...
    &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;{{CS_STATIC_BASE}}/src/browser/pages/fonts.css&quot;&gt;
  &lt;/head&gt;
  &lt;body&gt;
    ...
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
<hr>
<h3 id="2-5-vscode-설정의-font-family-수정"><code>2-5. vscode</code> 설정의 <code>Font Family</code> 수정</h3>
<p>좌측 하단 설정 아이콘에서 설정을 들어가거나 <code>ctrl + ,</code>로 설정에 들어갈 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/c2b0767a-b366-490f-be27-710cb4cf454d/image.png" alt="설정 들어가는 과정"></p>
<p>이후 <code>Font Family</code> 부분 맨 앞에 <code>JetBrainsMono</code>등 <strong><code>fonts.css</code>에서 <code>font-family</code>부분에 작성한 이름 그대로</strong> 추가한다. 끝에 <code>,</code>를 까먹지 않도록 하자</p>
<p>만약 <code>fonts.css</code>의 <code>font-family</code>에 이름을 띄어쓰기로 지정했다면 <code>ex) Fira Code</code> 작은 따옴표로 감싸주면 적용이 된다.</p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/094d405b-9b2f-49ed-a541-8f5563349bd8/image.png" alt="Fira Code 띄어쓰기 있는 모습"></p>
<p>이렇게 이름 사이에 공백이 있으면 아래처럼 <code>&#39;Fira Code&#39;</code> 따옴표로 감싸준다.</p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/c2ca2828-60ff-4b41-8ecf-8097e32b4d8e/image.png" alt="Font Family 수정"></p>
<hr>
<h3 id="2-6-code-server-재시작"><code>2-6. code-server</code> 재시작</h3>
<p><code>ssh</code>에서 <code>docker restart code-server</code>를 입력한다.</p>
<pre><code class="language-bash">&gt; docker restart code-server</code></pre>
<p>만약 <code>permission denied</code>라고 뜨며 <code>docker</code> 명령을 실행할 수 없다면 다음 명령어를 입력 후 <code>ssh</code>창을 닫고 다시 킨 다음 위의 <code>restart</code>명령을 실행해보자.</p>
<pre><code class="language-bash">&gt; sudo usermod -aG docker $USER</code></pre>
<hr>
<h2 id="필요시-editorlineheight-조정">필요시) <code>editor.lineHeight</code> 조정</h2>
<p>내가 사용하는 <code>JetBrains Mono</code> 폰트는 줄 간격을 더 두어야 보기 편할 것 같았다. 따라서 <code>설정</code>에서 줄 간격을 조정하였다.</p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/2594b413-8024-4b91-84fc-907965b2035a/image.png" alt="Editor: Line Height 기본값"></p>
<p>기본값은 <code>0</code>으로 <code>JetBrains Mono</code> 기준 <code>19</code>로 적용한 것과 동일했다.</p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/5ce8ab36-b5c1-4308-bdc8-c81b642c8639/image.png" alt="기본값"></p>
<p>나는 값을 <code>20</code>으로 설정했는데, 이러니 보기 훨 편했다.</p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/18ab983a-1055-419d-a55e-3affeaebed82/image.png" alt="값 20으로 설정"></p>
<hr>
<p>사지방에서 <code>GCP</code>를 통해 편하게 개발하는 것이 충분히 만족스럽지만, 사회에서 <code>VSCode</code>를 커스텀하던 욕심이 이어져 이런 삽질까지 오게 되었다. 결과가 만족스러워 시간낭비같지는 않다 ㅋㅋㅋ</p>
<p>최대한 세세하게 적어두고 나중에 필요할 때 다시 적용할 수 있도록 했다. 오늘도 삽질 끝.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[GCP와 code-server로 개발환경 세팅]]></title>
            <link>https://velog.io/@profile_exe/GCP%EC%99%80-code-server%EB%A1%9C-%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD-%EC%84%B8%ED%8C%85</link>
            <guid>https://velog.io/@profile_exe/GCP%EC%99%80-code-server%EB%A1%9C-%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD-%EC%84%B8%ED%8C%85</guid>
            <pubDate>Sun, 12 Feb 2023 07:19:53 GMT</pubDate>
            <description><![CDATA[<p>군대에서는 사이버지식정보방(사지방)에서 코딩 공부를 하게되는데, 터미널을 열 수가 없거나 권한이 제한되어있어 개발을 할 수가 없었다.</p>
<p> <code>React Native</code>를 공부하고 있는데, <a href="snack.expo.dev">expo snack</a>이라는 좋은 사이트가 있지만, <strong>네이티브 파일</strong>들에 접근할 수 없다는 한계가 불만이었다.</p>
<p> 이때 방법으로는 <code>goormIDE</code>같은 온라인 통합개발환경을 사용하는 것인데, <code>RN 컨테이너</code> 환경이 마음에 안들고(버전 등등) <code>blank</code>에서 직접 환경을 세팅하기에는 많은 삽질과 어려움이 필요했다.</p>
<p> 그래서 다른 방법이 없나 구글링을 해보니 <code>GCP(Google Cloud Platform)</code>에서 가상 머신을 만들고 이 위에 <code>code-server</code>를 설치해 <code>VSCode</code>를 통해 개발할 수 있는 방법이 있었다.</p>
<p> <a href="https://velog.io/@2wndrhs?tag=code-server">2wndrhs(이중곤)님의  블로그</a>에서 <code>code-server</code>를 여는 것까진 성공했으나, 이후 내가 겪은 문제들과 이를 어떻게 해결했는지 기록해두려 한다.</p>
<hr>
<h2 id="개발-환경-세팅">개발 환경 세팅</h2>
<p><a href="https://velog.io/@2wndrhs?tag=code-server">2wndrhs(이중곤)님의  블로그</a></p>
<p>위 블로그에서 <code>code-server</code> 구동까지 마친 후 읽으면 되겠다.</p>
<hr>
<h2 id="문제-1-사용자-권한">문제 <code>1.</code> 사용자 권한</h2>
<p> <code>code-server</code>를 통해 <code>VSCode</code>환경에서 파일 및 폴더를 생성, 삭제하려 했으나 <strong>권한이 부족</strong>하여 편집이 불가능했다.</p>
<h3 id="사용자-확인">사용자 확인</h3>
<pre><code class="language-bash">&gt; whoami
coder</code></pre>
<p><code>code-server</code>에서 사용되는 사용자는 coder다.</p>
<h3 id="소유권-부여">소유권 부여</h3>
<pre><code class="language-bash">&gt; sudo chown -R coder *</code></pre>
<p>모든 파일에 대한 소유권을 얻었다.</p>
<h3 id="파일-및-폴더-권한-부여">파일 및 폴더 권한 부여</h3>
<pre><code class="language-bash">&gt; sudo chmod ugo+rwx -R /home/coder/project</code></pre>
<p>이 명령어를 사용하면 소유권 부여는 안해도 되긴 한다. <strong>위험한 명령어니 꼭 필요한 부분까지만 적용</strong>하도록 하자.
나는 <code>project</code> 폴더 내에서만 작업을 하기때문에 <code>/home/coder/project</code>까지만 적용했다.
또한 <code>-R</code> 옵션을 통해 <code>project</code> 폴더 내의 모든 파일 및 폴더에 대해서도 재귀적으로 권한을 수정하였다.</p>
<hr>
<h2 id="문제-2-카메라-차단으로-인한-qr코드-스캔-불가">문제 <code>2.</code> 카메라 차단으로 인한 <code>QR</code>코드 스캔 불가</h2>
<p>영내로 들어오게되면 카메라는 특정 어플리케이션을 통해 기능이 작동하지 않도록 차단된다.
이는 <code>expo-cli</code>로 개발을 하면서도 <code>Expo Go</code>를 통한 <code>QR코드</code> 스캔이 불가능해 어플이 실제 어떻게 동작하는지 확인이 불가능하다.</p>
<h3 id="expo-cli의-터널링tunneling-기능-사용"><code>expo-cli</code>의 터널링(<code>tunneling</code>) 기능 사용</h3>
<blockquote>
</blockquote>
<p><a href="https://docs.expo.dev/workflow/expo-cli/#tunneling">Expo Cli tunneling</a>
Sometimes it&#39;s easier to connect to a dev server over a proxy URL that&#39;s accessible from any device with internet access, this is referred to as tunneling.</p>
<p>터널링을 통해 자신의 프로젝트를 인터넷이 연결된 외부 기기에서 접속할 수 있도록 할 수 있다.
사지방은 내가 사용하는 스마트폰과 같은 네트워크에 연결되어있지 않으므로 가장 적절한 방법이라고 볼 수 있다.</p>
<p> 해당 링크의 방법으로 <code>ngrok</code> 설치 후 <code>--tunnel</code>  옵션을 주면</p>
<pre><code class="language-bash">&gt; npm i -g @expo/ngrok
&gt; expo start --tunnel</code></pre>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/1a593f41-2660-4118-a54e-78333c7a1335/image.png" alt="expo-cli QR코드 및 URL"></p>
<pre><code class="language-bash">&gt; Metro waiting on exp://(주소).exp.direct:80</code></pre>
<p>해당 주소를 <code>Expo Go</code>의 <code>Enter URL manually</code> 란에다 적고 <code>Connect</code>를 누르면 연결이 된다.
<img src="https://velog.velcdn.com/images/profile_exe/post/bf96d992-e2f8-449a-9199-fa0812e61fc7/image.png" alt="Expo Go 화면"></p>
<h3 id="expo-cli-로그인"><code>expo-cli</code> 로그인</h3>
<pre><code class="language-bash">&gt; expo login -u 아이디 -p 비밀번호</code></pre>
<p><code>expo-cli</code>에 로그인을 해 두고 <code>Expo Go</code>에도 동일한 계정으로 로그인이 되어있다면 <code>--tunnel</code>로 프로젝트를 실행할 때 <code>URL</code>을 입력하지 않아도 <code>Expo Go</code> 상단에 프로젝트가 뜨게 된다.</p>
<p>처음에는 뜨지 않았는데, 한번 <code>URL</code>로 연결해두면 같은 계정인 경우 <code>Expo Go</code>에서 바로 프로젝트를 확인할 수 있도록 기능을 제공하는 것 같다.</p>
<p><img src="https://velog.velcdn.com/images/profile_exe/post/9a4debcc-b58f-4b05-a7ee-95cf4a2c474e/image.png" alt="프로젝트가 뜬 모습"></p>
<blockquote>
<p>RNproject on 0959...</p>
</blockquote>
<p>이 부분에서 on 뒤의 숫자 및 영어로 된 코드는
내 <code>docker</code> 에서 돌아가는 <code>code-server</code>의 <code>container id</code>다.</p>
<hr>
<h2 id="정리">정리</h2>
<ul>
<li><code>권한</code>: <strong>소유권</strong> 및 <strong>파일 및 폴더 권한</strong>을 수정하여 해결</li>
<li><code>QR 코드 스캔 불가</code>: <code>expo start --tunnel</code>로 터널링 및 로그인을 통해 <code>Expo Go</code>에서 <code>URL</code>로 접속할 수 있게 하여 해결</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[운영체제] 프로세스 큐, Swap]]></title>
            <link>https://velog.io/@profile_exe/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-Queue-Swap</link>
            <guid>https://velog.io/@profile_exe/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-Queue-Swap</guid>
            <pubDate>Tue, 21 Sep 2021 15:35:36 GMT</pubDate>
            <description><![CDATA[<p>운영체제는 <strong>프로세스 제어 블록(PCB)</strong>을 이용해서 프로세스들을 관리한다.</p>
<p><strong>PCB</strong>는 메모리에 존재하며 상태에 따라 <strong>Ready 큐</strong> 또는 <strong>Event 큐(대기큐)</strong>에서 관리된다.</p>
<p>이번 포스트에서는 <strong>프로세스 큐</strong>와 <strong>Swap</strong>에 대해 알아본다.</p>
<hr>
<h2 id="📖-multiple-blocked-queues">📖 <code>Multiple Blocked Queues</code></h2>
<p><img src="https://images.velog.io/images/profile_exe/post/18bdbbf4-d50a-4971-b615-f1692832f773/image.png" alt="Multiple Blocked Queues"></p>
<blockquote>
<p>각 <strong>큐(Queue)</strong>에는 <strong>PCB</strong>가 들어가 있으며, 큐는 <strong>Linked List</strong>로 구현
즉, <strong>PCB</strong>에 <strong>Linked List</strong>를 구현하기 위한 <strong>포인터 멤버(<code>Next</code>, <code>Prev</code>)</strong>가 존재</p>
</blockquote>
<h3 id="📌-ready-queue">📌 <code>Ready Queue</code></h3>
<blockquote>
</blockquote>
<h4 id="ready-상태에-있는-프로세스만-모아둔-큐"><code>Ready</code> 상태에 있는 프로세스만 모아둔 큐</h4>
<ul>
<li><strong>Scheduler</strong>가 <strong>Ready 큐</strong>에서 <strong>최우선순위</strong> 프로세스를 선택</li>
<li>선택된 프로세스를 <strong>Dispatch</strong>해서 <strong>processor(CPU)</strong>가 실행</li>
</ul>
<h3 id="📌-blockedwating-or-event-queue---대기큐">📌 <code>Blocked(Wating or Event) Queue</code> - 대기큐</h3>
<blockquote>
</blockquote>
<h4 id="blocked상태에-있는-프로세스만-모아둔-큐"><code>Blocked</code>상태에 있는 프로세스만 모아둔 큐</h4>
<ul>
<li>각각의 <strong><code>Device driver</code></strong>에 존재하는 큐</li>
<li><code>키보드</code>, <code>디스크</code>, <code>네트워크</code>, <code>타이머</code>, <code>세마포어</code> 등에 <strong>대기큐</strong>가 존재</li>
<li><strong><code>I/O 함수</code></strong> 등을 호출하면 <strong>Blocked</strong> 상태가 되며 <strong>PCB</strong>를 해당 장치의 <strong>대기큐로 이동</strong>시킴</li>
<li><strong>데이터가 들어오면</strong> <code>Device driver내의 함수</code>는 대기큐에서 해당 PCB 찾고 <strong>대기큐에서 제거</strong> 후 <code>Ready Queue</code>에 넘겨줌 -&gt; 이때 해당 프로세스의 상태를 <strong>Ready</strong> 상태로 변경</li>
</ul>
<h3 id="💡-데이터를-기다리는-프로세스-판별-방법">💡 데이터를 기다리는 프로세스 판별 방법</h3>
<ul>
<li><strong>포트 번호</strong>로 구별한다.</li>
<li>즉. <strong>PCB</strong>는 <strong>포트 번호</strong>로 구별한다.</li>
</ul>
<hr>
<h2 id="📖-임시-중단된-프로세스들">📖 임시 중단된 프로세스들</h2>
<p><img src="https://images.velog.io/images/profile_exe/post/94bbecea-9bfa-4285-8506-dbb8b0a0fc93/image.png" alt="Suspend 프로세스 상태 추가"></p>
<h3 id="💡-swap-out">💡 <code>Swap out</code></h3>
<ul>
<li><strong>장시간 대기</strong>하는 프로세스의 경우</li>
<li><strong>프로세스</strong>를 <strong>메모리</strong>에서 <strong>디스크</strong>의 <strong>swap</strong> 영역으로 이동</li>
<li><strong>Blocked</strong> -&gt; <strong>Suspend</strong>로 상태 변경</li>
</ul>
<h3 id="💡-swap-in">💡 <code>Swap in</code></h3>
<ul>
<li><strong>대기</strong>상태에서 <strong>Interrupt</strong>가 발생한 경우</li>
<li><strong>프로세스</strong>를 <strong>디스크</strong>의 <strong>swap</strong> 영역에서 <strong>메모리</strong>로 이동</li>
<li><strong>Suspend</strong> -&gt; <strong>Ready</strong>로 상태 변경</li>
</ul>
<blockquote>
</blockquote>
<h3 id="✍️-프로세서cpu는-입출력보다-빠르다">✍️ 프로세서(CPU)는 입출력보다 빠르다.</h3>
<ul>
<li>메모리에 있는 <strong>모든 process가 I/O를 대기</strong>하며 <strong>CPU</strong>는 <strong>idle</strong> 상태가 될 수 있음</li>
</ul>
<blockquote>
</blockquote>
<h3 id="✍️-더-많은-메모리-확보를-위해-swap이-필요하다">✍️ 더 많은 메모리 확보를 위해 <code>Swap</code>이 필요하다.</h3>
<ul>
<li><strong>장시간 대기상태의 프로세스</strong>를 <strong>디스크</strong>의 별도의 <strong>swap 영역</strong>으로 빼냄</li>
<li><strong>메모리</strong>에는 다른 프로세스를 넣어 실행함</li>
<li><strong><code>swap out</code></strong>된 프로세스는 <strong>Blocked -&gt; Suspend</strong> 상태로 변경</li>
<li><strong><code>swap in</code></strong>된 프로세스는 <strong>Suspend -&gt; Ready</strong> 상태로 변경</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[운영체제] Process State]]></title>
            <link>https://velog.io/@profile_exe/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-Process-State</link>
            <guid>https://velog.io/@profile_exe/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-Process-State</guid>
            <pubDate>Mon, 13 Sep 2021 10:08:40 GMT</pubDate>
            <description><![CDATA[<p>이번 포스트에서는 <strong>프로세스의 상태</strong>에 대해 알아보도록 하자</p>
<hr>
<h2 id="📖-프로세스-추적---trace-of-process">📖 프로세스 추적 - <code>Trace of Process</code></h2>
<blockquote>
</blockquote>
<p>프로세스에 대해 실행되는 <strong>명령 순서</strong></p>
<h3 id="💡-dispatcher">💡 <code>Dispatcher</code></h3>
<blockquote>
<p><strong>프로세서(CPU)</strong>를 한 프로세스에서 다른 프로세스로 <strong>전환</strong>하는 역할</p>
</blockquote>
<h3 id="💡-scheduler-kernel-내의-함수-이름">💡 <code>Scheduler</code> (<code>Kernel</code> 내의 함수 이름)</h3>
<blockquote>
</blockquote>
<p><strong>실행 가능한 Process들(Ready state)</strong>들 중 <strong>우선순위가 가장 높은</strong> 프로세스 선택
해당 프로세스에게 <strong>프로세서(CPU)</strong>를 <strong>넘겨주는(dispatch)</strong> 역할</p>
<h3 id="🚨-주의">🚨 주의</h3>
<p><strong>프로세스 스위치</strong> 사이에 <strong>반드시 스케줄러 함수 호출</strong>함</p>
<hr>
<h2 id="📖-프로세스-5상태---a-five-state-model">📖 프로세스 5상태 - <code>A Five-State Model</code></h2>
<blockquote>
<p>각 프로세스는 실행 도중 <strong>항상 다음 상태들 중 하나의 상태로 존재</strong></p>
</blockquote>
<h3 id="👶-new">👶 <code>New</code></h3>
<blockquote>
<p>프로그램의 <strong>실행을 준비</strong>하는 단계</p>
</blockquote>
<ul>
<li><strong>PCB 생성</strong></li>
<li><strong>Load</strong> 과정으로 <strong>Ready</strong> 상태가 됨</li>
</ul>
<h3 id="✔️-ready">✔️ <code>Ready</code></h3>
<blockquote>
<p><strong>프로세서</strong>만 주어지면 <strong>언제든 실행될 수 있는 상태</strong>
즉, <strong>실행 가능한 준비 상태</strong></p>
</blockquote>
<h3 id="🔄-running">🔄 <code>Running</code></h3>
<blockquote>
<p><strong>프로세서</strong>에 의해 <strong>현재 실행 중</strong>인 상태</p>
</blockquote>
<h3 id="🛑-blocked">🛑 <code>Blocked</code></h3>
<blockquote>
<p><strong>I/O 장치</strong>에서 <strong>대기(waiting)</strong>상태</p>
</blockquote>
<ul>
<li><code>키보드</code>, <code>파일</code>, <code>네트워크</code> 등에서 <strong>데이터 대기</strong></li>
<li><strong>프로세서</strong>가 주어져도 <strong>실행할 수 없는 상태</strong></li>
<li><strong>데이터가 도착</strong>하면 <strong>Ready</strong> 상태가 됨</li>
</ul>
<h3 id="❌-exit">❌ <code>Exit</code></h3>
<blockquote>
<p>프로그램 <strong>실행이 완료</strong>되어 <strong>폐기</strong>단계</p>
</blockquote>
<ul>
<li><strong>메모리</strong>에서 <strong>제거</strong></li>
<li><strong>PCB 소멸</strong></li>
</ul>
<hr>
<h2 id="📖-상태-변화-이벤트">📖 상태 변화 이벤트</h2>
<h3 id="💡-이벤트-대기---wait-event">💡 이벤트 대기 - <code>Wait Event</code></h3>
<blockquote>
<p><strong>Device driver</strong>에서 <strong>대기</strong> -&gt; <strong>Block State</strong>로 상태 변화</p>
</blockquote>
<h3 id="💡-이벤트-발생---event-occurs">💡 이벤트 발생 - <code>Event Occurs</code></h3>
<blockquote>
<p><strong>Device driver 실행</strong> -&gt; <strong>Ready State</strong>로 상태 변화</p>
</blockquote>
<h3 id="📌-keyboard-mouse---keyboard-mouse-interrupt">📌 <code>Keyboard</code>, <code>Mouse</code> - <code>Keyboard, Mouse interrupt</code></h3>
<blockquote>
</blockquote>
<h4 id="wait-event"><code>Wait Event</code></h4>
<p><code>scanf()</code>, <code>GetMessage()</code> 호출</p>
<blockquote>
</blockquote>
<h4 id="event-occurs"><code>Event Occurs</code></h4>
<p>키보드 - <code>Enter</code>
마우스 - 움직임, <code>Click</code></p>
<h3 id="📌-diskfile-read---diskor-usb-interrupt">📌 <code>Disk(File Read)</code> - <code>Disk(or USB) interrupt</code></h3>
<blockquote>
</blockquote>
<h4 id="wait-event-1"><code>Wait Event</code></h4>
<p><code>read()</code>, <code>fread()</code>, <code>fscanf()</code> 호출</p>
<blockquote>
</blockquote>
<h4 id="event-occurs-1"><code>Event Occurs</code></h4>
<p>디스크가 파일을 읽어 <strong>메모리에 전달</strong>한 후</p>
<h3 id="📌-network---network-interrupt">📌 <code>Network</code> - <code>Network interrupt</code></h3>
<blockquote>
</blockquote>
<h4 id="wait-event-2"><code>Wait Event</code></h4>
<p><code>receive()</code> 호출</p>
<blockquote>
</blockquote>
<h4 id="event-occurs-2"><code>Event Occurs</code></h4>
<p><strong>네트워크 보드</strong>에 <strong>데이터가 도착</strong>했을 때</p>
<h3 id="📌-timer---timer-interrupt">📌 <code>Timer</code> - <code>Timer interrupt</code></h3>
<blockquote>
</blockquote>
<h4 id="wait-event-3"><code>Wait Event</code></h4>
<p><code>Sleep(2000)</code>, <code>sleep(2)</code> 호출</p>
<blockquote>
</blockquote>
<h4 id="event-occurs-3"><code>Event Occurs</code></h4>
<p><strong>지정한 시간(2초)</strong> 지난 후
<strong>time quantum(0.1초)</strong> 초과 -&gt; <strong>시분할 시스템</strong></p>
<h3 id="📌-semaphore---다른-프로세스가-unlocks-호출-시에만">📌 <code>Semaphore</code> - 다른 프로세스가 <code>unlock(s)</code> 호출 시에만</h3>
<p><code>Interrupt</code>와는 무관.</p>
<blockquote>
</blockquote>
<h4 id="wait-event-4"><code>Wait Event</code></h4>
<p><code>lock</code>을 호출했지만 <strong>다른 프로세스가 이미 <code>lock()</code> 한 경우</strong></p>
<blockquote>
</blockquote>
<h4 id="event-occurs-4"><code>Event Occurs</code></h4>
<p>다른 프로세스가 <strong><code>unlock(s)</code></strong> 함수를 호출하여 <strong>lock을 풀어줄 때</strong></p>
<hr>
<h2 id="📖-프로세스-상태-변화">📖 프로세스 상태 변화</h2>
<p><img src="https://images.velog.io/images/profile_exe/post/cd8ea60d-9b60-4f18-8fca-cf506485a4ac/image.png" alt="프로세스 상태 변화도"></p>
<h3 id="🚨-주의-1">🚨 주의</h3>
<ul>
<li><strong>Clock interrupt</strong> - 시분할 time slice 초과 : 🔄 <strong>Running</strong> -&gt; ✔️ <strong>Ready</strong></li>
<li><strong>I/O API 호출</strong> : 🔄 <strong>Running</strong> -&gt; 🛑 <strong>Blocked</strong></li>
</ul>
<hr>
<h2 id="📖-running---blocked-상태로의-전환">📖 <code>Running -&gt; Blocked</code> 상태로의 전환</h2>
<blockquote>
<p><strong>실행중</strong>인 프로세스가 <strong>대기 상태</strong>로 전환되는 과정</p>
</blockquote>
<h3 id="📌-io-함수-호출---scanf-read-receive-등">📌 <code>I/O</code> 함수 호출 - <code>scanf(), read(), receive()</code> 등</h3>
<blockquote>
</blockquote>
<ul>
<li>궁극적으로 <strong>API 함수 호출</strong>하고 <strong>kernel</strong>속으로 진입</li>
<li>키보드, 디스크, 네트워크 device에 <strong>도착한 데이터가 없으면 데이터가 도착할 때까지 대기</strong></li>
<li><code>Running</code> 상태에서 <code>Blocked</code> 상태로 전환</li>
<li><code>Scheduler()</code> 함수 호출 - <code>processor</code>를 다른 <code>process</code>에게 넘겨 줌</li>
<li><strong>데이터가 도착</strong>하면 깨어나 <strong>해당 데이터를 가지고 리턴</strong></li>
</ul>
<h3 id="💡-scheduler-함수">💡 <code>Scheduler()</code> 함수</h3>
<blockquote>
</blockquote>
<ul>
<li><code>Ready</code>상태의 <code>process</code>들 중 <strong>우선순위가 가장 높은 프로세스</strong>를 선택</li>
<li><code>processor</code>를 해당 <code>process</code>에게 넘겨 줌</li>
<li>실행할 다른 프로세스가 없을 경우 <strong><code>processor</code>는 <code>idle</code>상태</strong><ul>
<li><strong><code>for(;;);</code> 무한 loop</strong> 돌면서 대기</li>
</ul>
</li>
<li><strong>Blocked</strong> 상태의 <code>process</code>들은 <strong>스케줄링 대상에서 제외</strong></li>
</ul>
<hr>
<h2 id="📖-blocked---ready-상태로의-전환">📖 <code>Blocked -&gt; Ready</code> 상태로의 전환</h2>
<blockquote>
</blockquote>
<p><strong>대기상태</strong>의 프로세스가 <strong>깨어나</strong> <strong>Ready</strong> 상태로 전환되는 과정
각 디바이스는 <strong>데이터가 도착</strong>했다는 것을 <code>processor</code>에게 <strong><code>interrupt(전기적 신호)</code></strong>를 통해 통보</p>
<h3 id="📌-interrupt-발생---timer-network-disk-등에서-발생">📌 <code>Interrupt</code> 발생 - <code>Timer, Network, Disk</code> 등에서 발생</h3>
<blockquote>
</blockquote>
<ul>
<li><code>processor</code>는 <strong>현재 실행 중인 <code>process</code>의 실행 보류</strong></li>
<li><strong>현재 실행 중인 <code>process</code></strong> : <code>Running</code> -&gt; <code>Ready</code> 상태로 전환</li>
<li><strong>Interrupt</strong> 고유 업무 수행<ul>
<li>해당 디바이스의 <strong>대기큐</strong>에서 데이터를 기다리는 <code>process</code>를 찾아 <strong>데이터 전달</strong></li>
<li>해당 <code>process</code>를 <strong><code>Blocked</code></strong>상태에서 <strong><code>Ready</code></strong>상태로 변환 <code>(깨우기)</code></li>
</ul>
</li>
<li><strong><code>Scheduler()</code></strong> 호출 : <code>Interrupt</code>의 마지막에 항상 <code>scheduler</code> 호출<ul>
<li>깨어난 프로세스가 <strong>최우선순위</strong>면 <strong>바로 실행</strong></li>
<li>그렇지 않으면 <code>interrupt</code>가 들어오기 전에 실행되었던 프로세스 계속 실행</li>
</ul>
</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[운영체제] Process - 프로세스]]></title>
            <link>https://velog.io/@profile_exe/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-Process-PCB</link>
            <guid>https://velog.io/@profile_exe/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-Process-PCB</guid>
            <pubDate>Mon, 13 Sep 2021 08:29:22 GMT</pubDate>
            <description><![CDATA[<p>간단하게 개념을 정리해보자</p>
<ul>
<li><code>Processor</code> : <code>CPU</code> + <code>제어장치</code></li>
<li><code>Program</code> : <code>디스크</code>에 있는 <strong>프로그램 파일</strong></li>
<li><code>Process</code> : <code>메모리</code>에 있는 <strong>실행중인 프로그램</strong></li>
</ul>
<p>이번 포스트에서는 <code>Process</code> 개념, 생성, 종료, 추적에 대해 알아본다.</p>
<hr>
<h2 id="📖-프로세스---process">📖 프로세스 - <code>Process</code></h2>
<blockquote>
</blockquote>
<ul>
<li>현재 <strong>실행중</strong>인 프로그램</li>
<li>컴퓨터에서 실행되는 프로그램의 <strong>인스턴스</strong></li>
<li><strong>프로세서</strong>에 할당되어 <strong>실행될 수 있는 객체</strong></li>
<li>명령어들의 순차 수행, 현재 상태, 연계된 시스템 자원들의 집합 등에 의해 특정지어지는 <strong>활성화 단위</strong></li>
</ul>
<h3 id="💡-process--program-code--set-of-data--pcb">💡 <code>Process = Program Code + set of data + PCB</code></h3>
<hr>
<h2 id="📖-프로세스-생성---process-creation">📖 프로세스 생성 - <code>Process Creation</code></h2>
<h3 id="📌-로그인">📌 로그인</h3>
<blockquote>
<p>터미널의 사용자가 <strong>시스템에 로그인</strong> 한 경우</p>
</blockquote>
<h3 id="📌-운영체제에-의한-생성">📌 운영체제에 의한 생성</h3>
<blockquote>
<p><strong>운영체제</strong>는 사용자가 기다릴 필요 없이 <strong>사용자 프로그램을 대신하여 기능을 수행하는</strong> <strong>프로세스를 생성</strong>할 수 있음</p>
</blockquote>
<h3 id="📌-실행중인-프로그램존재하는-프로세스에-의한-생성">📌 실행중인 프로그램(존재하는 프로세스)에 의한 생성</h3>
<blockquote>
<p><strong>모듈화</strong> 또는 <strong>병렬화</strong>를 이용하기 위해, 사용자 프로그램은 많은 프로세스의 생성을 명령할 수 있음
ex) <code>Visual Studio</code> -&gt; <code>컴파일러(+링커)</code> -&gt; <code>실행파일 실행(프로세스 생성)</code></p>
</blockquote>
<hr>
<h2 id="📖-프로세스-종료---process-termination">📖 프로세스 종료 - <code>Process Termination</code></h2>
<h3 id="📌-정상-종료---normal-completion">📌 정상 종료 - <code>Normal completion</code></h3>
<blockquote>
<p>정상 종료되는 경우 프로세스는 <strong>OS 서비스 호출</strong>하여 <strong>실행이 완료되었음을 알림</strong></p>
</blockquote>
<h3 id="📌-허용하지-않은-메모리-침범---bounds-violation">📌 허용하지 않은 메모리 침범 - <code>Bounds violation</code></h3>
<blockquote>
<p><strong>포인터 변수</strong>의 주소가 잘못된 경우 발생할 수 있음</p>
</blockquote>
<h3 id="📌-산술-오류---arithmetic-error">📌 산술 오류 - <code>Arithmetic error</code></h3>
<blockquote>
<p><strong>0으로 나누거나(division by zero)</strong> 하드웨어가 <strong>수용할 수 있는 것보다 더 큰 숫자를 저장</strong>한 경우</p>
</blockquote>
<h3 id="📌-잘못된-명령---invalid-instruction">📌 잘못된 명령 - <code>Invalid instruction</code></h3>
<blockquote>
<p>프로세스가 <strong>존재하지 않는 명령</strong>을 실행하려고 시도하는 경우</p>
</blockquote>
<h3 id="📌-부모-프로세스-종료---parent-termination">📌 부모 프로세스 종료 - <code>Parent termination</code></h3>
<blockquote>
<p>부모 프로세스가 종료되면 <strong>운영체제</strong>는 부모 프로세스의 <strong>모든 자식 프로세스</strong>를 종료시킬 수 있음</p>
</blockquote>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[운영체제] 멀티 프로그래밍 & 시분할 시스템]]></title>
            <link>https://velog.io/@profile_exe/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EB%A9%80%ED%8B%B0-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%8B%9C%EB%B6%84%ED%95%A0-%EC%8B%9C%EC%8A%A4%ED%85%9C</link>
            <guid>https://velog.io/@profile_exe/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EB%A9%80%ED%8B%B0-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%8B%9C%EB%B6%84%ED%95%A0-%EC%8B%9C%EC%8A%A4%ED%85%9C</guid>
            <pubDate>Sat, 11 Sep 2021 15:57:33 GMT</pubDate>
            <description><![CDATA[<p>운영체제는 컴퓨터 시스템의 자원을 효율적으로 사용할 수 있도록 한다. 여러 프로그램들을 동시에 실행했을 때, <code>CPU</code>가 각 프로그램들을 옮겨다니며 효율적으로 실행하는 것도 이 중 하나다.</p>
<p>운영체제가 여러 프로그램을 실행시키는 경우 <strong>멀티 프로그래밍</strong>과 <strong>시분할 시스템</strong> 방식이 존재한다.</p>
<p>이번 포스트에서는 각 방식의 개념과 방식에 따라 <code>CPU</code>를 <strong>어떠한 상황</strong>에 다른 프로그램으로 넘기는지 알아보도록 하자.</p>
<hr>
<h2 id="📖-멀티-프로그래밍---multiprogramming-system">📖 멀티 프로그래밍 - <code>Multiprogramming System</code></h2>
<blockquote>
</blockquote>
<h3 id="multiprogramming-또는-multitasking"><code>Multiprogramming</code> 또는 <code>Multitasking</code></h3>
<ul>
<li>여러 프로그램 동시에 실행</li>
</ul>
<h3 id="💡-cpu를-넘기는-경우">💡 CPU를 넘기는 경우</h3>
<p>프로세스(메모리에 올라가 실행 중인 프로그램) <code>A</code>, <code>B</code>가 있다고 가정</p>
<ul>
<li><strong>실행 중</strong>인 <code>A</code>가 <strong>I/O 함수(입출력 함수)를 호출</strong>했을때 <code>I/O 장치</code>에 <strong>데이터가 없는 경우</strong><ul>
<li><strong>CPU 이동</strong> : <code>A</code> ➡ <code>B</code>  (B 프로그램 실행)</li>
</ul>
</li>
</ul>
<ul>
<li><p><strong><code>I/O 장치</code></strong>에 <code>A</code>가 기다리던 <strong>데이터가 들어온 경우</strong></p>
<ul>
<li><code>A</code>는 <strong>준비상태</strong> - <code>CPU</code>가 오면 <strong>바로 실행 가능</strong>한 상태</li>
<li><strong>우선순위</strong>에 따라 <code>A</code>의 실행여부 결정 - <code>A</code>가 <strong>최우선순위면 바로 실행</strong></li>
<li><strong>CPU 이동</strong> : 원래 실행하던 프로그램 ➡ <strong>우선순위가 가장 높은</strong> 프로그램</li>
</ul>
</li>
<li><p><strong>현재 실행중인 프로그램 완료</strong> 또는 <strong>I/O로 인해 대기</strong> 중인 경우</p>
</li>
<li><p>🚨 <strong>시분할 처리 기능은 없음</strong></p>
</li>
</ul>
<blockquote>
</blockquote>
<h3 id="✍️--multiprogramming-정리">✍️  <code>Multiprogramming</code> 정리</h3>
<ul>
<li>현재 실행 중인 프로그램 <strong>완료 / 종료</strong></li>
<li>현재 실행 중인 프로그램 <strong>I/O로 인해 대기</strong></li>
<li>I/O 장치에 기다리던 <strong>데이터 도착</strong><blockquote>
</blockquote>
위 상황에서 <strong>CPU가 이동</strong>한다.</li>
</ul>
<hr>
<h2 id="📖-시분할-시스템---time-sharing-system">📖 시분할 시스템 - <code>Time Sharing System</code></h2>
<blockquote>
</blockquote>
<h3 id="time-sharing"><code>Time Sharing</code></h3>
<ul>
<li>여러 <strong>대화형</strong> 프로그램 동시에 실행</li>
<li><code>CPU</code>가 각 프로그램을 <strong>일정 시간(time quantum 또는 time slice)</strong>동안 번갈아 가면서 실행<ul>
<li><strong>Multiprogramming system 기능을 포함</strong></li>
</ul>
</li>
</ul>
<h3 id="💡-cpu를-넘기는-경우-1">💡 CPU를 넘기는 경우</h3>
<p><code>time slice</code>는 <strong>0.1초</strong>라고 가정</p>
<h4 id="time-slice-지나지-않은-경우---multiprogramming과-동일"><code>time slice</code> 지나지 않은 경우 - <code>Multiprogramming</code>과 동일</h4>
<ul>
<li><strong>실행 중</strong>인 프로그램이 <strong>I/O 함수를 호출</strong>했을 때</li>
</ul>
<ul>
<li><strong>I/O 장치</strong>에 기다리던 <strong>데이터가 들어온 경우</strong><ul>
<li><strong>우선순위</strong> 고려해서 실행 할 프로그램 결정</li>
</ul>
</li>
</ul>
<h4 id="time-slice-지난-경우"><code>time slice</code> 지난 경우</h4>
<ul>
<li>실행 중인 프로그램이 <strong>0.1초</strong>동안 <strong>I/O 없이</strong> 계속 실행했을 때
(<code>CPU</code>를 계속 사용했을 때)</li>
</ul>
<blockquote>
</blockquote>
<h3 id="✍️--time-sharing-정리">✍️  <code>Time Sharing</code> 정리</h3>
<ul>
<li>현재 실행 중인 프로그램 <strong>완료 / 종료</strong></li>
<li>현재 실행 중인 프로그램 <strong>I/O로 인해 대기</strong></li>
<li>I/O 장치에 기다리던 <strong>데이터 도착</strong></li>
<li>📌<strong>time slice</strong>동안 <strong>I/O 없이 계속 실행</strong>된 경우📌<blockquote>
</blockquote>
위 상황에서 <strong>CPU가 이동</strong>한다.</li>
</ul>
<hr>
<h2 id="📖-multiprogramming--time-sharing-목적">📖 <code>Multiprogramming</code> / <code>Time Sharing</code> 목적</h2>
<p><code>CPU</code> 스위칭은 <strong>오버헤드</strong>로 볼 수 있다.</p>
<h3 id="🎯-multiprogramming-목적---cpu-이용률-최대화">🎯 <code>Multiprogramming</code> 목적 - <code>CPU</code> <strong>이용률 최대화</strong></h3>
<ul>
<li><code>time slice</code>에 의한 <code>CPU</code> 스위칭 없음</li>
<li><strong>I/O 스위칭</strong>만 발생</li>
</ul>
<blockquote>
</blockquote>
<h3 id="📝-cpu-이용률-최대화-과정">📝 CPU 이용률 최대화 과정</h3>
<p>사용자 프로그램 계속 실행 ➡ 프로그램 실행 비율 ⬆️ ➡ <code>CPU</code> 이용률 ⬆️</p>
<h3 id="🎯-time-sharing-목적----응답-시간-최소화">🎯 <code>Time Sharing</code> 목적 - ** 응답 시간 최소화**</h3>
<ul>
<li><code>time slice</code>에 의한 <code>CPU</code> 스위칭 존재</li>
<li><strong>I/O 없이</strong> 실행만 한다면 다른 프로그램 실행</li>
</ul>
<blockquote>
</blockquote>
<h3 id="📝-응답시간-최소화-과정">📝 응답시간 최소화 과정</h3>
<p>번갈아가면서 프로그램 계속 실행 ➡ 응답시간 ⬆️</p>
<h3 id="💡-cpu-이용률--응답시간">💡 CPU 이용률 &amp; 응답시간</h3>
<blockquote>
</blockquote>
<p> <strong>CPU 이용률 (utilization)</strong> : <code>CPU</code>가 <strong>총 실행한 시간</strong> 중 <strong>사용자 프로그램을 사용한 시간</strong>의 비율</p>
<ul>
<li><strong>사용자 프로그램</strong> 실행(7초) + CPU 스위칭(0.6초) + I/O 스위칭(0.4초) + CPU 휴식상태(2초)</li>
<li><strong>CPU 이용률</strong> : 7초 / 10초 = 0.7</li>
</ul>
<blockquote>
</blockquote>
<p><strong>응답시간(response time)</strong> : <strong>입력</strong>이 주어지고 나서 <strong>첫 출력</strong>이 나오는데 걸린 시간</p>
<ul>
<li>키보드에서 <strong>엔터</strong> 입력 후 <strong>처음 화면 출력</strong>이 나오는데 걸린 시간</li>
<li>URL 주소 <strong>입력</strong> 후 <strong>처음으로 웹 페이지가 화면에 보여지는데</strong> 걸린 시간</li>
</ul>
<h3 id="💡-cpu의-idle-상태">💡 <code>CPU</code>의 <code>idle</code> 상태</h3>
<blockquote>
</blockquote>
<pre><code class="language-c">for (;;);</code></pre>
<p>의미없는 <strong>무한 루프</strong></p>
<hr>
<h2 id="📖-cpu-이용률--응답시간-비교">📖 CPU 이용률 &amp; 응답시간 비교</h2>
<h3 id="💬-주어진-상황">💬 주어진 상황</h3>
<blockquote>
<p><strong>10개의 프로그램</strong>이 돌고 있음</p>
</blockquote>
<ul>
<li>각 프로그램의 실행시간 : <strong>1초</strong></li>
<li>내 프로그램은 마지막 10번째에 실행</li>
<li>앞의 9개 프로그램은 <strong>I/O 없이</strong> 계산만 수행하고 종료 시 결과를 화면에 출력</li>
<li>내 프로그램은 실행 시 <strong>0.1초</strong>마다 결과를 화면에 출력(response)</li>
</ul>
<hr>
<h2 id="📈-cpu-이용률-processor-utilization-비교">📈 CPU 이용률 [<code>Processor utilization</code>] 비교</h2>
<blockquote>
</blockquote>
<h3 id="multiprogramming"><code>Multiprogramming</code></h3>
<ul>
<li><strong>I/O 작업</strong>을 대기하는 상황이 아니면 <strong>계속 실행</strong></li>
<li>그 만큼 <strong>프로그램을 실행시키는데 CPU 시간을 더 할애</strong><ul>
<li><strong>0.1초</strong>마다 <code>CPU</code>를 <strong>다른 프로그램으로 넘기지 않아도 되므로</strong></li>
</ul>
</li>
<li>따라서 <strong>CPU 이용률</strong>이 <code>time sharing</code>보다 더 <strong>높음</strong><ul>
<li>대신 <strong>응답시간은 더 길어짐</strong><h4 id="사용자-프로그램-실행10초--io-스위칭01초--101초">사용자 프로그램 실행(10초) + I/O 스위칭(0.1초) = 10.1초</h4>
<h4 id="✅-cpu-이용률--10초--101초--099">✅ <strong>CPU 이용률</strong> : 10초 / 10.1초 = 0.99</h4>
</li>
</ul>
</li>
</ul>
<blockquote>
</blockquote>
<h3 id="time-sharing-1"><code>Time Sharing</code></h3>
<ul>
<li><strong>0.1초</strong>마다 <code>CPU</code>를 다른 프로그램에게 넘김</li>
<li>넘기는 과정에서 <code>timer interrupt handler</code>, <code>scheduler()</code> 등의 코드 수행</li>
<li>이는 <code>Time Sharing</code>을 위한 <strong>추가적인 CPU overhead</strong><h4 id="사용자-프로그램-실행10초--cpu-스위칭04초--io-스위칭01초--105초">사용자 프로그램 실행(10초) + CPU 스위칭(0.4초) + I/O 스위칭(0.1초) = 10.5초</h4>
<h4 id="✅-cpu-이용률--10초--105초--095">✅ <strong>CPU 이용률</strong> : 10초 / 10.5초 = 0.95</h4>
</li>
</ul>
<h3 id="✍️-multiprogramming-cpu-이용률이-더-높음">✍️ <code>Multiprogramming</code> CPU 이용률이 더 높음</h3>
<h3 id="🚨-주의">🚨 주의</h3>
<ul>
<li><strong>사용자 프로그램 실행 시간은 동일</strong></li>
<li><strong>CPU의 총 사용 시간 차이</strong></li>
</ul>
<hr>
<h2 id="⏰-응답시간-response-time-비교">⏰ 응답시간 [<code>Response Time</code>] 비교</h2>
<blockquote>
</blockquote>
<h4 id="multiprogramming-1"><code>Multiprogramming</code></h4>
<ul>
<li>앞의 9개 프로그램은 <strong>I/O 없음</strong> 따라서 실행 중인 프로그램은 종료될 때까지 <strong>계속 실행</strong></li>
<li>따라서 <strong>9개의 프로그램 실행이 종료되어야</strong> 내 프로그램 실행</li>
<li><strong>9초</strong>가 지나서야 내 프로그램의 결과 출력 시작<h4 id="✅-응답시간--내-프로그램-시작9초--결과-출력01초--최소-91초-이상">✅ <strong>응답시간</strong> : 내 프로그램 시작(9초) + 결과 출력(0.1초) = 최소 9.1초 이상</h4>
</li>
</ul>
<blockquote>
</blockquote>
<h4 id="time-sharing-2"><code>Time Sharing</code></h4>
<ul>
<li><strong>0.1초</strong>씩 번갈아가면서 <strong>모든 프로그램 실행</strong></li>
<li><strong>1초마다(각 프로그램당 0.1초씩 10개)</strong> 내 프로그램 실행</li>
<li>실행 결과가 조금씩 출력되는 것 확인 가능<h4 id="✅-응답시간--내-프로그램-시작09초--결과-출력01초--최소-1초-이상">✅ <strong>응답시간</strong> : 내 프로그램 시작(0.9초) + 결과 출력(0.1초) = 최소 1초 이상</h4>
</li>
</ul>
<h3 id="✍️-time-sharing-응답시간이-더-빠름">✍️ <code>Time Sharing</code> 응답시간이 더 빠름</h3>
<h3 id="🚨-주의-1">🚨 주의</h3>
<ul>
<li><strong>결과 출력 시간은 동일</strong></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[운영체제] 운영체제 개념 및 목적]]></title>
            <link>https://velog.io/@profile_exe/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B0%9C%EB%85%90-%EB%B0%8F-%EB%AA%A9%EC%A0%81</link>
            <guid>https://velog.io/@profile_exe/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B0%9C%EB%85%90-%EB%B0%8F-%EB%AA%A9%EC%A0%81</guid>
            <pubDate>Fri, 10 Sep 2021 15:15:01 GMT</pubDate>
            <description><![CDATA[<p>학기가 시작되고 운영체제, 알고리즘, 데이터통신 등의 과목을 수강하게 되었다. 개인 프로젝트도 시작하겠지만 우선 학기 중 배우는 CS에 대해서 정리를 우선으로 하며 포스팅을 진행할 계획이다.</p>
<hr>
<h2 id="📖-운영체제---operating-system">📖 운영체제 - <code>Operating System</code></h2>
<p><a href="https://ko.wikipedia.org/wiki/%EC%9A%B4%EC%98%81_%EC%B2%B4%EC%A0%9C">운영체제 - Wikipedia</a></p>
<blockquote>
<p>응용 프로그램 실행을 <strong>제어</strong>하는 <strong>프로그램</strong>
응용 프로그램과 하드웨어 간의 <strong>인터페이스</strong></p>
</blockquote>
<blockquote>
<p>컴퓨터가 부팅이 되고 <strong>메인 메모리(RAM)</strong>에 제일 먼저 <strong>로드(Load)</strong>되어 <strong>응용 프로그램의 실행을 제어</strong>하고 <strong>응용프로그램과 하드웨어가 서로 통신할 수 있게</strong> 만드는 소프트웨어</p>
</blockquote>
<h3 id="✍️-os--kernel--system-programs">✍️ <code>OS = Kernel + System Programs</code></h3>
<h3 id="💡-kernel">💡 <code>Kernel</code></h3>
<ul>
<li><strong>메모리</strong>에 상주 - <strong>제일 높은 주소</strong>에 존재<ul>
<li><strong>Stack</strong>은 <strong>높은 주소에서 낮은 주소</strong>로 쌓인다. -&gt; Kernel 접근 방지</li>
</ul>
</li>
<li>시스템 <strong>부팅</strong> 시 <strong>메모리</strong>에 로드되는 <strong>함수들의 집합</strong></li>
</ul>
<h4 id="kernel-함수들"><code>Kernel</code> 함수들</h4>
<ul>
<li>Process 관리, Processor 스케줄링, 실기억장치 및 가상기억장치, 파일 시스템, 네트워크, 윈도우 관리 함수들, I/O device drive</li>
</ul>
<h4 id="kernel이-호출되는-시점"><code>Kernel</code>이 호출되는 시점</h4>
<ul>
<li>응용프로그램에서 <strong>API 함수를 호출</strong>하는 경우</li>
<li>입출력 장치에서 <strong>데이터가 들어올 경우</strong> <code>device driver</code> 함수가 실행됨</li>
</ul>
<h3 id="💡-system-programs">💡 <code>System Programs</code></h3>
<ul>
<li><strong>디스크</strong>에 <strong>실행 파일(Program)</strong>로 존재</li>
<li><strong>컴퓨터 시스템 관리</strong>에 필요한 프로그램<ul>
<li>ex) <code>설정</code> 또는 <code>제어판</code>에 존재하는 모든 프로그램들</li>
</ul>
</li>
<li><code>Application Program</code>(응용 프로그램) : <strong>System Program</strong>이 아닌 프로그램들</li>
</ul>
<hr>
<h2 id="📖-운영체제-목적">📖 운영체제 목적</h2>
<p>각 목적에 따른 구체적인 내용은 다음 단락부터 서술한다.</p>
<p><a href="https://ko.wikipedia.org/wiki/%EC%9A%B4%EC%98%81_%EC%B2%B4%EC%A0%9C#%EB%AA%A9%EC%A0%81">운영체제 목적 - Wikipedia</a></p>
<h3 id="1-편리성---convenience">1. 편리성 - <code>Convenience</code></h3>
<blockquote>
<p>사용자에게 컴퓨터의 <strong>프로그램을 쉽고 효율적으로 실행할 수 있는 환경</strong>을 제공</p>
</blockquote>
<h3 id="2-효율성자원성---efficiency">2. 효율성(자원성) - <code>Efficiency</code></h3>
<blockquote>
<p>하드웨어 및 소프트웨어 <strong>자원</strong>을 여러 사용자 간에 <strong>효율적 할당, 관리, 보호</strong>하는 것</p>
</blockquote>
<h3 id="3-발전성진화-가능성---ability-to-evolve">3. 발전성(진화 가능성) - <code>Ability to evolve</code></h3>
<blockquote>
<p>서비스를 방해하지 않고 <strong>새로운 시스템 기능</strong>을 효과적으로 개발, 테스트 및 도입</p>
</blockquote>
<hr>
<h2 id="📖-운영체제가-제공하는-서비스---편리성">📖 운영체제가 제공하는 서비스 - 편리성</h2>
<blockquote>
<h3 id="💻-프로그램-개발---program-development">💻 프로그램 개발 - <code>Program development</code></h3>
</blockquote>
<ul>
<li>에디터 &amp; 디버거</li>
</ul>
<blockquote>
<h3 id="✅-프로그램-실행---program-execution">✅ 프로그램 실행 - <code>Program execution</code></h3>
</blockquote>
<ul>
<li>프로그램 실행과정, <strong>자원 관리</strong>, <strong>스케줄링</strong></li>
</ul>
<blockquote>
<h3 id="📻-입출력-디바이스-접근---access-to-io-devices">📻 입출력 디바이스 접근 - <code>Access to I/O devices</code></h3>
</blockquote>
<ul>
<li>복잡한 내부는 숨기고 <strong>함수만 호출</strong>하도록 함</li>
</ul>
<blockquote>
<h3 id="📂-파일에-대한-접근-제어---controlled-access-to-files">📂 파일에 대한 접근 제어 - <code>Controlled access to files</code></h3>
</blockquote>
<ul>
<li>여러 장치, 사용자 동시 접근</li>
</ul>
<blockquote>
<h3 id="🔐-시스템-접근---system-access">🔐 시스템 접근 - <code>System access</code></h3>
</blockquote>
<ul>
<li>다중 사용자(<strong>자원보호</strong>, 동시성)</li>
</ul>
<blockquote>
<h3 id="🧾-회계---accounting">🧾 회계 - <code>Accounting</code></h3>
</blockquote>
<ul>
<li><strong>사용 통계</strong> 수집, <strong>성능 모니터링</strong></li>
<li>향후 <strong>기능 보완의 필요성 미리 예상</strong></li>
<li><strong>사용료</strong> 청구 목적</li>
</ul>
<blockquote>
<h3 id="🚨-오류-검출-및-응답---error-detection-and-response">🚨 오류 검출 및 응답 - <code>Error detection and response</code></h3>
</blockquote>
<ul>
<li>내부 및 외부 <strong>하드웨어</strong> 오류<ul>
<li><strong>메모리</strong> 오류</li>
<li>기기 고장</li>
</ul>
</li>
<li><strong>소프트웨어</strong> 오류<ul>
<li><strong>산술 오버플로우</strong>, <strong>division by zero</strong> (0으로 나누기)</li>
<li>금지된 메모리 영역 접근</li>
</ul>
</li>
<li>운영체제에서 허용할 수 없는 응용 프로그램 요청</li>
</ul>
<hr>
<h2 id="📖-자원-관리자로서의-운영체제---효율성">📖 자원 관리자로서의 운영체제 - 효율성</h2>
<h3 id="자원---resource">자원 - <code>Resource</code></h3>
<blockquote>
<p>컴퓨터에서 사용하고 있거나 사용할 수 있는 각각의 하드웨어 및 소프트웨어 요소</p>
</blockquote>
<ul>
<li><code>Processor(CPU)</code>, <code>memory</code>, <code>disks</code>, <code>printers</code>, <code>network cards</code>, <code>serial/parallel port</code>, <code>USB port</code>, <code>timer</code>, etc.</li>
</ul>
<p>운영체제는 각 자원을 관리하며 이에 대한 책임을 진다.</p>
<blockquote>
<h3 id="📟-자원-관리에-대한-책임">📟 자원 관리에 대한 책임</h3>
</blockquote>
<blockquote>
<h3 id="⚙️-일반-컴퓨터-소프트웨어와-동일한-방식으로-작동">⚙️ 일반 컴퓨터 소프트웨어와 동일한 방식으로 작동</h3>
</blockquote>
<ul>
<li>운영체제는 <strong>프로세서</strong>에 의해 실행되는 <strong>함수들의 집합</strong>, <strong>프로그램</strong> 또는 <strong>프로그램 모음</strong>이다.</li>
</ul>
<hr>
<h2 id="📖-운영체제의-진화-용이성---발전성">📖 운영체제의 진화 용이성 - 발전성</h2>
<blockquote>
<h3 id="➕-하드웨어-업그레이드-및-새로운-타입의-하드웨어-추가">➕ 하드웨어 업그레이드 및 새로운 타입의 하드웨어 추가</h3>
</blockquote>
<ul>
<li>하드웨어 <strong>업그레이드 및 추가</strong>를 통해 새로운 기능이 추가될 수 있다.</li>
<li>하드웨어 기능이 추가되면서 <strong>소프트웨어 기능도 추가</strong>됨<ul>
<li><strong>MMU의 연속 메모리 할당</strong> - 페이징 기법<blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
<blockquote>
<h3 id="🆕-새로운-서비스">🆕 새로운 서비스</h3>
</blockquote>
<ul>
<li>하드웨어는 동일한데 <strong>기능이 추가</strong><ul>
<li><strong>Graphical User Interface[GUI]</strong><blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
<blockquote>
<h3 id="🛠️-수정">🛠️ 수정</h3>
</blockquote>
<ul>
<li>오류 수정</li>
<li>수정이 새로운 결함을 가져올 수 있다...?</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[WEB] WSL2 - Node.js / npm / n 설치]]></title>
            <link>https://velog.io/@profile_exe/WEB-WSL2-Node.js-npm-n-installation</link>
            <guid>https://velog.io/@profile_exe/WEB-WSL2-Node.js-npm-n-installation</guid>
            <pubDate>Wed, 01 Sep 2021 14:37:43 GMT</pubDate>
            <description><![CDATA[<p><code>WSL2</code> 환경에서 <code>nodejs</code>를 이용한 웹 개발을 공부해보려 한다. 특히 리눅스 환경에서는 <code>n</code>이라는 도구를 통해서 쉽게 <code>nodejs</code> 버전을 관리할 수 있다.</p>
<p>이번 포스트에서는 <code>WSL</code> 환경에서 <code>nodejs</code>, <code>npm</code>, <code>n</code>을 설치하는 방법을 알아보겠다.</p>
<hr>
<h2 id="nodejs-설치"><code>Node.js</code> 설치</h2>
<p>아래 사이트에서 자신이 설치하고 싶은 <code>node</code>를 설치하자.
<a href="https://github.com/nodesource/distributions/blob/master/README.md#debinstall">NodeSource - Node.js Binary Distributions</a></p>
<p>해당 사이트에는 이렇게 나와있다.</p>
<blockquote>
</blockquote>
<h3 id="installation-instructions"><code>Installation instructions</code></h3>
<h4 id="nodejs-v16x">Node.js v16.x:</h4>
<pre><code class="language-bash"># Using Ubuntu
curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -
sudo apt-get install -y nodejs
&gt;
# Using Debian, as root
curl -fsSL https://deb.nodesource.com/setup_16.x | bash -
apt-get install -y nodejs</code></pre>
<p><code>(중략)</code></p>
<h4 id="nodejs-lts-v14x">Node.js LTS (v14.x):</h4>
<pre><code class="language-bash"># Using Ubuntu
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt-get install -y nodejs
&gt;
# Using Debian, as root
curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
apt-get install -y nodejs</code></pre>
<h4 id="nodejs-current-v16x">Node.js Current (v16.x):</h4>
<pre><code class="language-bash"># Using Ubuntu
curl -fsSL https://deb.nodesource.com/setup_current.x | sudo -E bash -
sudo apt-get install -y nodejs
&gt;
# Using Debian, as root
curl -fsSL https://deb.nodesource.com/setup_current.x | bash -
apt-get install -y nodejs</code></pre>
<p>나는 가장 최신 버전인 <code>16</code>번대 버전을 설치하고 싶어서 맨 아래 명령어로 설치를 했다.</p>
<pre><code class="language-bash">curl -fsSL https://deb.nodesource.com/setup_current.x | sudo -E bash -
sudo apt-get install -y nodejs</code></pre>
<hr>
<h2 id="npm-설치-with-aptitude"><code>npm</code> 설치 with <code>Aptitude</code></h2>
<p><code>npm</code>은 <code>Aptitude</code>라는 <strong>패키지 관리자</strong>를 이용해서 설치하도록 하겠다. </p>
<p><code>Aptitude</code>는 <code>APT</code>같은 패키지관리자이다. 사용법은 아래 링크를 참고
<a href="http://www.songtory.com/post/001003/1/245">Ubuntu 패키지 관리 사용법 - apt, dpkg, aptitude</a></p>
<p><code>Aptitude</code>를 설치하고 이것으로 <code>npm</code>을 설치하면 된다.</p>
<pre><code class="language-bash">sudo apt install aptitude
sudo aptitude install npm</code></pre>
<hr>
<h2 id="n-설치"><code>n</code> 설치</h2>
<p>깃허브 링크
<a href="https://github.com/tj/n#installation">n - Interactively Manage Your Node.js Versions</a></p>
<pre><code class="language-bash">npm install -g n</code></pre>
<p>만약 설치가 안된다면 명령어 앞에 <code>sudo</code>를 붙이거나 위 깃허브 링크의 <code>Installation</code>을 참고하자.</p>
<blockquote>
</blockquote>
<pre><code class="language-bash"># make cache folder (if missing) and take ownership
sudo mkdir -p /usr/local/n
sudo chown -R $(whoami) /usr/local/n
# make sure the required folders exist (safe to execute even if they already exist)
sudo mkdir -p /usr/local/bin /usr/local/lib /usr/local/include /usr/local/share
# take ownership of Node.js install destination folders
sudo chown -R $(whoami) /usr/local/bin /usr/local/lib /usr/local/include /usr/local/share</code></pre>
<p>그냥 간단하게 <code>sudo npm install -g n</code>으로 하는걸 추천한다.</p>
<hr>
<h2 id="n-사용해보기"><code>n</code> 사용해보기</h2>
<p>처음에 명령창에 <code>n</code>을 치면 설치된 <code>node.js</code>를 인식하지 못했다.
그래서 이번엔 <code>lts</code> 버전을 <code>n</code>을 통해 설치해보려 한다.</p>
<pre><code class="language-bash">n lts</code></pre>
<p><img src="https://images.velog.io/images/profile_exe/post/050af020-1b59-4d4d-8b00-3c8444b3f9be/image.png" alt="`node.js` lts 버전 설치"></p>
<p>현재버전은?</p>
<pre><code class="language-bash">n current # 또는 n lastest</code></pre>
<p><img src="https://images.velog.io/images/profile_exe/post/a4bc6cd8-a947-4572-8790-2274b8063eef/image.png" alt="`node.js` current 버전 설치"></p>
<p>물론 직접 버전을 입력해서 설치하는 것도 가능하다.</p>
<p>이후 콘솔창에 <code>n</code>을 입력하면 다음과 같이 버전을 선택할 수 있는 창이 나온다.</p>
<p><img src="https://images.velog.io/images/profile_exe/post/46ac9b1e-b7ce-4a23-9d1a-16ad44385b2e/image.png" alt="버전 선택 창"></p>
<p>위 아래 방향키로 원하는 버전 위에 하늘색 원을 놓고 <code>Enter</code>로 선택한다.
<code>node.js</code>의 설치, 제거, 버전 변경이 매우 편해졌다.</p>
<p>이것이 <code>n</code>을 설치해야 하는 이유다.</p>
<hr>
<h2 id="npm-notice"><code>npm notice</code></h2>
<p><code>npm</code>을 이용해 위의 <code>n</code>을 설치하는데, 새로운 버전이 출시되었다고 알려주었다.
이런 것들을 그냥 지나치는 사람들이 많은데, 메시지를 잘 읽고 확인하면 유용하다.</p>
<p><img src="https://images.velog.io/images/profile_exe/post/62048605-b76c-4111-8799-183cd0d45060/image.png" alt="npm의 새로운 버전이 나왔다는 이미지"></p>
<p><code>npm --version</code>으로 버전을 확인해보니 <code>7.21.0</code> 이 역시 위의 메시지에서 확인 가능하다.</p>
<blockquote>
<p>New patch version of npm available! <strong>7.21.0 -&gt; 7.21.1</strong>
npm notice Run <strong>npm install -g <a href="mailto:npm@7.21.1">npm@7.21.1</a></strong> to update!</p>
</blockquote>
<p>그렇다. 해당 버전으로 업그레이드를 하려면 다음과 같이 입력하면 된다.</p>
<pre><code class="language-bash">npm install -g npm@7.21.1</code></pre>
<p>만약 에러가 뜬다면 <code>sudo</code>를 붙여서 명령을 실행하자.</p>
<pre><code class="language-bash">sudo npm install -g npm@7.21.1</code></pre>
<p><img src="https://images.velog.io/images/profile_exe/post/ce952da5-3925-4d6d-aeca-16bd1a1005ca/image.png" alt="새로운 버전을 설치하는 이미지"></p>
<hr>
<h3 id="정리">정리</h3>
<ul>
<li><p><code>node.js</code> 설치</p>
<pre><code class="language-bash">curl -fsSL https://deb.nodesource.com/setup_current.x | sudo -E bash -
sudo apt-get install -y nodejs</code></pre>
</li>
<li><p><code>npm</code> 설치</p>
<pre><code class="language-bash">sudo apt install aptitude
sudo aptitude install npm</code></pre>
</li>
<li><p><code>n</code> 설치</p>
<pre><code class="language-bash">sudo npm install -g n</code></pre>
</li>
<li><p><code>n</code>에서 <code>node</code> 버전 설치 및 고르기</p>
<pre><code class="language-bash">n lts            # LTS 버전 설치
n current        # 또는 n lastest - 최신버전 설치
n                # 버전 관리</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[WEB] PHP - cURL 이용한 API 사용 feat. Ajax]]></title>
            <link>https://velog.io/@profile_exe/WEB-PHP-cURL-API</link>
            <guid>https://velog.io/@profile_exe/WEB-PHP-cURL-API</guid>
            <pubDate>Tue, 24 Aug 2021 12:48:44 GMT</pubDate>
            <description><![CDATA[<p>요즘 공모전을 보면 <strong>공공데이터</strong>를 활용하는 것들이 많다. 이 공공데이터는 데이터 자체를 이용할 수도 있지만, <strong>API</strong>를 이용하여 데이터를 서버에서 얻어올 수 있다.</p>
<p><code>PHP</code>에서는 <code>JavaScript</code>의 <code>fetch API</code>와 유사한 기능을 가진 <code>cURL(Client URL Library)</code>가 존재한다.</p>
<p>이번 포스트에서는 <code>식품의약품안전처 데이터활용서비스</code>의 <code>식품영양성분DB(NEW)</code> <strong>API</strong>를 <code>PHP</code>에서 사용해보도록 하겠다.</p>
<hr>
<h2 id="api-사용-준비"><code>API</code> 사용 준비</h2>
<p><a href="https://www.foodsafetykorea.go.kr/apiMain.do">식품 안전 나라 홈페이지</a>에서 <code>식품영양성분DB(NEW)</code>를 검색하면 해당 <code>API</code>를 사용할 수 있다. <code>OPEN-API</code>란의 <code>API</code> 인증키를 요청하는 버튼 <strong>OpenAPI 이용 신청</strong> 이 있다. 회원가입 및 로그인 후 이 버튼을 클릭해서 <code>API</code> 인증키를 발급받을 수 있다.</p>
<p><a href="https://www.foodsafetykorea.go.kr/api/openApiInfo.do?menu_grp=MENU_GRP31&amp;menu_no=656&amp;show_cnt=10&amp;start_idx=1&amp;svc_no=I2790">식품영양성분DB(NEW) API</a></p>
<p><code>API</code> 인증키는 홈페이지 상단의 <code>인증키 관리</code> -&gt; <code>인증키 신청 현황</code>에서 인증키 발급 정보 및 <code>API</code> 신청 목록 조회가 가능하다.</p>
<p><img src="https://images.velog.io/images/profile_exe/post/bf60a618-aeec-4acb-99cb-94d2d34b9e08/image.png" alt="인증키 신청 현황"></p>
<p>여기서 발급된 인증키를 복사해서 코드에서 사용할 것이다.</p>
<p><img src="https://images.velog.io/images/profile_exe/post/599899bd-54f1-4c0b-8bfd-337a1e1f69f0/image.png" alt="데이터활용 서비스 이미지"></p>
<p><img src="https://images.velog.io/images/profile_exe/post/7a6f1893-8eea-4fe9-b62a-1dcb9c7cf9cc/image.png" alt="API 요청 인자 목록"></p>
<p>해당 <code>API</code>에 대해 알아보면, <code>URL</code>형식으로만 불러오므로 <code>GET</code> 요청으로 데이터를 받아올 수 있다.</p>
<h3 id="🚨주의🚨">🚨주의🚨</h3>
<p><strong><code>Client Side</code></strong>에서 <code>JavaScript</code> 코드로 <code>API</code>를 사용하는 경우 <strong><code>CORS</code></strong> 때문에 요청이 불가능할 수 있다. 그렇기 때문에 <code>PHP</code>를 사용해 <strong><code>Server Side</code></strong>에서 <code>API</code>를 사용하는 것이다.</p>
<p><strong>요청인자</strong>의 <code>keyId</code>는 <strong>요청주소</strong>의 샘플에 나온 <code>I2790</code>이다.</p>
<hr>
<h2 id="fetch-api를-이용한-ajax"><code>fetch API</code>를 이용한 <code>Ajax</code></h2>
<p>클라이언트에서 데이터 요청을 하면 <code>PHP</code>에 <code>fetch API</code>를 이용해서 비동기적으로 요청을 한다(<code>Ajax</code>).</p>
<p>위에 첨부한 이미지를 잘 보면,
<strong>필수</strong> 요청인자들은 <code>/인자/인자/...</code> 이런식으로 <code>URL</code>에 붙이고, <strong>선택</strong> 요청인자들은 <code>Query String</code>으로 <strong>매개변수</strong>를 넣어주는 식으로 되어있다.</p>
<p>요청인자들은 객체에 담아서 이용하기 편하게 했다.</p>
<pre><code class="language-js">const essential_params = {    // 필수 요청 인자들
    keyId: &#39;자신의 API 인증키 입력&#39;,
    serviceId: &#39;I2790&#39;,        // 샘플에 나와있음
    dataType: &#39;json&#39;,        // 중요!! 받은 데이터 object로 파싱하기
    startIdx: 1,
    endIdx: 15
};

// 필수 인자들 &quot;/../../..&quot; 형식으로 url에 붙여주기
let url = `http://openapi.foodsafetykorea.go.kr/api/`
for (const key in essential_params) {
    url += `${essential_params[key]}/`
}

// 선택 요청 인자들
const params = {
    DESC_KOR: &#39;&#39;,
    RESEARCH_YEAR: &#39;&#39;,
    MAKER_NAME: &#39;&#39;,
    FOOD_CD: &#39;&#39;,
}

// fetch API에서 사용할 request body
// PHP에서 $객체-&gt;url, $객체-&gt;params 형태로 사용된다.
const request_body = {
    url: url,
    params: params
}

// fetch API에 넣을 init 객체
const init = {
    method: &#39;POST&#39;,
    mode: &#39;cors&#39;,    // 기본값이 cors이다.
    headers: {          // json 형식과 인코딩 명시
        &#39;Content-type&#39;: &#39;application/json; charset=utf-8&#39;,
    },
    body: JSON.stringify(request_body),    // json으로 파싱!!
}

fetch(&#39;api.php&#39;, init)
    .then((res) =&gt; res.json()) // json을 돌려주므로 json()을 이용해 객체로 파싱
    .then((data) =&gt; {           // 이 내용은 바로 아래에서 설명
        const res_obj = data.I2790;
        console.log(res_obj.RESULT);
        console.log(res_obj.row);
    });</code></pre>
<hr>
<h2 id="postman을-이용한-api-테스트"><code>Postman</code>을 이용한 <code>API</code> 테스트</h2>
<p><a href="https://www.postman.com/">Postman 홈페이지</a></p>
<p>이 프로그램을 이용하면 아주 간편하게 <code>API</code>를 테스트할 수 있다.</p>
<p><code>Workspaces</code> -&gt; 왼쪽의 <code>Collections</code> -&gt; <code>+</code>를 눌러 새로운 <code>Collection</code> 생성 -&gt; <code>Collection</code> 이름 옆의 <code>...</code> 누르고 <code>Add request</code></p>
<p><img src="https://images.velog.io/images/profile_exe/post/ddb343eb-a094-4c10-ae5b-ff6dab33caef/image.png" alt="Postman request 생성 이미지"></p>
<p><code>METHOD</code>의 <code>DEFAULT</code>값은 <code>GET</code>이다.
위의 첨부된 <code>API</code> 요청 주소 이미지를 참고해서 <code>URL</code>을 넣어서 <code>Send</code>하면 결과를 표시해준다.</p>
<p><img src="https://images.velog.io/images/profile_exe/post/cf41777c-4583-4b03-80ce-3e9366015640/image.png" alt="Postman 사용해서 요청 및 데이터 확인"></p>
<p>받은 데이터는 <code>JSON</code>형태로 값은 다음과 같다. 맨 밑줄 <code>...</code>는 생략</p>
<pre><code class="language-js">{
    &quot;I2790&quot;: {
        &quot;RESULT&quot;: {
            &quot;MSG&quot;: &quot;정상처리되었습니다.&quot;,
            &quot;CODE&quot;: &quot;INFO-000&quot;
        },
        &quot;total_count&quot;: &quot;59886&quot;,
        &quot;row&quot;: [
            {
                &quot;NUTR_CONT3&quot;: &quot;33.5&quot;,
                &quot;NUTR_CONT2&quot;: &quot;39.7&quot;,
                &quot;NUTR_CONT1&quot;: &quot;368.8&quot;,
                &quot;SERVING_SIZE&quot;: &quot;500&quot;,
                &quot;MAKER_NAME&quot;: &quot;&quot;,
                &quot;NUTR_CONT9&quot;: &quot;0.1&quot;,
                &quot;NUTR_CONT8&quot;: &quot;1.9&quot;,
                &quot;FOOD_CD&quot;: &quot;D000006&quot;,
                &quot;NUTR_CONT7&quot;: &quot;106.18&quot;,
                &quot;NUTR_CONT6&quot;: &quot;1264.31&quot;,
                &quot;NUTR_CONT5&quot;: &quot;16.9&quot;,
                &quot;NUTR_CONT4&quot;: &quot;8.5&quot;,
                &quot;DESC_KOR&quot;: &quot;꿩불고기&quot;,
                &quot;SAMPLING_MONTH_NAME&quot;: &quot;평균&quot;,
                &quot;SUB_REF_NAME&quot;: &quot;식약처(&#39;16) 제4권&quot;,
                &quot;SAMPLING_REGION_NAME&quot;: &quot;충주&quot;,
                &quot;GROUP_NAME&quot;: &quot;&quot;,
                &quot;RESEARCH_YEAR&quot;: &quot;2019&quot;,
                &quot;SAMPLING_REGION_CD&quot;: &quot;94&quot;,
                &quot;SAMPLING_MONTH_CD&quot;: &quot;AVG&quot;,
                &quot;NUM&quot;: &quot;1&quot;
            },
          ...</code></pre>
<p>이것을 통해 <code>JavaScript</code> 코드의 하단에 있던 <code>fetch API</code>에서 다음과 같이 사용한 이유가 나온다.</p>
<pre><code class="language-js">fetch(&#39;api.php&#39;, init)
    .then((res) =&gt; res.json()) // json을 돌려주므로 json()을 이용해 객체로 파싱
    .then((data) =&gt; {           // 이 내용은 바로 아래에서 설명
        const res_obj = data.I2790;
        console.log(res_obj.RESULT);
        console.log(res_obj.row);
    });</code></pre>
<p>맨 처음 <code>API</code> 아이디(<code>keyId</code>)가 나오고 그 아래 <code>RESULT</code>, <code>total_count</code>, <code>row</code>가 있고, <code>row</code>안에 데이터가 들어있는 형태이다.</p>
<p>그래서 <code>data.I2790</code>을 <code>res_obj</code>에 저장한 후 <code>RESULT</code>와 <code>row</code>를 출력한 것이다. 콘솔창을 확인해보면 다음과 같이 출력된다.</p>
<p><img src="https://images.velog.io/images/profile_exe/post/b326176a-c1e7-4214-bc09-0e51355a1bd9/image.png" alt="API 호출 후 Console 확인"></p>
<hr>
<h2 id="curl---client-url-library"><code>cURL - Client URL Library</code></h2>
<p><a href="https://www.php.net/manual/en/book.curl.php">curl - PHP document</a></p>
<blockquote>
<p>PHP는 Daniel Stenberg가 만든 라이브러리인 libcurl을 지원하므로 다양한 유형의 프로토콜을 사용하여 다양한 유형의 서버에 연결하고 통신할 수 있습니다. libcurl은 현재 http, https, ftp, gopher, telnet, dict, file 및 ldap 프로토콜을 지원합니다. libcurl은 또한 HTTPS 인증서, HTTP POST, HTTP PUT, FTP 업로드(PHP의 ftp 확장으로도 수행 가능), HTTP 양식 기반 업로드, 프록시, 쿠키 및 사용자+암호 인증을 지원합니다.</p>
</blockquote>
<p>위에 서술된 여러 <strong>프로토콜</strong>로 데이터를 <code>create</code>, <code>read</code>, <code>update</code>, <code>delete</code> 할 수 있는 <strong>라이브러리</strong>다.</p>
<h3 id="사용법">사용법</h3>
<p><a href="https://www.php.net/manual/en/curl.examples-basic.php">Basic curl example - PHP document</a></p>
<blockquote>
</blockquote>
<pre><code class="language-php">$ch = curl_init(&quot;http://www.example.com/&quot;);
$fp = fopen(&quot;example_homepage.txt&quot;, &quot;w&quot;);
&gt;
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_HEADER, 0);
&gt;
curl_exec($ch);
if(curl_error($ch)) {
    fwrite($fp, curl_error($ch));
}
&gt;
curl_close($ch);
fclose($fp);</code></pre>
<ul>
<li><code>curl_init()</code> : <code>cURL session</code> <strong>초기화</strong></li>
<li><code>curl_setopt()</code> : <strong>전송</strong>을 위한 <strong>옵션</strong> 지정</li>
<li><code>curl_exec()</code> : <code>session</code> <strong>실행</strong></li>
<li><code>curl_close()</code> : <code>session</code> <strong>종료</strong></li>
</ul>
<hr>
<h2 id="curl을-이용해-php에서-api-사용"><code>cURL</code>을 이용해 <code>PHP</code>에서 <code>API</code> 사용</h2>
<p>앞의 <code>JavaScript</code> 코드로 <code>Ajax</code>로 <code>PHP</code>에 요청 시 해당 데이터를 가지고 <code>API</code>를 사용하는 코드이다.</p>
<p>여기서 <code>http_build_query()</code>함수가 사용되었는데, 객체의 내용을 <code>Query String</code>으로 변경해주는 함수이다. 특정 반복문 필요 없이 간단하게 이 함수만 호출하면 되므로 매우 유용하다.</p>
<p>이를 이용하기 위해서 <code>JS</code>에서 보낸 <strong>선택 요청 인자</strong>들을 객체로 저장해둔 것이다.</p>
<pre><code class="language-php">function empty_filter($obj) {    // 선택 인자의 값이 없으면(&quot;&quot;) 제거
    foreach ($obj as $key =&gt; $value) {
        if (empty($value)) {
            unset($obj-&gt;$key);
        }
    }
    return $obj;
}

// fetch API로 보낸 request_body는 json 형태이므로 파싱 해서 PHP 객체로 사용
$ajax_data = json_decode(file_get_contents(&#39;php://input&#39;));

// 위의 필터링 함수를 사용해 불필요한 값 제거
$filtered_params = empty_filter(array($ajax_data-&gt;params)[0]);

// http_build_query() 함수로 객체를 QueryString으로 만들어준다.
$query = http_build_query($filtered_params);

// 선택 요청 인자가 존재하는 경우 붙여주기
// 존재하지 않는다면 url은 그대로이다.
$url = $ajax_data-&gt;url .= $query ? &#39;?&#39;.$query : &#39;&#39;;

// curl에 적용할 옵션들을 저장
$options = array(
    CURLOPT_URL =&gt; $url,    // 최종 url도 이때 넣어준다.
    CURLOPT_RETURNTRANSFER =&gt; true,  // 반환된 값을 string으로 변환해 저장
    CURLOPT_SSL_VERIFYPEER =&gt; false, // true인 경우 https 통신이 불가한 경우 발생
);

// curl 세션 초기화
$ch = curl_init();

// 이것도 유용한 함수 array를 받아서 한번에 옵션을 지정
curl_setopt_array($ch, $options);

// 실행 -&gt; API 서버에서 반환한 데이터를 $response 변수에 저장한다.
$response = curl_exec($ch);    

// 세션 종료
curl_close($ch);

echo $response;    // JSON 형태이므로 JavaScript에서 json()을 통해 파싱</code></pre>
<h3 id="🍯꿀팁">🍯꿀팁</h3>
<h4 id="1-http_build_query">1. <code>http_build_query()</code></h4>
<p><code>URL</code>에 <code>Query String</code>을 넣어줄 때, 반복문이나 하드코딩 없이 <code>http_build_query()</code> 함수를 사용해서 매우 간편하게 넣어줄 수 있다.</p>
<p><code>key</code>, <code>value</code> 쌍을 <code>array</code>객체에 저장하고 이것을 함수에 넣어주면 <code>키=값&amp;키=값&amp;키=값&amp;...</code> 형태의 <code>Query String</code>으로 변환되는 것이다.</p>
<h4 id="2-curl_setopt_array">2. <code>curl_setopt_array()</code></h4>
<p>인터넷에서 여러 코드들을 보면 <code>curl</code> 옵션 지정 시 이렇게 사용한다.</p>
<pre><code class="language-php">curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);</code></pre>
<p>깔끔해보이지 않고, 여러 옵션들을 한번에 적용하고 싶어서 알아보니 <code>curl_setopt_array()</code>가 존재했다.</p>
<p>이를 사용하면 설정할 옵션들을 한 변수에서 관리 가능하며, 옵션 적용도 한줄이면 되니 매우 간편하고 가독성도 증가했다.</p>
<pre><code class="language-php">// curl에 적용할 옵션들을 저장
$options = array(
    CURLOPT_URL =&gt; $url,
    CURLOPT_RETURNTRANSFER =&gt; true,
    CURLOPT_SSL_VERIFYPEER =&gt; false,
);

$ch = curl_init();

// array를 받아서 한번에 옵션을 지정
curl_setopt_array($ch, $options);</code></pre>
<p>이렇게 말이다. </p>
<p>유지보수와 가독성을 위해서 간단하고 명확한 코드 사용은 중요하다 생각한다. 비록 이건 큰 차이점이 없어보이지만 다음 상황에서는 큰 차이를 보일 수 있다.</p>
<ul>
<li><p>옵션 추가 및 삭제를 자주 하는 경우</p>
</li>
<li><p>옵션에 대한 데이터를 객체로 받아오는 경우</p>
<blockquote>
<p>데이터를 포함한 객체의 <code>key</code>, <code>value</code>를 <code>array</code>객체에 저장하고 <code>curl_setopt_array()</code> 에 <code>array</code>객체 넣기</p>
</blockquote>
</li>
<li><p>코드의 길이가 증가하는 경우 가독성을 유지하기 위해</p>
</li>
</ul>
<hr>
<h3 id="정리">정리</h3>
<ul>
<li><p><code>CORS</code> 때문에 <code>API</code> 사용(호출)은 <strong>서버</strong>쪽에서 진행 (여기서는 PHP)</p>
</li>
<li><p><code>API</code> 사용 시 필요한 데이터는 객체로 저장하여 보내기 (코드 간결 &amp; 가독성 증가)</p>
</li>
<li><p>쿼리 스트링은 <code>http_build_query()</code> 사용하기</p>
</li>
<li><p><code>cURL</code>의 옵션 설정은 옵션들을 <code>array</code>에 저장 후 <code>curl_setopt_array()</code> 사용하기</p>
</li>
</ul>
<hr>
<h3 id="api-예제-repository"><code>API</code> 예제 <code>repository</code></h3>
<p><a href="https://github.com/Profile-exe/API-Test-Example">Profile-exe/API Test Example - Github</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[WEB] PHP 게시판 페이징, 페이지네이션]]></title>
            <link>https://velog.io/@profile_exe/WEB-PHP-board-pagination</link>
            <guid>https://velog.io/@profile_exe/WEB-PHP-board-pagination</guid>
            <pubDate>Sat, 21 Aug 2021 11:30:10 GMT</pubDate>
            <description><![CDATA[<p>게시판의 글이 계속 생성되면, 스크롤이 끝없이 내려갈 것이다. 이를 방지하기 위해 한 페이지에 표시할 글의 개수를 정하여 일정 간격으로 페이지를 만드려고 한다.</p>
<p>이때, <code>JavaScript</code>에서 <code>display:none;</code>으로 자신이 쓴 글만 <strong>필터링</strong>해서 보여주는 코드가 있었는데, 페이지 네이션을 하니까 불필요한 페이지도 생기고 페이지가 바뀔때마다 필터링도 꺼졌다.</p>
<p>이 현상을 고치기 위해 두가지 방법을 사용했다.</p>
<blockquote>
</blockquote>
<ol>
<li><strong>AJAX</strong> - 필터링 on/off 시에 필요한 데이터만 받아와서 표시</li>
<li><strong>Cookie</strong> - 필터링 on/off 유지를 위해 페이지 갱신 시 쿠키 유무 확인 후 필터링이 유지되도록 처리</li>
</ol>
<p>이번 포스트에서는 수시간의 뻘짓을 거쳐 완성된 <strong>글 필터링 &amp; 페이지네이션</strong> 코드를 보여주겠다.</p>
<p>코드가 많아서 스크롤이 길긴 한데, 프로젝트 링크를 이곳이랑 맨 아래 걸어두니 
<code>/classes/pagination.class.php</code>, <code>/js/my_board_list.js</code>, <code>/js/manage_cookie.js</code>, <code>/manage_board/process_board_list.php</code>, <code>/lib/delete_parameter.php</code></p>
<p>이 코드들을 참고하기를 바란다.</p>
<p><a href="https://github.com/Profile-exe/CRUDboardsite">게시판 프로젝트 링크 - Github</a></p>
<h3 id="프로젝트-디렉토리-구조-일부">프로젝트 디렉토리 구조 일부</h3>
<blockquote>
</blockquote>
<p>classes 폴더
...
└── <strong>pagination.class.php</strong></p>
<blockquote>
</blockquote>
<p>lib 폴더
...
└── <strong>delete_parameter.php</strong></p>
<blockquote>
</blockquote>
<p>js 폴더
...
├──  <strong>manage_cookie.js</strong>
└──  <strong>my_board_list.js</strong></p>
<blockquote>
</blockquote>
<p>manage_board 폴더
...
└──  <strong>process_board_list.php</strong></p>
<hr>
<h2 id="paging--pagination"><code>Paging &amp; Pagination</code></h2>
<p>둘의 차이에 대해 알아보았는데, 잘 설명된 글이 있었다.
<a href="https://drsggg.tistory.com/222">해당 블로그</a></p>
<blockquote>
</blockquote>
<ul>
<li><strong>paging</strong> - 자원을 보존하기 위해 데이터베이스에서 항목의 한 페이지를 로드하는 작업
ex) SQL 및 Model단</li>
</ul>
<blockquote>
</blockquote>
<ul>
<li><strong>pagination</strong>은 사용자가 다음에 로드 할 페이지를 선택할 수 있도록 일련의 페이지 번호를 제공하는 UI 요소
ex 각종 버튼들 : &lt;&lt; 이전 1 2 3 4 5 다음 &gt;&gt;</li>
</ul>
<p>즉 <code>paging</code>을 이용해 <code>pagination</code>을 완성한다고 생각하면 되겠다.</p>
<hr>
<h2 id="1-pagination-클래스">1. <code>pagination</code> 클래스</h2>
<p>어떤 방식을 적용할지 보다가 클래스로 변환하신 분이 있어서 이 코드를 가져와서 내 방식대로 고쳐보았다. 달라진 점은 <a href="https://ymw0210.tistory.com/37">이 블로그를 참고</a>해라.</p>
<pre><code class="language-php">&lt;?php
require_once $_SERVER[&#39;DOCUMENT_ROOT&#39;].&#39;/classes/db.class.php&#39;;
require_once $_SERVER[&#39;DOCUMENT_ROOT&#39;].&#39;/lib/delete_parameter.php&#39;;

class Pagination {

    //클래스 내부에서 하단 페이지넘버 처리에 필요한 변수
    private
        $page,
        $total_page,
        $first_page,
        $last_page,
        $prev_page,
        $next_page,
        $total_block,
        $next_block,
        $next_block_page,
        $prev_block,
        $prev_block_page,
        $page_nav = &quot;&quot;,
        $PHP_SELF = &quot;/index.php&quot;;
    //설정에서 register_globals=Off 인 경우에 $PHP_SELF 수퍼변수는 동작하지 않기때문에 경로를 지정해주는것이 좋다.

    //클래스 외부에서 필요한 변수
    public
        $limit_idx,
        $page_set;

    //페이지 줄수, 블럭수 받아 데이터 정리
    public function __construct($page_count, $block_count, $page_num, $is_my_board) {
        $block_set      = $block_count; // 한페이지 블럭수
        $this-&gt;page_set = $page_count;  // 한페이지 줄수

        $result = DB::query(&quot;SELECT count(board_id) AS total FROM board {$is_my_board}&quot;)[0];
        $total = $result[&#39;total&#39;]; // 전체글수

        $this-&gt;total_page  = ceil($total / $this-&gt;page_set);        // 총페이지수(올림함수)
        $this-&gt;total_block = ceil($this-&gt;total_page / $block_set);  // 총블럭수(올림함수)

        $this-&gt;page = $page_num;                         // parameter로 현재 페이지정보를 받아옴
        $block = ceil($this-&gt;page / $block_set);    // 현재블럭(올림함수)
        $this-&gt;limit_idx = ($this-&gt;page - 1) * $this-&gt;page_set; // limit 시작위치

        $this-&gt;first_page = (($block - 1) * $block_set) + 1;    // 첫번째 페이지번호
        $this-&gt;last_page  = min($this-&gt;total_page, $block * $block_set); // 마지막 페이지번호

        $this-&gt;prev_page = $this-&gt;page - 1; // 이전페이지
        $this-&gt;next_page = $this-&gt;page + 1; // 다음페이지

        $this-&gt;prev_block = $block - 1; // 이전블럭
        $this-&gt;next_block = $block + 1; // 다음블럭

        // 이전블럭을 블럭의 마지막으로 하려면...
        $this-&gt;prev_block_page = $this-&gt;prev_block * $block_set; // 이전블럭 페이지번호

        // 이전블럭을 블럭의 첫페이지로 하려면...
        //$prev_block_page = $prev_block * $block_set - ($block_set - 1);

        $this-&gt;next_block_page = $this-&gt;next_block * $block_set - ($block_set - 1); // 다음블럭 페이지번호
    }

    //하단 페이지 넘버링
    public function BottomPageNumber(): string {
        $this-&gt;page_nav .= ($this-&gt;prev_page &gt; 0) ?
            &quot;&lt;li class=&#39;page-item&#39;&gt;&lt;a class=&#39;page-link&#39; href=&#39;$this-&gt;PHP_SELF?page=$this-&gt;prev_page&#39;&gt;Prev&lt;/a&gt;&lt;/li&gt;&quot; :
            &quot;&lt;li class=&#39;page-item disabled&#39;&gt;&lt;span class=&#39;page-link&#39;&gt;Prev&lt;/span&gt;&lt;/li&gt;&quot;;

        $this-&gt;page_nav .= ($this-&gt;prev_block &gt; 0) ?
            &quot;&lt;li class=&#39;page-item&#39;&gt;&lt;a class=&#39;page-link&#39; href=&#39;$this-&gt;PHP_SELF?page=$this-&gt;prev_block_page&#39;&gt;...&lt;/a&gt;&lt;/li&gt;&quot; :
            &quot;&lt;li class=&#39;page-item disabled&#39;&gt;&lt;span class=&#39;page-link&#39;&gt;...&lt;/span&gt;&lt;/li&gt;&quot;;

        for ($i = $this-&gt;first_page; $i &lt;= $this-&gt;last_page; $i++) {
            $this-&gt;page_nav .= ($i != $this-&gt;page) ?
                &quot;&lt;li class=&#39;page-item&#39;&gt;&lt;a class=&#39;page-link&#39; href=&#39;$this-&gt;PHP_SELF?page=$i&#39;&gt;$i&lt;/a&gt;&lt;/li&gt;&quot; :
                &quot;&lt;li class=&#39;page-item disabled&#39;&gt;&lt;span class=&#39;page-link&#39;&gt;$i&lt;/span&gt;&lt;/li&gt;&quot;;
        }

        $this-&gt;page_nav .= ($this-&gt;next_block &lt;= $this-&gt;total_block) ?
            &quot;&lt;li class=&#39;page-item&#39;&gt;&lt;a class=&#39;page-link&#39; href=&#39;$this-&gt;PHP_SELF?page=$this-&gt;next_block_page&#39;&gt;...&lt;/a&gt;&lt;/li&gt;&quot; :
            &quot;&lt;li class=&#39;page-item disabled&#39;&gt;&lt;span class=&#39;page-link&#39;&gt;...&lt;/span&gt;&lt;/li&gt;&quot;;

        $this-&gt;page_nav .= ($this-&gt;next_page &lt;= $this-&gt;total_page) ?
            &quot;&lt;li class=&#39;page-item&#39;&gt;&lt;a class=&#39;page-link&#39; href=&#39;$this-&gt;PHP_SELF?page=$this-&gt;next_page&#39;&gt;Next&lt;/a&gt;&lt;/li&gt;&quot; :
            &quot;&lt;li class=&#39;page-item disabled&#39;&gt;&lt;span class=&#39;page-link&#39;&gt;Next&lt;/span&gt;&lt;/li&gt;&quot;;

        // 페이지 파라미터 중복 제거
        $this-&gt;PHP_SELF = delete_parameter($this-&gt;PHP_SELF, &#39;page&#39;);

        return $this-&gt;page_nav;
    }
}</code></pre>
<ul>
<li><p><strong>생성자</strong> - <code>__construct()</code></p>
<blockquote>
</blockquote>
<p>한 페이지에 보여줄 줄 수(<code>$page_count</code>), 네비게이션에 보여줄 블럭 수(<code>$block_count</code>), 현재 페이지(<code>$page_num</code>), 필터링 on/off 유무(<code>$is_my_board</code>)를 받는다.
받은 정보를 바탕으로한 필요한 정보들을 필드에 저장해둔다.</p>
</li>
<li><p><strong>네비게이션 생성 메소드</strong> - <code>BottomPageNumber()</code></p>
<blockquote>
</blockquote>
<p>뷰포트 하단에 표시할 <code>nav</code>의 내용인 <code>html 텍스트</code>를 <code>string</code> 형태로 저장하고 반환한다. 이 <code>string</code>을 <strong>AJAX</strong>이용 시 <strong>응답 데이터</strong>로 반환해주고, <code>JavaScript</code>에서 <code>innerHTML</code>에 넣어주는 식으로 화면에 표시할 것이다.</p>
</li>
<li><p><em>부트스트랩*</em>의 <code>pagination</code> CSS가 적용되게 작성했다.</p>
</li>
<li><p><code>parameter</code>를 지워주는 함수 - <code>delete_parameter()</code></p>
<blockquote>
</blockquote>
<p><code>URL</code>에 존재하는 <strong>특정 값</strong>을 지운다. <code>page</code>가 여러개 붙지 않게 제거해주는 역할이다.
<code>/lib/delete_parameter.php</code>에 존재한다. <code>require_once</code>로 가져와서 사용</p>
</li>
</ul>
<hr>
<h2 id="2-process_board_listphp">2. <code>process_board_list.php</code></h2>
<p><code>JavaScript</code> 파일에서 <code>JSON</code>형태로 데이터를 넘겨주면, <code>json_decode()</code>를 통해 <code>PHP 객체</code>로 사용할 수 있다.</p>
<pre><code class="language-php">&lt;?php
require_once $_SERVER[&#39;DOCUMENT_ROOT&#39;].&#39;/classes/db.class.php&#39;;
require_once $_SERVER[&#39;DOCUMENT_ROOT&#39;].&#39;/classes/pagination.class.php&#39;;

// 세션 시작 - $_SESISON[&#39;user_id&#39;] 사용을 위해.
if (session_status() == PHP_SESSION_NONE) {
    session_start();
}

$post_data = json_decode(file_get_contents(&#39;php://input&#39;)); // JSON 형태로 받은 데이터 객체로 parsing

// 필터링 유무에 따라 WHERE 구문 삽입 유무로 쿼리문 결정
// WHERE이 삽입되면 자신이 작성한 글만 fetch
$my_board = $post_data-&gt;is_my_board ? &quot;WHERE board.user_id = &#39;{$_SESSION[&#39;user_id&#39;]}&#39;\n&quot; : &#39;&#39;;

$sql = &quot;
    SELECT
        board_id, board_title, user_name, date_format(created, &#39;%m-%d %H:%i&#39;) as created, view_count 
    FROM board INNER JOIN user
    ON board.user_id = user.user_id
&quot;;

$sql .= $my_board;  // 만약 필터링 off인 경우 $my_board == &#39;&#39; 이므로 아무런 영향이 없다.

// 게시글 줄 수, 블럭 수
$pagination = new Pagination($post_data-&gt;page_count, 5, $post_data-&gt;page_num, $my_board);

// 페이지네이션을 위한 LIMIT
$sql .= trim(&quot;    
    ORDER BY created DESC LIMIT {$pagination-&gt;limit_idx}, {$pagination-&gt;page_set}
&quot;);

$result = DB::query($sql);

$topic_list = &#39;&#39;;
if ($result) {  // 글이 존재하는 경우 table-row로 생성
    foreach ($result as $index =&gt; $row) {
        $topic_list .= &quot;
            &lt;tr style=&#39;cursor:pointer&#39; onclick=&#39;location.href=\&quot;/manage_board/board_read.php?id={$row[&#39;board_id&#39;]}\&quot;&#39;&gt;
                &lt;th class=&#39;col-1 text-center&#39; scope=&#39;row&#39;&gt;{$row[&#39;board_id&#39;]}&lt;/th&gt;
                &lt;td class=&#39;col-7&#39;&gt;{$row[&#39;board_title&#39;]}&lt;/td&gt;
                &lt;td class=&#39;col-1 text-center&#39;&gt;{$row[&#39;user_name&#39;]}&lt;/td&gt;
                &lt;td class=&#39;col-2 text-center&#39;&gt;{$row[&#39;created&#39;]}&lt;/td&gt;
                &lt;td class=&#39;col-1 text-center&#39;&gt;{$row[&#39;view_count&#39;]}&lt;/td&gt;
            &lt;/tr&gt;
        &quot;;
    }
}
// table-row랑 pagenation의 nav를 반환할 것임
$response = array(
    &#39;topic_list&#39; =&gt; $topic_list,
    &#39;page_nav&#39;   =&gt; $pagination-&gt;BottomPageNumber()
);

echo json_encode($response);    // JSON으로 encoding 후 반환</code></pre>
<ul>
<li><p>필터링 유무 - <code>$my_board</code></p>
<blockquote>
</blockquote>
<p>필터링 유무를 <code>$my_board</code>에 값을 넣어 <code>WHERE ~</code> 쿼리문을 넣을지 말지 결정한다. 필터링이 꺼져있으면 <code>$my_board</code>는 <code>&#39;&#39;</code> 이므로 <code>$sql .= $my_board</code>에서 아무런 영향을 끼치지 않는다.</p>
</li>
<li><p>페이지네이션 객체 - <code>$pagination</code></p>
<blockquote>
</blockquote>
<p><code>$pagination</code>에 페이지네이션 객체를 생성한 후 <code>public</code>필드인 <code>limit_idx</code>와 <code>page_set</code>으로 쿼리문의 <code>OFFSET</code>, <code>LIMIT</code>을 결정한다.</p>
</li>
<li><p>데이터 생성</p>
<blockquote>
</blockquote>
</li>
<li><p><em>필터링 유무로 결정(WHERE 유무)*</em>되는 쿼리문의 결과를 받아와서 글 목록을 <code>$topic_list</code>로 만들어둔다. 또한, 페이지네이션 객체에서 <code>nav</code>를 생성하는 <code>BottomPageNumber()</code> 함수를 호출하여 해당 <code>string</code>을 저장한다.</p>
</li>
<li><p>응답 내용 저장 - <code>$response</code></p>
<blockquote>
</blockquote>
</li>
<li><p><em>연관배열*</em>을 사용하여 해당 <code>key</code>이름에 맞게 저장 후 <code>json_encode()</code>로 <code>echo</code> 하여 응답(response)을 해준다.</p>
</li>
</ul>
<hr>
<h2 id="3-my_board_listjs">3. <code>my_board_list.js</code></h2>
<p><code>AJAX</code>를 이용하여 데이터를 전달받고 이를 <code>innerHTML</code>에 넣어서 화면을 갱신한다.
쿠키 유무에 따라 필터링 스위치 작동 유무를 결정한다.</p>
<pre><code class="language-js">const url = new URL(window.location.href);
const urlParams = url.searchParams;

let page = urlParams.get(&#39;page&#39;);
if (page === null) page = &#39;1&#39;;

const body = {          // AJAX request body
    id: &#39;&#39;,
    page_count: 10,     // 한 페이지에 표시할 글의 수
    page_num: page,     // 현재 페이지
    is_my_board: false  // 필터링 on / off 유무
}

function my_board_checked() {       // 페이지 변경 시 필터링 스위치 checked 확인
    const board_switch = document.getElementById(&#39;my_boards_switch&#39;);
    if (get_cookie(&#39;my_board_filter&#39;) !== &#39;&#39;) { // 쿠키 유무로 스위치 on/off 확인
        body.is_my_board = true;
        board_switch.click();       // 스위치 활성화 되어 있으면 click()로 재활성화
    } else {
        body.is_my_board = false;
    }
}

document.getElementById(&#39;my_boards_switch&#39;).addEventListener(&#39;click&#39;, (e) =&gt; {
    // 체크 된 경우 true
    if (e.target.checked) {
        body.id = e.target.value;
        body.is_my_board = true;
        set_cookie(&#39;my_board_filter&#39;, true, 1);     // 쿠키 생성
    } else {
        body.is_my_board = false;
        set_cookie(&#39;my_board_filter&#39;, false, 0);    // 쿠키 제거
    }
    get_board_list(body);   // 목록 불러오기
});

function get_board_list(body) { // AJAX 이용
    const init = {
        method: &#39;POST&#39;,
        cache: &#39;no-cache&#39;,
        headers: {
            &#39;Content-Type&#39;: &#39;application/json; charset=utf-8&#39;
        },
        body: JSON.stringify(body)  // JSON 형태로 POST request
    }

    fetch(&#39;/manage_board/process_board_list.php&#39;, init)
        .then((res) =&gt; res.text())
        .then((data) =&gt; {
            const response_obj = JSON.parse(data);  // JSON 형태로 응답받고 parsing 후 사용
            document.getElementById(&#39;board_list&#39;).innerHTML = response_obj[&#39;topic_list&#39;];
            document.getElementById(&#39;page-list&#39;).innerHTML = response_obj[&#39;page_nav&#39;];
        });
}

my_board_checked();
get_board_list(body);</code></pre>
<ul>
<li><p>현재 페이지 값 얻기 - <code>page</code></p>
<blockquote>
</blockquote>
<p><code>URL</code> 객체를 이용하여 <code>urlParams.get(&#39;page&#39;)</code>를 통해 <code>page</code> 파라미터를 얻어와서 저장한다.</p>
</li>
<li><p><strong>request body</strong> - <code>body</code></p>
<blockquote>
</blockquote>
<p><code>fetch API</code>를 이용해 요청 시 <code>request body</code> 부분이다.</p>
<blockquote>
<ul>
<li><strong><code>id</code></strong> - 로그인 시 <strong>사용자의 <code>user_id</code></strong></li>
<li><strong><code>page_count</code></strong> - 한 페이지에 <strong>표시할 글의 수</strong></li>
<li><strong><code>page_num</code></strong> - <strong>현재 페이지</strong></li>
<li><strong><code>is_my_board</code></strong> - <strong>필터링 유무</strong></li>
</ul>
</blockquote>
</li>
<li><p>페이지 갱신 시 필터링 유지 - <code>my_board_checked()</code></p>
<blockquote>
</blockquote>
<p><code>manage_cookie.js</code>에 있는 <code>get_cookie</code>로 쿠키를 얻어와서 <strong>필터링 유무 판별</strong>한다.</p>
<blockquote>
<ul>
<li><code>true</code> - <code>request body(body 변수)</code>에 있는 필터링 유무(<code>is_my_board</code>)를 <code>true</code>로 변경하고, <code>switch</code>를 <strong>활성화</strong>시킨다.</li>
<li><code>false</code> - 필터링 유무를 <code>false</code>로 변경한다. <code>switch</code>는 페이지 갱신 시 기본적으로 비활성화 상태이므로 그대로 놔두면 된다.</li>
</ul>
</blockquote>
</li>
<li><p>스위치 클릭 유무에 따른 필터링 - <code>switch</code>의 <strong>이벤트 리스너</strong></p>
<blockquote>
<ul>
<li><code>checked</code> - <strong>로그인을 한 경우만 활성화 가능</strong>하게 만들었다. 그러므로 사용자 사용자 아이디를 전달해준다. </li>
</ul>
</blockquote>
<ul>
<li>사용자의 아이디 저장 - <code>id = e.target.value</code></li>
<li>필터링 유무 변경 - <code>is_my_board = true</code></li>
<li><strong>쿠키 생성</strong> - <code>manage_cookie.js</code>의 <code>set_cookie()</code>사용<blockquote>
<ul>
<li><code>unchecked</code> - 필터링을 안하므로 아이디를 전달해줄 필요가 없다.</li>
</ul>
</blockquote>
</li>
<li>필터링 유무 변경 - <code>is_my_board = false</code></li>
<li><strong>쿠키 제거</strong> - <strong>수명을 0으로 하면 쿠키가 제거된다</strong></li>
</ul>
</li>
<li><p><code>AJAX</code>로 데이터 받아서 화면에 뿌려주기</p>
<blockquote>
</blockquote>
<p>사용자 아이디, 필터링 유무 등의 필요한 정보는 <strong><code>body</code></strong> 에 저장해두었으므로 <code>init</code>객체의 <code>body</code>필드에다가 <code>JSON.stringify()</code>를 이용하여 저장 후 요청한다.</p>
<blockquote>
</blockquote>
<p><code>echo</code>로 응답했으므로 <code>text()</code>로 받은 후 <code>JSON.parse()</code>를 사용해 일반 객체로 만들어준다.</p>
<blockquote>
</blockquote>
<p>이후 <strong>연관배열</strong> 형태의 <code>key</code>들이 객체의 <strong>필드 이름</strong>이 되었으므로 <code>HTML</code>에 뿌릴 위치를 <code>getElementById()</code>로 가지고와서 <code>innerHTML</code>에 <code>객체[&#39;필드 이름&#39;]</code>을 넣어준다.</p>
</li>
</ul>
<hr>
<h2 id="4-indexphp">4. <code>index.php</code></h2>
<p>로그인을 하지 않은 경우 <strong>필터링 스위치</strong>를 <code>disabled</code> 해준다. 로그인을 해서 필터링을 작동시킬 수 있으면 <code>input</code>태그의 <strong><code>value</code></strong>에 <code>$_SESSION[&#39;user_id&#39;]</code>를 넣어서 <code>JavaScript</code>에서 사용할 수 있도록 해준다.</p>
<p><code>...</code>은 생략을 뜻한다.</p>
<pre><code class="language-php">// 세션 시작
if (session_status() == PHP_SESSION_NONE) {
    session_start();
}

...

$my_boards_switch = &#39;
    &lt;input type=&quot;checkbox&quot; class=&quot;form-check-input&quot; id=&quot;my_boards_switch&quot; disabled&gt;
    &lt;label for=&quot;my_boards_switch&quot; class=&quot;form-check-label&quot;&gt;내가 쓴 글&lt;/label&gt;
&#39;;

// 로그인을 한 경우
if (isset($_SESSION[&#39;user_id&#39;]) &amp;&amp; $_SESSION[&#39;user_id&#39;] != &#39;&#39;) {
...
    $my_boards_switch = &quot;
        &lt;input type=&#39;checkbox&#39; value=&#39;\&quot;{$_SESSION[&#39;user_id&#39;]}\&quot;&#39; class=&#39;form-check-input&#39; id=&#39;my_boards_switch&#39;&gt;
        &lt;label for=&#39;my_boards_switch&#39; class=&#39;form-check-label&#39;&gt;내가 쓴 글&lt;/label&gt;
    &quot;;
}

...
                    &lt;div class=&quot;form-check form-switch&quot;&gt;
                        &lt;?=$my_boards_switch?&gt;
                    &lt;/div&gt;
...
                &lt;tbody id=&quot;board_list&quot; &gt;
                &lt;/tbody&gt;
...
        &lt;footer class=&quot;mt-5&quot;&gt;
            &lt;nav id=&quot;nav-pagination&quot; class=&quot;position-absolute bottom-0 start-50 translate-middle&quot;&gt;
                &lt;ul id=&quot;page-list&quot; class=&quot;pagination d-flex justify-content-center&quot;&gt;
                &lt;/ul&gt;
            &lt;/nav&gt;
        &lt;/footer&gt;</code></pre>
<ul>
<li>로그인 유무에 따른 스위치 활성/비활성 - <code>$my_board_switch</code><blockquote>
</blockquote>
</li>
<li><em>로그인을 안한 경우*</em> 기본 값으로 <code>disabled</code>가 들어가있다.<blockquote>
</blockquote>
</li>
<li><em>로그인을 한 경우*</em> 사용자 아이디를 <code>value</code>에 넣어서(<code>value=&#39;\&quot;{$_SESSION[&#39;user_id&#39;]}\&quot;&#39;)</code> <code>my_board_list.js</code>에서 <code>e.target.value</code>로 가져올 수 있도록 했다.</li>
</ul>
<hr>
<h3 id="정리">정리</h3>
<p>이번에 페이지네이션과 필터링 기능을 구현하며 많은 시행착오를 겪었다. 거의 하루를 날려버렸는데, 이렇게 뻘짓을 좀 하고 해결한 코드를 보고 나니 머릿속으로 대강 정리가 되었다. </p>
<ul>
<li><p><code>JavaScript</code>는 <code>DOM</code> 조작이 가능하므로 <code>HTML</code>쪽에서 값을 넘겨주고 싶으면 태그에서 <code>value</code>등의 어트리뷰트에 담기</p>
</li>
<li><p>응답 내용을 명시적으로 하고 싶으면 <strong>연관배열</strong>을 사용해 <code>JSON</code>형태로 반환하기</p>
</li>
<li><p><code>AJAX</code>로 값을 넘겨주는 데이터는 <code>request body</code> 전용 객체를 만들어서 값을 저장한 후 <code>JSON.stringify()</code>를 이용해 <code>string</code>형태로 넘겨주기</p>
</li>
<li><p><code>PHP</code>와 달리 <code>JavaScript</code>에서는 <code>Cookie</code> 수명 기준이 <code>ms</code> 단위였음.. (1000 곱해주기)</p>
</li>
</ul>
<p>동아리에서 웹 강의를 듣고 생활 코딩 강의도 쭉 돌려보고나니 직접 무언가를 만들어 보는 편이 좋을 것 같다고 생각해 시작하게 된 게시판 만들기였다. </p>
<p>이제 검색 기능 구현? 정도 남았는데, 구현 할지 안할지는 좀 고민이 된다. 현재 포스트 기준 88번의 Commit을 했는데, 생각했던 기능들은 거의 구현한 것 같다.</p>
<p>다음 프로젝트는 프레임워크를 이용해서 진행해보려 한다. <code>node js</code> 또는 <code>django</code>인데 고민 좀 해봐야겠다.</p>
<hr>
<h3 id="게시판-프로젝트-url">게시판 프로젝트 URL</h3>
<p><a href="https://github.com/Profile-exe/CRUDboardsite">Github - Profile-exe/CRUDboardsite</a></p>
]]></description>
        </item>
    </channel>
</rss>