<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jeong_twelve.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sun, 01 Feb 2026 09:58:31 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jeong_twelve.log</title>
            <url>https://velog.velcdn.com/images/jeong_twelve/profile/978d55f0-4fe3-40b2-ab7d-6dbe28f5ddd1/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jeong_twelve.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jeong_twelve" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[딥러닝 기초 이론]]></title>
            <link>https://velog.io/@jeong_twelve/ANN-DNN-%EB%8B%A8%EC%B8%B5%EB%8B%A4%EC%B8%B5-%ED%8D%BC%EC%85%89%ED%8A%B8%EB%A1%A0</link>
            <guid>https://velog.io/@jeong_twelve/ANN-DNN-%EB%8B%A8%EC%B8%B5%EB%8B%A4%EC%B8%B5-%ED%8D%BC%EC%85%89%ED%8A%B8%EB%A1%A0</guid>
            <pubDate>Sun, 01 Feb 2026 09:58:31 GMT</pubDate>
            <description><![CDATA[<h1 id="인공-신경망ann">인공 신경망(ANN)</h1>
<h2 id="뉴런">뉴런</h2>
<p>신경세포 1개를 의미, 인간의 뇌는 약 1000억 개의 뉴런으로 구성</p>
<ul>
<li>신경세포체: 핵이 있는 세포 부분</li>
<li>가지돌기: 다른 세포에서 신호를 받는 부분</li>
<li>축삭돌기: 다른 세포에 신호를 주는 부분</li>
<li>시냅스: 돌기 사이에서 다른 뉴런에 신호를 전달하는 부분
<img src="https://velog.velcdn.com/images/jeong_twelve/post/f131d1b2-2939-4df0-b552-c9b3ec91a462/image.png" alt="">
가지돌기에 자극을 받게 되면, 신경세포체를 통해 축삭돌기에 화학물질이 전달되어, 전이 발생,
전이의 정도가 특정 임계값을 넘으면, 시냅스를 통해 신호를 다음 뉴런에 전달</li>
</ul>
<p>이런 뉴런의 활동을 코딩, 즉 논리회로로 만든것이 퍼셉트론(=ANN)</p>
<ul>
<li>$$x_0,x_1,x_2,x_3,...,x_n$$의 각각의 입력값을 받아서(=가지 돌기 자극)</li>
<li>$$x_0 * w_0+b,x_1<em>w_1+b,x_n</em>w_n+b$$ 각각을 가중치와 곱해서(bias도 더함)</li>
<li>$$\textstyle\sum_{i=1}^n(x_i*w_i+b)$$ 이 특정 입계값을 넘으면 1, 아니면 0과 같은 알고리즘으로 구성</li>
<li>예: if $$\textstyle\sum_{i=1}^n(x_i*w_i+b)&gt;0$$ then 1, else 0</li>
<li>실제 결과와 오차가 생기면, 가중치를 업데이트함<ul>
<li>실제 결과와의 오차를 기반으로 가중치 업데이트를 할 수 있는 구조로 인해, <strong>학습</strong>이 가능
<img src="https://velog.velcdn.com/images/jeong_twelve/post/a1deec67-31ae-4e40-99fe-cd3505be7096/image.png" alt=""></li>
</ul>
</li>
</ul>
<p>여기서 오차에 따라 가중치 업데이트 하는 과정이 앞 포스트의 경사하강법</p>
<h3 id="다층-퍼셉트론">다층 퍼셉트론</h3>
<p>기존 퍼셉트론으론 XOR 게이트 구현이 입력층 만으로는(1개의 직선만으로는) 불가능하다는 한계 직면,
<img src="https://velog.velcdn.com/images/jeong_twelve/post/4c0e2d47-344d-4d55-a1c3-003dcb253133/image.png" alt="">
-&gt; OR, NAND, AND 게이트 조합으로 XOR을 구현하듯이 입력과 출력 사이에 은닉층을 두면 XOR 문제 해결 가능하다는 관점
<img src="https://velog.velcdn.com/images/jeong_twelve/post/3ffd6f53-3d98-465c-991a-009fd4face81/image.png" alt="">
-&gt; 따라서 layer를 여러 층 둘수록(직선이 많아질수록) 더욱 복잡한 문제를 해결할 수 있을 것이다는 결론이 도출됨
<img src="https://velog.velcdn.com/images/jeong_twelve/post/702f224b-2661-43b7-a2a4-0d6658a84938/image.png" alt=""></p>
<ul>
<li><strong>은닉층이 2개 이상인 신경망을 Deep Neural Network, DNN(=다층 퍼셉트론)이라고 함</strong></li>
</ul>
<h1 id="activation-function-non-linear-function">Activation function, Non-linear function</h1>
<p>세상의 많은 문제는 linear(선형)보다 non-linear(비선형) 문제가 더 많다.
하지만 퍼셉트론의 연산의 구조가 <strong>선형적인</strong> 선밖에 표현할 수 없기 때문에 한계에 직면,
-&gt; 다층 퍼셉트론에서 은닉층 각각의 Activation function으로 non-linear function을 사용하여, 출력층에서 non-linear function이 출력되도록 함.
<img src="https://velog.velcdn.com/images/jeong_twelve/post/a2d72eb3-bde8-4abc-b8fb-59af7a315735/image.png" alt="">
ex: $$sigmoid(Wx+b)=\frac{1}{1+e^{-(Wx+b)}}$$
<img src="https://velog.velcdn.com/images/jeong_twelve/post/2f703286-266b-45fc-973e-bc1e74d27e23/image.png" alt=""></p>
<h3 id="--depth은닉층의-수와-width가중합-계산-노드-수를-넓게-할수록-딥러닝은-보다-복잡한-형태의-문제를-모사할-수-있다">-&gt; depth(은닉층의 수)와 width(가중합 계산 노드 수)를 넓게 할수록, 딥러닝은 보다 복잡한 형태의 문제를 모사할 수 있다.</h3>
<ul>
<li>depth와 width를 통칭, network capacity라고 한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[딥러닝 사전지식]]></title>
            <link>https://velog.io/@jeong_twelve/%EB%94%A5%EB%9F%AC%EB%8B%9D-%EC%82%AC%EC%A0%84%EC%A7%80%EC%8B%9D</link>
            <guid>https://velog.io/@jeong_twelve/%EB%94%A5%EB%9F%AC%EB%8B%9D-%EC%82%AC%EC%A0%84%EC%A7%80%EC%8B%9D</guid>
            <pubDate>Tue, 27 Jan 2026 07:25:00 GMT</pubDate>
            <description><![CDATA[<h1 id="선형회귀linear-gegression">선형회귀(Linear Gegression)</h1>
<ul>
<li>독립변수(x)의 값에 따라, 종속적으로 변하는 종속변수(y) <pre><code>ex: y=ax+b</code></pre></li>
<li>선형 회귀는 한 개 이상의 독립 변수와 종속 변수간의 관계를 선형 모델로 구현</li>
</ul>
<h3 id="--단순-선형-회귀simple-linear-regression">- 단순 선형 회귀(Simple linear regression)</h3>
<ul>
<li>독립 변수가 하나인 경우</li>
<li>ex: y=wx+b,<ul>
<li>여기서 w:가중치, b:편향<h3 id="--다중-선형-회귀multiple-linear-regression">- 다중 선형 회귀(multiple linear regression)</h3>
</li>
</ul>
</li>
<li>독립 변수가 여러 개인 경우</li>
<li>ex: y=w1x1+w2x2+....+wnxn+b<ul>
<li>여기서 w1, w2, w3,...wn:가중치, b:편향</li>
</ul>
</li>
</ul>
<h1 id="선형회귀-예측-방법">선형회귀 예측 방법</h1>
<ul>
<li>독립 변수와 종속 변수 간의 관계가 선형적으로 표현이 될 것 같다면,
x와 y의 관계를 유추하기 위해 가설 작성<pre><code>ex: H(x) = wx+b</code></pre></li>
<li>가설식으로 예측된 값과, 실제 값 간의 차이를 가장 작게 만드는 적절한 가중치 w, 편향 b를 구하기 위해 오차 계산식을 정의해야함.<ul>
<li>이때 손실함수 등장</li>
</ul>
</li>
</ul>
<h3 id="손실함수">손실함수</h3>
<p><strong>MAE(Mean Absolute Error): 절대 평균 오차</strong></p>
<ul>
<li>실제 값과 예측값의 차이의 절대값의 평균</li>
<li>MAE = $$\frac{\textstyle\sum_{i=0}^N|y_i-\hat{y_i}|}{N}$$</li>
</ul>
<p><strong>MSE(Mean Squared Error): 평균 제곱근 오차</strong></p>
<ul>
<li>실제 값과 예측 값의 차이를 제곱한 값의 평균</li>
<li>MSE = $$\frac{\textstyle\sum_{i=0}^N(y_i-\hat{y_i})^2}{N}$$</li>
</ul>
<p><strong>RMSE(Root Mean Squared Error): 평균 제곱근 편차</strong></p>
<ul>
<li>MSE 값은 제곱 값이므로, 실제 오류 평균보다 값이 커지는 특성이 있으므로, 이를 줄이기 위해 MSE에 루트를 씌운 함수</li>
<li>RMSE = $$\sqrt{\frac{\textstyle\sum_{i=0}^N(y_i-\hat{y_i})^2}{N}}$$</li>
</ul>
<p><strong>RMSLE(Root Mean Squared Log Error):평균 제곱근 대수 오차</strong></p>
<ul>
<li>RMSE에 log를 적용한 함수</li>
<li>log 0은 존재할 수 없으므로 +1이 더해져있음</li>
<li>RMSLE = $$\sqrt{\frac{\textstyle\sum_{i=0}^N(\log(y_i+1)-\log(\hat{y_i}+1))^2}{N}}$$</li>
</ul>
<p><strong>회귀 문제에서는 주로 MSE를 많이 사용함(미분 계산에 용이하기 때문)</strong>
MSE로 오차를 계산한다면,
$$\frac{\textstyle\sum_{i=0}^N(y_i-\hat{y_i})^2}{N}=cost(w,b)$$
즉, $$cost(w,b)$$를 최소화하는 w와 b를 구하면 된다.</p>
<hr>
<p>이제 오차를 최소화하는 가중치 w와 편향 b를 찾아야 한다.</p>
<h3 id="최소-제곱법">최소 제곱법</h3>
<p>$$\frac{\textstyle\sum_{i=0}^N(y_i-\hat{y_i})^2}{N}=cost(w,b)$$가 최소가 되기 위해서는 각각 w,b로 편미분했을 때의 값이 0이면 된다.</p>
<ul>
<li>$$\frac{\partial}{\partial w}\textstyle\sum_{i=0}^n(y_i-(wx_i+b))^2=0$$</li>
<li>$$\frac{\partial}{\partial b}\textstyle\sum_{i=0}^n(y_i-(wx_i+b))^2=0$$</li>
</ul>
<p>하지만 최소제곱법은 $$w_i, b$$의 갯수만큼의 연립방정식이 필요하므로 독립변수가 많으면 계산이 어렵다. 특히, 딥러닝/머신러닝의 경우 대부분 독립변수가 매우 많아 다른 방법이 필요하다.</p>
<h3 id="경사하강법">경사하강법</h3>
<ul>
<li>$$w=w-\alpha\frac{\partial}{\partial w}cost(w,b)$$</li>
<li>$$b=b-\alpha\frac{\partial}{\partial b}cost(w,b)$$</li>
<li><strong>$$\alpha$$: Learning rate(하이퍼 파라미터, 임의로 설정해줘야 하는 값, 주로 0.01, 0.05, 0.1 등)</strong></li>
</ul>
<p>위와 같이 미분값(기울기값)이 +면, w는 더 낮은 값으로, 미분값(기울기값)이 -면, w는 더 큰 값으로 계산되며, 오차가 최소값에 근접한 값이 될 때까지 반복한다.</p>
<ul>
<li>Learning rate가 너무 크면, 최소값에 근접하지 못하고 발산할 수도 있고, 너무 낮게 잡으면 최소값에 근접하기 전에 끝날 수도 있다.
(따라서 일반적으로는 테스트를 통해 적절하게 설정한다.)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[baekjoon] 2075 N번째 큰 수(python)]]></title>
            <link>https://velog.io/@jeong_twelve/baekjoon-2075-N%EB%B2%88%EC%A7%B8-%ED%81%B0-%EC%88%98python</link>
            <guid>https://velog.io/@jeong_twelve/baekjoon-2075-N%EB%B2%88%EC%A7%B8-%ED%81%B0-%EC%88%98python</guid>
            <pubDate>Mon, 19 Jan 2026 07:31:56 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jeong_twelve/post/eaab3a1e-30bb-41df-a0c7-21417a6d753e/image.png" alt="">
예제 입력:</p>
<pre><code>5
12 7 9 15 5
13 8 11 19 6
21 10 26 31 16
48 14 28 35 25
52 20 32 41 49</code></pre><p>예제 출력:</p>
<pre><code>35</code></pre><p>메모리가 12MB로 넉넉하지 않아 최대한 리스트 사이즈를 작게 유지해야하면서도 시간제한이 1초라 정렬 알고리즘을 사용할 수 없었다.
어떻게 풀어야하나 찾아봤더니 파이썬 내장 자료구조 중에 heapq라는 것을 알게 되었다.
<img src="https://velog.velcdn.com/images/jeong_twelve/post/4b404cb2-0e6d-4d89-92a1-e29a0de96b83/image.png" alt="">
이런 구조로
왼쪽 이미지의 Min Heap은 리스트로 나타내면,
[10, 15, 30, 40, 50, 100, 40]이 된다.
(현재 인덱스 - 1) // 2 로 부모노드를 찾을 수 있다.</p>
<p>또한 메모리 크기를 만족하기 위해 heap의 크기를 입력 N으로 유지해주었다.</p>
<pre><code class="language-python">import sys, heapq

input = sys.stdin.readline
size = int(input())
matrix = []
for _ in range(size):
    num_lst = list(map(int, input().split()))

    if not matrix:
        for num in num_lst:
            heapq.heappush(matrix, num)
    else:
        for num in num_lst:
            if matrix[0] &lt; num:
                heapq.heapreplace(matrix, num)

print(matrix[0])</code></pre>
<p>if not matrix 조건문을 통해 첫 12 7 9 15 5이 들어와서 heappush를 통해 이진트리가 생성되고 [5, 7, 9, 15, 12]
else 조건문을 통해 다음 줄의 입력들이 들어오며 0번째 인덱스 값보다 큰 값이 들어오면 heap의 이진트리에 들어오고 0번째 인덱스가 나가며 가장 작은 값이 다시 0번째 인덱스에 오게 되며 heap이 조절된다.
마지막 줄까지 다 정렬되고 나면 0번째 인덱스에 끝에서 N번째 큰 입력이 남아있게 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[baekjoon] 1068 트리(python)]]></title>
            <link>https://velog.io/@jeong_twelve/baekjoon-1068-%ED%8A%B8%EB%A6%ACpython</link>
            <guid>https://velog.io/@jeong_twelve/baekjoon-1068-%ED%8A%B8%EB%A6%ACpython</guid>
            <pubDate>Sun, 14 Jul 2024 06:17:54 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-python">import sys
input = sys.stdin.readline
cnt = int(input())
lst = list(map(int, input().strip().split()))   #각 노드마다 연결된 하위 노드 리스트로 보관
delete_node = int(input())
dict = {}
for i in range(cnt):
    dict[i] = []
for i in range(cnt):
    if lst[i] == -1:
        continue
    else:
        if i != delete_node:
            dict[lst[i]].append(i)  #하위 보관 리스트 추가, delete할 노트는 추가하지 않음

def loop(delete_node):     #재귀를 통해 삭제할 노드에 연결된 하위 노드들도 삭제
    if len(dict[delete_node]) != 0:
        while len(dict[delete_node]) != 0:
            for _ in range(len(dict[delete_node])):
                loop(dict[delete_node][0])
                dict[delete_node].pop(0)
    dict.pop(delete_node)

loop(delete_node)
cnt = 0
key_lst = dict.keys()
for i in key_lst:
    if len(dict[i]) == 0:   #리스트 길이가 0이면 리프 노드이므로 cnt 1추가
        cnt += 1
print(cnt)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[baekjoon] 1025 제곱수 찾기[python]]]></title>
            <link>https://velog.io/@jeong_twelve/baekjoon-1025-%EC%A0%9C%EA%B3%B1%EC%88%98-%EC%B0%BE%EA%B8%B0python</link>
            <guid>https://velog.io/@jeong_twelve/baekjoon-1025-%EC%A0%9C%EA%B3%B1%EC%88%98-%EC%B0%BE%EA%B8%B0python</guid>
            <pubDate>Thu, 11 Jul 2024 07:19:31 GMT</pubDate>
            <description><![CDATA[<p>문제가 제일 이해하기 어려웠다.
내가 난독증이 있나 의심했던 것 같다.</p>
<p>첫 쨋 줄에 표의 가로, 세로 사이즈가 들어오고
그 아래로 행과 열의 숫자들을 입력해준 다음,
일정 (행,열)등차의 인덱스 문자들이 문자열로 붙었을 때, 가장 큰 제곱수를 찾는 문제이다.</p>
<p>힌트에 브루트포스(무식한 힘)알고리즘이라고 되어있어 그냥 전부 다 탐색하는 개무식한 알고리즘을 사용했다.</p>
<blockquote>
<h2 id="접근">접근</h2>
<p>전체 숫자를 다 다른 등차로 찾아서 max값을 출력해야될 것 같다는 생각이 들었다.</p>
</blockquote>
<ul>
<li>모든 행과 열 i,j 순회</li>
<li>-등차도 가능하므로 행 등차(-n ~ n), 열 등차(-m ~ m)까지 설정하여 시작 [i][j]에서 시작해 모든 등차 순회 후 제곱수 여부를 확인하고 더 큰 제곱수라면 해당 값으로 초기화해준다.</li>
</ul>
<p>코드는 다음과 같다.</p>
<pre><code class="language-python">import sys
input = sys.stdin.readline

N, M = map(int,input().split())
board = [list(input().strip()) for _ in range(N)]   #표
answer = -1 #제곱수가 담길 변수

def sqrt_check(S):
    S = int(S)
    return int(S ** 0.5) ** 2 == S


for i in range(N): #시작 x좌표
    for j in range(M): # 시작 y좌표
        for row_d in range(-N,N): # 행의 각 등차 0도 가능
            for col_d in range(-M,M): # 열의 각 등차 0도 가능
                S = &quot;&quot;
                x,y = i,j
                if row_d == 0 and col_d == 0:   #하지만 행과 열 둘 다 등차가 0이 되면 무한루프에 빠지므로 예외처리 해준다
                    continue
                while 0 &lt;= x &lt; N and 0 &lt;= y &lt; M:    #인덱스 범위안에 드는지 확인
                    S += board[x][y]
                    if sqrt_check(S):
                        answer = max(answer,int(S)) #기존 최대 제곱수와 새 제곱수 중 더 큰 값으로 초기화
                    x += row_d  #등차 더해줌
                    y += col_d  #등차 더해줌
print(answer)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[baekjoon] 1016 제곱ㄴㄴ수(python)]]></title>
            <link>https://velog.io/@jeong_twelve/baekjoon-1016-%EC%A0%9C%EA%B3%B1%E3%84%B4%E3%84%B4%EC%88%98python</link>
            <guid>https://velog.io/@jeong_twelve/baekjoon-1016-%EC%A0%9C%EA%B3%B1%E3%84%B4%E3%84%B4%EC%88%98python</guid>
            <pubDate>Wed, 10 Jul 2024 05:04:15 GMT</pubDate>
            <description><![CDATA[<p>min과 max를 입력받아 min부터 max까지의 수 중에 어떤 수의 제곱수의 배수가 아닌 수(=제곱ㄴㄴ수)의 갯수를 찾으면 되는 문제이다.
알고리즘 분류에 &quot;에라토스테네스의 체&quot;라고 되어있길래 한 번 찾아봤다.</p>
<h2 id="에라토스테네스의-체">에라토스테네스의 체</h2>
<p>소수를 찾는 빠르고 쉬운 방법이다.
방법은 예시와 함께 설명하겠다.
최대값:120</p>
<blockquote>
<ol>
<li>제곱했을 때 최대값을 넘는 가장 작은 수의 -1을 구한다. (ex: 제곱해서 최대값을 넘는 가장 작은수:11, 11 - 1 = 10)</li>
<li>2부터 시작해 10까지 모두 돌며 1~120까지 도는데 나누어 떨어지는 수는 지운다.</li>
<li>남은 숫자들은 모두 소수이다.</li>
</ol>
</blockquote>
<p>위의 방법을 사용하여 제곱ㄴㄴ수를 구하는 방법을 생각해보았다.
우선 나는 에라토스테네스의 체 처럼 전체 범위의 갯수를 만들고 배수들을 지우는 방식으로 결정했다.
min부터 max까지 갯수의 리스트를 만들고 True로 모두 채운다.
예를 들어 min이 20이고 max가 22이면
lst[0]은 20을 가리키고 lst[1]은 21을 가리킨다.</p>
<pre><code class="language-python">cnt = max - min + 1
lst = [True] * (max - min + 1)</code></pre>
<p>2부터 시작해 제곱했을 때 최대값을 넘는 가장 작은 수를 구하는 것 까진 같은데
그 수들의 제곱이 나눠지는지 확인해야하므로 제곱을 해서 
탐색한다.</p>
<pre><code class="language-python">for i in range(2, int(max**0.5 + 1)):
    square = i**2</code></pre>
<p>탐색 횟수, 시간을 최대한 줄이기 위해 min부터 max까지 모두 돌기보단, min이상 가장 작은 square의 배수를 찾고, max까지 square만큼 키우면서 제외해준다. (이미 작은 수의 배수로 결정된 수는 False로 바뀌어있으므로 if문으로 걸러 조금이라도 시간을 아껴준다)</p>
<pre><code class="language-python">    for j in range((((min - 1) // square) + 1) * square, max+1, square):
        if lst[j - min] == True:
            lst[j - min] = False
            cnt -= 1</code></pre>
<p>min 이상 가장 작은 square의 배수를 찾는 공식이 (((min-1)//square)+1)<em>square인 이유는 예시를들면 간단하다.
min을 19으로 잡고 max를 32로 넉넉잡고 square를 4라고 한다면, ((18//4)+1)</em>4를 해주면 20이 나오게 된다. (다른 예시를 여러가지 넣어보면서 왜 저렇게 되어야하는지 확인해보는 것도 좋을 것 같다)</p>
<p>전체 코드는 다음과 같다.</p>
<pre><code class="language-python">import sys
input = sys.stdin.readline
min, max = map(int, input().strip().split())
cnt = max - min + 1
lst = [True] * (max - min + 1)
print(max ** 0.5)
for i in range(2, int(max**0.5 + 1)):
    square = i**2
    for j in range((((min - 1) // square) + 1) * square, max+1, square):
        if lst[j - min] == True:
            lst[j - min] = False
            cnt -= 1
print(cnt)</code></pre>
<p>아직 알고리즘이 익숙치않아 자신이 없어서 골드1 문제길래 쫄았는데 생각보다 에라토스테네스의 체를 활용하면 간단한 문제였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[inception]Docker 기본 개념과 명령어]]></title>
            <link>https://velog.io/@jeong_twelve/inceptionDocker-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90%EA%B3%BC-%EB%AA%85%EB%A0%B9%EC%96%B4</link>
            <guid>https://velog.io/@jeong_twelve/inceptionDocker-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90%EA%B3%BC-%EB%AA%85%EB%A0%B9%EC%96%B4</guid>
            <pubDate>Mon, 15 Jan 2024 11:56:08 GMT</pubDate>
            <description><![CDATA[<h1 id="docker">Docker</h1>
<p><img src="https://velog.velcdn.com/images/jeong_twelve/post/9ae793bb-32fc-48f1-bd3b-ed08b93329ea/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/jeong_twelve/post/2ae29eb6-df1f-4e55-b7ae-e8df27a32e7e/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/jeong_twelve/post/6767c961-d2da-48f0-b0de-8c9da833a187/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/jeong_twelve/post/7a83768c-6121-48d3-9443-ea5e0b809d13/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/jeong_twelve/post/0605c0fe-de37-4d4b-85eb-2d24ed07cadd/image.jpg" alt=""></p>
<h1 id="도커-기본-명령어">도커 기본 명령어</h1>
<blockquote>
<h4 id="docker-pull-os명서버-프로그램명-">docker pull os명(서버, 프로그램명) :</h4>
<p>해당 OS나 프로그램이 설치된 OS 이미지를 도커 서버에 다운로드 한다.</p>
</blockquote>
<h4 id="docker-images-">docker images :</h4>
<p>현재 도커서버가 가진 이미지 목록을 보여준다</p>
<h4 id="docker-rmi-이미지이름image-id-">docker rmi 이미지이름(image ID) :</h4>
<p>이미지 목록에서 해당 이미지 삭제</p>
<h4 id="docker-ps-">docker ps :</h4>
<p>실행중인 컨테이너 목록을 보여준다. (-a 옵션: 실행중이거나 꺼져있는 컨테이너 등 모든 컨테이너 목록)</p>
<h4 id="docker-stop-container-id-">docker stop container ID :</h4>
<p>실행중인 컨테이너 ID 종료</p>
<h4 id="docker-rm-container-id-">docker rm container ID :</h4>
<p>컨테이너 ID 삭제(실행 중엔 삭제 안됨)</p>
<h3 id="컨테이너가-실행중이라면"><strong>컨테이너가 실행중이라면</strong></h3>
<p>docker stop, docker rm, docker rmi 순으로 삭제</p>
<h3 id="한-번에-삭제하기"><strong>한 번에 삭제하기</strong></h3>
<ol>
<li>컨테이너 stop: docker stop $(docker ps -a -q)</li>
<li>컨테이너 삭제: docker rm $(docker ps -a -q)</li>
<li>이미지 삭제: docker rmi $(docker images -q)</li>
</ol>
<p>-----여기서 -q옵션은 컨테이너 ID만 출력하는 옵션-----</p>
<h3 id="docker-run-이미지-이름-">docker run 이미지 이름 :</h3>
<p>이미지를 기반으로 컨테이너 실행 (이미지가 없으면 hub에서 다운받아 실행)
-d 옵션: detached 모드로 컨테이너 실행(백그라운드, 데몬)
-p 옵션: Host 운영체제 port:컨테이너 포트로 host os와 컨테이너 포트포워딩 (ex: docker run -p 8080:80 debian:bullseye)
-it 옵션: 보통 둘이 같이 씀, 터미널 실행하고 입력(키보드) 전달</p>
<h3 id="docker-attach-컨테이너-id이름-">docker attach 컨테이너 ID(이름) :</h3>
<p>실행중인 컨테이너 접근</p>
<h3 id="docker-commit-컨테이너id-docker_hubid이미지명태그명-">docker commit 컨테이너ID docker_hubID/이미지명:태그명 :</h3>
<p>현재 작동중인 컨테이너 보존(이미지로 만듦)</p>
<h3 id="docker-push-이미지명-">docker push 이미지명 :</h3>
<p>docker-hub에 업로드</p>
<h3 id="docker-logs-컨테이너id-">docker logs 컨테이너ID :</h3>
<p>해당 컨테이너 ID 로그 출력</p>
<h3 id="docker-build-">docker build :</h3>
<p>dockerfile을 이미지로 빌드한다
-t 이미지명 : 만들어낼 이미지 이름 설정 옵션
-f 도커파일명 : 도커파일 이름 지정 옵션</p>
<h1 id="dockerfile">Dockerfile</h1>
<p><strong>Dockerfile: DockerImage를 생성하기 위한 스크립트(설정파일)이다.</strong></p>
<blockquote>
<h3 id="dockerfile의-장점">Dockerfile의 장점</h3>
</blockquote>
<ul>
<li>이미지가 어떻게 만들어졌는지를 기록한다.</li>
<li>배포에 용이하다</li>
<li>컨테이너(이미지)가 특정 행동을 수행하도록 한다.(entrypoint, cmd...)</li>
</ul>
<blockquote>
<h2 id="dockerfile-명령어">Dockerfile 명령어</h2>
</blockquote>
<h4 id="from--기본이미지--tag명">FROM : 기본이미지 + tag명</h4>
<h4 id="workdir--작업수행-디렉토리-설정">WORKDIR : 작업수행 디렉토리 설정</h4>
<p>(WORKDIR /bin)</p>
<h4 id="add--호스트-운영체제-파일을-컨테이너-내부에-넣어줌">ADD : 호스트 운영체제 파일을 컨테이너 내부에 넣어줌</h4>
<p>(ADD ./압축파일.zip ./ -&gt; 호스트 OS의 압축파일.zip을 ./(workdir 주소로 이동))
<span style="color: red">ADD는 압축파일이 풀린 상태로 올려짐</span></p>
<h4 id="copy--add와-비슷하지만-압축이-안-풀림">COPY : ADD와 비슷하지만 압축이 안 풀림</h4>
<h4 id="run--리눅스에서-실행되는-명령어-실행-타이밍이-도커파일을-이미지로-빌드할-때-실행되기-때문에-주로-라이브러리를-설치하는-용도로-쓰인다">RUN : 리눅스에서 실행되는 명령어, 실행 타이밍이 도커파일을 이미지로 빌드할 때 실행되기 때문에 주로 라이브러리를 설치하는 용도로 쓰인다.</h4>
<p>(RUN apt-get update)</p>
<h4 id="entrypoint--컨테이너가-실행될-때-실행할-명령어-또는-프로세스를-지정하는-명령어cmd와-비슷하지만-span-stylecolor-redentrypoint는-컨테이너가-실행될-때-무조건-동작하는-명령어이다spancmd는-대체될-수-있는-변수같은-존재">ENTRYPOINT : 컨테이너가 실행될 때 실행할 명령어 또는 프로세스를 지정하는 명령어(CMD와 비슷하지만 <span style="color: red">ENTRYPOINT는 컨테이너가 실행될 때 무조건 동작하는 명령어이다.</span>CMD는 대체될 수 있는 변수같은 존재.)</h4>
<p>(ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;, &quot;-Dspring.profiles.active=dev&quot;, &quot;application.jar&quot;]</p>
<h4 id="cmd--entrypoint와-동작-자체는-같지만-실행순서가-늦다">CMD : ENTRYPOINT와 동작 자체는 같지만 실행순서가 늦다.</h4>
<p>예를들어 도커파일을 다음과 같이 작성하면,</p>
<pre><code class="language-dockerfile">WORKDIR /app
COPY build/aws-v3-0.0.3.jar ./application.jar
ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;, &quot;-Dspring.profiles.active=dev&quot;, &quot;application.jar&quot;]
CMD [&quot;--server.port=8080&quot;]</code></pre>
<p>해당 순으로 명령어가 동작한다.
java -jar -Dspring.profiles.active=dev application.jar --server.port=8080
즉 ENTRYPOINT는 무조건 실행할 명령어를 넣어야하고(목적) CMD는 옵션 같은 것을 넣는게 좋다(옵션).
또한 CMD 명령어는 dockerfile에 명시되어 있어도 RUN 명령문 이미지명 뒤의 마지막 명령에 의해 대체될 수 있다.
dockerfile을 위와같이 작성해도 해당 dockefile 빌드 후에 RUN 할 때,
<strong>docker run -dit -p 8080:5000 java-server --server.port=5000</strong> 이렇게 실행하면 --server.port=5000이 CMD가 되어 dockerfile 안의 CMD는 무시된다.</p>
<h4 id="env--도커파일-내부의-환경변수-설정">ENV : 도커파일 내부의 환경변수 설정</h4>
<p>두 가지 방법이 있다.</p>
<ol>
<li>ENV [key] [value] : 단일 환경변수에 하나의 값 지정</li>
<li>ENV [key]=[value] : 한 번에 여러개의 값 지정
2번 예시<pre><code>ENV NAME=&quot;wonlee&quot;\
 NICK=won\
 AGE=22</code></pre><span style="color: red">1번 방법으로 여러개의 환경변수 설정 시 여러개의 이미지가 덮어씌워진 layer가 만들어져 효율이 떨어진다.</span></li>
</ol>
<h1 id="docker-compose">Docker-compose</h1>
<p><img src="https://velog.velcdn.com/images/jeong_twelve/post/80f0f9c2-a79d-42f9-b69c-8170aad9a50e/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/jeong_twelve/post/8c4e2596-00fb-4116-a7c7-aa6a0c81f3b8/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/jeong_twelve/post/dc6de236-1925-420c-9dc6-2243dba8a8a0/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/jeong_twelve/post/52767fa5-4cc5-4548-9389-8ee7db82401c/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/jeong_twelve/post/d4c87fd0-91b6-48de-9e22-84f0ab0b1947/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/jeong_twelve/post/46f57fcf-34c7-41cc-b7e5-f3783f2598f4/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/jeong_twelve/post/d350745f-3b59-4ceb-8c07-2067df6e041c/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/jeong_twelve/post/cf4b0d3c-eb0f-4199-a045-4b184f289a62/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[webserv 허용함수 정리]]></title>
            <link>https://velog.io/@jeong_twelve/webserv-%ED%97%88%EC%9A%A9%ED%95%A8%EC%88%98-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@jeong_twelve/webserv-%ED%97%88%EC%9A%A9%ED%95%A8%EC%88%98-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Mon, 07 Aug 2023 13:05:27 GMT</pubDate>
            <description><![CDATA[<p>webserve 과제에서 허용하고 있는 함수들은 다음과 같다.</p>
<pre><code>execve, dup, dup2, pipe, strerror, gai_strerror,
errno, dup, dup2, fork, htons, htonl, ntohs, ntohl,
select, poll, epoll (epoll_create, epoll_ctl,
epoll_wait), kqueue (kqueue, kevent), socket,
accept, listen, send, recv, bind, connect,
getaddrinfo, freeaddrinfo, setsockopt, getsockname,
getprotobyname, fcntl, close, read, write, waitpid,
kill, signal, access, stat, opendir, readdir and
closedir.</code></pre><p>아는 함수들도 있겠지만 복습한다는 개념으로 모두 다시 정리해보려 한다.</p>
<h1 id="1-execve함수">1. execve()함수</h1>
<p>현재 수행되고 있는 프로세스를 대신하여 1번째 인자로 들어오는 새로운 프로세스를 수행시키는 함수 (자신은 종료됨)</p>
<ul>
<li>헤더: unistd.h</li>
<li>형태: <strong>int</strong> execve(<strong>const char *</strong>pathname, <strong>char *const</strong> argv[], <strong>char *const</strong> envp[]);</li>
<li>인수: <strong>const char *</strong>pathname 실행시킬 파일의 경로</li>
<li><em>char <em>const</em></em> argv[] 파일인자의 포인터</li>
<li><em>char <em>const</em></em> envp[] 환경변수의 포인터</li>
<li>반환: 실패 시 -1, 현재 수행중인 프로세스 계속 진행</li>
</ul>
<h1 id="2-dup-dup2함수">2. dup(), dup2()함수</h1>
<p>File descriptor를 복제하는 함수.</p>
<ul>
<li>헤더: unistd.h</li>
<li>형태: <strong>int</strong> dup(<strong>int</strong> fd);</li>
<li><em>int*</em> dup2(<strong>int</strong> fd, <strong>int</strong> fd2);</li>
<li>인수: (dup)<strong>int</strong> fd 복제할 file descriptor, dup이 돌려주는 파일 서술자는 가장 낮은 서술자를 반환합니다.</li>
<li>반환: 실패 시 -1</li>
</ul>
<h1 id="3-pipe함수">3. pipe()함수</h1>
<p>서로 독립된 프로세스들이 데이터를 주고 받게 해주는 함수
하나의 파이프 및 파이프에 대해 두 개의 파일 디스크립터 생성.
하나의 파이프를 프로세스들이 공유</p>
<ul>
<li>헤더: unistd.h</li>
<li>형태: <strong>int</strong> pipe(<strong>int</strong> fd[2]);</li>
<li>인수: <strong>int</strong> fd[2] 크기가 2인 int형 배열 요구
fd[0]: 함수 호출 후 fd[0]에 데이터를 입력 받을 수 있는 파일 디스크립터가 담김(파이프 출구)
fd[1]: 함수 호출 후 데이터를 출력할 수 있는 파일 디스크립터가 담김(파이프 입구)</li>
<li>반환: 실패 시 -1</li>
</ul>
<h1 id="4-strerror함수">4. strerror()함수</h1>
<p>오류 메세지 문자열을 가리키는 포인터(문자열)를 얻어온다</p>
<ul>
<li>헤더: string.h // c++ cstring</li>
<li>형태: <strong>char *</strong>strerror(<strong>int</strong> errnum);</li>
<li>인수: <strong>int</strong> errnum 에러 발생시 에러 넘버가 들어온다(아래에서 기술).</li>
<li>반환: 실패 시 -1 성공 시 오류 번호에 해당하는 오류 문자열을 가리키는 포인터</li>
</ul>
<h1 id="5-errno">5. errno</h1>
<p>errno는 함수가 아니다. 광역변수로써 라이브러리 함수 수행 중 에러가 발생하면 에러 코드를 가지게 된다.</p>
<ul>
<li>헤더: errno.h</li>
<li>반환: 에러없이 복귀되었다면 0, 수행 중 에러가 발생했다면 0 이외의 에러 값을 갖는다.</li>
</ul>
<h1 id="6-fork함수">6. fork()함수</h1>
<p>현재 실행되는 프로세스에 대해 독자적인 복사본 프로세스를 생성한다.</p>
<ul>
<li>헤더: unistd.h</li>
<li>형태: <strong>pid_t</strong> fork(<strong>void</strong>);</li>
<li>반환: 실패 시 -1, 자식 프로세스(새로 생성된 프로세스) 0, 부모프로세스에는 자식 프로세스의 pid가 반환된다.</li>
</ul>
<h1 id="7-htonα-ntohα함수">7. hton+α(), ntoh+α()함수</h1>
<ul>
<li>헤더:  arpa/inet.h<table>
<thead>
<tr>
<th align="left"><strong>함수 구분</strong></th>
<th align="center"><strong>설명</strong></th>
</tr>
</thead>
<tbody><tr>
<td align="left">uint32_t htonl(uint32_t hostlong)</td>
<td align="center">long 형 호스트 바이트 순서 데이터를 네트워크 바이트 순서값 구함</td>
</tr>
<tr>
<td align="left">uint16_t htons(uint16_t hostshort)</td>
<td align="center">short 형 호스트 바이트 순서 데이터를 네트워크 바이트 순서값 구함</td>
</tr>
<tr>
<td align="left">uint32_t ntohl(uint32_t netlong)</td>
<td align="center">long 형 네트워크 바이트 순서 데이터를 호스트 바이트 순서 데이터 값 구함</td>
</tr>
<tr>
<td align="left">uint16_t ntohs(uint16_t netshort)</td>
<td align="center">short 형 네트워크 바이트 순서 데이터를 호스트 바이트 순서 데이터 값 구함</td>
</tr>
</tbody></table>
</li>
</ul>
<blockquote>
<h4 id="바이트-순서">바이트 순서</h4>
<p>바이트 순서는 해당하는 OS 시스템이 내부적으로 데이터를 표현하는 방법을 의미한다.
시스템이 내부적으로 데이터를 처리하는데 Big-Endian을 사용하느냐, Little-endian을 쓰느냐는 시스템의 CPU에 따라 달라진다.
이것을 호스트 바이트 순서라고 한다.</p>
</blockquote>
<p>ex: 0x12345678의 32비트 데이터를 표현</p>
<h4 id="big-endian--0x12-0x34-0x56-0x78">Big Endian : 0x12 0x34 0x56 0x78</h4>
<p><strong>높은 주소 -&gt; 낮은 주소</strong></p>
<h4 id="little-endian--0x78-0x56-0x34-012">Little Endian : 0x78 0x56 0x34 012</h4>
<p><strong>낮은 주소 -&gt; 높은 주소</strong></p>
<p>네트워크는 Big Endian을 사용하고, 혹시나 Big Endian을 사용하는 CPU와 little Endian을 사용하는 CPU가 통신을 하게되면 문제가 발생할 수 있다. 
따라서 통신할 때, 원활하게 사용하기 위해 변환해주는 함수가 hton+α 함수들이다.</p>
<h1 id="8-select-poll-epoll-kqueue함수">8. select, poll, epoll, kqueue()함수</h1>
<blockquote>
<h2 id="사전지식-">사전지식 :</h2>
</blockquote>
<h4 id="멀티플렉싱-멀티쓰레드-멀티프로세스">멀티플렉싱, 멀티쓰레드, 멀티프로세스</h4>
<p>하나의 서버에서 여러 개의 클라이언트 요청을 처리하기 위해 고안된 방법이다.</p>
<h4 id="멀티-프로세스-">멀티 프로세스 :</h4>
<pre><code>여러개의 프로그램을 띄워놓고 각 프로그램에서 처리하게끔 하는 것.
프로그램을 짜다가 중간에 여러개의 요청을 처리해야 하는 로직을 마주하면 fork 함수를 수행하여 같은 내용을 복사된 별도의 프로세스(자식 프로세스)에서 처리하게 하는 것이다. 
주의해야 할 점은 여러 개의 프로세스가 동시에 돌고 있는 것이므로 어떤 것은 수행을 일찍 마칠 수도 있는데, 반드시 제대로 종료(할당받은 자원들 메모리 반납 등..)할 수 있도록 신경써야 한다는 것이다.(안 그러면 좀비 쓰레드가 된다) 
단점으로는 많은 양의 자원(메모리)를 소모하는 많은 양의 연산이 필요하다는 점이 있다.    </code></pre><h4 id="멀티-쓰레드-">멀티 쓰레드 :</h4>
<pre><code>하나의 프로세스에서 여러 개의 쓰레드를 생성해 요청을 처리하는 방식.
멀티 프로세스는 기존의 프로세스가 완전히 새로운 별개의 프로세스로 복사되어 생성되기 때문에 자원 소모가 많다. 
하지만 멀티 쓰레드는 근본적으로 하나의 프로세스로 실행 흐름만 달라지고 함수 실행에 해당되는 스택영역은 독립적으로 가져가고 데이터와 힙 영역(메모리)를 공유한다.
따라서 프로세스 간 같은 데이터를 공유하기 어려웠던 멀티 프로세스와 달리 같은 메모리를 공유하므로 좀 더 편리할 수도 있으나 독이 될 수도 있다(동시에 같은 곳에서 접속하고 변경을 주려해 예상치 못한 문제가 발생 할 수 있다).
해당 문제를 방지하기 위해 뮤텍스나 세마포어 등을 이용한다(philosopher과제 참조)</code></pre><h4 id="멀티-플렉싱-">멀티 플렉싱 :</h4>
<pre><code>하나의 통신채널(서버)을 통해 둘 이상의 데이터(클라이언트,시그널)를 전송하는데 사용되는 기술로, 
간단하게 두 개의 클라이언트를 위해 두 개의 프로세스, 두 개의 쓰레드를 두는 것이 아니라 하나의 하나의 프로세스 혹은 쓰레드에서 두 개 이상의 데이터의 입출력을 다룰 수 있는 기술이다.
커널에서는 하나의 쓰레드가 여러 개의 소켓(파일)을 핸들링 할 수 있는 select, poll, epoll, iocp(window), kqueue(Mac)을 제공한다.</code></pre><p>서버의 리스팅 소켓을 bind, listen하고 클라이언트 쪽에서 connect가 들어오면 accept하여 클라이언트 소켓을 반환하고 그 소켓을 통해 클라이언트와 데이터 송수신
관리할 때 fd_set이라는 구조체를 사용</p>
<h2 id="8-0-fd_set-구조체">8-0. fd_set 구조체</h2>
<ul>
<li>fd를 관리하기 위해 디자인 된 구조체</li>
<li>배열 형태로 0번 인덱스부터 fd 0을 매핑하고 있음
<img src="https://velog.velcdn.com/images/jeong_twelve/post/b40b5661-c79f-472b-8957-e08a52d9027b/image.png" alt="">
fd_set 관련 함수</li>
<li><blockquote>
<p>FD_ZERO:인자로 전달된 fd_set의 모든 비트를 0으로 초기화</p>
</blockquote>
</li>
<li><blockquote>
<p>FD_SET:인자로 전달된 fd_set의 인덱스를 1로 설정</p>
</blockquote>
</li>
<li><blockquote>
<p>FD_CLR:인자로 전달된 fd_set의 인덱스를 0로 설정</p>
</blockquote>
</li>
<li><blockquote>
<p>FD_ISSET:인자로 전달된 fd_set의 해당 인덱스가 1이면 양수를 반환
<img src="https://velog.velcdn.com/images/jeong_twelve/post/a05e2921-c112-4683-aef2-c706b88480a4/image.png" alt=""></p>
</blockquote>
</li>
</ul>
<h2 id="8-1-select함수">8-1. select()함수</h2>
<p>어느 소켓의 fd에 read, write, exception이 발생했는지 확인하는 함수</p>
<ul>
<li>헤더: sys/time.h // sys/types.h // unistd.h</li>
<li>형태: <strong>int</strong> select(<strong>int</strong> n, <strong>fd_set *</strong>readfds, <strong>fd_set *</strong>writefds, <strong>fd_set *</strong>exceptfds, <strong>struct timeval *</strong>timeout);</li>
</ul>
<p>fd_set을 전달하여 호출하면 변화가 발생한(입력 받은 데이터가 존대하거나 출력이 가능한 상황 등) 소켓의 디스크립터만 1로 설정</p>
<p>fd_set에 대한 주소값을 전달하고 각 액션에 대한 결과를 적용하기 때문에 원본을 복사하여 복사본을 전달해야 함.
<img src="https://velog.velcdn.com/images/jeong_twelve/post/967feb77-0845-41a7-9384-54945e658e28/image.png" alt="">
위와 같은 예시에서 readset에 fd_set을 전달했을 때 호출 후 아래와 같이 변했다면 fd1, fd3에 읽어들일 데이터가 있다(입력 버퍼에 데이터가 있다)라고 볼 수 있다.</p>
<ul>
<li>인수: <strong>int</strong> n 관리하는 파일의 개수, <strong>파일의 개수는 최대 파일 지정 번호 + 1</strong></li>
<li><em>fd_set **</em>readfds 읽을 데이터가 있는지 검사하기 위한 파일 목록</li>
<li><em>fd_set **</em>writefds 쓰여진 데이터가 있는지 검사하기 위한 파일 목록</li>
<li><em>fd_set **</em>exceptfds 파일에 예외 사항들이 있는지 검사하기 위한 파일 목록</li>
<li><em>struct timeval **</em>timeout fd_set에 등록된 파일들에 데이터 변경이 있는지를 timeout동안 기다린다. 만약 timeout시간동안 변경이 없다면 0을 반환 한다. timeout을 NULL로 하면, 데이터가 있을 때까지 무한정 기다리고, 멤버 값이 모두 0이면 즉시 반환한다.</li>
<li>반환: 실패 시 -1, 타임 아웃 시 0, 이벤트 발생 파일 디스크립터 수 &gt; 0</li>
</ul>
<p>select를 이용한 멀티플렉싱 서버의 구현
-1단계 : 서버 소켓과 fd_set 생성</p>
<pre><code class="language-cpp">int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    struct timeval timeout;
    fd_set reads, cpy_reads;

    socklen_t    adr_sz;
    int fd_max, str_len, fd_num, i;
    ...;

    serv_sock=socket(PF_INET, SOCK_STREAM, 0);
    ...;

    if (bind(serv_sock, ( struct sockaddr *)&amp;serv_adr, sizeof(serv_adr)) == -1)
    {
        printf(&quot;bind() error&quot;);
    }
    if (listen(serv_sock, 5) == -1)
    {
        printf(&quot;listen() error&quot;);
    }

    FD_ZERO(&amp;reads);    //fd_set 초기화
    FD_SET(serv_sock, &amp;reads);    //서버 소켓을 관리 대상으로 지정
    fd_max=serv_sock;    //최대 파일 디스크립터 값</code></pre>
<p>-2단계 : select 함수 호출</p>
<pre><code class="language-cpp">    while(1)
    {
        cpy_reads = reads;    //원본 fd_set 복사
        timeout.tv_sec = 5;
        timeout.tv_usec = 5000;    //타임아웃 설정

        if ((fd_num = select(fd_max + 1, &amp;cpy_reads, 0, 0, &amp;timeout)) == -1)
            break;    //아직 서버 소켓만 있으므로 connect 연결 요청 시 서버소켓에 데이터가 들어오게 됨
        if (fd_num == 0)
            continue ;    //타임아웃 시 continue</code></pre>
<p>-3단계 : 소켓에 따른 구분
-&gt;for문으로 fd_set의 인덱스르 하나씩 순회하면서 변화가 있는 인덱스를 찾아냄
-&gt;만약 그 fd가 서버 소켓이면 connect요청이므로 새로운 소켓을 생성하여 fd_set에 등록</p>
<pre><code class="language-cpp">        for (i = 0; i &lt; fd_max + 1; i++)
        {
            if (FD_ISSET(i, &amp;cpy_reads))
            {
                if (i == serv_sock)
                {
                    adr_sz = sizeof(clnt_adr);
                    clnt_sock = accept(serv_sock, (struct sockaddr *)&amp;clnt_adr, &amp;adr_sz);
                    FD_SET(clnt_sock, &amp;reads);
                    if (fd_max &lt; clnt_sock);
                        fd_max = clnt_sock;
                    printf(&quot;connected client: %d\n&quot;, clnt_sock);
                }    // 변화가 일어난 소켓이 서버 소켓이면 connect 요청인 경우
                else
                {
                    str_len = read(i, buf, BUF_SIZE);
                    if (str_len == 0)    //close request!
                    {
                        FD_CLR(i, &amp;reads);
                        close(i);
                        printf(&quot;closed client: %d\n&quot;, i);
                    }
                    else
                        write(i, buf, str_len);    //echo!
                }     // 다른 소켓인 경우에는 데이터 read
            }
        }
    }
    return (0);
}</code></pre>
<p><strong>단점</strong> : 이벤트별로 감시할 파일들을 fd_set이라는 fd비트 배열에 등록하고 등록된 fd에 어떠한 이벤트가 발생했을 경우 fd_set을 확인하는 방식으로 동작하므로 한 번에 다수의 fd를 조회하여 I/O 상태를 관찰하기 때문에 지속적인 polling이 필요하다(CPU 낭비).
또한 한 번에 조회할 수 있는 fd의 수가 1024개로 제한되어있으며
커널에 의해 완성되는 기능이 아니라 함수에 의해 완성되는 기능이므로 select 함수의 호출을 통해 전달된 정보는 커널에 등록되지 않은 것이며 함수를 호출할 때마다 매번 관련 정보를 전달해야 한다.
최대 1024개의 file descriptor를 하나하나 체크하기 때문에 O(n)의 계산량을 가지고 있으며 당연하게도 file descriptor 수가 증가하면 성능이 떨어진다.</p>
<h2 id="8-2-poll함수">8-2 poll()함수</h2>
<ul>
<li>헤더: #include &lt;poll.h&gt;</li>
<li>형태: <strong>int</strong> poll(<strong>struct poolfd *</strong>ufds, <strong>unsigned int</strong> nfds, <strong>int</strong> timeout);
select와 비슷한 함수로 여러 file descriptor에 대해서 I/O를 수행하기 위한 준비가 될 때까지 기다리는 함수이다.</li>
</ul>
<p>select의 경우 입출력 이벤트가 발생했을 때 넘겨주는 정보가 너무 적어서 poll함수는 어떤 이벤트를 기다리는지 설정할 수 있고 해당 이벤트가 발생하면 revents를 채워서 돌려준다. </p>
<p>우선 poll 함수의 첫 번째 인자인 pollfd구조체에 대해 알아보자. <strong>중요</strong></p>
<pre><code class="language-cpp">struct pollfd
{
    int fd;         // 관심있어하는 파일지시자
    short events;   // 발생된 이벤트
    short revents;  // 돌려받은 이벤트
};</code></pre>
<p>해당 구조체에는 3가지 멤버변수가 있는데, 이 구조체에 우리가 관심있어하는 파일 지시자를 세팅하고(fd), 관심있어하는 파일 지시자가 어떤 이벤트가 발생하는걸 기다릴 것인지(events)를 지정하게 된다. 그럼 poll은 해당 fd에 해당 events가 발생하는지 검사하고 해당 events가 발생하면 revents를 채워서 돌려주게 된다.</p>
<p>revents는 events가 발생했을때 커널에서 이 events에 어떻게 반응했는지에 대한 반응값이다. 후에 revents 값을 조사함으로써, 해당 fd에 어떤 event가 일어났고 그 event를 어떻게 처리했는지(입/출력이 제대로 이뤄졌는지, 아님 에러가 발생했는지)를 알아내서 적절한 조치를 취할 수 있게 된다. 커널에서 설정해주는 값이다.</p>
<p><strong>short</strong> events에 세팅할 수 있는 내용들에 대해 알아보자
이 이벤트들은 OR(|)로 여러가지를 줄 수 있으며 사용자가 설정한다.
아래의 표를 보면 revents에는 모든 event가 설정되어질 수 있지만, events에는 아래 3개는 설정할 수 없다.
<img src="https://velog.velcdn.com/images/jeong_twelve/post/9bf249b2-aba3-4952-93c8-4f4fc640c92a/image.png" alt=""></p>
<p>2번째 인자인 nfds는 pollfd의 배열의 크기이다.</p>
<p>3번째 인자인 timeout은 select의 time과 같은 역할을 한다.
-1일 경우, 이벤트가 발생하기 전까지 영원히 기다린다.
0일 경우, 기다리지 않고 곧바로 다음 루틴을 진행한다.
0보다 큰 양의 정수일 경우, 해당 시간만큼 기다리게 된다. 해당 시간내에 어떤 이벤트가 발생하면 즉시 되돌려주며, 시간을 초과하게 될 경우 0을 return한다.</p>
<ul>
<li>반환: 에러 시 -1, 성공 시 revent가 발생한 pollfd 구조체의 숫자를 돌려준다.</li>
</ul>
<p>poll 함수를 이용한 멀티플랙싱 echo 서버 프로그램</p>
<pre><code class="language-cpp">#include &lt;stdio.h&gt;
#include &lt;string.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;netinet/in.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;arpa/inet.h&gt;
#include &lt;unistd.h&gt;
#include &lt;sys/select.h&gt;
#include &lt;poll.h&gt;

#define PORT 20162
#define BUFFER_SIZE 100
#define LISTEN_QUEUE_SIZE 5
#define theNumberOfFDs 100

int main(int argc, char* argv[])
{
    int listenFD, connectFD;
    struct sockaddr_in listenSocket, connectSocket;

    socklen_t addrSz; // address size를 구할 변수

    int i;
    ssize_t strLen;
    char buf[BUFFER_SIZE];

    //if (argc != 2)
    //{
    //    printf(&quot;Usage : %s &lt;port&gt;\n&quot;, argv[0]);
    //    exit(1);
    //}

    listenFD = socket(PF_INET, SOCK_STREAM, 0);
    memset(&amp;listenSocket, 0, sizeof(listenSocket));

    listenSocket.sin_family = AF_INET;
    listenSocket.sin_addr.s_addr = htonl(INADDR_ANY);
    listenSocket.sin_port = htons(PORT);


    if (bind(listenFD, (struct sockaddr *) &amp;listenSocket, sizeof(listenSocket)) == -1) {
        printf(&quot;Can not bind.\n&quot;);
        return -1;
    }

    if (listen(listenFD, LISTEN_QUEUE_SIZE) == -1) {
        printf(&quot;Listen fail.\n&quot;);
        return -1;
    }

    // pollfd 배열 구조체 생성
    struct pollfd pollFDs[theNumberOfFDs];

    pollFDs[0].fd = listenFD; // 0번째 배열에는 listen을 지정
    pollFDs[0].events = POLLIN; // 읽도록 만든다.
    pollFDs[0].revents = 0; // 처음에는 0으로 초기화 한다(아직 아무 일도 일어나지 않았으니)

    for (i = 1; i &lt; theNumberOfFDs; i++)
        pollFDs[i].fd = -1; // 0번째 배열은 listen을 위한것이니 1번째부터 모두 -1로 초기화

    while (1)
    {
        int result = poll(pollFDs, theNumberOfFDs, -1); // -1 :: 무한 대기

        if (result &gt; 0)
        {
            if (pollFDs[0].revents == POLLIN)
            {
                // 새로운 커넥션 요청이 들어왔을 때
                connectFD = accept(listenFD, (struct sockaddr*)&amp;connectSocket, &amp;addrSz);

                for (i = 1; i &lt; theNumberOfFDs; i++)
                {
                    if (pollFDs[i].fd == -1) // 비어있는 fd슬롯을 찾아서 넣어준다.
                    {
                        pollFDs[i].fd = connectFD;
                        pollFDs[i].events = POLLIN;
                        pollFDs[i].revents = 0;
                        break; // 모두 다 넣고 break를 통해 한번만 실행
                    }
                }
            }

            for (i = 1; i &lt; theNumberOfFDs; i++)
            {
                switch (pollFDs[i].revents)
                {
                    // no events
                    case 0:
                        break;

                    // data is ready
                    case POLLIN:
                        strLen = read(pollFDs[i].fd, buf, BUFFER_SIZE);
                        printf(&quot;%lu bytes read\n&quot;, strLen);

                        buf[strLen] = &#39;\0&#39;;
                        fputs(buf, stdout);
                        fflush(stdout);

                        write(pollFDs[i].fd, buf, strlen(buf));


                    // 슬롯 초기화
                    // default:
                        // close(pollFDs[i].fd);
                        // pollFDs[i].fd = -1;
                        // pollFDs[i].revents = 0;


                }
            }
        }


    }

    close(listenFD);

    return 0;
}</code></pre>
<p>echo 클라이언트 프로그램</p>
<pre><code class="language-cpp">#include &lt;stdio.h&gt;
#include &lt;string.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;netinet/in.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;arpa/inet.h&gt;
#include &lt;unistd.h&gt;
#include &lt;sys/select.h&gt;
#include &lt;poll.h&gt;

#define PORT 20162
#define BUFFER_SIZE 100
#define LISTEN_QUEUE_SIZE 5
#define theNumberOfFDs 100

int main(int argc, char** argv)
{
    if (argc != 2)
    {
        printf(&quot;Usage: %s IPv4-address\n&quot;, argv[0]);
        return -1;
    }

    struct sockaddr_in connectSocket;

    memset(&amp;connectSocket, 0, sizeof(connectSocket));

    connectSocket.sin_family = AF_INET;
    inet_aton(argv[1], (struct in_addr*) &amp;connectSocket.sin_addr.s_addr);
    connectSocket.sin_port = htons(PORT);

    int connectFD = socket(AF_INET, SOCK_STREAM, 0);

    if (connect(connectFD, (struct sockaddr*) &amp;connectSocket, sizeof(connectSocket)) == -1)
    {
        printf(&quot;Can not connect.\n&quot;);
        return -1;
    }

    else
    {
        int readBytes, writtenBytes;
        char sendBuffer[BUFFER_SIZE];
        char receiveBuffer[BUFFER_SIZE];

    while(1)
    {
            //서버에 문자열을 보낸 뒤 서버가 보낸 echo를 받아 출력.
            printf(&quot;input :: &quot;);

            fgets(sendBuffer,BUFFER_SIZE,stdin);

            write(connectFD, sendBuffer, strlen(sendBuffer));


            readBytes = read(connectFD, receiveBuffer, BUFFER_SIZE);
            printf(&quot;%d bytes read\n&quot;, readBytes);
            receiveBuffer[readBytes] = &#39;\0&#39;;

            fputs(receiveBuffer, stdout);
            fflush(stdout);
     }



    }

    close(connectFD);
    return 0;


}</code></pre>
<p>poll 함수 특징
우선 file descriptor가 무제한적이고 select에 비해 해당 fd에 대해 보다 많은 정보를 되돌려줌으로, 보통 select보다 선호된다.</p>
<h2 id="8-3-epoll-apiepoll_create-epoll_ctl-epoll_wait함수">8-3 epoll API(epoll_create, epoll_ctl, epoll_wait)함수</h2>
<p>epoll API는 select와 poll함수와 비교해서 fd의 준비 상태를 알리는 방식에서 더 유연성을 갖는다.(또한 리눅스에서만 호환성을 갖는다. 윈도우는 IOCP)
앞의 select 함수와 poll 함수는 모든 fd에 대해 이벤트 발생을 찾아야해 횟수가 큰 반복문이 필요했으며 함수 호출 시 매번 전달해야하는 관찰 대상 정보들 때문에 상대적으로 속도가 느렸다.</p>
<p>epoll API의 장점은 우선 상태변화의 확인을 위한, 전체 파일 디스크립터를 대상으로 하는 반복문이 필요가 없다는 점이고 select 함수에 대응하는 epoll_wait 함수 호출 시, 관찰 대상의 정보를 매번 전달할 필요가 없다는 점이다.</p>
<p>epoll 기반 프로그램 구현에 필요한 함수와 구조체들을 정리해보자</p>
<h3 id="epoll_create함수">epoll_create()함수</h3>
<p>epoll 파일 디스크립터 저장소(epoll 인스턴스) 생성</p>
<ul>
<li>헤더: sys/epoll.h</li>
<li>형태: <strong>int</strong> epoll_create(<strong>int</strong> size);</li>
<li>인수: <strong>int</strong> size epoll 감시할 fd의 수</li>
<li>반환: 성공 시 epoll의 다른 함수에서 사용할 새로운 fd 반환, 실패 시 -1 반환
소멸 시 close 함수 사용함.</li>
</ul>
<h3 id="epoll_ctl함수">epoll_ctl()함수</h3>
<p>저장소(epoll 인스턴스)에 fd 등록 및 삭제</p>
<ul>
<li>헤더: sys/epoll.h</li>
<li>형태: <strong>int</strong> epoll_ctl(<strong>int</strong> epfd, <strong>int</strong> op, <strong>int</strong> fd, <strong>struct epoll_event *</strong>event);</li>
<li>인수: <strong>int</strong> epfd : 관찰대상을 등록할 epoll인스턴스의 fd</li>
<li><em>int*</em> op : 관찰대상의 추가, 삭제 또는 변경여부 지정</li>
<li><em>int*</em> fd : 등록할 관찰대상의 fd</li>
<li><em>struct epoll_event **</em>ev : 관찰대상의 관찰 이벤트 유형</li>
<li>반환: 성공 시 epoll의 다른 함수에서 사용할 새로운 fd 반환, 실패 시 -1 반환
소멸 시 close 함수 사용함.</li>
</ul>
<h4 id="2번째-인자인-op에-전달-가능한-상수와-그-의미">2번째 인자인 op에 전달 가능한 상수와 그 의미</h4>
<p>1) EPOLL_CTL_ADD : 관심있는 fd를 epoll 인스턴스에 등록
2) EPOLL_CTL_MOD : 기존 fd의 이벤트 발생상황을 변경
3) EPOLL_CTL_DEL : 기존 fd를 epoll 인스턴스 관심 목록에서 삭제 이때, 4번째 인자인 event에 NULL 전달.</p>
<h4 id="epoll_event-구조체">epoll_event 구조체</h4>
<p>이벤트가 발생한 fd를 묶는 용도로 사용되나, fd를 epoll 인스턴스에 등록할 때, 이벤트 유형을 등록하는 용도로도 사용된다.</p>
<pre><code class="language-cpp">struct epoll_event{

uint32_t    events;    /* epoll 이벤트 (비트 마스트) */    //수신할 데이터가 존재하는 이벤트 발생 시

epoll_data_t    data;    /* 사용자 데이터 */            //epoll 인스턴스에 sockfd 파일 디스크립터 등록을 위함임
};</code></pre>
<blockquote>
<p>epoll_event 구조체 멤버인 events에 저장 가능한 상수와 이벤트 유형
<img src="https://velog.velcdn.com/images/jeong_twelve/post/ec0fdc7a-18bf-4a7a-813e-3d1e9141a7b9/image.png" alt="">
물론 해당 상수들 또한 OR(|)연산자를 이용해서 둘 이상을 함께 등록할 수 있다.</p>
</blockquote>
<p>event 구조체 속 epoll_data_t 구조체</p>
<pre><code class="language-cpp">typedef union epoll_data {

void    *ptr;    /* 사용자 정의 데이터 포인터 */

int    fd;    /* 파일 디스크립터 */

uint32_t    u32;    /* 32비트 정수 */

uint64_t    u64;     /* 64비트 정수 */

} epoll_data_t;
</code></pre>
<blockquote>
<h4 id="epoll_ctl-예시">epoll_ctl 예시:</h4>
</blockquote>
<ul>
<li>epoll_ctl(A, EPOLL_CTL_ADD, B, C); : epoll 인스턴스 A에 파일 디스크립터 B를 등록한다. 이 때 C 이벤트 관찰을 목적으로 한다.</li>
<li>epoll_ctl(A, EPOLL_CTL_DEL, B, NULL); : epoll 인스턴스 A에서 파일 디스크립터 B 삭제</li>
</ul>
<h3 id="epoll_wait-함수">epoll_wait() 함수</h3>
<p>select 함수와 마찬가지로 fd의 변화를 대기하는 함수이다.
구조체 epoll_event 기반의 배열을 넉넉히 선언 후, epoll_wait 함수 호출 시 인자로 전달하면, 상태변화(이벤트)가 발생한 파일 디스크립터의 정보가 이 배열에 별도로 묶인다.-&gt; select 함수 사용시처럼 전체 관심대상 파일 디스크립터를 대상으로한 반복문이 필요없다.</p>
<ul>
<li>헤더: sys/epoll.h</li>
<li>형태: <strong>int</strong> epoll_wait(<strong>int</strong> epfd, <strong>struct epoll_event *</strong>events, <strong>int</strong> maxevents, <strong>timeout</strong>);</li>
<li>인수: <strong>int</strong> epfd : 이벤트 발생의 관찰 영역인 epoll 인스턴스의 파일 디스크립터</li>
<li><em>struct. epoll_event **</em>events : 이벤트가 발생한 fd가 채워질 버퍼의 주소값</li>
<li><em>int*</em> maxevents : 두 번째 인자로 전달된 주소값의 버퍼에 등록 가능한 최대 이벤트 수 - 버퍼를 동적할당 해야함.</li>
<li><em>int*</em> timeout : 1/1000초 단위의 대기시간, -1 전달 시 이벤트 발생까지 무한대기</li>
<li>반환: 실패 시 -1 반환, 성공 시 이벤트가 발생한 fd 수</li>
</ul>
<blockquote>
<h4 id="epoll_wait-예시">epoll_wait 예시:</h4>
</blockquote>
<pre><code class="language-cpp">int event_cnt;
struct epoll_event *ep_events;
...
ep_events=malloc(sizeof(struct epoll_event) * EPOLL_SIZE);    //EPOLL_SIZE는 매크로 상수값
event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
...</code></pre>
<p>epoll 함수를 이용한 멀티플렉싱 echo 서버 프로그램</p>
<pre><code class="language-cpp">#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;stdbool.h&gt;
#include &lt;unistd.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;arpa/inet.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;epoll.h&gt;

#define BUFFER_SIZE 100
#define EPOLL_SIZE 50
void    error_handling(char *message)
{
    fputs(message, stderr);
    fputc(&#39;\n&#39;, stderr);
    exit(EXIT_FAILURE);
}

int main(int argc, char** argv)
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t adr_sz;
    int    str_len, i;
    char    buf[BUF_SIZE];

    struct epoll_event *ep_events;
    struct epoll_event event;
    int epfd, event_cnt;

    if (argc != 2);    //실행파일 경로/PORT번호를 입력으로 받아야 함
    {
        printf(&quot;Usage : %s &lt;port&gt; \n&quot;, argv[0]);
        exit(EXIT_FAILURE);
    }

    /*서버 주소정보 초기화 */
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);    //TCP 소켓 생성
    memset(&amp;serv_sock, 0, sizeof(serv_sock));
    serv_soc.sin_family = AF_INET;
    serv_soc.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_soc.sin_port = htons(PORT);

    /*서버 주소정보를 토대로 주소 할당*/
    if (bind(serv_sock, (struct sockaddr *)&amp;serv_adr, sizeof(serv_adr)) == -1)
        error_handling(&quot;bind() error&quot;);

    /*클라이언트로부터 연결요청을 수락할 준비 완료(진짜 서버소켓이 됌)*/
    if (listen(serv_sock, 5) == -1)
        error_handling(&quot;listen() error&quot;);

    epfd=epoll_creat(EPOLL_SIZE);    //epoll 인스턴스 생성(관심 대상 파일 디스크립터 저장소)
    ep_events = malloc(sizeof (struct epoll_event) * EPOLL_SIZE);
    /*epoll 인스턴스에 있는 fd 중 실제로 이벤트가 발생한 fd를 따로 모아놓는 동적배열
    최대 EPOLL_SIZE 만큼 이벤트가 발생할 수 있음*/

    event.events=EPOLLIN;    //수신한 데이터가 있는 이벤트
    event.data.fd = serv_sock;    //서버소켓이 대상
    epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &amp;event);    //epoll 인스턴스에 이벤트 등록

    while(1)
    {
        event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
        /*epoll 인스턴스에 있는 관심대상에서 이벤트가 발생할 때까지 무한 대기*/
        if (event_cnt == -1)
        {
            puts(&quot;epoll_wait() error&quot;);
            break;
        }
        for (i = 0; i &lt; event_cnt; ++i)    //이벤트 발생한 파일 디스크립터에 대해서만 반복문(select와 가장 큰 차이)
        {
            if (ep_events[i].data.fd == serv_sock)
            /*클라이언트의 연결요청도 데이터 전송을 통해 이루어지므로 서버소켓에 수신된 데이터가 존재한다는 것은 클라이언트의 연결요청이 있었다는 의미*/
            {
                adr_sz = sizeof(clnt_adr);
                clnt_sock = accept(serv_sock, (struct sockaddr *)&amp;clnt_adr, &amp;adr_sz);    //클라이언트의 연결요청을 수락

                /*클라이언트와의 송수신을 위해 새로 생성된 소켓에 대해 이벤트 등록*/
                event.events=EPOLLIN;
                event.data.fd = clnt_sock;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &amp;event);

                printf(&quot;connected client: %d \n&quot;, clnt_sock);
            }
            else    //클라이언트의 메세지를 실제로 수신하는 소켓에 대해 (accept 함수호출로 생성된 소켓)
            {
                str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);
                if (str_len == 0)
                {
                    epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
                    //클라이언트가 종료했으므로 이 소켓 또한 관심대상에서 제외

                    close(ep_events[i].data.fd);    //이 소켓의 연결도 종료
                    printf(&quot;close client: %d \n&quot;, ep_events[i].data.fd);
                }
                else
                    write(ep_events[i].data.fd, buf. str_len);    //수신한 문자열을 다시 클라이언트로 에코
            }
        }
    }
    close(serv_sock);    //서버소켓 소멸
    close(epfd);    //epoll 인스턴스 소멸
    return (0);
}</code></pre>
<h2 id="8-4-kqueue-api함수">8-4 kqueue() API함수</h2>
<p>kqueue는 FreeBSD환경에서 주로 사용된다(epoll은 linux).
커널에 event를 저장할 queue를 생성하고 I/O event가 queue에 쌓이면(event가 발생하면) 해당부분만 처리하는 방식이기 때문에 select, poll처럼 event가 발생한 FD를 찾기위해 전체를 탐색할 필요가 없다. epoll과 환경만 다르고 같다.</p>
<h3 id="kqueue-함수">kqueue() 함수</h3>
<ul>
<li>헤더: sys/event.h</li>
<li>형태: <strong>int</strong> kqueue(<strong>void</strong>);</li>
<li>반환: 커널에 새로운 event queue를 만들고, fd를 return한다.
return된 fd는 아래 kevent()함수에서 이벤트를 등록, 모니터링 하는데 사용된다.
이 queue는 fork(2)로 자식 프로세스 분기 시 상속되지 않는다. error 발생시 -1 반환</li>
</ul>
<h3 id="kevent함수">kevent()함수</h3>
<ul>
<li><p>헤더: sys/event.h</p>
</li>
<li><p>형태: <strong>int</strong> kevent(<strong>int</strong> kq, <strong>const struct kevent *</strong>changelist, <strong>int</strong> nchanges, <strong>struct kevent *</strong>eventlist, <strong>int</strong> nevents, <strong>const struct timespec *</strong>timeout);</p>
</li>
<li><p>인수: <strong>int</strong> kq : kqueue()로부터 반환받은 event queue의 fd.</p>
</li>
<li><p><em>const struct kevent **</em>changelist : &lt;sys/event.h&gt;에 정의된 kevent 구조체 배열의 인자에 대한 pointer. changelist 배열에 저장된 kevent 구조체(이벤트)들은 kqueue에 등록된다.</p>
</li>
<li><p><em>int*</em> nchanges : 등록할 이벤트의 갯수</p>
</li>
<li><p><em>struct kevent **</em>eventlist : 발생한 event가 return 될 배열</p>
</li>
<li><p><em>int*</em> nevents : eventlist 배열의 크기</p>
</li>
<li><p><em>const struct timespec **</em>timeout : timespec구조체 포인터 전달, NULL일 경우 이벤트 발생까지 무한대기한다.</p>
</li>
<li><p>반환: kevent()는 이 배열에서 생성한 kevent를 최대 nevents만큼 정리하여 담아주고 그 갯수를 return한다. </p>
</li>
</ul>
<p>kqueue() 함수의 모든 작업은 kevent라는 구조체에 의해서 이루어진다.
kevent는 ident, filter라는 인자를 하나의 key로 삼아 식별되며, kqueue 내에는 독립적인 kevent만 존재하게 된다. 식별자인 ident 외 filter까지 하나의 key로 보는 이유는 filter가 기존 저장된 event가 이미 존재하는지 판단하기 때문이다.
filter는 kevent의 초기 등록시 실행되며, I/O event가 발생할 때마다 filter가 확인을 하게되고, 신규 event로 판단하면 해당 kevent는 kqueue에 배치된다. 사용자가 kqueue에서 kevent를 검색하려고 할 때도 실행되는데 만약 event 발생 조건에 부합되지 않는다면 해당 kevent는 kqueue에서 제거되고 return되지 않는다. 이렇게 선분류를 해주는 filter덕분에 kqueue에는 최소한의 kevent가 배치될 수 있다.</p>
<h3 id="struct-kevent-구조체">struct kevent 구조체</h3>
<p>#include &lt;sys/event.h&gt;</p>
<pre><code class="language-cpp">struct kevent {
    uintptr_t ident;        /* identifier for this event */
    int16_t   filter;       /* filter for event */
    uint16_t  flags;        /* action flags for kqueue */
    uint32_t  fflags;       /* filter flag value */
    intptr_t  data;         /* filter data value */
    void      *udata;       /* opaque user data identifier */
};</code></pre>
<ul>
<li><p>ident : event 식별자, fd번호가 </p>
</li>
<li><h2 id="filter--event-선처리할-때-사용되는-filter">filter : event 선처리할 때 사용되는 filter</h2>
<ul>
<li><p>EVFILT_READ : FD를 ident로 지정(모니터링) -&gt; 읽을 data가 있을 때마다 event return. fd의 종류에 따라 조금씩 다른 동작을 한다.(socket, vnodes, fifo, pipe 등)</p>
</li>
<li><p>EVFILT_WRITE : FD를 ident로 지정(모니터링) -&gt; 쓸 data가 있을 때마다 event return.</p>
</li>
<li><p>EVFILT_EMPTY : FD를 ident로 지정(모니터링) -&gt; 쓸 data가 없을 때마다 event return.</p>
</li>
<li><p>EVFILT_VNODE : FD를 fflags에서 지정한 event를 ident로 지정 -&gt; event 발생 시 반환</p>
</li>
<li><p>EVFILT_PROC : 감시할 pid 또는 fflags에서 지정한 event를 ident로 지정 -&gt; event 발생 시 반환</p>
</li>
<li><p>EVFILT_SIGNAL : signal number를 ident로 지정 -&gt; signal 발생 시 반환</p>
</li>
<li><p>EVFILT_TIMER : 임의의 timer를 ident로 지정 -&gt; 주기마다 반환</p>
<ul>
<li>flags : event에 수행할 작업</li>
<li></li>
</ul>
</li>
<li><p>EV_ADD : kqueue에 이벤트를 추가
  있는 event를 또 추가하면 인자가 update(덮어쓰기)되어 중복 방지
  추가된 event는 EV_DISABLE 플래그로 재정의되지 않는 한 자동으로 활성화</p>
</li>
<li><p>EV_ENABLE : kevent() 호출 시 event 반환을 허용</p>
</li>
<li><p>EV_DISABLE : 이벤트를 비활성화 하여 kevent()가 반환하지 않도록 함. 필터 자체는 비활성화 되지 않음</p>
</li>
<li><p>EV_DISPATCH : 이벤트 전달 직후 EV_DISABLE 설정</p>
</li>
<li><p>EV_DELETE : kqueue에서 이벤트를 제거</p>
</li>
<li><p>EV_RECEIPT : kequeue 대량 변경 시 유용 (보류중인 event는 배제)</p>
</li>
<li><p>EV_ONESHOT : event 감지로 인한 첫 번째 filter 실행만 반환</p>
</li>
<li><p>EV_CLEAR : 사용자가 event 검색 후 상태 재설정
  재 상태 대신 상태 변화를 보고하는 필터에 유용</p>
</li>
<li><p>EV_ERROR : 각종 error</p>
</li>
</ul>
</li>
<li><p>fflags : filter별 flag</p>
</li>
<li><p>data : filter별 data 값. filter가 EVFILT_READ인 경우 data에는 read가 가능한 바이트 수가 기록된다.</p>
</li>
<li><p>udata : event와 함께 등록하여 event return시 사용할 수 있는 user-data이다. udata 또한 event의 식별자로 사용될 수 있다(optional - kevent64() 및 kevent_qos()는 인자 flags로 udata를 식별자로 사용할지 말지 결정할 수 있다).</p>
</li>
</ul>
<p>kevent 구조체를 쉽게 초기화하기 위한 EV_SET() 함수도 제공된다.</p>
<pre><code class="language-cpp">EV_SET(&amp;kev, ident, filter, flags, fflags, data, udata); // kevent 구조체의 주소를 전달</code></pre>
<p>kqueue를 활용한 멀티플렉싱 서버 구현</p>
<pre><code class="language-cpp">#include &lt;sys/types.h&gt;
#include &lt;sys/event.h&gt;
#include &lt;sys/time.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;arpa/inet.h&gt;
#include &lt;unistd.h&gt;
#include &lt;fcntl.h&gt;

#include &lt;iostream&gt;
#include &lt;map&gt;
#include &lt;vector&gt;

using namespace std;

void exit_with_perror(const string&amp; msg)
{
    cerr &lt;&lt; msg &lt;&lt; endl;
    exit(EXIT_FAILURE);
}

void change_events(vector&lt;struct kevent&gt;&amp; change_list, uintptr_t ident, int16_t filter,
        uint16_t flags, uint32_t fflags, intptr_t data, void *udata)
{
    struct kevent temp_event;

    EV_SET(&amp;temp_event, ident, filter, flags, fflags, data, udata);
    change_list.push_back(temp_event);
}

void disconnect_client(int client_fd, map&lt;int, string&gt;&amp; clients)
{
    cout &lt;&lt; &quot;client disconnected: &quot; &lt;&lt; client_fd &lt;&lt; endl;
    close(client_fd);
    clients.erase(client_fd);
}

int main()
{
    /* 서버 소켓 초기화와 listen 함수로 대기 */
    int server_socket;
    struct sockaddr_in server_addr;

    if ((server_socket = socket(PF_INET, SOCK_STREAM, 0)) == -1)
        exit_with_perror(&quot;socket() error\n&quot; + string(strerror(errno)));

    memset(&amp;server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(8080);
    if (bind(server_socket, (struct sockaddr*)&amp;server_addr, sizeof(server_addr)) == -1)
        exit_with_perror(&quot;bind() error\n&quot; + string(strerror(errno)));

    if (listen(server_socket, 5) == -1)
        exit_with_perror(&quot;listen() error\n&quot; + string(strerror(errno)));
    fcntl(server_socket, F_SETFL, O_NONBLOCK);

     /* kqueue 초기화 */
    int kq;
    if ((kq = kqueue()) == -1)
        exit_with_perror(&quot;kqueue() error\n&quot; + string(strerror(errno)));


    map&lt;int, string&gt; clients; // map for client socket:data
    vector&lt;struct kevent&gt; change_list; // kevent vector for changelist
    struct kevent event_list[8]; // kevent array for eventlist

    /* 서버소켓에 탐지할 이벤트 추가 */
    change_events(change_list, server_socket, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, NULL);
    cout &lt;&lt; &quot;echo server started&quot; &lt;&lt; endl;

    /* 이벤트 감지 후 처리할 반복문 */
    int new_events;
    struct kevent* curr_event;
    while (1)
    {
        /* 새로운 이벤트 감지 및 갯수 반환 */
        new_events = kevent(kq, &amp;change_list[0], change_list.size(), event_list, 8, NULL);
        if (new_events == -1)
            exit_with_perror(&quot;kevent() error\n&quot; + string(strerror(errno)));

        change_list.clear(); // clear change_list for new changes

        for (int i = 0; i &lt; new_events; ++i)
        {
            curr_event = &amp;event_list[i];

            /* 에러 처리 */
            if (curr_event-&gt;flags &amp; EV_ERROR)
            {
                if (curr_event-&gt;ident == server_socket)
                    exit_with_perror(&quot;server socket error&quot;);
                else
                {
                    cerr &lt;&lt; &quot;client socket error&quot; &lt;&lt; endl;
                    disconnect_client(curr_event-&gt;ident, clients);
                }
            }
            else if (curr_event-&gt;filter == EVFILT_READ)
            {
                if (curr_event-&gt;ident == server_socket)
                {
                    /* 새 클라이언트 accept */
                    int client_socket;
                    if ((client_socket = accept(server_socket, NULL, NULL)) == -1)
                        exit_with_perror(&quot;accept() error\n&quot; + string(strerror(errno)));
                    cout &lt;&lt; &quot;accept new client: &quot; &lt;&lt; client_socket &lt;&lt; endl;
                    fcntl(client_socket, F_SETFL, O_NONBLOCK);

                    /* 클라이언트 소켓 감지 이벤트 추가 - add read, write */
                    change_events(change_list, client_socket, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, NULL);
                    change_events(change_list, client_socket, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, NULL);
                    clients[client_socket] = &quot;&quot;;
                }
                else if (clients.find(curr_event-&gt;ident)!= clients.end())
                {
                    /* 클라이언트로부터 data read */
                    char buf[1024];
                    int n = read(curr_event-&gt;ident, buf, sizeof(buf));

                    if (n &lt;= 0)
                    {
                        if (n &lt; 0)
                            cerr &lt;&lt; &quot;client read error!&quot; &lt;&lt; endl;
                        disconnect_client(curr_event-&gt;ident, clients);
                    }
                    else
                    {
                        buf[n] = &#39;\0&#39;;
                        clients[curr_event-&gt;ident] += buf;
                        cout &lt;&lt; &quot;received data from &quot; &lt;&lt; curr_event-&gt;ident &lt;&lt; &quot;: &quot; &lt;&lt; clients[curr_event-&gt;ident] &lt;&lt; endl;
                    }
                }
            }
            else if (curr_event-&gt;filter == EVFILT_WRITE)
            {
                /* data를 클라이언트에게 send */
                map&lt;int, string&gt;::iterator it = clients.find(curr_event-&gt;ident);
                if (it != clients.end())
                {
                    if (clients[curr_event-&gt;ident] != &quot;&quot;)
                    {
                        int n;
                        if ((n = write(curr_event-&gt;ident, clients[curr_event-&gt;ident].c_str(),
                                        clients[curr_event-&gt;ident].size()) == -1))
                        {
                            cerr &lt;&lt; &quot;client write error!&quot; &lt;&lt; endl;
                            disconnect_client(curr_event-&gt;ident, clients);  
                        }
                        else
                            clients[curr_event-&gt;ident].clear();
                    }
                }
            }
        }

    }
    return (0);
}</code></pre>
<p>vector를 통해 탐지할 이벤트를 관리, return되는 event는 크기 8의 event_list로 받음. (만약 반환할 이벤트가 설정 배열 길이인 8보다 많다면, nevents만큼만 배열에 담고 나머지는 다음 kevent 호출 때 반환.)
(nevents 크기는 최대 60000개까지 가능)</p>
<p>client 연결 해제 시에는 DELETE 이벤트를 등록하지 않고 close()만 하도록 함. close()시 닫힌 fd를 참조하는 모든 이벤트는 비활성화되고 삭제되기 때문이다.</p>
<p>kqueue는 select나 poll에 비해 이벤트 처리에서 효율적인데, 그 이유는 다음과 같다.</p>
<ul>
<li><strong>이벤트 발생 시, 해당 이벤트에 접근하는 시간복잡도가 O(1)이다.</strong>
select와 poll의 경우 이벤트 발생 시 해당 이벤트에 접근하는 시간복잡도가 O(N)이나, kqueue는 발생한 이벤트를 정리하여 return해주기 때문에 O(1)로 접근 가능하다.</li>
<li><strong>등록된 이벤트를 따로 관리할 필요가 없다.</strong>
select는 fd_set, poll은 poll_fd 구조체의 배열로 모니터링할 이벤트들을 사용자가 관리하고, 이를 select()나 poll()에 전달해야 하지만, kqueue의 경우 새로 등록할 이벤트, 발생한 이벤트만 관리해주면 된다. 모니터링되는 이벤트는 kqueue, 즉 커널에서 관리된다.</li>
</ul>
<h1 id="9-getaddrinfo">9. getaddrinfo</h1>
<p>getaddrinfo는 domain address를 받아서 네트워크 주소 정보(IP address)를 가져오는 함수이다.</p>
<p>예를들면, <a href="http://www.google.co.kr%EB%9D%BC%EB%8A%94">http://www.google.co.kr라는</a> domain address가 있는데, 이 주소는 사람이 알아보기 쉬운 주고이긴 하지만, 컴퓨터는 이 주소를 가지고 해당되는 구글의 서버를 찾아가지 못한다. 그래서 이 domain address와 대응되는 IP주소가 무엇인지를 알아 낸 뒤에 그 IP주소로 연결을 해야한다. 
즉, Domain address -&gt; IP address 변환을 하고 싶을 때 사용하는 함수라는 뜻이다.
이걸 전문용어로 DNS (Domain Name System/Service) resolving 이라고 한다.</p>
<p>getaddrinfo 함수는 총 4개의 매개변수를 가진다.
그 중, 1~3번째는 입력 매개변수이고, 4번째 매개변수는 결과를 사용자에게 돌려주는 출력 매개변수이다.
결과는  addrinfo 구조체 (strcut addrinfo) 의 linked list로 돌려준다.</p>
<p>이 결과는 사용을 끝낸 뒤엔 freeaddrinfo 함수로 메모리 해제를 해주어야 한다. 그렇지 않으면 메모리 누수가 발생한다.</p>
<p><strong>헤더</strong></p>
<pre><code class="language-cpp">#include &lt;sys/types.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netdb.h&gt;</code></pre>
<ul>
<li>형태 : <strong>int</strong> getaddrinfo(<strong>const char *</strong>hostname, <strong>const char *</strong>service, <strong>const struct addrinfo *</strong>hints, <strong>struct addrinfo **</strong>result);</li>
<li>인수 : <strong>const char *</strong>hostname : 호스트 이름 혹은 주소 문자열(주소 문자열은 =&gt; IPv4의 점으로 구분하는 10진 주소 문자열이거나 IPv6의 16진 문자열)</li>
<li><em>const char **</em>service : 서비스 이름 혹은 10진수로 표현한 포트 번호 문자열</li>
<li><em>const struct addrinfo **</em>hints : getaddrinfo 함수에게 말그대로 힌트를 준다. 희망하는 유형을 알려주는 힌트를 제공한다. addrinfo 구조체에 hint로 줄 정보를 채운 뒤, 그것의 주소값을 넘기면 된다. 이 힌트는 반환받을 result를 제한하는 기준을 지정하는 것이다. 예를들면, IPv4주소만 받고 싶거나, IPv6주소만 받고 싶을 수도 있고, 둘다 받고 싶을수도 있다. 이럴땐hints의 ai_family의 값을 조작하면 된다. 별도의 hint를 제공하지 않을 경우, NULL을 넣는다.</li>
<li><em>struct addrinfo ***</em>result : DNS서버로부터 받은 네트워크 주소 정보(IP 주소)를 돌려주는 output 매개변수이다. addrinfo 구조체를 사용하며, 링크드 리스트이다. 이addrinfo 구조체에 대해서는 아래 그림을 참고하면 이해하기 쉽다. 이 result의 내용중 필요한것들은 적절히 copy하여 사용자의 변수로 옮겨두어야 하며, result는 사용이 끝나는 즉시 freeaddrinfo 함수로 메모리 해제를 해주어야 한다.</li>
<li>반환 : 성공 시 0 반환, 실패 시 아래 에러 중 하나 반환</li>
</ul>
<p><strong>EAI_ADDRFAMILY</strong>
지정된 네트워크 호스트에 요청했던 address family 주소가 없는 경우이다. 예를들어 IPv4주소만 갖는 네트워크 호스트에게 &#39;IPv6주소를 내놔라&#39; 하고 요청한다면, 이런 에러가 반환된다.</p>
<p><strong>EAI_AGAIN</strong>
nameserver가 일시적인 오류 표시를 반환했다. 일정 시간 이후에 다시 시도하라는 의미이다.</p>
<p><strong>EAI_BADFLAGS</strong>
hints.ai_flags에 잘못된 플래그가 포함되어 있다. 또는 hints.ai_flags에는 AI_CANONNAME이 포함되었고 이름이 NULL인 경우이다.</p>
<p><strong>EAI_FAIL</strong>
nameserver가 지속되는 오류 표시를 반환했다. EAI_AGAIN과는 달리 다시 시도해도 실패할 것이라는 의미다.</p>
<p><strong>EAI_FAMILY</strong>
요청한 address family는 지원되지 않는다.</p>
<p><strong>EAI_MEMORY</strong>
getaddrinfo를 수행하기에 메모리가 부족한 경우다. 거의 막장인 상황이다. kernel의 OOM killer가 제대로 작동하길 기대하면서 기다렸다가 일정 시간 지난 후 재시도 해보고 그래도 같은 에러를 받는다면 속편하게 재부팅 하라.</p>
<p><strong>EAI_NODATA</strong>
지정한 네트워크 호스트가 있긴 한데... 네트워크 주소가 정의되어 있지 않은 경우다.</p>
<p><strong>EAI_NONAME</strong>
노드 또는 서비스를 알 수 없다.
또는 노드와 서비스 모두가 NULL이다.
또는 AI_NUMERICSERV가 hints.ai_flags에 지정되었는데, 정작 service는 포트 번호를 나타내는 숫자형태의 문자열이 아니었다.
어떤 경우이건, 입력 매개변수를 잘못 넣었다는 의미다. 자신이 매개변수를 잘 넣었는지? 스스로의 코드를 다시 점검 해볼 것.</p>
<p><strong>EAI_SERVICE</strong>
요청한 서비스를 요청한 소켓 유형에 사용할 수 없다. 다른 소켓 유형을 통해 사용할 수 있다. 예를 들어 서비스가 &quot;쉘&quot;(스트림 소켓에서만 사용할 수있는 서비스)이고 hints.ai_protocol이 IPPROTO_UDP이거나 hints.ai_socktype이 SOCK_DGRAM 인 경우 이 오류가 발생할 수 있다.
또는 서비스가 NULL이 아니고 hints.ai_socktype이 SOCK_RAW (서비스 개념을 지원하지 않는 소켓 유형) 인 경우 오류가 발생할 수 있습니다.</p>
<p><strong>EAI_SOCKTYPE</strong>
요청한 소켓 유형이 지원되지 않는다. 
예를 들어, hints.ai_socktype 및 hints.ai_protocol이 일치하지 않는 경우다. ai_socktype에는 SOCK_DGRAM 를 넣고, ai_protocol에는 IPPROTO_TCP를 넣은 hint를 입력 매개변수로 넘기면 발생할 수 있는 에러이다.</p>
<p><strong>EAI_SYSTEM</strong>
위 모든 경우를 제외한 뭔가 다른 시스템 오류다, 골치아픈 경우다. 자세한 내용은 errno를 확인해야 하는 경우다.</p>
<blockquote>
<p>const struct addrinfo *hints의 addrinfo 구조체 원형</p>
</blockquote>
<pre><code class="language-cpp">struct addrinfo {
   int          ai_flags;           // 추가적인 옵션을 정의 할 때 사용함. 여러 flag를 bitwise OR-ing 하여 넣는다 
   int          ai_family;          // address family를 나타냄. AF_INET, AF_INET6, AF_UNSPEC 
   int          ai_socktype;        // socket type을 나타냄. SOCK_SREAM, SOCK_DGRAM 
   int          ai_protocol;        // IPv4와 IPv6에 대한 IPPROTO_xxx와 같은 값을 가짐. 
   socklen_t    ai_addrlen;         // socket 주소인 ai_addr의 길이를 나타냄 
   char        *ai_canonname;       // 호스트의 canonical name을 나타냄 
   struct sockaddr    *ai_addr;     // socket 주소를 나타내는 구조체 포인터 
   struct addrinfo    *ai_next;     // 주소정보 구조체 addrinfo는 linked list이다. 다음 데이터의 포인터 
};</code></pre>
<p>getaddrinfo()에서 hints 인자를 넣을 때, 위의 int형 4개 필드에만(최대 4개) 값을 지정해준다. 나머지는 0(또는 NULL)이어야 한다. 실제 우리는 memset을 활용해서 전체 구조체를 0으로 설정하고, 4개 이하의 필드에만 값을 지정해준다.</p>
<blockquote>
<p>ai_flags : 이 필드는 기본 동작을 수정해준다. 여러 예시들을 알아보자.</p>
</blockquote>
<ul>
<li>AI_PASSIVE : 이 경우, host 인자는 NULL이어야 한다. 클라이언트 connect()함수를 호출할 때 활성화된 소켓으로 이용할 수 있는 소켓 주소를 가져온다.</li>
<li>AI_CANONNAME : 기본적으로 ai_canonname필드는 NULL이다. 이 플래그가 설정되면 addrinfor 구조체 첫 번째의 ai_canonname필드에 가서 host의 규범적(canonical) 이름을 나타낸다.</li>
<li>AI_NUMERICHOST : 호스트 이름으로 &quot;12.23.12.23&quot;과 같이 숫자로 된 IP주소를 사용함을 의미한다.</li>
<li>AI_NUMERICSERV : 기본적으로 service 인자는 서비스이름이나 포트번호인데, 이 플래그가 사용되면 service 인자가 포트번호여야 한다.</li>
</ul>
<blockquote>
<p>ai_family : 기본적으로 getaddrinfo()는 IPv4와 IPv6 소켓 주소를 리턴한다.</p>
</blockquote>
<ul>
<li>AF_INET : IPv4 주소로 리턴한다.</li>
<li>AF_INET6 : IPv6 주소로 리턴한다.</li>
<li>AF_UNSPEC : 프로토콜에 관계없이, 사용할 수 있다.</li>
</ul>
<blockquote>
<p>ai_socktype : 소켓의 종류 3가지에 따라 적어준다.</p>
</blockquote>
<ul>
<li>SOCK_STREAM : TCP</li>
<li>SOCK_DGRAM : UDP</li>
<li>SOCKRAW : RAW</li>
</ul>
<blockquote>
<p>ai_protocol : 0 또는 IPPROTO</p>
</blockquote>
<h4 id="getaddrinfo-함수-예제">getaddrinfo 함수 예제</h4>
<p><a href="http://www.google.com%EC%9D%98">www.google.com의</a> 80포트 네트워크 주소를 얻는 예제다.</p>
<pre><code class="language-cpp">#include &lt;sys/types.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netdb.h&gt;

int main(int argc,char *argv[])
{
    int status;
    struct addrinfo hints;
    struct addrinfo *servinfo;  // 결과를 저장할 변수

    memset(&amp;hints, 0, sizeof(hints)); // hints 구조체의 모든 값을 0으로 초기화
    hints.ai_family = AF_UNSPEC;     // IPv4와 IPv6 상관하지 않고 결과를 모두 받겠다
    hints.ai_socktype = SOCK_STREAM; // TCP stream sockets

    status = getaddrinfo(&quot;www.google.com&quot;, &quot;80&quot;, &amp;hints, &amp;servinfo);
}</code></pre>
<h1 id="10-gai_strerror-함수">10. gai_strerror 함수</h1>
<ul>
<li>형태 : <strong>const char *</strong>gai_strerror(<strong>int</strong> errcode);</li>
<li>인수 : <strong>int</strong> errcode : getaddrinfo 함수에서 반환된 EAI 코드
해당 errcode를 매개변수로 받아 다음의 오류 메세지를 출력한다.
<img src="https://velog.velcdn.com/images/jeong_twelve/post/b5658228-d72e-4c24-abd4-ad2d45b2f8f8/image.png" alt=""></li>
</ul>
<h1 id="11-freeaddrinfo-함수">11. freeaddrinfo 함수</h1>
<ul>
<li>형태 : <strong>void</strong> freeaddrinfo(<strong>strcut addrinfo *</strong>result);</li>
<li>인수 : <strong>strcut addrinfo *</strong>result : getaddrinfo함수에서 마지막에 넣어줬던 result addrinfo 구조체의 주소
getaddrinfo()함수에서 동적 할당한 구조체 메모리 해제</li>
</ul>
<h1 id="12-setsockopt-함수">12. setsockopt 함수</h1>
<ul>
<li>헤더 : &lt;sys/socket.h&gt;</li>
<li>형태 : <strong>int</strong> setsockopt(<strong>int</strong> sockfd, <strong>int</strong> level, <strong>int</strong> optname,  <strong>const void *</strong>optval, <strong>socklen_t</strong> optlen);</li>
<li>인수 : <strong>int</strong> sockfd : socket(2), accept(2) 등으로 생성된 socket descriptor</li>
<li><em>int*</em> level : optname 값이 socket level인지 특정 protocol에 대한 설정인지를 지정하는 값
. SOL_SOCKET : optname이 socket level에서 설정하는 option명임을 지정함
. IPPROTO_IP : optname이 IP protocol level에서 설정하는 option명임을 지정함
. IPPROTO_TCP : optname이 TCP protocol level에서 설정하는 option명임을 지정함</li>
<li><em>int*</em> optname : level의 종류에 따라 다른 설정이름을 갖는다.</li>
</ul>
<blockquote>
<p><strong>SOL_SOCKET level의 상수</strong>
SO_ACCEPTCONN : accept된 connection 여부 조회(get only) : 1이면 accept(2)된 connection.
 SO_BROADCAST : datagram socket에 boradcast flag값을 (set/get)
 SO_DOMAIN : socket에 설정된 domain값 (ex. AF_INET, AF_UNIX 등...)을 얻는다. (get only)
 SO_ERROR : socket error를 읽고 지움. (get only)
 SO_DONTROUTE : gateway를 통해서 전송을 금지하고 직접 연결된 host끼리만 전달하게 함. (set/get)
 SO_KEEPALIVE : cconnection-oriented socket에서 keep alive message를 전송할 수 있도록 함. (set/get)
 SO_LINGER : linger option 설정 (set/get)</p>
</blockquote>
<pre><code class="language-cpp">struct linger {
         int l_onoff;    /* linger active */
         int l_linger;   /* how many seconds to linger for */
};
l_onoff를 1로 설정하면, close(2), shutdown(2) 함수를 실행하면 미전송된 데이터를 정상적으로 전송하거나
     linger timeout이 도래되면 함수를 return함.  그렇지 않으면 바로 return되고 background로 작업하게 됨.</code></pre>
<p>SO_OOBINLINE : out of bound data를 직접 읽을 수 있게 set/get (주로 X.25에서 사용)
 SO_PROTOCOL : socket에 설정된 protocol을 읽음.
 SO_RCVBUF : socket에서 읽을 수 있는 최대 buffer의 크기를 set/get함
 SO_REUSEADDR : bind(2) 시에 local 주소를 재사용할 것인지 여부를 set/get함
 SO_SNDBUF : socket에서 write할 수 있는 최대 buffer의 크기를 set/get함
 SO_TYPE : 설정된 socket의 type(ex. SOCK_STREAM, SOCK_DGRAM0을 get함 </p>
<p><strong>const void *</strong>optval : 설정값을 저장하기 위한 버퍼의 포인터
<strong>socklen_t</strong> optlen : optval의 크기
설정값(optval)을 void * 로 넘기는 이유는 설정하고자 하는 소켓옵션에 따라서, boolean, interger, 구조체등 다양한 크기를 가지는 데이터형이 사용되기 때문이다. 만약 변경하고자 하는 소켓옵션이 boolean값을 사용한다면, 0혹은 1값을 사용하면 된다.</p>
<ul>
<li>반환: 정상 작동 시 0 반환, 에러 시 -1 반환(상세 오류 내용은 errno에 설정됨)<blockquote>
<p> EBADF     : sockfd 유효한 descriptor가 아님.
EFAULT    : optval가 이 프로세스의 유효한 메모리 번지가 아님.
EINVAL    : optval이나 optlen이 유효하지 않음. 
ENOPROTOOPT : level이 알려지지 않은 값.
ENOTSOCK  : sockfd가 file descriptor이지 socket descriptor가 아님.</p>
</blockquote>
</li>
</ul>
<p>근데 사실 이건 다 의미없다.
<a href="https://cjwoov.tistory.com/30">https://cjwoov.tistory.com/30</a>
해당 블로그를 읽어보자.^^</p>
<h1 id="13-getsockname-함수">13. getsockname 함수</h1>
<p>지정된 소켓에 대한 로컬 이름(IP와 port 번호)을 얻어내는 함수이다.</p>
<ul>
<li><p>헤더 : &lt;sys/socket.h&gt;</p>
</li>
<li><p>형태 : <strong>int</strong> getsockname(<strong>int</strong> sockfd, <strong>struct sockaddr *</strong>addr, <strong>socklen_t *</strong>addrlen);</p>
</li>
<li><p>인자 : <strong>int</strong> sockfd : 정상적으로 생성된 socket descriptor. 주로 connect(2), accept(2), bind(2)된 socket descriptor.</p>
</li>
<li><p><em>struct sockaddr **</em>addr :  자신의 주소를 저장할 buffer.</p>
<ul>
<li>정상적으로 실행되면, sockfd에 설정된 자신의 address주소값이 채워진다. (OUTPUT용)</li>
<li>socket에 자신의 주소는 client socket에서는 bind(2)를 하지 않는 이상은 connect(2)시에 자동으로 할당되며,
server socket도 자신의 주소는 port번호만 설정하는 것이 일반적이라 accept(2)시에 할당된다.
따라서 대부분은 connect(2), accept(2) 후의 socket descriptor에 사용한다.
구조체 종류는 Address Family (AF_* 상수)의 종류에 따라 다른 구조체를 parameter로 전달해야 한다.</li>
</ul>
</li>
<li><p><em>socklen_t **</em>addrlen : </p>
<ul>
<li>두번째 파라미터인 addr 구조체의 크기를 설정하고 함수를 호출해야 합니다. (INPUT)</li>
<li>getsockname( ) 호출이 끝나면 실제로 addr에 채워진 사이즈가 저장됩니다. (OUTPUT)</li>
</ul>
</li>
<li><p>반환 : 정상 실행 시 0 반환, 오류 발생 시 -1 반환 (상세 오류정보 errno 전역변수에 저장됨)</p>
<blockquote>
<p>EBADF    : sockfd가 유효하지 않은 descriptor입니다.
EFAULT   : addr 변수가 유효한 메모리 영역이 아닙니다.
EINVAL   : addrlen 변수가 유효한 메모리 영역이 아닙니다.
ENOBUFS  : 시스템에서 처리를 위한 resource 부족합니다.
ENOTCONN : socket이 연결된 상태가 아닙니다. (connect()를 호출하지 않았거나 accept()되지않은 socket입니다.
ENOTSOCK : sockfd가 socket이 아닙니다.</p>
</blockquote>
</li>
<li><p>예제 : </p>
<pre><code class="language-cpp">#include &lt;sys/socket.h&gt;
</code></pre>
</li>
</ul>
<p>......</p>
<p>int       connected_fd;
struct    sockaddr_in my_addr;
socklen_t addr_len   = sizeof(struct sockaddr_in);
char      my_ip_addr[16];</p>
<p>......</p>
<p>if(getsockname(connected_fd, &amp;my_addr, &amp;addr_len) == -1) {
    fprintf(stderr, &quot;getsockname() 호출 error : %s\n&quot;, strerror(errno));
    return -1;<br>}</p>
<p>strncpy(my_ip_addr, inet_ntoa(my_addr.sin_addr.s_addr), 16);
printf(&quot;나의 IP address는 %s입니다.\n&quot;, my_ip_addr);</p>
<p>......</p>
<pre><code># 14. fcntl 함수
- 헤더 : &lt;fcntl.h&gt; &lt;unistd.h&gt;
- 형태 : 
**int** fcntl(**int** fd, **int** cmd);
**int** fcntl(**int** fd, **int** cmd, **long** arg);
**int** fcntl(**int** fd, **int** cmd, **struct flock ***lock);
- 인자 : 
**int** fd : open(2), socket(2) 등의 시스템 호출을 통해서 만들어진 파일 지정자
**int** cmd : fd 에 대한 특성을 제어하기위한 값
**int** arg : cmd 값에 따라 추가로 필요한 인자값
1. **F_DUPFD** : **기존 fd 를 복제한다.** 언뜻보면 dup2(2) 함수와 매우 비슷한데, dup2 는 복사될 파일지정자를 사용자가 지시하는 반면, F_DUPFD 를 사용할경우 arg 와 같은 크기의 파일지정자를 되돌려주거나, 이미 사용되어지고 있다면, arg 보다 큰 할당가능한 파일지정번호중 가장 작은 번호를 되돌려준다.
이 복사된 파일지정번호는 잠금, 파일위치 포인터, 플레그 등을 공유한다. 즉 파일지정자들중 하나에서 파일의 위치가 변경된다면(lseek등을 이용), 다른 파일지정자도 변경된다.
그렇지만 close-on-exec 는 공유하지 않는다.    (close-on-exec)아래에 기술 예정
2. **F_GETFD** : fd 플래그들을 조회한다.(리턴값으로 fd에 대한 플래그를 넘겨준다) 현재는 FD_CLOEXEC 플래그 하나만 반환해준다. (close-on-exec와 관련)
3. **F_SETFD** : fd 플래그들을 설정한다(FD_CLOEXEC(close-on-exec) 의 값을 지정된 비트값으로 설정한다.). 설정할 새로운 플래그 값은 세 번째 인수 arg에 지정한다.
4. **F_GETFL** : 파일지정자에 대한 플래그값 - open(2) 호출시 지정한 플래그를 되돌려준다.
5. **F_SETFL** : arg 에 지정된 값으로 파일지정자 fd 의 플래그를 재 설정한다. 현재는 단지 O_APPEND, O_NONBLOCK, O_SYNC, O_DSYNC, O_RSYNC, O_FSYNC, O_ASYNC 만을 설정할수 있다. 다른 플래그들 (O_WRONLY 와 같은) 은 영향을 받지 않는다.
6. **F_GETOWN** : 이것은 비동기 입출력과 관련되어서 사용되며, SIGIO와 SIGURG 신호를 받는 프로세스 아이디를 얻기 위해서 사용된다.
7. **F_SETOWN** : 비동기 입출력과 관련되어서 사용되며, SIGIO, SIGURG 시그널을 수신하는 프로세스 아이디(혹은 그룹)을 설정하기 위해서 사용된다.

**struct flock ***lock : 전체나 파일의 일부를 다른 프로세스에서 사용하지 못하게 제한 해주기 위한 잠금 옵션
파일을 열 때 open() 함수나 fopen() 함수의 mode를 이용하여 다른 프로세스가 읽기나 쓰기를 제한할 수 있다. 그러나 이것은 파일 전체에 대해 적용되며 제한을 변경하기 위해서는 파일을 닫았다가 다시 열어야 한다.
fcntl()은 오픈된 파일에 대해서 필요에 따라 제한을 여러 번 변경하실 수 있으며, 파일 전체 뿐만 아니라 일부만 제한할 수 있다. 즉, 파일의 특정 부분을 &quot;잠금&quot; 상태를 만들 수 있어서 fcntl() 함수를 &quot;파일 잠금 함수&quot;라기 보다는 &quot;레코드 잠금 함수&quot;로 부른다.
#### 주의할 점
주의 하실 점은 파일에 대한 잠금은 프로세스별로 잠금 정보를 지정하는 것 뿐이지 실제로 다른 프로세스가 읽고 쓰는 것을 못하게 하는 것은 아니다. 즉, 쓰기를 제한했다고 해서 다른 프로세스에서 write() 가 실행이 안 되거나 에러가 발생하지 않는다.

잠금 정보는 하나의 파일을 여러 프로세스가 동시에 사용하는 경우, 같은 시간에 쓰기를 하거나 아직 한쪽에서 변경 중이라면 다른 프로세스가 읽지를 못하게 하기 위한 정보를 주고 받기 위한 방법으로 이해해야 한다.

쓰기를 하기 전에 쓰기 잠금이 가능한지를 확인한 후에 write() 를 실행하는게 보통이다. 그러나 확인없이 쓰기를 한다면 쓰기가 가능하다. 그러므로 잠금 정보는 프로세스가 쓰기를 못하게 한다가 아니라 지금은 읽어 서는 안 된다 또는 쓰기를 해서는 안 된다라는 정보로 이용해야 한다.

잠금옵션인 lock을 사용하기 위해서는 cmd에 다음과 같은 명령어를 써줘야 한다.
F_GETLK : 레코드의 잠금 상태를 돌려주며, 정보는 세번째 인수인 lock에 담겨져 온다.
F_SETLK : 레코드 잠금을 요청하며, 다른 프로세스가 먼저 선점해서 실패했다면 즉시 -1 로 복귀한다.
F_SETLKW : 끝의 W는 wait의 약자로 레코드 잠금을 요청했는데, 다른 프로세스가 먼저 선점해서 실패했다면 그 프로세스가 해제할 때까지 대기한다.

struct flock *lock의 구조체 struct flock
```cpp
struct flock {
        short   l_type;
        short   l_whence;
        off_t   l_start;
        off_t   l_len;
        pid_t   l_pid;
        __ARCH_FLOCK_PAD
};</code></pre><p>l_type 은 어떻게 잠금을 할지, 해제할지를 정한다. 즉, 아래와 같은 상수가 정의되어 있다.
F_RDLCK : 다른 프로세스가 읽기 잠금만 가능하게하고 쓰기 잠금은 못하게 한다.
F_WRLCK : 다른 프로세스는 읽기 잠금과 쓰기 잠금 모두 불가능하도록 한다.
F_UNLCK : 잠금을 해제한다.</p>
<p>l_whence 는 블록할 영역을 지정하는 기준 위치를 지정한다. 즉, 파일 첫 부분부터 따질지, 아니면 현재 읽기/쓰기 포인터를 기준으로 따질지를 정한다.
SEEK_SET : 파일의 시작 위치
SEEK_CUR : 현재 읽기/쓰기 포인터를 기준
SEEK_END : 파일의 끝을 기준
즉, l_whence가 SEEK_CUR이고 l_start가 0이면서 l_len이 0 이면 파일 전체를 가르키게 된다.</p>
<p>l_pid 는 F_GETLK 를 실행하여 레코드에 대한 잠금 상태 정보를 구할 때, 이미 잠금을 실행하고 있는 프로세스의 ID 이다.</p>
<h2 id="141-close-on-exec">14.1 CLOSE-ON-EXEC</h2>
<p>보통 프로세스에서 exec를 시켜서 새로운 프로세스를 실행시키면 이 새로운 프로세스는 기존의 프로세스의 이미지를 덮어쓰게 된다. 그러면서 특별한 설정이 없을경우 열린파일지정자를 그대로 넘겨주게 된다.
그러나 때때로 exec 를 이용해서 프로세스를 만들기전에 기존에 열렸던 파일지정자들을 깨끗하게 정리하고 싶을때가 있을것이다. 이러한 경우를 close-on-exec 시킨다라고 말하며, fcntl 을 이용해서 열린 파일지정자에 대해서 close-on-exec 작동을 하도록 할수 있다.
예제 : </p>
<pre><code class="language-cpp">#include &lt;unistd.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;sys/stat.h&gt;

int main()
{
    int fd;
    int val;
    fd = open(&quot;exec_copy.txt&quot;, O_CREAT);

    // FD_CLOEXEC 값을 fcntl 을 이용해서 
    // 가져온 다음 세팅되어 있는지 검사한다.  
    val = fcntl(fd, F_GETFD, 0);
    if (val &amp; FD_CLOEXEC)
        printf(&quot;close-on-exec bit on\n&quot;);
    else
        printf(&quot;close-on-exec bit off\n&quot;);


    // FD_CLOEXEC 를 세팅한다. 
    val |= FD_CLOEXEC;
    if (val &amp; FD_CLOEXEC)
        printf(&quot;close-on-exec bit on\n&quot;);
    else
        printf(&quot;close-on-exec bit off\n&quot;);
    fcntl(fd, F_SETFD, val);

    // loop 프로그램을 exec 시킨다.  
    execl(&quot;/home/my_cvs/test/c_source/loop&quot;, &quot;./loop&quot;, 0);
}</code></pre>
<p>다양한 예제는 아래 블로그에서 확인할 수 있다.
<a href="https://www.joinc.co.kr/w/Site/system_programing/File/Fcntl#CLOSEONEXEC">https://www.joinc.co.kr/w/Site/system_programing/File/Fcntl#CLOSEONEXEC</a></p>
<h1 id="끝">끝!</h1>
<p>참고 : 
<a href="https://hyeonski.tistory.com/9">https://hyeonski.tistory.com/9</a>
<a href="https://sncap.tistory.com/226">https://sncap.tistory.com/226</a>
<a href="https://www.it-note.kr/120">https://www.it-note.kr/120</a>
<a href="https://learn.microsoft.com/ko-kr/windows/win32/api/winsock2/nf-winsock2-setsockopt">https://learn.microsoft.com/ko-kr/windows/win32/api/winsock2/nf-winsock2-setsockopt</a>
<a href="https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;blogId=sojuchoigo&amp;logNo=120021310463">https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;blogId=sojuchoigo&amp;logNo=120021310463</a>
<a href="https://www.joinc.co.kr/w/man/2/getsockname">https://www.joinc.co.kr/w/man/2/getsockname</a>
<a href="https://badayak.com/entry/C%EC%96%B8%EC%96%B4-%EB%A0%88%EC%BD%94%EB%93%9C-%EC%9E%A0%EA%B8%88-%ED%95%A8%EC%88%98-fcntl">https://badayak.com/entry/C%EC%96%B8%EC%96%B4-%EB%A0%88%EC%BD%94%EB%93%9C-%EC%9E%A0%EA%B8%88-%ED%95%A8%EC%88%98-fcntl</a>
<a href="https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;blogId=neakoo35&amp;logNo=30131475424">https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;blogId=neakoo35&amp;logNo=30131475424</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Multiflexing]echo 서버, 클라이언트 만들어보기]]></title>
            <link>https://velog.io/@jeong_twelve/Multiflexingecho-%EC%84%9C%EB%B2%84-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@jeong_twelve/Multiflexingecho-%EC%84%9C%EB%B2%84-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Fri, 04 Aug 2023 07:17:35 GMT</pubDate>
            <description><![CDATA[<h3 id="소켓-네트워크에서-서버와-클라이언트가-서로-특정-포트를-이용하여-양방향-통신하도록-만들어주는-소프트웨어-장치">소켓: 네트워크에서 서버와 클라이언트가 서로 특정 포트를 이용하여 양방향 통신하도록 만들어주는 소프트웨어 장치</h3>
<h4 id="소켓-프로그래밍-소켓을-사용하여-네트워크를-구성해나가는-과정">소켓 프로그래밍: 소켓을 사용하여 네트워크를 구성해나가는 과정</h4>
<p>서버소켓과 클라이언트 소켓의 흐름은 다음과 같다.</p>
<h2 id="서버소켓">서버소켓:</h2>
<pre><code>1. 소켓 생성: Create
2. 서버가 사용할 주소 지정하여 결합(IP, Port): Bind
3. 연결 요청 대기: Listen
4. 요청 수신시 받기: Accept
5. 연결 수립시 데이터 송수신: send/recv
6. 송수신 완료, 소켓 닫기: Close</code></pre><h2 id="클라이언트-소켓">클라이언트 소켓:</h2>
<pre><code> 1. 소켓 생성: Create
2. 서버 연결 요청: Connect
3. 연결 요청 받으면 데이터 송수신: send/recv
4. 송수신 완료, 소켓 닫기: Close</code></pre><p><img src="https://velog.velcdn.com/images/jeong_twelve/post/24fa3d5a-5b01-4349-9fe7-1cba47cb1200/image.png" alt="">
이미지 출처: <a href="https://saynot.tistory.com/entry/%EC%86%8C%EC%BC%93-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-TCP-Echo-Client-Server-%EA%B5%AC%ED%98%84">https://saynot.tistory.com/entry/%EC%86%8C%EC%BC%93-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-TCP-Echo-Client-Server-%EA%B5%AC%ED%98%84</a></p>
<h1 id="서버클라이언트-함수-정리">서버/클라이언트 함수 정리</h1>
<h1 id="-서버-함수-정리">-&gt;서버 함수 정리</h1>
<h2 id="1-socket-함수">1. Socket() 함수</h2>
<p>-Socket 함수는 어떤 프로토콜을 가진 소켓으로 통신할 것인지 결정하여 생성하는 함수이다.</p>
<ul>
<li>헤더: sys/types.h, sys/socket.h</li>
<li>형태: <strong>int</strong> socket(<strong>int</strong> domain, <strong>int</strong> type, <strong>int</strong> protocol)</li>
<li>인수: <strong>int</strong> domain 인터넷을 통해 통신할 지, 같은 시스템 내에서 프로세스 끼리 통신할 지의 여부를 설정합니다. </li>
<li><em>int*</em> type 데이터의 전송 형태 지정 </li>
<li><em>int*</em> protocol 통신에 있어 특정 프로토콜을 사용을 지정하기 위한 변수이며, 보통 0 값을 사용합니다.</li>
<li>반환: -1 == 실패, -1 이외 소켓 디스크립터</li>
</ul>
<h4 id="int-domain--인터넷을-통해-통신할지-같은-시스템-프로세스-끼리-통신할지의-여부를-설정">int domain : 인터넷을 통해 통신할지, 같은 시스템 프로세스 끼리 통신할지의 여부를 설정</h4>
<p>PF_INET, AF_INET    IPv4 인터넷 프로토콜을 사용합니다.
PF_INET6    IPv6 인터넷 프로토콜을 사용합니다.
PF_LOCAL, AF_UNIX    같은 시스템 내에서 프로세스 끼리 통신합니다.
PF_PACKET    Low level socket 을 인터페이스를 이용합니다.
PF_IPX    IPX 노벨 프로토콜을 사용합니다.</p>
<p>|테스트1|<span style="color:red">강조3</span>|테스트3|</p>
<table>
<thead>
<tr>
<th align="left"><strong>domain</strong></th>
<th align="center"><strong>내용</strong></th>
</tr>
</thead>
<tbody><tr>
<td align="left">PF_INET, AF_INET</td>
<td align="center">IPv4 인터넷 프로토콜을 사용합니다.</td>
</tr>
<tr>
<td align="left">PF_INET6</td>
<td align="center">IPv6 인터넷 프로토콜을 사용합니다.</td>
</tr>
<tr>
<td align="left">PF_LOCAL, AF_UNIX</td>
<td align="center">같은 시스템 내에서 프로세스 끼리 통신합니다.</td>
</tr>
<tr>
<td align="left">PF_PACKET</td>
<td align="center">Low level socket 을 인터페이스를 이용합니다.</td>
</tr>
<tr>
<td align="left">PF_IPX</td>
<td align="center">IPX 노벨 프로토콜을 사용합니다.</td>
</tr>
</tbody></table>
<h4 id="int-type-데이터의-전송-형태를-지정하며-아래와-같은-값을-사용할-수-있다">int type: 데이터의 전송 형태를 지정하며 아래와 같은 값을 사용할 수 있다.</h4>
<table>
<thead>
<tr>
<th align="left"><strong>type</strong></th>
<th align="center"><strong>내용</strong></th>
</tr>
</thead>
<tbody><tr>
<td align="left">SOCK_STREAM</td>
<td align="center">TCP/IP 프로토콜을 이용합니다.</td>
</tr>
<tr>
<td align="left">SOCK_DGRAM</td>
<td align="center">UDP/IP 프로토콜을 이용합니다.</td>
</tr>
</tbody></table>
<h2 id="2-bind-함수">2. bind() 함수</h2>
<p>-bind 함수는 주소정보를 앞에서 Socket()함수로 생성한 소켓에 할당하는 것이다.</p>
<ul>
<li>헤더: sys/types.h, sys/socket.h</li>
<li>형태: <strong>int</strong> bind(<strong>int</strong> sockfd, <strong>(struct sockaddr)</strong> <em>myaddr, *</em>socklen_t** addrlen)</li>
<li>인수: <strong>int</strong> sockfd 주소정보(IP/Port)할당 할 소켓의 파일 디스크립터(socket()의 반환 값).</li>
<li><em>(struct sockaddr)*</em> *myaddr 할당 하고자하는 주소정보를 지니는 구조체 변수의 값.</li>
<li><em>socklen_t*</em> addrlen myaddr 구조체 변수의 길이정보.</li>
<li>성공 시 0, 실패시 -1 반환</li>
</ul>
<h3 id="2-1-sockaddr-구조체">2-1. sockaddr 구조체</h3>
<pre><code class="language-cpp">struct sockaddr_in
{
    sa_family_t    sin_family;  주소체계(Address Family)
    uint16_t       sin_port;    16비트 TCP/UDP PORT번호
    struct in_addr sin_addr;    32비트의 IP주소
    char           sin_zero[8]; 항상 0;
}

struct in_addr{
    in_addr_t    s_addr;  32비트의 IPv4 인터넷 주소가 담긴다.
}

struct sockaddr
{
    sa_family_t sin_family    주소체계(Address Family)
    char        sa_data[14];  주소정보
}</code></pre>
<p>bind 함수의 2번째 인자 <strong>(struct sockaddr)</strong> *myaddr는
sockaddr 구조체로 캐스팅 된 내가 정의한 sockaddr_in 구조체의 주소이다.
위 코드블록의 구조체에 대한 분석을 보면 우리가 매개변수로 형변환해서 넘겨야할 구조체는 맨 아래에 있는 sockaddr 구조체인 것을 알 수 있다.
그 중 char sa_data[14]에는 bind 함수가 요구대로 IP(4byte)와 PORT(2byte)가 담겨지고 남은 부분은 0(8개 총 8byte)으로 채워야한다.</p>
<p>불편한 이 부분을 해결하기 위해 만들어진 구조체가 sockaddr_in(최상단) 구조체이며</p>
<ol>
<li>sin_family-&gt;socket()에서 domain 인자로 들어갔던 값이 담긴다(ex: AF_INET...)</li>
<li>sin_port-&gt; 포트번호를 빅엔디안(네트워크 바이트 순서)으로 저장</li>
<li>sin_addr-&gt; 32비트 IP주소를 빅엔디안(네트워크 바이트 순서)으로 저장, in_addr 구조체는 속에 in_addr_t는 32비트 정수 자료형이다.
이 곳에는 자신의 IP를 할당하는데 자신의 랜카드가 2개 이상이여서 IP주소가 2개 이상 있지 않는다면 보통 INADDR_ANY를 사용하여 자동으로 할당되도록 할 수 있다.</li>
<li>sin_zero-&gt;sin_zero는 항상 0이어야 하는데 이를 어기면 간혹 IP주소를 터무니 없는 값으로 인식하는 경우가 생긴다 따라서 일반적으로 memset이나 zeroMemory 등으로 초기화한 후 사용한다.</li>
</ol>
<h4 id="기본-코드">기본 코드:</h4>
<pre><code class="language-cpp"> memset(&amp;servAddr, 0, sizeof(servAddr));
 servAddr.sin_family=AF_INET;
 servAddr.sin_addr.s_addr=htonl(INADDR_ANY);
 servAddr.sin_port = htons(atoi(&quot;8080&quot;));</code></pre>
<p>htonl,htons 의미:
htonl은 host to network long으로 이를 이해하기 앞서 빅엔디안과 리틀엔디안에 대해 알아야한다. 빅엔디안은 높은 메모리 주소에 뒤의 값을 넣는다. 예를 들자면 char *ch = &quot;abcd&quot;;라는 문자열이 있을 경우 빅엔디안은 메모리가 낮은곳에서부터 a,b,c,d를 저장한다는 뜻이다. 물론 리틀 엔디안은 그 반대이다. 이들은 cpu가 데이터를 메모리에 저장하는 방식에 따라 다른게 되는데 네트워크 통신시에 데이터 전송을 정해놓지 않으면 cpu가 다르면 통신이 되지 않는 상황이 벌어질 수 있어 네트워크는 모두 빅 엔디안으로 통일하게 됐다. htonl,htons 함수는 호스트가 무엇이든 네트워크 즉 빅엔디안으로 long 또는 short 타입으로 변경한다는 뜻이다.</p>
<h2 id="3listen함수">3.listen()함수</h2>
<p>-주소가 할당된 소켓이 연결요청 대기상태로 들어간다.</p>
<ul>
<li>헤더: sys/types.h, sys/socket.h</li>
<li>형태: <strong>int</strong> listen(<strong>int</strong> sock, <strong>int</strong> backlog);</li>
<li>인수: <strong>int</strong> sock 클라이언트로부터 연결 요청을 받아들이기 위한 소켓 파일 디스크럽터(socket()함수 리턴값)</li>
<li><em>int*</em> backolog 연결요청 대기 큐의 크기에 대한 설정(크기정보 전달)
이 Queue의 크기만큼 클라이언트의 연결요청을 대기시킬 수 있다.
5가 전달되면 큐의 크기가 5가 되어서 클라이언트의 연결요청을 5개까지 대기시킬 수 있다. 적절한 인자의 값은 서버의 성격마다 다르지만 웹 서버와 같이 잦은 연결 요청을 받는 서버의 경우 최소 15번 이상을 전달해야한다.</li>
<li>반환: 실패 시 -1, 성공 시 -1 이외 소켓 디스크립터</li>
</ul>
<p>연결 요청 대기 상태: 서버의 소켓과 큐가 완전히 준비되어 클라이언트의 연결 요청을 받아들일 수 있는 상태.
연결 요청 대기 큐: 연결 요청을 대기시킬 수 있는 일종의 대기실로 서버 측에서 accept 함수로 허락하기 전까지 머물러있다.</p>
<p><span style="color:red">listen 함수 호출을 성공하게 되면, 이제 여러 클라이언트들이 연결을 요청해 올 것이고, 모든 연결 요청은 서버가 미리 만들어 놓은 대기실로 들어가 순서대로 연결요청이 수락될 때까지 기다려야 한다.</span></p>
<p>listen()함수를 호출하면 서버 소켓 상태는 CLOSE에서 LISTEN 상태로 변경되고, 연결을 요청한 클라이언트 소켓은 SYN_RCVD 상태에서 3-way-handshaking을 완료하고 ESTABLISHED 상태가 된다.</p>
<h2 id="4accept함수">4.accept()함수</h2>
<p>-대기상태의 클라이언트 요청을 수락한다.</p>
<ul>
<li>헤더: sys/types.h, sys/socket.h</li>
<li>형태: <strong>int</strong> accept(<strong>int</strong> sock, <strong>(struct sockaddr*)</strong> addr, <strong>socklen_t*</strong> addrlen);</li>
<li>인수: <strong>int</strong> sock 서버 소켓의 파일 디스크럽터</li>
<li><em>(struct sockaddr</em>)** addr 연결요청 한 클라이언트의 주소정보를 담을 변수의 주소 값.-&gt; <span style="color:red">bind()함수에서 &quot;서버&quot;소켓에 주소정보를 담기위해 직접 멤버에 접근해 프로토콜을 지정해주었지만</span> accept() 함수에서는 <span style="color:red">struct sockaddr_in 구조체만 정의해주고 그 변수만 인자로 넣어주면 된다.(이 변수에 클라이언트 주소정보가 담긴다)</span></li>
<li><em>socklen_t**</em> addrlen 두 번째 인자로 전달된 addr의 크기정보를 전달.
단! 미리 변수에 크기정보를 저장한 뒤, 주소를 전달한다.-&gt; bind와 다른점
<span style="color:red">bind()에서는 인자 자체에 크기 변수가 들어갔는데, accept()에서는 크기 정보가 들어가 있는 변수의 주소이다.</span>
ex:<pre><code class="language-cpp">int  client_addr_size;
</code></pre>
</li>
</ul>
<p>client_addr_size = sizeof( client_addr);
client_socket = accept( server_socket, (struct sockaddr*)&amp;client_addr,
                                                          &amp;client_addr_size);
if ( -1 == client_socket){
   printf( &quot;클라이언트 연결 수락 실패\n&quot;);
   exit( 1);
}</p>
<pre><code>sizeof()로 크기를 전달하던 bind와 차이점이다.
- 반환: 실패 시 -1, 성공 시 -1 이외 생성된 소켓 디스크립터

## 5.read()/write()
-데이터를 수신/송신한다.

- 헤더: sys/types.h, sys/socket.h
- 형태: **ssize_t** read(**int** fd, **void ***buf, **size_t** nbytes);
**ssize_t** write(**int** fd, **void ***buf, **size_t** nbytes);
- 인수: (read)**int** fd 데이터를 받을 소켓의 파일 디스크럽터(클라이언트)
(read)**void ***buf 수신할 데이터를 저장할 버퍼의 주소 값 전달
(read)**size_t** nbytes 수신할 최대 바이트 수 전달

(write)**int** fd 데이터를 보낼 소켓의 파일 디스크럽터(클라이언트)
(write)**void ***buf 전송할 데이터를 저장할 버퍼의 주소 값 전달
(write)**size_t** nbytes 전송할 데이터의 바이트 수 전달
- 반환: 실패 시 -1, 성공 시 바이트 수

&lt;span style=&quot;color:blue&quot;&gt;인자1. fd &lt;/span&gt;
read  : 데이터를 받을 대상 소켓의 파일 디스크립터 (클라이언트)
write : 데이터를 보낼 대상 소켓의 파일 디스크립터 (클라이언트)

&lt;span style=&quot;color:blue&quot;&gt;인자2. buf &lt;/span&gt;
read  : 데이터를 받을 버퍼의 주소 값
write : 데이터를 보낼 버퍼의 주소 값  

&lt;span style=&quot;color:blue&quot;&gt; 인자3. nbytes &lt;/span&gt;
read  : 수신할 최대 바이트 수 전달
write : 송신할 데이터의 바이트 수 전달

## 6. close()
-연결된 소켓을 종료한다.

- 헤더: unistd.h
- 형태: **int** close(**int** fd);
- 인수: **int** fd 닫고자 하는 파일 또는 소켓의 파일 디스크립터
- 반환: 실패 시 -1, 성공 시 0

# -&gt;클라이언트 함수 정리
서버와 connect 함수만 다르고 다 서버에서 정리한 내용이라 connect 함수만 다뤄보겠다.
## 1. Connect()함수
-listen 중인 서버 소켓에 연결요청을 한다.
connect()가 성공적으로 끝나면 바로 read/write 가능
- 헤더: sys/types.h, sys/socket.h
- 형태: **int** connect(**int** sock, **(struct sockaddr) ***servaddr, **socklen_t** addrlen);
- 인수: **int** sock socket()함수를 통해 얻은 클라이언트 소켓의 파일 디스크럽터
**(struct sockaddr) ***servaddr sockaddr 구조체로 캐스팅 된 내가 정의한 sockaddr_in 구조체 주소
클라이언트-&gt;서버로 연결요청을 하기 때문에 주소정보가 서버측과 동일해야 한다.
따라서 두 번째 인자에서 서버측 구현과 동일하게 서버의 주소정보 구조체가 들어간다.
**socklen_t** addrlen 인자2에 넣어준 구조체 변수의 길이정보(sizeof)
socklen_t는 &lt;sys/socket.h&gt;에 저장된 길이정보 자료형이다. 그냥 sizeof(serv_addr)하면 된다.
- 반환: 실패 시 -1, 성공 시 0
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[RFC 7230 스리슬쩍 햝아먹기]]></title>
            <link>https://velog.io/@jeong_twelve/RFC-7230-%EC%8A%A4%EB%A6%AC%EC%8A%AC%EC%A9%8D-%ED%96%9D%EC%95%84%EB%A8%B9%EA%B8%B0</link>
            <guid>https://velog.io/@jeong_twelve/RFC-7230-%EC%8A%A4%EB%A6%AC%EC%8A%AC%EC%A9%8D-%ED%96%9D%EC%95%84%EB%A8%B9%EA%B8%B0</guid>
            <pubDate>Mon, 31 Jul 2023 13:33:09 GMT</pubDate>
            <description><![CDATA[<p>HTTP 1.1에 관한 최신 문서는 RFC 9112이나 21년 개정되어 번역본도 없고 그거 일일이 번역하고 이해하다가 헛구역질이 올라올 것 같아 누군가 열심히 노력해서 번역본을 만들어놓으신 RFC 7230~7235를 읽고 요약해보기로 했다.</p>
<blockquote>
<p>RFC 7230부터 7235까지가 HTTP 1.1 관련 문서다.</p>
</blockquote>
<h4 id="7230-http-11메세지-구문과-라우팅-기본-규칙-메세지-구조-요청과-응답-메시지-포맷-헤더-필드">7230: http 1.1메세지 구문과 라우팅 기본 규칙, 메세지 구조, 요청과 응답, 메시지 포맷, 헤더 필드</h4>
<h4 id="7231-http-11-의미론과-컨텐츠에-관련된-내용-각각의-메소드에-의미-설명get-post-put-delete">7231: http 1.1 의미론과 컨텐츠에 관련된 내용, 각각의 메소드에 의미 설명(get, post, put, delete)</h4>
<h4 id="7232-조건부-요청을-위한-메커니즘클라이언트가-서버에게-자원-다운로드-전-해당-자원-미수정-확인">7232: 조건부 요청을 위한 메커니즘(클라이언트가 서버에게 자원 다운로드 전 해당 자원 미수정 확인)</h4>
<h4 id="7233-부분적인-자원-요청-지원을-위한-범위-요청-메커니즘자원의-일부분만-요청할-수-있게-해줌-파일-다운로드와-스트리밍에-유용">7233: 부분적인 자원 요청 지원을 위한 범위 요청 메커니즘(자원의 일부분만 요청할 수 있게 해줌. 파일 다운로드와 스트리밍에 유용)</h4>
<h4 id="7234-캐싱-메커니즘-웹-캐시-동작과-헤더-필드-설명">7234: 캐싱 메커니즘, 웹 캐시 동작과 헤더 필드 설명.</h4>
<h4 id="7235-인증과-보안을-위한-메커니즘-설명-서버에-대한-접근-제어를-위한-기본적인-인증-방법들과-보안-헤더에-관한-내용을-다룸">7235: 인증과 보안을 위한 메커니즘 설명, 서버에 대한 접근 제어를 위한 기본적인 인증 방법들과 보안 헤더에 관한 내용을 다룸.</h4>
<h1 id="1-주요-표기법">1. 주요 표기법</h1>
<blockquote>
<p>연산자를 사용하여 쉼표로 구분된 리스트를 콤팩트하게 정의 가능한 목록 확장자가 있는 [RFC5234]-ABNF- 표기법을 사용한다. </p>
</blockquote>
<h4 id="alpha-letters">ALPHA (letters)</h4>
<h4 id="cr-carriage-return">CR (carriage return)</h4>
<h4 id="crlf-cr-lf">CRLF (CR LF)</h4>
<h4 id="ctl-controls">CTL (controls)</h4>
<h4 id="digit-decimal-0-9">DIGIT (decimal 0-9)</h4>
<h4 id="dquote-double-quote">DQUOTE (double quote)</h4>
<h4 id="hexdig-hexadecimal-0-9a-fa-f">HEXDIG (hexadecimal 0-9/A-F/a-f)</h4>
<h4 id="htab-horizontal-tab">HTAB (horizontal tab)</h4>
<h4 id="lf-line-feed">LF (line feed)</h4>
<h4 id="octet-any-8-bit-sequence-of-data">OCTET (any 8-bit sequence of data)</h4>
<h4 id="sp-space">SP (space)</h4>
<h4 id="vchar-any-visible-usascii-character">VCHAR (any visible [USASCII] character)</h4>
<h1 id="2-architecture">2. Architecture</h1>
<blockquote>
<p>클라이언트는 HTTP 요청을 서버에 요청 메세지 형식, method, URI와 프로토콜 버전을 포함한 Request-line을 시작으로 헤더 필드, 요청 수정자, 클라이언트 정보, 표현 메타데이터, 헤더 부분의 끝을 나타내는 빈줄을 포함하여, 마지막으로 페이로드 본문을 포함하는 메세지 본문을 보낸다.</p>
</blockquote>
<blockquote>
<p>서버는 클라이언트의 요청에 하나 또는 다수의 HTTP 응답 메시지를 각각의 프로토콜 버전, 성공 또는 에러 코드, 원문으로된 상태 코드를 포함한 status-line을 시작으로, 가능하다면 서버 정보, 리소스 메타데이터와 표현 메타데이터를 포함하는 헤더 필드 다음에 헤더 부분의 끝을 나타내는 빈 줄을 포함하여, 마지막으로 페이로드 body를 포함한 메시지 본문(만약에 있다면)을 통해 응답한다.</p>
</blockquote>
<ul>
<li>요청 메세지 : 클라이언트가 서버에서 요청하는 메세지
형식:
<img src="https://velog.velcdn.com/images/jeong_twelve/post/dcda425b-5933-47a7-8c3a-3e0900ef8221/image.png" alt=""></li>
</ul>
<pre><code>- 메서드 : 어떻게 처리를 해야 하는지를 담고 있음
- GET : 존재하는 자원에 대한 요청
- POST : 새로운 자원을 생성
- PUT : 존재하는 자원에 대한 변경
- DELETE : 존재라는 자원에 대한 삭제
get, post, put, delete는 메서드이다.
- 경로 : 가져오려는 리소스의 경로를 표시
- 프로토콜 버전 : HTTP 프로토콜의 버전을 표시
- 헤더 : 서버에 대한 추가 정보를 전달. 호스트의 정보나 접속하고 있는 사용자의 정보, 그리고 열려고 하는 페이지의 정보 등을 확인할 수 있습니다.
- user-agent : 웹 브라우저의 다른 표현. 요청하는 웹 브라우저의 정보 및 운영체제를 표시
- accep-encoding : 클라이언트가 이해할 수 있는 압축 방식을 표시. 이를 표시해서 서버에 전송하면 서버는 필요한 경우 리소스를 압축하여 반환
- 공백 라인 : 헤더와 본문 구분하는 역할
- 본문 : POST처럼 서버에 새로운 자원을 추가하는 경우에 들어가는 선택적인 요소</code></pre><p>다음 예시는 URI에서 GET 요청을 위한 일반적인 메세지 교환을 보여준다.</p>
<pre><code class="language-cpp">/****************************/
/*           URI            */ 
/****************************/
    http://www.example.com/hello.txt


/****************************/
/*      CLIENT REQUEST      */ 
/****************************/

     GET /hello.txt HTTP/1.1
     User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
     Host: www.example.com
     Accept-Language: en, mi


/****************************/
/*      SERVER RESPONSE     */ 
/****************************/

     HTTP/1.1 200 OK
     Date: Mon, 27 Jul 2009 12:28:53 GMT
     Server: Apache
     Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
     ETag: &quot;34aa387-d-1568eb00&quot;
     Accept-Ranges: bytes
     Content-Length: 51
     Vary: Accept-Encoding
     Content-Type: text/plain

     Hello World! My payload includes a trailing CRLF.</code></pre>
<p>중개자(Intermediary)의 3가지 형태는 다음과 같다.</p>
<pre><code>Proxy = 웹 서버에서 가져온 데이터를 클라이언트에 전송 후 캐시로 저장(같은 데이터를 요청하면 서버까지 가지않고 캐시에서 바로 데이터 전송)
Gateway(reverse proxy) = HTTP 이외의 네트워크(사설망)과의 통로역할
Tunnel(TLS) = 안전한 통신을 위해 사용</code></pre><p>Cache: 캐시는 이전 응답 메세지의 로컬 보관소로 메시지의 저장, 검색, 삭제를 관리하는 서브 시스템이다. 캐시는 캐시 가능한 응답을 저장하여 향후 동일한 요청에 대한 응답 시간과 네트워크 대역폭 사용을 줄인다.
하지만 캐시는 서버가 터널(TLS)역할을 하는 동안에는 사용할 수 없다.</p>
<p>캐시의 효과는 체인의 참여자 중 하나가 해당 요청에 적용할 수 있는 캐시된 응답을 가지고 있는 경우 요청/응답 체인이 단축되는 것이다.  다음은 B가 UA 또는 A에 의해 캐시되지 않은 요청에 대한 O(경유 C)의 이전 응답의 캐시된 사본을 가지고 있는 경우의 결과 체인을 보여준다.</p>
<pre><code>  &gt;        &gt;
UA ====  A ==== B - - - - C - - - -  O
    &lt;        &lt;
</code></pre><h1 id="3-message-format">3. Message Format</h1>
<p>HTTP 메시지 형식에 대해 설명하는 부분이다. 
HTTP 메시지는 start-line을 시작으로, 여러 헤더필드, 헤더 부문의 끝을 나타내는 빈 줄 및 선택적 메시지 본문으로 구성된다.</p>
<pre><code>HTTP-message   = start-line
                 *( header-field CRLF )
                 CRLF
                 [ message-body ]
</code></pre><p>분석하는 일반적인 절차는 start-line을 읽고, 각 헤더 필드를 빈 행까지 필드 이름으로 해시 테이블로 읽은 다음 분석된 데이터를 사용하여 메시지 본문이 필요한지 여부를 확인한다. 메시지 본문이 표시된 경우, 메시지 본문 길이와 동일한 octet의 양을 읽거나 커넥션을 닫을 때까지 스트림으로 읽는다.
(octet: 네트워크 관련 분야에서는 서로 연결되는 두 대의 장비가 1byte=8bit라는 것을 보장할 수 없으므로 octet이라는 표현을 사용해 1바이트는 8bit로 정확하게 명시하는 것, byte와 같다고 생각하면 편하다.)</p>
<p>발신자는 start-line과 첫 번째 헤더 필드 사이에 공백을 보내선 안되고, start-line과 첫 번째 헤더 필드 사이에 공백을 수신하는 수신자는 메시지를 유효하지 않은 것으로 거부하거나 메시지를 더 이상 처리하지 않고 각 공백이 지정된 줄을 소비해야 한다.</p>
<p>HTTP 메시지는 클라이언트-&gt;서버(요청), 서버-&gt;클라이언트(응답)두 종류가 있으므로 두 유형의 메세지(요청, 응답)는 start-line(요청, 응답)과 메세지 본문의 길이를 결정하기 위한 알고리즘에서만 다르다.</p>
<p>이론적으로 클라이언트도 요청을 수신할 수 있고 서버도 응답을 수신할 수 있지만 서버는 요청만 예상(수신)하도록 구현되고 클라이언트는 응답만 예상(수신)하도록 구현된다.</p>
<pre><code>start-line     = request-line / status-line</code></pre><h2 id="request-line">Request line</h2>
<p>method 토큰을 시작으로, 공백(SP), request-target, 공백(SP), 프로토콜 버전, 그리고 CRLF를 끝으로 한다.</p>
<pre><code>request-line   = method SP request-target SP HTTP-version CRLF</code></pre><h4 id="method">method</h4>
<p>method 토큰은 대상 리소스를 실행하기 위한 요청 메서드를 표시한다. 요청 메서드는 대 소문자를 구별한다.</p>
<pre><code>method = token</code></pre><h4 id="request-target">request target</h4>
<p>request target은 요청을 적용할 대상 리소스를 식별한다.</p>
<p>세 구성요소(method, request-target, HTTP-version CRLF)는 공백이 허용되지 않기 때문에 수신자는 공백을 분할하여 request-line을 구성 요소 부분으로 분할한다.</p>
<p>유효하지 않은 request-line의 수신자는 400(Bad Request)오류 또는 301(Moved Permanently)리다이렉트로 응답해야 하며 리다이렉트없이 request데이터를 수정해서 처리하면 안된다.</p>
<p>만약 서버가 처리 할 수 있는 시작줄의 길이를 초과하면 501에러 처리해야한다. 모든 HTTP 발신자와 수신자는 최소 octets 8000길이의 request-line을 지원하는 것을 권장한다.</p>
<h2 id="status-line">Status Line</h2>
<p>응답 메세지의 첫 번째 줄은 status-line이며,
프로토콜 버전, 공백(SP), status-code, 공백(SP), status-code를 설명하는 빈 텍스트 구분, CRLF 로 구성된다.</p>
<pre><code>status-line = HTTP-version SP status-code SP reason-phrase CRLF</code></pre><h4 id="status-code">status-code</h4>
<p>status-code 요소는 서버가 클라이언트의 해당 요청을 이해하고 충족하려고 시도한 결과를 설명하는 세 자리의 정수 코드이다. 응답 메시지의 나머지 부분은 해당 상태 코드에 대해 정의된 의미론을 고려하여 해석된다.</p>
<pre><code>status-code    = 3DIGIT</code></pre><h4 id="reason-phrase">reason-phrase</h4>
<p>reason-phrase요소는 숫자 상태 코드와 관련된 텍스트 설명을 제공하는 목적으로 존재한다. 클라이언트는 reason-pharse 내용을 무시해야 한다.</p>
<pre><code>reason-phrase  = *( HTAB / SP / VCHAR / obs-text )</code></pre><h2 id="header-fields">Header Fields</h2>
<p>각 헤더 필드는 대소문자를 구분하지 않는 필드 이름 뒤에 콜론(&quot;:&quot;), 선택적 앞의 공백(OWS), 필드 값,선택적 뒤의 공백(OWS)으로 구성된다.</p>
<pre><code>header-field   = field-name &quot;:&quot; OWS field-value OWS

field-name     = token
field-value    = *( field-content / obs-fold )
field-content  = field-vchar [ 1*( SP / HTAB ) field-vchar ]
field-vchar    = VCHAR / obs-text

obs-fold       = CRLF 1*( SP / HTAB )
                    ; obsolete line folding
                    ;</code></pre><p>field-name 토큰은 해당 field-value를 헤더 필드에서 정의한 의미론을 갖는 것으로, 레이블을 지정한다. 예를 들어 Date 헤더 필드는 메시지의 시작 타임 스탬프를 포함하는 필드로 정의된다.</p>
<h4 id="whitespace">Whitespace</h4>
<p>OWS(선택적 공백), RWD(필수 공백), BWS(불량 공백) 세가지 규칙 사용.</p>
<ul>
<li>OWS 규칙은 0 이상의 선형 공백 octets이 나타나는 곳에 사용된다. 가독성을 향상시키기 위해 선택적 공백(OWS)을 선호하는 프로토콜 요소의 경우, 발신자는 단일 SP로서 선택적 공백(OWS)을 생성해야 한다. 그렇지 않은경우, 발신자는 내부 메시지 필터링 중에 유효하지 않거나 원하지 않는 프로토콜 요소를 화이트아웃하는 데 필요한 경우를 제외하고 선택적 공백을 생성해서는 안 된다.</li>
<li>RWS 규칙은 필드 토큰을 분리하기 위해 하나 이상의 선형 공백 octets이 필요한 경우에 사용된다. 발신자는 단일 SP로서 RWS를 생성해야 한다.</li>
<li>BWS 규칙은 오직 역사적인 이유로 선택적 공백(OWS)을 허락하는 문법에서 사용된다. 발신자는 메시지에서 BWS를 생성해서는 안된다. 수신자는 이러한 잘못된 공백을 구문 분석한 후 프로토콜 요소를 해석하기 전에 제거해야 한다.</li>
</ul>
<pre><code>OWS            = *( SP / HTAB )        //공백이 0개 이상
                    ; optional whitespace
RWS            = 1*( SP / HTAB )    //공백이 1개 이상
                    ; required whitespace
BWS            = OWS        //공백이 0개 이상
                    ; &quot;bad&quot; whitespace</code></pre><h4 id="message-body">Message body</h4>
<p>메세지에서 본문이 허용되는 시기에 대한 규칙은 요청과 응답에 따라 다르다.
요청에서 메세지 본문이 있으면 Content-length 또는 Transfer-Encoding 헤더 필드로 표시된다.
응답에서 메세지 본문의 존재 여부는 응답하는 요청 메서드와 응답 상태 코드(status-code)에 따라 달라진다.
Head 요청 메서드에 대한 응답은 헤더 필드가 있는 경우 요청 메서드가 GET 되었을 때의 값만 나타내므로 메세지 본문을 포함하지 않는다.
CONNECT 요청 메서드에 2xx (Successful) 응답은 메시지 본문 대신 터널 모드로 전환한다. 모든 1xx (informational), 204 (No Content) 및 304 (Not Modified) 응답에는 메시지 본문이 포함되지 않는다. 다른 모든 응답에는 메시지 본문이 포함되지만, 본문의 길이는 0일 수 있다.</p>
<h4 id="transfer-encoding">Transfer-Encoding</h4>
<p>Transfer-Encoding 헤더 필드에는 메시지 본문을 형성하기 위해 페이 로드 본문에 적용된(또는 적용될) 전송 코딩 순서에 해당하는 전송 코딩 이름을 나열한다. 전송 코딩은 아래 #4에서 다룬다.</p>
<pre><code>Transfer-Encoding = 1#transfer-coding</code></pre><h4 id="content-length">Content-Length</h4>
<p>메시지에 Transfer-Encoding 헤더 필드가 없는 경우, Content-Length 헤더 필드는 잠재적 페이 로드(순수 데이터)본문에 대해 예상되는 크기를 10진수로 제공할 수 있다. 페이 로드 본문을 포함하는 메시지의 경우, Content-Length 필드 값은 본문(및 메시지)이 끝나는 위치를 결정하는 데 필요한 프레임 정보를 제공한다. 페이 로드 본문이 포함되지 않은 메시지의 경우, Content-Length는 선택된 표현의 크기를 나타낸다.</p>
<pre><code>Content-Length = 1*DIGIT</code></pre><h4 id="message-body-length">Message body Length</h4>
<p>메시지 본문의 길이는 다음 중 하나에 의해 결정된다(우선 순위 순서대로)</p>
<ol>
<li><p>HEAD 요청에 대한 응답과 1xx(Informational), 204(No Content) 또는 304(Not Modified) 상태 코드 응답은 메시지에 있는 헤더 필드에 관계 없이 헤더 필드 다음의 첫 번째 빈 줄로 항상 종료되므로 메시지 본문을 포함할 수 없다. </p>
</li>
<li><p>CONNECT 요청에 대한 2xx(Successful) 응답은 헤더 필드를 마치는 빈 줄 직후 커넥션은 터널이 됨을 내포한다. 클라이언트는 이러한 메시지에 수신된 Content-Length 또는 Transfer-Encoding 헤더 필드를 반드시 무시해야 한다.</p>
</li>
<li><p>Transfer-Encoding 헤더 필드가 있고 청크 전송 코딩(Section 4.1)이 최종 인코딩인 경우, 전송 코딩이 데이터가 완료되었음을 나타낼 때까지 청크 데이터를 읽고 디코딩 하여 메시지 본문 길이를 결정한다.
응답에 Transfer-Encoding 헤더 필드가 있고 청크 분할 전송 코딩이 최종 인코딩이 아닌 경우, 서버가 커넥션을 닫을 때까지 커넥션을 읽어 메시지 본문 길이를 결정한다. 요청에 Transfer-Encoding 헤더 필드가 있고 청크 전송 코딩이 최종 인코딩이 아닌 경우 메시지 본문 길이를 신뢰할 수 없다; 서버는 400(Bad Request) 상태 코드로 응답한 다음, 커넥션을 반드시 닫아야 한다.
메시지가 Transfer-Encoding 및 Content-Length 헤더 필드와 함께 수신되면 Transfer-Encoding이 Content-Length를 재정의한다. 그러한 메시지는 요청 smuggling(Section 9.5) 또는 응답 분할(Section 9.4)을 수행하려는 시도를 나타낼 수 있으며 오류로 취급되어야 한다. 발신자는 이러한 메시지를 다운 스트림으로 전달하기 전에 수신된 Content-Length 필드를 반드시 제거해야 한다.</p>
</li>
<li><p>Transfer-Encoding이 없는 메시지를 수신하거나 여러 Content-Length 헤더 필드들의 field-value가 다르거나 단일 Content-length 헤더 필드가 잘못된 경우, 메시지 프레임은 유효하지 않으며 수신자는 이를 복구할 수 없는 오류로 간주 해야 한다. 요청 메시지인 경우, 서버는 400 (Bad Request) 상태 코드로 응답한 다음 커넥션을 닫아야 한다. 프락시에서 수신한 응답 메시지인 경우, 프락시는 서버에 대한 커넥션을 닫고 수신된 응답을 무시하고, 502 (Bad Gateway)을 클라이언트에게 전송해야 한다. 사용자 에이전트가 수신한 응답 메시지인 경우, 사용자 에이전트는 서버에 대한 커넥션을 닫고 수신된 응답을 삭제해야 한다.</p>
</li>
<li><p>Transfer-Encoding이 없는 유효한 Content-Length 헤더 필드가 있는 경우, 10진수 값은 octet으로 예상되는 메시지 본문 길이를 정의한다. 발신자가 커넥션을 닫거나 표시된 octet 수를 수신하기 전에 시간이 초과되면, 수신자는 메시지가 불완전한 것으로 간주하고 커넥션을 반드시 닫아야 한다.</p>
</li>
<li><p>이 메시지가 요청 메시지이고 위의 메시지 중 하나가 참이 아니면 메시지 본문 길이가 0이다.(메시지 본문이 없음).</p>
</li>
<li><p>그렇지 않으면, 이것은 선언된 메시지 본문 길이가 없는 응답 메시지이므로, 메시지 본문 길이는 서버가 커넥션을 닫기 전에 수신한 octet 수로 결정된다.</p>
</li>
</ol>
<h1 id="4-transfer-codings">4. Transfer Codings</h1>
<p>전송 코딩이란 안전한 전송을 보장하기 위해 리소스(데이터)에 적용돼야 하는 인코딩 변환 방식을 나타낸다.</p>
<p>헤더에 삽입되는 문법은 다음과 같다.</p>
<pre><code>/*********************/
/*      REQUEST      */ 
/*********************/
TE: compress
TE: deflate
TE: gzip
TE: trailers

TE: trailers, deflate;q=0.5 // 여러 방식을 함께 기술할 때는 콤마 사용

/**********************/
/*      RESPONSE      */ 
/**********************/
Transfer-Encoding: chunked
Transfer-Encoding: compress
Transfer-Encoding: deflate
Transfer-Encoding: gzip

Transfer-Encoding: gzip, chunked // 여러 방식을 함께 기술할 때는 콤마 사용</code></pre><pre><code>청크 전송 코딩은 페이 로드 본체의 각각 자체 크기를 표시하여 전송하며, OPTIONAL 트레일러를 포함하는 헤더 필드 뒤에, 일련의 청크를 순차적으로 전송하기 위해 페이 로드 본체를 감싼다. 청크를 사용하면 알 수 없는 크기의 콘텐츠 스트림을 길이가 제한된 버퍼의 순서로 전송할 수 있으며, 이를 통해 송신자는 커넥션 영속성을 유지하고 수신자는 전체 메시지를 수신한 시점을 알 수 있다.



chunked-body   = *chunk
                  last-chunk
                  trailer-part
                  CRLF

chunk           = chunk-size [ chunk-ext ] CRLF
                        chunk-data CRLF
chunk-size    = 1*HEXDIG
last-chunk    = 1*(&quot;0&quot;) [ chunk-ext ] CRLF

chunk-data     = 1*OCTET ; a sequence of chunk-size octets</code></pre><h4 id="chunked">Chunked</h4>
<p>콘텐츠의 사이즈가 큰 경우나 얼마인지 모를 때 사용되는 인코딩 방식으로 콘텐츠 크기 계산에도 시간이 걸리고 전송에도 시간이 걸리므로, 일정 크기로 잘라 전송하는 방식이다. 따라서 content-length는 헤더에서 생략된다.
서버는 크기가 0인 chunk로 콘텐츠의 끝임을 알려주고 다음 응답을 기다리거나 종료한다.</p>
<pre><code>HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked

this is jinhyeok...\r\n
hello world~~~~~~~\r\n
hahaha\r\n
0\r\n
\r\n</code></pre><h4 id="compress">compress</h4>
<p>Lempel-Ziv-Welch (LZW) 알고리즘(압축 방식의 한 종류)을 사용한 인코딩.</p>
<h4 id="deflate">deflate</h4>
<p>Lempel-Ziv (LZ77) 압축 알고리즘과 허프만 코딩을 결합하여 사용하는 zlib 데이터 포맷 방식의 인코딩.</p>
<h4 id="gzip">gzip</h4>
<p>Lempel-Ziv (LZ77) 32비트 Cyclic Redundancy Check(CRC) 방식의 인코딩.</p>
<h1 id="5-message-routing">5. Message Routing</h1>
<p>HTTP 요청시 주소창에 URL을 적게 되는데 이 때의 라우팅에 대해 기술하고 있다.</p>
<h4 id="inbound클라이언트에서-서버쪽으로-커넥션인-경우">Inbound(클라이언트에서 서버쪽으로) 커넥션인 경우</h4>
<ul>
<li>유효한 URL(URI)인지 검사</li>
<li>캐시에 먼저 요청</li>
<li>캐시에서 해당 응답을 못 찾으면 프록시 사용</li>
<li>프록시로도 충족되지 않으면 대상 리소스에 직접 요청</li>
</ul>
<p>인바운드 커넥션이 형성된 뒤에 클라이언트는 URL을 4가지 방식의 포맷으로 만들어 요청 메시지를 보내게 된다.
request target = </p>
<ul>
<li>origin-form</li>
<li>absolute-form</li>
<li>authority-form</li>
<li>asterisk-form</li>
</ul>
<h4 id="origin-form">origin-form</h4>
<p>대부분의 request-target의 공통 양식은 original-form이다.</p>
<pre><code>origin-form    = absolute-path [ &quot;?&quot; query ]</code></pre><p>예를 들어, 클라이언트는 <a href="http://www.example.org/where?q=now">http://www.example.org/where?q=now</a> 같은 식별된 리소스의 표시로 검색할 것이다.</p>
<p>원서버로부터 직접 포트 80에 대한 TCP 커넥션을 열고(또는 재사용하고) 호스트 &quot;<a href="http://www.example.org&quot;%EA%B3%BC">www.example.org&quot;과</a> 다음줄을 보냈을 것이다.</p>
<p>GET /where?q=now HTTP/1.1
Host: <a href="http://www.example.org">www.example.org</a></p>
<p>요청 메시지의 나머지 부분이 뒤따른다.</p>
<h4 id="absolute-form">absolute-form</h4>
<p>GET과 함께 주로 사용되며 프록시에 연결됐을 때 사용한다.</p>
<pre><code class="language-cpp">GET http://www.example.org/pub/WWW/TheProject.html HTTP/1.1</code></pre>
<h4 id="authority-form">Authority-form</h4>
<p>하나 또는 그 이상의 프락시들을 통해 터널을 설립하기 위해 CONNECT 요청을 할 때 사용된다.(CONNECT만 사용 가능)</p>
<pre><code class="language-cpp">CONNECT www.example.com:80 HTTP/1.1</code></pre>
<h4 id="asterisk-form">Asterisk-form</h4>
<p>서버 전체 OPTIONS 요청에서만 사용된다.
asterisk-form  = &quot;*&quot;
즉, OPTIONS 요청에서만 사용되며, 서버 자체를 나타낼 때 사용된다.</p>
<pre><code class="language-cpp">OPTIONS * HTTP/1.1</code></pre>
<p>예를 들어
OPTIONS <a href="http://www.example.org:8001">http://www.example.org:8001</a> HTTP/1.1 요청은
host “<a href="http://www.example.org&quot;%EC%9D%98">www.example.org&quot;의</a> 8001 포트에 연결된 후 
마지막 프락시에 의해 
OPTIONS * HTTP/1.1
Host: <a href="http://www.example.org:8001%EC%9D%B4">www.example.org:8001이</a> 전송됐을 것이다.</p>
<h1 id="6-connection-management">6. Connection Management</h1>
<p>서버와 클라이언트의 연결을 어떤 방식으로 관리할지를 기술하는 부분이다.
Connection 헤더 필드는 커넥션이 완료된 뒤 연결을 유지할지,  끊을지를 정하게 된다.</p>
<p>“Connection” 헤더 필드를 통해 발신자는 현재 커넥션에 대해 원하는 제어 옵션을 표시할 수 있다. 메시지를 전달하기 전에 프락시 또는 게이트웨이가 수신된 커넥션 옵션을 반드시 제거하거나 바꾸어야 한다.</p>
<pre><code class="language-cpp">Connection: keep-alive
Connection: close</code></pre>
<p>단 keep-alive는 HTTP/1.1에서 default이다.</p>
<p>Connection 헤더 필드는 hop-by-hop만을 위한 헤더 필드를 end-to-end와 구분짓는다.</p>
<p>hop-by-hop : 데이터가 중간 노드들에게도 데이터를 전달하고 확인하며 이동되는 방식.
end-to-end : 중간 매개는 상관하지 않고 데이터를 전달하는 방식.</p>
<p>7은 앞에서 사용했던 헤더 필드 값 정의를 가독성 좋게 나타내기 위한 ABNF를 알려주는 것이라 생각해 생략했다.</p>
<h1 id="8-iana-considerations">8. IANA Considerations</h1>
<p>헤더 필드 목록, URI scheme 등에 관한 내용이다.</p>
<pre><code>   /* Header Field */
   +-------------------+----------+----------+---------------+
   | Header Field Name | Protocol | Status   | Reference     |
   +-------------------+----------+----------+---------------+
   | Connection        | http     | standard | Section 6.1   |
   | Content-Length    | http     | standard | Section 3.3.2 |
   | Host              | http     | standard | Section 5.4   |
   | TE                | http     | standard | Section 4.3   |
   | Trailer           | http     | standard | Section 4.4   |
   | Transfer-Encoding | http     | standard | Section 3.3.1 |
   | Upgrade           | http     | standard | Section 6.7   |
   | Via               | http     | standard | Section 5.7.1 |
   +-------------------+----------+----------+---------------+
   | Close             | http     | reserved | Section 8.1   |
   +-------------------+----------+----------+---------------+

   /* URI Scheme */ 
   +------------+------------------------------------+---------------+
   | URI Scheme | Description                        | Reference     |
   +------------+------------------------------------+---------------+
   | http       | Hypertext Transfer Protocol        | Section 2.7.1 |
   | https      | Hypertext Transfer Protocol Secure | Section 2.7.2 |
   +------------+------------------------------------+---------------+

   /* HTTP Transfer Coding */
   +------------+--------------------------------------+---------------+
   | Name       | Description                          | Reference     |
   +------------+--------------------------------------+---------------+
   | chunked    | Transfer in a series of chunks       | Section 4.1   |
   | compress   | UNIX &quot;compress&quot; data format [Welch]  | Section 4.2.1 |
   | deflate    | &quot;deflate&quot; compressed data            | Section 4.2.2 |
   |            | ([RFC1951]) inside the &quot;zlib&quot; data   |               |
   |            | format ([RFC1950])                   |               |
   | gzip       | GZIP file format [RFC1952]           | Section 4.2.3 |
   | x-compress | Deprecated (alias for compress)      | Section 4.2.1 |
   | x-gzip     | Deprecated (alias for gzip)          | Section 4.2.3 |
   +------------+--------------------------------------+---------------+

   /* Content Coding */
   +------------+--------------------------------------+---------------+
   | Name       | Description                          | Reference     |
   +------------+--------------------------------------+---------------+
   | compress   | UNIX &quot;compress&quot; data format [Welch]  | Section 4.2.1 |
   | deflate    | &quot;deflate&quot; compressed data            | Section 4.2.2 |
   |            | ([RFC1951]) inside the &quot;zlib&quot; data   |               |
   |            | format ([RFC1950])                   |               |
   | gzip       | GZIP file format [RFC1952]           | Section 4.2.3 |
   | x-compress | Deprecated (alias for compress)      | Section 4.2.1 |
   | x-gzip     | Deprecated (alias for gzip)          | Section 4.2.3 |
   +------------+--------------------------------------+---------------+</code></pre><h1 id="9-security-considerations">9. Security Considerations</h1>
<p>응답 분할: 요청에 있는 파라미터가 응답헤더로 다시 전달되는 경우, 파라미터 내 개행문자 CR 혹은 LF가 존재하면 HTTP응답이 분리될 수 있다. HTTP 응답 분할 공격은 이러한 취약점을 통해 응답 메시지에 악의적인 코드를 주입함으로써 캐시를 훼손하는 취약점이다.</p>
<p>예시는 해당 블로그에서 해결할 수 있다.
<a href="https://m.blog.naver.com/skinfosec2000/220694143144">https://m.blog.naver.com/skinfosec2000/220694143144</a></p>
<h1 id="요약">요약</h1>
<h3 id="start-line시작줄부분">start-line(시작줄)부분</h3>
<p>method 토큰을 시작으로, 공백 (SP), request-target, 공백 (SP), 프로토콜 버전, 그리고 CRLF를 끝으로 한다.</p>
<p>시작줄 정보가 유효하지 않으면 400또는 301(리다이렉트)상태코드로 응답한다.</p>
<p>또한 서버는 리다이렉트없이 request데이터를 수정해서 처리하면 안된다.</p>
<p>만약 서버가 처리 할 수 있는 시작줄의 길이를 초과하면 501에러 처리해야한다.</p>
<h3 id="헤더부분">헤더부분</h3>
<p>start-line(시작줄)이후 헤더필드가 CRLF기준으로 나누어져있다. 만약 시작줄과 헤더 블록이 분리(공백삽입 또는 CRLF두번 이상 삽입)되어 있다면 클라이언트의 request를 무시해야 된다.</p>
<p>클라이언트는 Host헤더를 무조건 보내야하며, 시작줄바로 다음에 위치 할 것을 권장한다. 헤더와 콜론사이에 공백이 있어서는 안된다. 헤더의순서는 큰 영향이 없다. 쿠키헤더만 예외적으로 파싱해야 하는 부분이 있다.</p>
<h3 id="body부분">body부분</h3>
<p>conf파일에서 지정한 client_max_bodysize를 초과하는 request가 들어오면 413에러페이지를 응답한다.</p>
<h3 id="클라이언트의-행동">클라이언트의 행동</h3>
<p>클라이언트는 서버에 연결시도시 절대 자동재시도를 하면 안된다.</p>
<h3 id="서버의-행동">서버의 행동</h3>
<p>응답을 보내고 바로 소켓을 close하면 안된다. 클라이언트는 응답을 수신하지 못하는 상황이 발생 할 수 있다. 일반적으로 서버는 클라이언트와의 읽기/쓰기 연결에서 쓰기만 닫고, 클라이언트에서 close를 송신하면 서버에서 끝까지 패킷을 읽다가 완전히 종료한다. 서비스거부공격이나 응답분할공격이 의심되면 요청을 차단 할 수도있다.</p>
<h3 id="http의-오해">http의 오해</h3>
<p>웹브라우저와 서버간의 통신만을 위한 프로토콜이 아니다. 사물통신(IOT)와의 통신에도 사용 될 수 있으므로 웹브라우저에 국한되서 작동한다고 생각하는 것은 잘못된 것이다.</p>
<h3 id="응답분할-공격-예방법">응답분할 공격 예방법</h3>
<p>인코딩된 CR 및 LF (%0D %0A)데이터에 대한 요청을 필터링(헤더가 두 개 이상으로 나뉘어지는 것을 방지).</p>
<h3 id="http-로그데이터-취급">http 로그데이터 취급</h3>
<p>본질적으로 기밀이며, 취급은 각 국가의 법고하 규정에 의해 제한될 수 있다. http에서는 재식별을 막는 방법이 역부족하므로 클라이언트에서 로그데이터에 접속하는 키가 가명일지라도 안전하지 않다.</p>
<p>참고 블로그
<a href="https://velog.io/@augus-xury/RFC">https://velog.io/@augus-xury/RFC</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Webserv 바탕 지식 주워먹기]]></title>
            <link>https://velog.io/@jeong_twelve/Webserv-%EB%B0%94%ED%83%95-%EC%A7%80%EC%8B%9D-%EC%A3%BC%EC%9B%8C%EB%A8%B9%EA%B8%B0</link>
            <guid>https://velog.io/@jeong_twelve/Webserv-%EB%B0%94%ED%83%95-%EC%A7%80%EC%8B%9D-%EC%A3%BC%EC%9B%8C%EB%A8%B9%EA%B8%B0</guid>
            <pubDate>Sun, 30 Jul 2023 11:27:34 GMT</pubDate>
            <description><![CDATA[<h1 id="1-웹이-작동하는-방식">1. 웹이 작동하는 방식</h1>
<p><img src="https://velog.velcdn.com/images/jeong_twelve/post/c112e1d2-d664-473a-b882-79b9834a9c16/image.png" alt=""></p>
<h2 id="웹-클라이언트">웹 클라이언트</h2>
<ul>
<li>사용자가 웹에 접근하는 프로그램</li>
<li>우리가 아는 크롬, 사파리 등등</li>
</ul>
<h2 id="웹-서버">웹 서버</h2>
<ul>
<li>웹 페이지, 사이트 또는 앱을 저장하는 프로그램</li>
<li>서버는 클라이언트에서 요청한 HTTP 메세지를 확인한 후, 이에 맞는 데이터를 처리한 뒤에 다시 클라이언트에 응답</li>
</ul>
<h2 id="was">WAS</h2>
<ul>
<li>웹 어플리케이션 서버로 이름에서 알 수 있듯이 사용자 컴퓨터나 장치에 웹 어플리케이션을 수행해주는 미들웨어</li>
<li>리퀘스트가 오면 서버는 요청에 필요한 페이지의 로직이나 데이터베이스의 연동을 위해 WAS에 처리를 요청
WAS는 동적인 페이지 처리를 담당하고 DB에서 데이터 정보를 받아옴</li>
</ul>
<h2 id="db">DB</h2>
<ul>
<li>데이터의 정보를 저장하는 곳</li>
</ul>
<h1 id="2-http-메세지">2. HTTP 메세지</h1>
<h2 id="http">HTTP</h2>
<ul>
<li>웹 브라우저와 웹 서버 간에 데이터를 주고 받기 위해 사용하는 인터넷 프로토콜</li>
</ul>
<h2 id="특징">특징</h2>
<ul>
<li>클라이언트 / 서버 모델을 따름 : 서로 관계를 맺고 있는 컴퓨터 프로그램이 클라이언트와 서버로 구분이 됨</li>
</ul>
<h2 id="http-메세지">HTTP 메세지</h2>
<ul>
<li>요청 메세지 : 클라이언트가 서버에서 요청하는 메세지</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jeong_twelve/post/dcda425b-5933-47a7-8c3a-3e0900ef8221/image.png" alt=""></p>
<pre><code>- 메서드 : 어떻게 처리를 해야 하는지를 담고 있음
- GET : 존재하는 자원에 대한 요청
- POST : 새로운 자원을 생성
- PUT : 존재하는 자원에 대한 변경
- DELETE : 존재라는 자원에 대한 삭제
- 경로 : 가져오려는 리소스의 경로를 표시
- 프로토콜 버전 : HTTP 프로토콜의 버전을 표시
- 헤더 : 서버에 대한 추가 정보를 전달. 호스트의 정보나 접속하고 있는 사용자의 정보, 그리고 열려고 하는 페이지의 정보 등을 확인할 수 있습니다.
- user-agent : 웹 브라우저의 다른 표현. 요청하는 웹 브라우저의 정보 및 운영체제를 표시
- accep-encoding : 클라이언트가 이해할 수 있는 압축 방식을 표시. 이를 표시해서 서버에 전송하면 서버는 필요한 경우 리소스를 압축하여 반환
- 공백 라인 : 헤더와 본문 구분하는 역할
- 본문 : POST처럼 서버에 새로운 자원을 추가하는 경우에 들어가는 선택적인 요소</code></pre><ul>
<li>응답 메세지 : 클라이언트의 요청을 해석한 서버가 응답하는 메세지</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jeong_twelve/post/056250b7-a862-442a-b565-7beb57758b01/image.png" alt=""></p>
<pre><code>- 프로토콜 버전 : HTTP 프로토콜의 버전을 나타냄
- 상태 코드 : 클라이언트 요청의 성공 여부를 숫자로 나타낸 것
    - 1xx (조건부 응답) : 요청을 받았으며 작업을 계속
    - 2xx (성공) : 정상적으로 요청을 수행했을 때 나타나는 코드
    - 3xx (리다이렉션 오류) : 클라이언트가 요청을 마치기 위해 추가 동작을 취해야 할 때 나타냄
    - 4xx (요청 오류) : 클라이언트의 요청에 오류가 있을 때 나타나는 코드
    - 5xx (서버 오류) : 서버가 들어온 요청을 수행하지 못했을 때 나타남
- 헤더
- content-type : 전달한 리소스의 타입. 
- content-encoding : 응답 메세지의 헤더의 accept-encoding처럼 컨텐츠가 압축된 방식을 표시
-date : 해당 메세지가 만들어진 날짜와 시간을 포함
- 본문</code></pre><h1 id="3-프록시-서버">3. 프록시 서버</h1>
<h2 id="프록시">프록시</h2>
<ul>
<li>클라이언트와 서버 사이에서 HTTP 메세지를 대신 전달하는 중계 기능</li>
</ul>
<h2 id="특징-1">특징</h2>
<ul>
<li>주고 받은 적이 있는 데이터의 사본이 있어서 다시 요청할 시 캐싱해 둔 데이터를 반환하여 전송시간을 줄일 수 있음</li>
<li>메세지가 지나갈 때마다 via 헤더에 정보를 추가해야 하는데, 프록시 서버의 정보 또한 기록되어 나중에 메세지 접근을 제어하거나 추적하는데 사용할 수 있음.</li>
<li>익명성을 보호</li>
</ul>
<h2 id="유형">유형</h2>
<ul>
<li>포워드 프록시 : 서버의 메세지를 클라이언트에게 전달하는 역할 ⇒ 클라이언트에게 데이터를 전달</li>
<li>리버스 프록시 : 클라이언트의 요청을 다수 서버에 분배하여 전달하는 역할 ⇒ 앞에 있던 클라이언트가 뒤에 있는 서버에게 역으로 데이터를 전달</li>
</ul>
<h2 id="via-헤더">Via 헤더</h2>
<ul>
<li>해당 http 메시지가 거쳐온 프록시 또는 게이트웨이의 정보를 담고 있음</li>
</ul>
<blockquote>
<aside>
💡 [<프로토콜 이름 />]<프로토콜 버전> <호스트>[:<포트>]
[<프로토콜 이름 />]<프로토콜 버전> <내부 프록시의 이름 or 별칭>
</blockquote>
</aside>

<h1 id="4-dns">4. DNS</h1>
<h2 id="ip">IP</h2>
<ul>
<li>인터넷 장치 각각을 식별할 수 있는 고유한 주소</li>
<li>인터넷 상에서 서로 데이터를 주고 받을 때 필요한 규약</li>
<li>종류</li>
<li>IPv4</li>
<li>IPv6</li>
<li>사설 IP 주소와 공인 IP 주소
라우터가 공공 IP 주소를 가지며 그 라우터에 속한 기기들은 각자를 구분하는 사설 IP 주소를 가짐</li>
<li>LAN : 기기들이 연결된 지역 네트워크</li>
<li>WAN : LAN들을 연결한 광역 네트워크</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jeong_twelve/post/91746b23-dcd2-43d6-bf22-a1794bcef266/image.png" alt=""></p>
<ul>
<li>사설 IP로 통신하는 과정
라우터를 거치며 자동으로 공인 IP 주소로 변경하는데 이는 라우터에 내장된 NAT(network address translation)이 담당</li>
</ul>
<h2 id="도메인">도메인</h2>
<ul>
<li>외우기 힘든 IP 대신 사람이 기억하기 쉽게 만든 인터넷 주소</li>
<li>구성 요소</li>
<li>국가 도메인</li>
<li>일반 도메인</li>
</ul>
<h2 id="dns">DNS</h2>
<ul>
<li>도메인 주소를 IP 주소로 변환해주는 역할을 하는 시스템</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jeong_twelve/post/4f3e8523-e239-4b9f-8a54-63dc46f8de0d/image.png" alt=""></p>
<ul>
<li>DNS 서버는 평소에 수많은 도메인 이름들을 기억하다가 그에 맞는 IP 주소를 반환</li>
</ul>
<h1 id="5-tcp">5. TCP</h1>
<p>우리가 네트워크에서 데이터를 주고 받는 과정에서 한꺼번에 많은 양을 하면 큰 과부하가 올 수 있기에, 패킷이라는 단위로 쪼개서 다양한 경로로 주고 받는다. 하지만 이 과정에서 쪼개진 패킷들이 제대로 도착했는지 그리고 순서대로 잘 들어왔는지를 파악해야 했음. 이를 해결하기 위해 사용된 프로토콜이 TCP!</p>
<ul>
<li>TCP의 기능<ol>
<li>오류 없는 데이터를 전송</li>
<li>순서에 맞는 전달</li>
<li>조각나지 않는 데이터 스트림</li>
</ol>
</li>
<li>TCP/IP는 패킷 교환 네트워크 프로토콜의 집합</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jeong_twelve/post/90ea564b-7694-43de-bd07-b1650a0ec9e7/image.png" alt=""></p>
<ul>
<li><p>TCP와 IP
IP는 적힌 IP주소와 일치하는 장소로 보내는 데만 집중 → 비연결형
TCP는 목적지에 데이터가 안정적으로 도착하는 데 집중. 그래서 수신자가 데이터를 받을 수 있는지를 계속 파악함 → 연결형</p>
<p><img src="https://velog.velcdn.com/images/jeong_twelve/post/002bd39e-7671-470d-bd04-acbc65e6dc4c/image.png" alt=""></p>
</li>
<li><p>보내는 쪽 : 데이터에 대한 요청이 들어오면 TCP에서는 데이터를 적당한 크기의 패킷을 자르고 IP에 전달
  받는 쪽 : IP는 도착한 패킷의 주소가 내 주소와 일치하는지 확인한 뒤 맞다면 받는 쪽 컴퓨터의 TCP로 전달하고, TCP는 받는 패킷을 다시 재조립</p>
</li>
<li><p>UDP와 TCP
TCP의 단점으론 데이터 교환 과정에서 확인하는 게 많아서 전송 속도가 느림
UDP는 TCP보다 빠르지만, 3 웨이 핸드쉐이크 처럼 확인하는 과정을 스킵해서 데이터의 유실 가능성이 늘어나고 순서가 바뀔 위험이 있음</p>
</li>
</ul>
<h1 id="6-http의-역사">6. HTTP의 역사</h1>
<h2 id="http11">HTTP/1.1</h2>
<p><img src="https://velog.velcdn.com/images/jeong_twelve/post/8d58aeac-6bf3-41f6-909b-5e52c3a48033/image.png" alt=""></p>
<ul>
<li>한 번 TCP 연결을 맺으면 끊지 않고 계속 유지할 수 있음
이로 인해 핸드쉐이크를 계속 하는 과정을 줄이고 안정적이고 속도감 있는 통신이 가능</li>
</ul>
<h2 id="http2">HTTP/2</h2>
<ul>
<li>이전 버전에선 1:1 대응을 이루고 있어서 순서대로 처리가 되지 않으면 밀리는 문제가 생김
이를 고치기 위해 모든 요청과 응답을 병렬적으로 처리 → 더 높은 성능과 안정적인 처리</li>
<li>바이너리 프레이밍
텍스트 형식인 HTTP 메시지를 바이너리 형태로 캡슐화</li>
</ul>
<h2 id="http3">HTTP/3</h2>
<ul>
<li>TCP가 아닌 UDP 위에서 작동하는 방식인 QUIC를 적용</li>
<li>아직 draft 상태</li>
</ul>
<h1 id="7-web">7. WEB</h1>
<h2 id="웹-10">웹 1.0</h2>
<ul>
<li>주로 읽기 전용</li>
</ul>
<h2 id="웹-20">웹 2.0</h2>
<ul>
<li>양방향 소통이 가능해져 읽기와 쓰기가 가능해짐</li>
<li>하지만 소유권 문제에서 서비스에서 발생하는 데이터는 모두 기업이 소유하고, 이를 활용한 이익도 기업이 독점</li>
</ul>
<h2 id="웹-30">웹 3.0</h2>
<ul>
<li>초기 시맨틱 웹에 초점 : 컴퓨터가 문장, 단어의 뜻을 이해하고 논리적인 추론을 하는 웹 기술</li>
<li>최근 탈중앙화에 초점 : 데이터를 일부 기업이나 플랫폼을 독점하는 현상에서 벗어남. 즉 일반 사용자들이 소유를 하는 형태</li>
</ul>
<h1 id="8-https-ssl-tls">8. HTTPS, SSL, TLS</h1>
<h2 id="https">HTTPS</h2>
<ul>
<li>http에서 보안이 강화된 프로토콜</li>
</ul>
<h2 id="ssltls">SSL/TLS</h2>
<ul>
<li>클라이언트와 서버가 서로 데이터를 암호화해 통신할 수 있도록 돕는 보안계층</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jeong_twelve/post/fc71e8ae-0812-4133-b012-4566b5e4658b/image.png" alt=""></p>
<ul>
<li>tls는 ssl 3.0</li>
</ul>
<h2 id="암호화-기법">암호화 기법</h2>
<ul>
<li>대칭키 : 하나의 키로 암호화와 복호화를 둘 다 할 수 있는 암호화 기법
공개키 : 서로 다른 키 두 개로 암호화, 복호화를 한다는 특징</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jeong_twelve/post/c2cb5bcd-b667-43cd-b24d-f7698ac4675a/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jeong_twelve/post/dfe4f7a2-6b47-4910-97f6-3b88f52d8f21/image.jpg" alt="">
-출처: TCP/IP가 보이는 그림책(성안당)-</p>
<h2 id="ssl-동작-과정">SSL 동작 과정</h2>
<ul>
<li>핸드 셰이크 → 세션 → 세션 종료</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jeong_twelve/post/54517ba9-80c1-4e40-b42d-e9eace224844/image.png" alt=""></p>
<h1 id="9-쿠키">9. 쿠키</h1>
<ul>
<li>브라우저에서 서버로 요청이 있을 때 도메인 단위로 발급하는 작은 텍스트 파일
어떤 상태를 기억해야 할 대 매우 간단히 사용할 수 있음
웹사이트를 방문하거나 배너에 노출되거나 배너를 클릭하는 등 개발자가 사전에 설정해 놓은대로 유저의 디바이스의 특정 위치에 자동 저장
<img src="https://velog.velcdn.com/images/jeong_twelve/post/dc3114cb-a096-46d7-bf8a-3e04a376e5d4/image.png" alt=""></li>
</ul>
<h1 id="참고사항">참고사항</h1>
<p><a href="https://brunch.co.kr/magazine/webnetwork">해치지 않는 웹 네트워크 매거진
  https://brunch.co.kr/magazine/webnetwork</a>
42서울 카뎃 hoslim의 slack 정리노트</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Push_swap 맛있게 부어먹기]]></title>
            <link>https://velog.io/@jeong_twelve/Pushswap-%EB%A7%9B%EC%9E%88%EA%B2%8C-%EB%B6%80%EC%96%B4%EB%A8%B9%EA%B8%B0</link>
            <guid>https://velog.io/@jeong_twelve/Pushswap-%EB%A7%9B%EC%9E%88%EA%B2%8C-%EB%B6%80%EC%96%B4%EB%A8%B9%EA%B8%B0</guid>
            <pubDate>Mon, 26 Dec 2022 10:19:21 GMT</pubDate>
            <description><![CDATA[<h1 id="주인장이-귀찮아서-천천히-할-예정입니다">주인장이 귀찮아서 천천히 할 예정입니다</h1>
<h1 id="과제-설명">과제 설명</h1>
<blockquote>
<h2 id="push_swap">push_swap</h2>
<p>터미널에 
./push_swap &quot;3 2 5 8 7&quot; 1 0 &quot;15 79&quot;
등 숫자를 입력하면 해당 숫자들을 stack a에 저장한 후 stack b를 활용하여 해당 숫자들을 sa, sb, ss, pa, pb, ra, rb, rr, rra, rrb, rrr 만을 활용하여 정렬해야 한다.</p>
</blockquote>
<ul>
<li>sa (swap a): a의 최상위에 있는 인자 두 개를 바꾼다.</li>
<li>sb (swap b): b의 최상위에 있는 인자 두 개를 바꾼다.</li>
<li>ss : sa와 sb를 동시에 실행한다.</li>
<li>pa (push a): b의 최상위 인자를 a의 최상위로 보낸다.</li>
<li>pb (push b): a의 최상위 인자를 b의 최상위로 보낸다.</li>
<li>ra (rotate a): a의 최상위 인자를 a의 마지막으로 보낸다.</li>
<li>rb (rotate b): b의 최상위 인자를 b의 마지막으로 보낸다.</li>
<li>rr : ra와 rb를 동시에 실행한다.</li>
<li>rra (reverse rotate a): a의 마지막 인자를 a의 최상위로 보낸다.</li>
<li>rrb (reverse rotate b): b의 마지막 인자를 b의 최상위로 보낸다.</li>
<li>rrr : rra와 rrb를 동시에 실행한다.</li>
</ul>
<p>필자는 stack a와 stack b를 양방향 리스트로 구현하고
알고리즘은 모래시계 알고리즘을 사용했다.</p>
<h1 id="사용한-구조체들">사용한 구조체들</h1>
<pre><code> typedef struct s_info
{
    int            str_cnt;        //인자로 넣은 숫자들의 갯수
    int            parse_cnt;
    int            *num_arr;        //인자로 넣은 숫자들 배열
}                t_info;

typedef struct s_node    //양뱡향 리스트
{
    int                num;
    struct s_node    *next;
    struct s_node    *prev;
}                t_node;

typedef struct s_stack {
    int        len;
    t_node    *head;
    t_node    *tail;
}                t_stack;

typedef struct s_ps
{
    t_stack    *a;        //a스택
    t_stack    *b;        //b스택
    t_stack    *cmd_stack;    //ra, rb 등등 명령어가 저장될 스택
    int        cmd_cnt;    //저장된 명령어 갯수
}                t_ps;</code></pre><h1 id="main-문">main 문</h1>
<pre><code>int    main(int ac, char **av)
{
    t_info    info;
    t_ps    ps;
    int        chunk;

    init_info(&amp;info, ac, av);    //info 구조체 내부 인자 배열에 숫자들 넣는 함수
    init_ps(&amp;info, &amp;ps);    //해당 인자들 정렬한 배열을 만들고 기존 비정렬 배열과 비교해 인덱스 번호를 딴 배열을 만드는 함수 -&gt; 모래시계 알고리즘을 사용하기 위해 인덱스 번호를 딴 배열은 꼭 필요함
    if (check_sort(&amp;ps))    //정렬이 되어있는지 확인, 정렬이 되어있다면 free 후 코드 종료
    {
        free_ps(&amp;ps);
        free_exit(&amp;info);
    }
    chunk = get_chunk(ps.a-&gt;len);    //모래시계 알고리즘을 효율적으로 하기 위해 필요한 덩어리 값(chunk) 구하는 함수
    if (info.str_cnt &lt; 6)    //인자가 5개 이하일 경우 인자 갯수에 비해 명령의 수(ra,rb, ...)의 갯수가 너무 많으므로 과제 조건을 맞추기 위한 하드코딩
        sort_under_5(&amp;ps);
    else
    {
        a_to_b(&amp;ps, 0, chunk);    //a인자들 모두 b로 보냄(모래시계)
        b_to_a(&amp;ps);    //모든 인자들에서 가장 큰 값을 a로 보냄
    }
    rr_rrr_check(&amp;ps);    //rr이나 rrr이 나오는 경우를 확인 후 바꿈
    print_all_cmd(ps.cmd_stack);    //모든 명령들 출력
    free(info.num_arr);
    free_ps(&amp;ps);
    return (0);
}</code></pre><p>우선 모래시계 알고리즘은 a의 모든 인자들을 b로 보낸 후 b에서 가장 큰 값을 b스택의 최상위로 올려 a로 보내며 정렬을 한다. 따라서 인자가 아무리 적어도 명령의 갯수가 인자 * 2배이므로 인자가 5개일 때 최솟값이 10개이다. 따라서 과제가 원하는 명령의 수를 충족하려면 5개 이하 인자일 때 하드코딩을 통해 따로 처리해주어야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[내가 짠 so_long 코드를 되새김질 해보자]]></title>
            <link>https://velog.io/@jeong_twelve/%EB%82%B4%EA%B0%80-%EC%A7%A0-solong-%EC%BD%94%EB%93%9C%EB%A5%BC-%EB%90%98%EC%83%88%EA%B9%80%EC%A7%88-%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@jeong_twelve/%EB%82%B4%EA%B0%80-%EC%A7%A0-solong-%EC%BD%94%EB%93%9C%EB%A5%BC-%EB%90%98%EC%83%88%EA%B9%80%EC%A7%88-%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Sun, 25 Dec 2022 10:10:41 GMT</pubDate>
            <description><![CDATA[<h4 id="필자는-사용할-이미지-파일들을-찾기도-귀찮고-xpm-파일로-변환하기도-굉장히-귀찮아서-그냥-대충-이미지-파일을-썼으니-진짜-고퀄로-하고싶다면-어디-한-번-화이팅">필자는 사용할 이미지 파일들을 찾기도 귀찮고 .xpm 파일로 변환하기도 굉장히 귀찮아서 그냥 대충 이미지 파일을 썼으니 진짜 고퀄로 하고싶다면 어디 한 번 화이팅...!</h4>
<h3 id="사용하는-구조체의-종류들">사용하는 구조체의 종류들</h3>
<pre><code class="language-c">typedef struct s_data {
    char    **ber_arr;    //map 정보가 담길 2차원 배열들
    void    *mlx;        //mlx_init()의 리턴값이 담길 주소 포인터
    void    *win;        //mlx_new_window()의 리턴값이 담길 주소 포인터
    void    *grass;        //ber_arr[][]의 값이 &#39;0&#39;일 경우 땅 이미지를 넣을 포인터
    void    *wall;        //&quot;&quot; &#39;1&#39;일 경우 벽 이미지를 넣을 포인터
    void    *player;    // &quot;&quot; &#39;P&#39;일 경우 캐릭터 이미지를 넣을 포인터
    void    *chest;        // &quot;&quot; &#39;C&#39;일 경우 아이템 이미지를 넣을 포인터
    void    *door;        // &quot;&quot; &#39;E&#39;일 경우 문 이미지를 넣을 포인터
    char    *map;        
    int        items;        //캐릭터가 이동하며 아이템을 획득할 경우 하나씩 줄어들 아이템의 갯수 (0이 되고 E로 탈출해야 게임 종료)
    int        walk;        //걸음수를 terminal에 출력하기 위한 걸음수 체크 변수
    int        now_x;        //캐릭터의 현재 위치
    int        now_y;        //캐릭터의 현재 위치
    int        x;            //캐릭터의 다음 위치
    int        y;            //캐릭터의 다음 위치
    int        box_width;    //ber파일의 인자값 갯수
    int        box_height;    //ber파일의 인자값 갯수
    int        img_width;    //mlx_xpm_file_to_image의 3,4번째 인자값으로 사용할 이미지의 크기, 따로 설정하지 않아도 사진 크기에 맞게 조절됨 (주의, xpm으로 변환할 png파일들은 크기가 모두 같아야 함.)
    int        img_height;    //&quot;&quot;
}                t_data;

typedef struct s_dfs {
    int    items;            //dfs에서 검사할 유효한 아이템
    int    door;            //dfs에서 검사할 유효한 문
    int    **arr;            //dfs에서 사용할 ber_arr과 똑같은 이중배열 포인터
}                t_dfs;</code></pre>
<h3 id="main함수">main함수</h3>
<pre><code class="language-c">int    main(int ac, char **av)
{
    t_dfs    tdfs;
    t_data    data;
    int        fd;

    if (ac == 2)
    {
        fd = open(av[1], O_RDONLY);
        if (fd &lt;= 0)
            print_err0(&quot;File open fail.\n&quot;);
        data.mlx = mlx_init();
        data.ber_arr = map_read(av[1], &amp;data); //2차원 배열에 .ber파일의 내용들을 개행 기준으로 split해서 담는 함수.
        valid_test(&amp;data);    //split해서 담은 내용들을 하나씩 흝어 P와 E가 하나만 있고 C가 1개 이상 있는지 검사한다.
        data = set_win_img(data);    //윈도우 창을 만들고, 사진을 넣는 함수.
        dfs(&amp;data, &amp;tdfs);    //dfs를 통해 이루어질 수 없는 맵(player가 아이템을 먹을 수 없거나 탈출구로 나갈 수 없는 맵) 검사
        mlx_hook(data.win, KEYPRESS, 0, &amp;deal_key, &amp;data);    //키보드로 w,a,s,d 입력시 캐릭터가 이동하도록 하는 함수(deal_key)의 주소를 담은 hook 함수
        mlx_hook(data.win, 17, 0, &amp;deal_mouse, &amp;data);    //마우스로 window의 왼쪽 상단 X를 눌러 종료 할 경우를 위한 deal_mouse 함수의 주소를 담은 hook 함수
        mlx_loop(data.mlx);
    }
    else
        print_err0(&quot;Map is missing.\n&quot;); //argv의 인자값이 1개보다 더 들어왔을 경우를 위한 에러처리.
    return (0);
}</code></pre>
<h3 id="set_win_img함수">set_win_img함수</h3>
<pre><code class="language-c">t_data    set_win_img(t_data data)
{
    int    i;

    i = 0;
    while (data.ber_arr[i])
        i++;
    data.box_height = i;
    data.items = 0;
    data.walk = 0;
    data.box_width = ft_strlen(data.ber_arr[0]);
    data.win = mlx_new_window(data.mlx, data.box_width * 64, \
                                        data.box_height * 64, &quot;my_mlx&quot;);
    data.wall = mlx_xpm_file_to_image(data.mlx, &quot;./image/walls.xpm&quot;, \
                                        &amp;data.img_width, &amp;data.img_height);
    data.grass = mlx_xpm_file_to_image(data.mlx, &quot;./image/grass.xpm&quot;, \
                                        &amp;data.img_width, &amp;data.img_height);
    data.player = mlx_xpm_file_to_image(data.mlx, &quot;./image/player.xpm&quot;, \
                                        &amp;data.img_width, &amp;data.img_height);
    data.chest = mlx_xpm_file_to_image(data.mlx, &quot;./image/chest_01.xpm&quot;, \
                                        &amp;data.img_width, &amp;data.img_height);
    data.door = mlx_xpm_file_to_image(data.mlx, &quot;./image/door.xpm&quot;, \
                                        &amp;data.img_width, &amp;data.img_height);
    put_image(&amp;data);
    return (data);
}</code></pre>
<p>각종 void 이미지 포인터에 .xpm 파일을 넣어주는 함수이다.</p>
<p>ㅡ
ㅡ</p>
<h3 id="put_image함수">put_image함수</h3>
<pre><code class="language-c">void    put_image(t_data *data)
{
    int    i;
    int    j;
    int    count;

    i = -1;
    data-&gt;y = 0;
    count = 0;
    while (data-&gt;ber_arr[++i])
    {
        j = -1;
        data-&gt;x = 0;
        while (data-&gt;ber_arr[i][++j])
        {
            check_map(data, i, j);
            data-&gt;x += 64;
            count++;
        }
        data-&gt;y += 64;
    }
    check_wall(data);
    if (count != data-&gt;box_height * data-&gt;box_width)
        print_err(&quot;Error\nMap must be rectangular.\n&quot;, data);
}</code></pre>
<p>check_map을 통해 이미지를 window에 출력하고 check_wall을 통해 벽으로 둘러쌓여있는지 확인한 후 맵이 사각형이 아니면 에러문 출력 함수로 넘어간다. (free해줌)</p>
<p>ㅡ
ㅡ</p>
<h3 id="check_map-check_wall-함수">check_map, check_wall 함수</h3>
<pre><code class="language-c">void    check_map(t_data *data, int i, int j)
{
    if (data-&gt;ber_arr[i][j] == &#39;1&#39;)
        mlx_put_image_to_window(data-&gt;mlx, data-&gt;win, \
                    data-&gt;wall, data-&gt;x, data-&gt;y);
    else if (data-&gt;ber_arr[i][j] == &#39;0&#39;)
        do_mlx_put_image_grass(data);
    else if (data-&gt;ber_arr[i][j] == &#39;P&#39;)
    {
        do_mlx_put_image_grass(data);
        mlx_put_image_to_window(data-&gt;mlx, data-&gt;win, \
                    data-&gt;player, data-&gt;x, data-&gt;y);
        data-&gt;now_x = j;
        data-&gt;now_y = i;
    }
    else if (data-&gt;ber_arr[i][j] == &#39;C&#39;)
    {
        do_mlx_put_image_grass(data);
        do_mlx_put_image_chest(data);
        data-&gt;items++;
    }
    else if (data-&gt;ber_arr[i][j] == &#39;E&#39;)
        mlx_put_image_to_window(data-&gt;mlx, data-&gt;win, \
                    data-&gt;door, data-&gt;x, data-&gt;y);
    else
        print_err(&quot;Error\nWrong Map\n&quot;, data);
}

void    check_wall(t_data *data)
{
    int    i;

    i = -1;
    while (++i &lt; data-&gt;box_width)
    {
        if (data-&gt;ber_arr[0][i] != &#39;1&#39;)
            print_err(&quot;Error\nMap must be closed/surrounded by walls\n&quot;, data);
        if ((data-&gt;ber_arr[data-&gt;box_height - 1][i]) != &#39;1&#39;)
            print_err(&quot;Error\nMap must be closed/surrounded by walls\n&quot;, data);
    }
    i = -1;
    while (++i &lt; data-&gt;box_height)
    {
        if ((data-&gt;ber_arr[i][0]) != &#39;1&#39;)
            print_err(&quot;Error\nMap must be closed/surrounded by walls\n&quot;, data);
        if ((data-&gt;ber_arr[i][data-&gt;box_width - 1]) != &#39;1&#39;)
            print_err(&quot;Error\nMap must be closed/surrounded by walls\n&quot;, data);
    }
}</code></pre>
<p>check_map 함수는 이미지를 넣음과 동시에 1,0,P,C,E가 아닌 문자가 나오면 에러문을 출력한다.
check_wall 함수는 벽으로 둘러쌓여있지 않으면 에러문을 출력한다.</p>
<p>ㅡ
ㅡ</p>
<h2 id="dfs-함수">dfs 함수</h2>
<pre><code class="language-c">void    dfs(t_data *data, t_dfs *tdfs)
{
    int    x;
    int    y;
    int    i;

    i = -1;
    x = data-&gt;now_x;    //처음 player의 위치
    y = data-&gt;now_y;    //처음 player의 위치
    tdfs-&gt;items = data-&gt;items;    //아이템 전체의 갯수
    tdfs-&gt;door = 0;        //문을 만나면 하나씩 증가하므로 0으로 초기화
    init_tdfs(data, tdfs);    //tdfs-&gt;arr[][]의 값을 모두 정수 0으로 초기화
    find_items(data, tdfs, x, y);     //맵 전체 아이템 검사
    free_tdfs_arr(data, tdfs);    //tdfs-&gt;arr[][] free
    init_tdfs(data, tdfs);    //tdfs-&gt;arr[][]의 값을 모두 정수 0으로 초기화
    find_door(data, tdfs, x, y);    //맵 전체 문 검사
    free_tdfs_arr(data, tdfs);        //tdfs-&gt;arr[][] free
    if (tdfs-&gt;items != 0 || tdfs-&gt;door != 1)    //아이템이 0이 아니거나 문 갯수가 1이 아니면 에러문 출력
        print_err(&quot;Error\nInvalidMapFile\n&quot;, data);
}</code></pre>
<p>ㅡ
ㅡ</p>
<h2 id="find_items-함수">find_items 함수</h2>
<pre><code class="language-c">void    find_items(t_data *data, t_dfs *tdfs, int x, int y)
{
    //상하좌우 한칸씩 이동하며 재귀로 맵 전체 아이템 검사
    if (check_items_dfs(data, tdfs, x + 1, y))
        find_items(data, tdfs, x + 1, y);
    if (check_items_dfs(data, tdfs, x, y + 1))
        find_items(data, tdfs, x, y + 1);
    if (check_items_dfs(data, tdfs, x - 1, y))
        find_items(data, tdfs, x - 1, y);
    if (check_items_dfs(data, tdfs, x, y - 1))
        find_items(data, tdfs, x, y - 1);
}</code></pre>
<p>ㅡ
ㅡ</p>
<h2 id="check_items_dfs-함수">check_items_dfs 함수</h2>
<pre><code class="language-c">int    check_items_dfs(t_data *data, t_dfs *tdfs, int x, int y)
{
    if (data-&gt;ber_arr[y][x] == &#39;1&#39; || tdfs-&gt;arr[y][x] || \
        data-&gt;ber_arr[y][x] == &#39;E&#39; || !tdfs-&gt;items)
        return (0);    //벽이거나, 이미 방문해서 1이 찍혀있거나, 출구거나 아이템 갯수가 0개면 리턴으로 종료
    if (data-&gt;ber_arr[y][x] == &#39;C&#39;)
        tdfs-&gt;items--;    //아이템을 만나면 아이템 수 하나씩 감소
    tdfs-&gt;arr[y][x] = 1;    //방문한 흔적을 남기기 위해 0-&gt;1로 값 변경
    return (1);
}</code></pre>
<p>ㅡ
ㅡ</p>
<h2 id="find_door-함수">find_door 함수</h2>
<p>find_items와 같음</p>
<pre><code class="language-c">void    find_door(t_data *data, t_dfs *tdfs, int x, int y)
{
    if (check_doors_dfs(data, tdfs, x + 1, y))
        find_door(data, tdfs, x + 1, y);
    if (check_doors_dfs(data, tdfs, x, y + 1))
        find_door(data, tdfs, x, y + 1);
    if (check_doors_dfs(data, tdfs, x - 1, y))
        find_door(data, tdfs, x - 1, y);
    if (check_doors_dfs(data, tdfs, x, y - 1))
        find_door(data, tdfs, x, y - 1);
}</code></pre>
<p>ㅡ
ㅡ</p>
<h2 id="check_doors_dfs-함수">check_doors_dfs 함수</h2>
<pre><code class="language-c">int    check_doors_dfs(t_data *data, t_dfs *tdfs, int x, int y)
{
    if (data-&gt;ber_arr[y][x] == &#39;1&#39; || tdfs-&gt;arr[y][x])
        return (0);    //1이거나 이미 방문했으면 리턴 0으로 종료
    if (data-&gt;ber_arr[y][x] == &#39;E&#39;)    
        tdfs-&gt;door++;    //문을 만나면 1씩 +
    tdfs-&gt;arr[y][x] = 1;    //방문 흔적 남기기
    return (1);
}</code></pre>
<p>ㅡ
ㅡ</p>
<h2 id="deal_key-함수">deal_key 함수</h2>
<pre><code class="language-c">int    deal_key(int key_code, t_data *data)
{
    //key_code 방향키에 따른 x, y좌표 변경 인자를 가지고 move_player 함수 실행
    if (key_code == ESC)
        good_bye(data);
    else if (key_code == UP)
        move_player(data, data-&gt;now_x, data-&gt;now_y - 1);
    else if (key_code == LEFT)
        move_player(data, data-&gt;now_x - 1, data-&gt;now_y);
    else if (key_code == RIGHT)
        move_player(data, data-&gt;now_x + 1, data-&gt;now_y);
    else if (key_code == DOWN)
        move_player(data, data-&gt;now_x, data-&gt;now_y + 1);
    return (0);
}</code></pre>
<p>ㅡ
ㅡ</p>
<h2 id="move_player-함수">move_player 함수</h2>
<pre><code class="language-c">void    move_player(t_data *data, int x, int y)
{
    if (data-&gt;ber_arr[y][x] == &#39;1&#39; || \
            (data-&gt;items &amp;&amp; data-&gt;ber_arr[y][x] == &#39;E&#39;))
        return ;    //벽이거나, 아이템이 남은 상태로 출구에 갈 수 없도록 리턴
    if (!data-&gt;items &amp;&amp; data-&gt;ber_arr[y][x] == &#39;E&#39;)
        good_bye(data);        //아이템을 다 먹고 출구로 가면 게임 종료
    if (data-&gt;ber_arr[y][x] == &#39;C&#39;)
    {
        data-&gt;items--;        //아이템 먹어서 갯수 줄어듦
        data-&gt;ber_arr[y][x] = 0;    //땅을 의미하는 &#39;0&#39;으로 바꾸기
        mlx_put_image_to_window(data-&gt;mlx, data-&gt;win, \
                    data-&gt;player, x * 64, y * 64);    //땅 이미지 넣기
    }
    mlx_put_image_to_window(data-&gt;mlx, data-&gt;win, \
            data-&gt;grass, data-&gt;now_x * 64, data-&gt;now_y * 64);    //원래 있던 곳에 풀 이미지 넣기
    data-&gt;walk++;    //걸음 수 증가
    ft_printf(&quot;%d\n&quot;, data-&gt;walk);    //걸음 수 터미널 출력
    mlx_put_image_to_window(data-&gt;mlx, data-&gt;win, \
                    data-&gt;grass, x * 64, y * 64);    //이동하는 방향 풀 이미지 넣기
    mlx_put_image_to_window(data-&gt;mlx, data-&gt;win, \
                    data-&gt;player, x * 64, y * 64);    //이동된 방향 플레이어 이미지 넣기
    data-&gt;now_x = x;    //현재 위치 조정
    data-&gt;now_y = y;    //현재 위치 조정
}</code></pre>
<p>ㅡ
ㅡ</p>
<h2 id="deal_mouse-함수">deal_mouse 함수</h2>
<pre><code class="language-c">int    deal_mouse(t_data *data)
{
    good_bye(data);
    return (0);
}</code></pre>
<p>ㅡ
ㅡ</p>
<h2 id="good_bye-함수">good_bye 함수</h2>
<pre><code class="language-c">void    good_bye(t_data *data)
{
    int    i;

    i = -1;
    while (data-&gt;ber_arr[++i])
        free(data-&gt;ber_arr[i]);
    free(data-&gt;ber_arr);    //ber_arr free
    mlx_destroy_window(data-&gt;mlx, data-&gt;win); //윈도우 종료
    exit(1);    //exit로 탈출
}</code></pre>
<p>이상 모든 과정과 함수에 대한 설명이 끝났다. 이해가 안되는 부분이 있으면 질문 적어주시면 감사하겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[so_long의 기록들]]></title>
            <link>https://velog.io/@jeong_twelve/solong%EC%9D%98-%EA%B8%B0%EB%A1%9D%EB%93%A4</link>
            <guid>https://velog.io/@jeong_twelve/solong%EC%9D%98-%EA%B8%B0%EB%A1%9D%EB%93%A4</guid>
            <pubDate>Sun, 25 Dec 2022 09:08:40 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="minilibx란">Minilibx란?</h1>
<p>minilibx는 화면에서 무언가를 그리기 위한 기본적인 그래픽 라이브러리이다. 
간단한 창 생성, 그리기 도구, 이미지 기능 및 이벤트 관리 시스템을 제공한다.
C언어 환경에서 사용하기 위해 &lt;mlx.h&gt; 헤더를 추가해야 한다.</p>
</blockquote>
<h4 id="컴파일을-하기-위해서-일련의-처리가-필요하다">컴파일을 하기 위해서 일련의 처리가 필요하다.</h4>
<ul>
<li>설정되어 있는 헤더 디렉토리에서 자동으로 찾는 방법.
  gcc -Lmlx -lmlx -framework OpenGL -framework Appkit -Imlx</li>
<li>라이브러리 경로를 직접 지정하는 방법.
  cc -L/(라이브러리 경로) -Lmlx -lmlx -framework OpenGL -framework Appkit -Imlx<pre><code>만약 해당 경로에 라이브러리가 존재하지 않는다면 내장 라이브러리에서 찾는다.</code></pre></li>
</ul>
<h1 id="예제">예제</h1>
<h3 id="mlx_init----초기화">mlx_init  -&gt; 초기화</h3>
<pre><code>#include &lt;mlx.h&gt;

int main(void)
{
    void    *mlx_ptr;

    mlx_ptr = mlx_init();
    return (0);
}</code></pre><p>mlx.h 파일에 선언되어 있는 mlx_init함수를 활용해서 포인터를 초기화 할 수 있는데, mlx_ptr은 mlx_init() 함수가 리턴하는 mlx 구조체의 주소를 가리키게 된다.</p>
<p>ㅡ
ㅡ
ㅡ
ㅡ</p>
<h3 id="mlx_new_window---화면-띄우기">mlx_new_window  -&gt;화면 띄우기</h3>
<blockquote>
<p>함수 원형</p>
</blockquote>
<pre><code>void *mlx_new_window(void *mlx_ptr, int size_x, int size_y, char *title);
</code></pre><p>#include &lt;mlx.h&gt;</p>
<p>int main()
{
    void    *mlx_ptr;
    void    *win_ptr; // 생성할 윈도우를 가리키는 포인터</p>
<pre><code>mlx_ptr = mlx_init();    
win_ptr = mlx_new_window(mlx_ptr, 500, 500, &quot;Hellow World!&quot;); 
mlx_loop(mlx_ptr); // loop를 돌면서 event를 기다리고, 생성한 윈도우를 Rendering한다. 
return (0);</code></pre><p>}</p>
<pre><code>&gt; ## 함수 인자
size_x는 만들려는 window의 width, size_y는 height를 의미한다. title로 주어지는 문자열은 window의 제목으로 나타난다. mlx_ptr은 mlx_init 함수로부터 반환받은 구조체의 주소이며 mlx_ptr로 지칭되는 연결에 해당하는 window를 만들기 위해 이용된다.

&gt; #### 화면에 빈 모니터를 띄울 수 있다.
&gt; - 만약 초기화 하지 않은 mlx_ptr이 mlx_new_window 함수로 전달된다면 segfault를 발생한다.
&gt; - 이 함수는 창을 생성하는 것 까지만 작동하고 실제로 모니터상에 창을 띄우지 않는다. mlx_loop 함수를 주석처리하면 실행 시 아무것도 보이지 않는다.

ㅡ
ㅡ
ㅡ
ㅡ
### mlx_xpm_file_to_image
&gt;함수 원형</code></pre><p>void * mlx_xpm_file_to_image(void *mlx_ptr, char *filename, int *width, int *height);</p>
<pre><code>
메모리에 새로운 이미지를 생성한다. 이때, new_image는 빈 이미지이고, mlx_xpm_file_to_image는 xpm파일을 메모리에 생성하는 것이다.
&gt;## 함수 인자
- mlx_ptr : 연결 식별자(mlx_init 리턴값)
- filename : image로 만들 xpm 파일 이름
- width, height : xpm 파일의 가로, 세로 길이
## 반환값
이미지 식별자로 null이 아닌 포인터를 반환한다. 실패시 NULL 반환.

ㅡ
ㅡ
ㅡ
ㅡ
### mlx_hook()
&gt;함수 원형</code></pre><p>int    mlx_hook(void <em>win_ptr, int x_event, int x_mask, int (</em>funct)(), void *param);</p>
<pre><code>
mlx_hook 함수를 사용하면 액션에 따른 값을 2번째 매개변수인 x_event로 받을 수가 있다.
즉, x_event에 2를 넣으면 키를 눌렀을 때 동작을 hook함수의 4번째 인자인 함수가 동작하는 그런 식이다.
02: KeyPress
03: KeyRelease
04: ButtonPress
05: ButtonRelease

3번째 인자인 x_mask는 Mac OS X에서 Mask 값이 사용되지 않기 때문에 x_mask 값을 0으로 지정하여 사용하지 않는 인자임을 암시하도록 하여도 내부적으로는 사용하지 않아 동작에는 지장이 없다.

ㅡ
ㅡ
ㅡ
ㅡ
### mlx_loop_hook()
&gt;함수 원형</code></pre><p>int mlx_loop_hook(void <em>mlx_ptr, int (</em>funct_ptr)(), void *param)</p>
<pre><code>아무 이벤트도 일어나지 않을 경우 인자로 받았던 함수가 호출된다.
이벤트 발생 조건 없이 (*funct_ptr)()에 매개변수로 입력된 함수를 무한대로 실행한다. (주의. 진짜 속도가 너무 빨라서 so_long에서 사용할때는 속도를 지연시켜야 한다. 해당 방법은 다른 문서에서 기술 예정)

&gt;## 함수 인자
*mlx_ptr : 윈도우 식별자(mlx_new_window 리턴값)
(*funct_ptr)() : 호출할 함수의 주소(포인터)
*param : 호출한 함수 내에서 필요한 파라미터 데이터의 주소값

ㅡ
ㅡ
ㅡ
ㅡ
### mlx_loop()
&gt;함수 원형</code></pre><p>int mlx_loop(void *mlx_ptr)</p>
<pre><code>
그래픽 시스템은 양방향이다. 한쪽에서는 스크린에 디스플레이할 픽셀, 이미지 등을 명령하고, 한쪽에서는 키보드나 마우스로부터 &quot;이벤트&quot;를 받는다.
이때 이벤트를 수신하는 함수이다. 이벤트를 기다린 다음 이 이벤트와 관련된 사용자 정의 함수를 호출하는 무한 루프이다.

다음 세 가지 이벤트에 다른 기능을 할당할 수 있다.
- 키를 누를 때 (mlx_key_hook)
- 마우스 버튼을 누를 때 (mlx_mouse_hook)
- 창의 일부를 다시 그려야 할 때 (이를 &quot;expose&quot; 이벤트라고 하며 이를 처리하는 것은 프로그램의 작업이다.) (mlx_expose_hook)

mlx_key_hook과 mlx_mouse_hook은 해당 문제에서 필자가 사용하지만 mlx_expose_hook은 사용하지 않으므로 특별히 기술하지 않기로 결정했다.

-&gt; 무한루프이므로 함수 마지막에 쓰여야 한다.

ㅡ
ㅡ
ㅡ
ㅡ
## mlx_destroy_window
&gt;함수 원형</code></pre><p>int mlx_destroy_window(void *mlx_ptr, void *win_ptr)</p>
<p>```</p>
<p>지정된 window를 끈다.</p>
<blockquote>
<p>인자
*mlx_ptr : 연결 식별자(mlx_init 리턴값)
*win_ptr : 윈도우 식별자 (mlx_new_window 리턴값)</p>
</blockquote>
<p>크게 사용되는 mlx 함수들을 모두 기술했다.
mlx_put_image_to_window는 그냥 xpm파일을 넣어주는 함수라 간단해서 다음 문서에서 내가 짠 함수를 하나씩 따라가면서 함께 기술하겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ft_printf의 기록들]]></title>
            <link>https://velog.io/@jeong_twelve/ftprintf%EC%9D%98-%EA%B8%B0%EB%A1%9D%EB%93%A4</link>
            <guid>https://velog.io/@jeong_twelve/ftprintf%EC%9D%98-%EA%B8%B0%EB%A1%9D%EB%93%A4</guid>
            <pubDate>Thu, 01 Dec 2022 11:22:01 GMT</pubDate>
            <description><![CDATA[<p>해당 벨로그 주인장은 보너스를 구현하지 않았다. 나중에 해볼 의향이 있으나 그거 할 시간에 다음 과제를 밀겠다.</p>
<h1 id="1-과제-설명">1. 과제 설명</h1>
<h4 id="printf를-구현하시오">printf를 구현하시오</h4>
<h4 id="ft_printf의-프로토타입은-int-ft_printfconst-char---이어야-합니다">ft_printf의 프로토타입은 int ft_printf(const char , ...); 이어야 합니다.</h4>
<p>The prototype of ft_printf should be int ft_printf(const char *, ...);</p>
<h4 id="당신은-libc의-printf-함수를-다시-구현해야-합니다">당신은 libc의 printf 함수를 다시 구현해야 합니다.</h4>
<p>You have to recode the libc’s printf function</p>
<h4 id="실제-printf-함수처럼-버퍼-관리를-수행해서는-안-됩니다">실제 printf 함수처럼 버퍼 관리를 수행해서는 안 됩니다.</h4>
<p>It must not do the buffer management like the real printf</p>
<h4 id="다음과-같은-서식-지정자들을-구현할-것입니다-cspdiuxx">다음과 같은 서식 지정자들을 구현할 것입니다: cspdiuxX%</h4>
<p>It will manage the following conversions: cspdiuxX%</p>
<h4 id="모든-서식문자에서-0-플래그와-최소-필드-너비의-조합을-어떤-조합도-처리할-것입니다">모든 서식문자에서 0.* 플래그와 최소 필드 너비의 조합을 어떤 조합도 처리할 것입니다.</h4>
<p>It will manage any combination of the following flags: ’-0.*’ and minimum field width with all conversions</p>
<h4 id="실제-printf-함수와-비교될-것입니다">실제 printf 함수와 비교될 것입니다.</h4>
<p>It will be compared with the real printf</p>
<h4 id="라이브러리를-생성할-때에는-ar-명령어를-이용하세요-libtool-명령어는-허용되지-않습니다">라이브러리를 생성할 때에는 ar 명령어를 이용하세요. libtool 명령어는 허용되지 않습니다.</h4>
<p>You must use the command ar to create your librairy, using the command libtool is forbidden.</p>
<p>-&gt; 그냥 %+(cspdiuxX%)의 기능을 잘 구현하시면 됩니다.</p>
<h1 id="2-가변-인수-함수va_list-va_start-var_arg-va_end">2. 가변 인수 함수(va_list, va_start, var_arg, va_end)</h1>
<h4 id="가변-인수는-함수가-선언될-때-매개변수로-고정인수를-받고-으로-받는-나머지-인수들을-말한다">가변 인수는 함수가 선언될 때 매개변수로 고정인수를 받고 ...으로 받는 나머지 인수들을 말한다.</h4>
<p>-&gt; 고정인수는 반드시 하나 이상 있어야한다!</p>
<blockquote>
<h2 id="va_list-ap">va_list ap</h2>
<p>함수로 전달되는 인수들은 스택(stack)이라는 기억 장소에 저장되며 함수는 스택에서 인수를 꺼내 쓴다. 스택에 있는 인수를 읽을 때 포인터 연산을 해야 하는데 현재 읽고 있는 번지를 기억하기 위해 va_list형의 포인터 변수 하나가 필요하다. 변수 이름은 ap로 되어 있는데 ap는 어디까지나 지역 변수일 뿐이므로 이름은 마음대로 정할 수 있되 관습적으로 가변 인수를 다루는 매크로에서는 ap라는 이름을 사용한다. va_list타입은 char *형으로 정의되어 있다. 가변 인수를 읽기위한 포인터 변수를 선언했다고 생각하면 된다.</p>
</blockquote>
<h2 id="va_startap-마지막고정인수">va_start(ap, 마지막고정인수)</h2>
<p>이 명령은 가변 인수를 읽기위한 준비를 하는데 ap 포인터 변수가 첫 번째 가변 인수를 가리키도록 초기화한다. 첫 번째 가변 인수의 번지를 조사하기 위해서 마지막 고정 인수를 전달해주어야 한다. va_start 내부에서는 마지막 고정 인수 다음 번지로 ap를 맞추어 주므로 이후부터 ap 번지를 읽으면 순서대로 가변 인수를 읽을 수 있다.</p>
<h2 id="va_argap인수타입">va_arg(ap,인수타입)</h2>
<p>가변 인수를 실제로 읽는 명령이다. va_start가 ap를 첫 번째 가변 인수 번지로 맞추어 주므로 ap위치에 있는 값을 읽기만 하면 된다. 단, ap번지에 있는 값이 어떤 타입인지를 알아야 하므로 두 번째 인수로 읽고자 하는 값의 타입을 지정해주어야 한다. 에를 들어 ap위치에 있는 정수값을 읽고자 한다면 va_arg(ap,int)를 호출하고 실수값을 읽고자 한다면 va_arg(ap,double)이라고 호출하면 된다. 이 명령은 ap위치에서 타입에 맞는 값을 읽어 리턴해주며 또한 ap를 다음 가변 인수 위치로 옮겨준다. 그래서 va_arg를 반복적으로 호출하면 전달된 가변 인수를 순서대로 읽을 수 있다.
va_arg함수는 매크로 함수이므로 인수타입에 int, double 같은 타입 이름을 인수로 전달할 수 있다. va_arg의 두 번째 인수는 내부적으로 sizeof 연산자와 캐스트 연산자로 전달되기 때문에 타입명이 될 수 있다.</p>
<h2 id="va_endap">va_end(ap)</h2>
<p>이 명령은 가변 인수를 다 읽은 후 뒷정리를 하는데 별다른 동작은 하지 않고 실제로 없어도 상관이 없다. 이 명령이 필요한 이유는 호환성 때문인데 플랫폼에 따라서는 가변 인수를 읽은 후에 뒷처리를 할 필요가 있기 때문이다. intel계열의 CPU에서는 아무 일도 하지 않는다. 하지만 호환성을 위해 관례적으로 넣어주는 것이 좋다.</p>
<h1 id="3-과제-진행ft_print">3. 과제 진행(ft_print)</h1>
<ol>
<li>가변인수를 사용하기 위해 va_list ap;로 ap 선언</li>
<li>printf함수는 출력되는 문자의 길이를 출력해주기 때문에 길이를 세어줄 int    count; 함수 선언 후 0으로 초기화 (생각해보니 0은 초기화 해줄 필요 없을 것 같음)</li>
<li>va_start(ap, str);로 고정인수 str 다음의 가변인수를 가르키도록 지정</li>
<li>count+=ret_count(함수)(str,ap); 카운트 해주는 함수 만듦</li>
<li>va_end(ap);로 정리해줌.</li>
<li>return (count);로 길이를 리턴해준다<h1 id="4-과제-진행2ret_count">4. 과제 진행2(ret_count)</h1>
</li>
<li>int ret_count(const char *save, va_list ap);</li>
<li>str의 인덱스 넘버를 가르킬 int 변수 i 선언 후 0 초기화</li>
<li>길이를 리턴해주기 위해 int 변수 count 선언 후 0 초기화</li>
<li>while(save[i])로 반복 실행.</li>
<li>while문 내부에서 i = find_next(함수)(save, ap, &amp;count, i);로 save의 모든 문자에 대해 % + cisduxX%인지 검사</li>
<li>i++; 후 while문 종료</li>
<li>return (count); 로 길이 리턴<h1 id="5-과제-진행3find_next">5. 과제 진행3(find_next)</h1>
</li>
<li>int fin_next(const char *save, va_list ap, int *count, int i);</li>
<li>if와 else로 분기.
if는 save[i]==% &amp;&amp; save[i + 1] != &#39;\0&#39; 이 조건, else는 *count += ft_putchar(save[i]); 로 출력해주고 ft_putchar가 정상 출력되면 return (1)을 해주기 때문에 count가 1씩 올라감</li>
<li>다시 if문 내부에서 if와 else if 여러개로 분기.
c,s,p,d,u,x,X,%의 조건을 처리한다.
else로 %뒤에 cspduxX%를 제외한 다른 값이 나오면 오류처리를 해주는게 맞으나 기계평가에서 어떻게 될지 모르기 때문에 따로 처리해주지 않고 넘어갔다.</li>
<li>return (i);로 인덱스 번호 리턴</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[GetNextLine 과제 시작]]></title>
            <link>https://velog.io/@jeong_twelve/GetNextLine-%EA%B3%BC%EC%A0%9C-%EC%8B%9C%EC%9E%91</link>
            <guid>https://velog.io/@jeong_twelve/GetNextLine-%EA%B3%BC%EC%A0%9C-%EC%8B%9C%EC%9E%91</guid>
            <pubDate>Mon, 28 Nov 2022 09:10:18 GMT</pubDate>
            <description><![CDATA[<h1 id="0-선행지식">0. 선행지식</h1>
<h3 id="--1-파일-디스크럽터-fd">- 1. 파일 디스크럽터 (fd)</h3>
<ul>
<li><p>운영체제가 만든 파일 또는 소켓의 지칭을 편히 하기 위해 부여된 숫자이다.</p>
</li>
<li><p>기본적으로 파일 디스크럽터는 정수형으로 넘버링되고 0,1,2는 이미 할당이 되어있어 3부터 부여한다.
0 : 표준입력 (Standard Input)
1 : 표준출력 (Standard Output)
2 : 표준에러 (Standard Error)</p>
<h3 id="--2-read-함수">- 2. read() 함수</h3>
<blockquote>
<p>size_t read (int fd, void *buf, size_t bytes)</p>
</blockquote>
</li>
<li><p>bytes 수 만큼 fd를 읽어 buf에 저장한다.</p>
</li>
<li><p>리턴값 : 읽어 온 바이트 수, 실패 시 -1.</p>
<ul>
<li>파일을 끝까지 읽었으면,  다음 번에는 더 이상 읽을 바이트가 없기 때문에 0을 반환.<h3 id="--3-gcc--d-플래그">- 3. gcc -d 플래그</h3>
</li>
</ul>
</li>
<li><p>외부에서 <strong>#define</strong>을 정의한다</p>
</li>
<li><p>이 문제에서 컴파일은 다음과 같이 진행된다.</p>
<blockquote>
<p>$ gcc -Wall -Wextra -Werror -D BUFFER_SIZE=32 get_next_line.c get_next_line_utils.c</p>
</blockquote>
</li>
</ul>
<p>즉, BUFFER_SIZE를 컴파일 할 때 정하게 된다.</p>
<h3 id="--4-static-변수">- 4. static 변수</h3>
<ul>
<li>데이터 영역에 저장되어 프로그램 종료시까지 남아있기 때문에, 다음 line을 읽을 시작 주소를 계속 저장할 수 있도록 backup 버퍼를 static 변수로 선언해야 한다.</li>
</ul>
<h1 id="2-목표">2. 목표</h1>
<ul>
<li>GNL 함수를 loop 안에서 호출하면 fd의 텍스트를 EOF가 올 때 까지 한 번에 한 줄씩 읽을 수 있다.</li>
<li>GNL 함수를 처음 호출했을 때 파일을 끝까지 읽었다 하더라도, 두 번째 호출했을 때는 두 번째 line부터 시작해야한다.</li>
<li>file로부터, redirection으로부터, stdin으로부터 읽었을 때 함수가 제대로 동작해야 한다.</li>
<li>BUFFER_SIZE가 1일 때도, 9999일 때도, 10000000(1000만)일 때도 함수가 제대로 동작해야 한다.</li>
</ul>
<h1 id="3-아이디어">3. 아이디어</h1>
<ol>
<li>파일을 read 할 임시 버퍼를 만든다.(buf)</li>
<li>read한 버퍼를 백업할 static 버퍼를 만든다.(backup)</li>
<li>return 해줄 line 문자열을 선언한다.</li>
<li>buf에 BUFFER_SIZE+1(널 값)만큼 malloc으로 할당해주고</li>
<li>함수(make_line)를 만들어 line에 값을 넣어준다. (매개변수 fd, backup, buf)<ul>
<li>해당 함수 내부에서 개행 문자를 찾으면 해당 backup데이터가 리턴되고 그 값이 line에 들어간다.</li>
</ul>
</li>
<li>free(buf);
 buf = NULL;로 버퍼 메모리를 해제하고 포인터가 가르키는 값을 NULL로 초기화 해준다.</li>
<li>지금 line과 backup 값은 개행을 포함하고 그 다음 문자들도 일부분 가지고 있으므로 line은 개행까지만 가지고 있어야 하고 backup은 개행 다음 부분부터 끝까지를 가지고 있어야 하므로 새로운 함수(make_backup(line))을 이용해 backup에 값을 넣고 포인터 매개변수로 받은 line에 값을 변경해준다.</li>
<li>line값을 리턴해주면 개행을 포함한 한 줄이 출력되고 backup은 줄바꿈 이후의 데이터 값을 가지고 있다.</li>
</ol>
<h3 id="static-변수를-사용해야-하는-이유">static 변수를 사용해야 하는 이유</h3>
<p>버퍼 사이즈가 10이라고 가정하고</p>
<blockquote>
<p>abcd
1234</p>
</blockquote>
<p>를 읽는다고 생각하면 한 번 부르면 abcd를, 한 번 더 부르면 1234를 *line에 넣어줘야 한다.
그런데 버퍼 사이즈가 10이라 한 번에 파일을 끝까지 다 읽어버리기 때문에 처음 함수가 호출 됐을 때 나머지 1234를 따로 저장해둬야 한다. 그래서 따로 저장할 떄 함수가 끝나도 날아가지 않게 하려고 static(정적)변수를 쓰게 된다.</p>
<h1 id="4-주의해야-할-부분들">4. 주의해야 할 부분들</h1>
<h3 id="--1-buffer_size가-0보다-작거나-같거나-fd가-0보다-작은-경우">- 1. BUFFER_SIZE가 0보다 작거나, 같거나 fd가 0보다 작은 경우</h3>
<ul>
<li>return (NULL);로 NULL값을 리턴해주어야 한다.</li>
</ul>
<h3 id="--2-read함수가-0이나--1을-반환하는-경우">- 2. read함수가 0이나 -1을 반환하는 경우</h3>
<p>*<em>파일을 끝까지 다 읽어서 0을 반환 || 빈 파일을 읽어 0을 반환 *</em>
    - line = 0 으로 메모리 해제가 된 상황. 남은 backup을 line에 넣어준다. main에서 다른 파일을 read할 수도 있으니 free(backup) 후 backup = NULL;을 해준다.
-&gt;  해당 설정을 해줘야 main함수에서 getnextline(int fd)함수를 다시 사용할 때 backup이 메모리 오류를 일으키지 않는다.</p>
<h3 id="--3-txt파일에-개행이-하나도-없거나-개행-다음에-바로-0이-있는-경우">- 3. txt파일에 개행이 하나도 없거나 개행 다음에 바로 &#39;\0&#39;이 있는 경우</h3>
<ul>
<li>make_backup 함수에서 <pre><code>int i = 0;
while (line[i] &amp;&amp; line[i] != &#39;\n&#39;)
  i++;
if (line[i] == &#39;\0&#39; || line[i + 1] == &#39;\0&#39;)
  return (NULL);</code></pre>해당 부분에서 line[i] == &#39;\0&#39;은 txt파일 내에 개행이 없을 때의 예외처리이고 line[i + 1] == &#39;\0&#39; 은 개행 다음에 &#39;\0&#39;이 있을 경우의 예외처리이다. 해당 조건들은 line과 backup을 편집할 편집할 필요가 없으므로 NULL을 리턴해주면 된다.</li>
</ul>
<h3 id="--4-이중포인터-backup의-세로-축-크기를-몇으로-정적할당-할-것인가">- 4. 이중포인터 backup의 세로 축 크기를 몇으로 정적할당 할 것인가?</h3>
<p>backup의 세로 축은 fd가 들어간다. *backup이 가리키는 값을 fd에 따라서 따로 관리하고 싶었다.(?) 한 파일이 끝나기 전에 gnl로 다른 파일을 호출할 수도 있으니까.</p>
<p>파일 디스크럽터는 0~OPEN_MAX까지의 값을 가질 수 있으며, OPEN_MAX 값은 플랫폼에 따라 다르다고 한다.</p>
<p>OPEN_MAX 값은 헤더파일에 과제를 제출하기 전 헤더파일에 설정해 주는데, 임의로 값을 넣게되면 재정의 되었다고 오류가 발생한다. OPEN_MAX가 정의되어 있는 디렉토리는 POSIX 디렉토리이며 POSIX는 MAC OS가 따르는 일부 규정이다. 흔히 OPEN_MAX는 OS마다 값이 달라 사용하지 말고 연결 리스트로 구현하라고 하지만 본인은 42서울에서 공부할 때 MAC OS를 통해 개발을 하고 POSIX에 해당 내용이 규정이 되어있어 굳이 다른 OS의 OPEN_MAX값을 고려해야한다고 생각하지 않는다고 디펜스를 했다.(나름 설득력이 있었던 모양이다)</p>
<h3 id="--5-buffer_size의-최대-크기는">- 5. BUFFER_SIZE의 최대 크기는?</h3>
<p>100만까지는 되는데 1000만 부터는 Segmentation fault가 뜬다.
-&gt; 자동변수는 stack 영역에 저장되는데, 보통 스택 사이즈가 윈도우는 1메가, 리눅스는 8메가로 설정되어있다. 만약에 char buf[BUFFER_SIZE + 1]; 라고 선언하고 여기에 스택 사이즈보다 큰 수를 받으면 스택 오버플로우가 생길 수 있다.</p>
<h4 id="스택-오버플로우-피하기">스택 오버플로우 피하기</h4>
<ol>
<li>정적 변수로 선언하여 데이터 영역에 잡는다.</li>
<li>전역 변수로 선언하여 데이터 영역에 잡는다.</li>
<li>malloc 등을 사용, 동적 할당하여 힙 영역에 잡는다.</li>
<li>시스템 설정 스택 영역 사이즈를 늘린다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[FireWall(방화벽),UFW, SSH 설명]]></title>
            <link>https://velog.io/@jeong_twelve/FireWall%EB%B0%A9%ED%99%94%EB%B2%BDUFW-SSH-%EC%84%A4%EB%AA%85</link>
            <guid>https://velog.io/@jeong_twelve/FireWall%EB%B0%A9%ED%99%94%EB%B2%BDUFW-SSH-%EC%84%A4%EB%AA%85</guid>
            <pubDate>Wed, 23 Nov 2022 07:19:53 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="firewall-방화벽">FireWall (방화벽)</h1>
</blockquote>
<ul>
<li><h3 id="보안이-필요한-네트워크-통로를-단일화하여-관리함으로써-외부의-불법침입으로부터-내부의-정보자산을-보호하기-위한-시스템">보안이 필요한 네트워크 통로를 단일화하여 관리함으로써 외부의 불법침입으로부터 내부의 정보자산을 보호하기 위한 시스템.</h3>
</li>
<li><h3 id="사용자-인증-주소변환-감사기록-등이-장점이다">사용자 인증, 주소변환, 감사기록 등이 장점이다.</h3>
</li>
<li><h3 id="바이러스와-같이-맬웨어-형태로-존재하는-위협은-방어할-수-없다">바이러스와 같이 맬웨어 형태로 존재하는 위협은 방어할 수 없다.</h3>
</li>
<li><blockquote>
<p>멜워어란 프로그래밍 가능한 기기, 서비스 또는 네트워크를 손상시키거나 악용하도록 설계된 악성 소프트웨어를 말한다.</p>
</blockquote>
</li>
</ul>
<blockquote>
<h1 id="ufw-">UFW :</h1>
</blockquote>
<h3 id="uncomplicated-firewall의-약자">Uncomplicated FireWall의 약자.</h3>
<h4 id="debian-계열-및-다양한-리눅스-환경에서-작동되는-사용하기-쉬운-방화벽-관리-프로그램이며-간단한-명령어로-작동이-가능하다">Debian 계열 및 다양한 리눅스 환경에서 작동되는 사용하기 쉬운 방화벽 관리 프로그램이며 간단한 명령어로 작동이 가능하다.</h4>
<h1 id="사용-이유">사용 이유</h1>
<h4 id="보안이-필요한-네트워크의-통로를-단일화하여-보안을-강화시키기-위해-특히-ufw의-간단한-사용법이-큰-인기를-끈다">보안이 필요한 네트워크의 통로를 단일화하여 보안을 강화시키기 위해, 특히 UFW의 간단한 사용법이 큰 인기를 끈다.</h4>
<h1 id="자주쓰는-명령어">자주쓰는 명령어</h1>
<ul>
<li><h4 id="sudo-ufw-status-verbose">sudo ufw status verbose</h4>
</li>
<li><blockquote>
<p>ufw의 상태 확인</p>
</blockquote>
</li>
<li><h4 id="sudo-ufw-enable">sudo ufw enable</h4>
</li>
<li><blockquote>
<p>ufw 작동 시작</p>
</blockquote>
</li>
<li><h4 id="sudo-ufw-allow-port">sudo ufw allow &lt;port!&gt;</h4>
</li>
<li><blockquote>
<p>포트 허용</p>
</blockquote>
</li>
<li><h4 id="sudo-ufw-deny-port">sudo ufw deny &lt;port!&gt;</h4>
</li>
<li><blockquote>
<p>포트 불가</p>
</blockquote>
</li>
<li><h4 id="sudo-ufw-status-numbered">sudo ufw status numbered</h4>
</li>
<li><blockquote>
<p>현재 사용하는 규칙 확인</p>
</blockquote>
</li>
<li><h4 id="sudo-ufw-delete-num">sudo ufw delete &lt;num!&gt;</h4>
</li>
<li><blockquote>
<p>원하는 번호의 규칙 삭제</p>
</blockquote>
</li>
<li><h4 id="sudo-ufw-reset">sudo ufw reset</h4>
</li>
<li><blockquote>
<p>리셋</p>
</blockquote>
</li>
</ul>
<blockquote>
<h1 id="ssh-">SSH :</h1>
</blockquote>
<h3 id="secure-shell-protocol의-약자">Secure Shell Protocol의 약자.</h3>
<h4 id="네트워크-프로토콜-중-하나로-컴퓨터와-컴퓨터가-같은-네트워크를-통해-서로-통신을-할-때-보안적으로-안전하게-통신하기-위해-사용하는-프로토콜이다-보안적으로-좋다">네트워크 프로토콜 중 하나로 컴퓨터와 컴퓨터가 같은 네트워크를 통해 서로 통신을 할 때 보안적으로 안전하게 통신하기 위해 사용하는 프로토콜이다. (보안적으로 좋다)</h4>
<h1 id="사용하는-이유-">사용하는 이유 :</h1>
<h4 id="공개키방식의-암호화를-사용한다">공개키방식의 암호화를 사용한다.</h4>
<h4 id="public-key를-통해-메세지를-전송하기-전-암호화를-하고-절대-외부에-노출되어서는-안되는-본인의-컴퓨터-내부에-저장된-private-key로-복호화한다">public key를 통해 메세지를 전송하기 전 암호화를 하고 절대 외부에 노출되어서는 안되는, 본인의 컴퓨터 내부에 저장된 private key로 복호화한다.</h4>
<h4 id="ip-spoofing아이피-위변조-방지">ip spoofing(아이피 위/변조) 방지</h4>
<h1 id="자주쓰는-명령어-1">자주쓰는 명령어</h1>
<ul>
<li><h4 id="sudo-service-ssh-status--sudo-systemcnl-status-ssh">sudo service ssh status / sudo systemcnl status ssh</h4>
</li>
<li><blockquote>
<p>동작 중인지 확인</p>
</blockquote>
</li>
<li><h4 id="sudo-service-ssh-restart--sudo-systemcnl-restart-ssh">sudo service ssh restart / sudo systemcnl restart ssh</h4>
</li>
<li><blockquote>
<p>재시작</p>
</blockquote>
</li>
<li><h4 id="vim-etcsshsshd_config">vim /etc/ssh/sshd_config</h4>
</li>
<li><blockquote>
<p>ssh의 설정을 바꾸는 파일 열기</p>
</blockquote>
</li>
<li><blockquote>
<p>permitrootlogin no로 root유저의 ssh접근 방지</p>
</blockquote>
</li>
<li>ss -tunpl</li>
<li><blockquote>
<p>포트 확인
<img src="https://velog.velcdn.com/images/jeong_twelve/post/059abe2a-70c8-4bc0-af47-04d2f2a041f9/image.png" alt=""></p>
</blockquote>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Partioning (파티셔닝) + LVM]]></title>
            <link>https://velog.io/@jeong_twelve/Partioning-%ED%8C%8C%ED%8B%B0%EC%85%94%EB%8B%9D-LVM</link>
            <guid>https://velog.io/@jeong_twelve/Partioning-%ED%8C%8C%ED%8B%B0%EC%85%94%EB%8B%9D-LVM</guid>
            <pubDate>Wed, 23 Nov 2022 06:55:43 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="partion-정의">Partion 정의</h1>
</blockquote>
<h4 id="디스크의-스토리지-영역을-분리하는-것">디스크의 스토리지 영역을 분리하는 것</h4>
<h4 id="위치와-크기는-테이블에-저장됨">위치와 크기는 테이블에 저장됨</h4>
<h4 id="각-파티션은-운영체제에-논리적으로-독립된-디스크로-인식된다">각 파티션은 운영체제에 논리적으로 독립된 디스크로 인식된다.</h4>
<ul>
<li><h2 id="논리-파티션">논리 파티션</h2>
확장 파티션이 갖는 범위 안에서 생성되는 파티션
데이터를 저장할 수 있지만 운영체제 설치는 불가능
디스크 할당 용량만큼 생성 가능</li>
<li><h2 id="확장-파티션">확장 파티션</h2>
부족한 파티션 영역을 확장시키는 용도로 사용한다
실제 데이터 저장이 불가능하다</li>
</ul>
<h2 id="-">-</h2>
<blockquote>
<h1 id="lvm-">LVM :</h1>
</blockquote>
<h4 id="logical-volume-manager">Logical Volume manager</h4>
<h4 id="--logical-volume을-효율적이고-유연하게-관리하기-위한-커널의-한-부분이자-프로그램이다-여러-디스크-공간-짜투리-공간을-합쳐-하나로-만든-것이다">-&gt; Logical Volume을 효율적이고 유연하게 관리하기 위한 커널의 한 부분이자 프로그램이다. (여러 디스크 공간, 짜투리 공간을 합쳐 하나로 만든 것이다)</h4>
<blockquote>
<h1 id="사용-목적">사용 목적</h1>
</blockquote>
<h4 id="여러-디스크-공간을-합쳐서-하나인-양-사용하기-위해">여러 디스크 공간을 합쳐서 하나인 양 사용하기 위해</h4>
<h4 id="사용하기-애매한-공간의-디스크-파티션짜투리들을-활용하기-위해">사용하기 애매한 공간의 디스크 파티션(짜투리)들을 활용하기 위해</h4>
<h4 id="기존에-사용-중인-디스크의-공간을-확장할-수-있기-때문에">기존에 사용 중인 디스크의 공간을 확장할 수 있기 때문에</h4>
<p>4MB(Physical extend)</p>
<h2 id="--1">-</h2>
<blockquote>
<h1 id="파티셔닝에-자주-쓰이는-명령어">파티셔닝에 자주 쓰이는 명령어</h1>
</blockquote>
<h2 id="fdisk--l-">fdisk -l :</h2>
<h4 id="--현재-디바이스명과-용량-확인">-&gt; 현재 디바이스명과 용량 확인</h4>
<h2 id="df--h-">df -h :</h2>
<h4 id="--파티션의-용량도-함께-파악">-&gt; 파티션의 용량도 함께 파악</h4>
]]></description>
        </item>
        <item>
            <title><![CDATA[CentOS와 Debian의 차이점]]></title>
            <link>https://velog.io/@jeong_twelve/CentOS%EC%99%80-Debian%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90</link>
            <guid>https://velog.io/@jeong_twelve/CentOS%EC%99%80-Debian%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90</guid>
            <pubDate>Wed, 23 Nov 2022 05:16:02 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h2 id="centos-">CentOS :</h2>
</blockquote>
<h4 id="--redhat에서-공개한-rhelred-hat-enterprise-linux을-그대로-가져와-로고만-바꿈">- RedHat에서 공개한 RHEL(Red Hat Enterprise Linux)을 그대로 가져와 로고만 바꿈.</h4>
<h4 id="--대규모-커뮤니티에서-지원">- 대규모 커뮤니티에서 지원.</h4>
<h4 id="--rpm-패키지-형식--yum을-패키지-관리자로-사용">- RPM 패키지 형식 / YUM을 패키지 관리자로 사용</h4>
<h4 id="--centos가-debian보다-데스크톱-응용-프로그램을-지원하는데-약간의-우위를-가지고-있다">- CentOS가 Debian보다 데스크톱 응용 프로그램을 지원하는데 약간의 우위를 가지고 있다.</h4>
<h4 id="--엔터프라이즈급에서-사용한다">- 엔터프라이즈급에서 사용한다.</h4>
<blockquote>
<h2 id="debian">Debian:</h2>
</blockquote>
<h4 id="--deb-패키지-형식--apt를-패키지-관리자로-사용하며-많은-패키지를-가지고-있다">- DEB 패키지 형식 / APT를 패키지 관리자로 사용하며 많은 패키지를 가지고 있다.</h4>
<h4 id="--패키지-설치-및-업그레이드가-간단하다">- 패키지 설치 및 업그레이드가 간단하다.</h4>
<h4 id="--해당-사유들로-개인-사용자들이-많이-쓴다">- 해당 사유들로 개인 사용자들이 많이 쓴다.</h4>
]]></description>
        </item>
        <item>
            <title><![CDATA[born2beroot  과제 virtual box와 debian 초기설정]]></title>
            <link>https://velog.io/@jeong_twelve/born2beroot-%EA%B3%BC%EC%A0%9C-%EC%A7%84%ED%96%89-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@jeong_twelve/born2beroot-%EA%B3%BC%EC%A0%9C-%EC%A7%84%ED%96%89-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 23 Nov 2022 05:02:09 GMT</pubDate>
            <description><![CDATA[<h2 id="과정-하나씩-정리하기">과정 하나씩 정리하기.</h2>
<ol>
<li>debian 이미지 iso파일(광학 디스크의 압축 파일) 다운로드 하기</li>
<li>virtual box 실행 및 새로 만들기 위해 new 클릭</li>
<li>이름과 경로를 백업할 goinfre로 설정</li>
<li>Type : Linux, Version : Debian (64-bit)로 설정.</li>
<li>start 클릭, 잘 안 된다면 goinfre 용량 문제일 가능성이 큼. (자리 옮겨볼 것)</li>
<li>시작할 때 설치한 debian 파일 찾아서 넣고 start 클릭. -&gt; command + c로 크기 조절 가능</li>
<li>첫 검은 화면에서 Graphical install과 Install 중에 Install 선택.(gui 쓰지 말라고 과제에 명시)</li>
<li>언어(english)와 지역 선택(other-asia-south korea). 이후에 나오는 timezone 등을 편하게 하려 하는거라 굳이 안 해도 상관없음.</li>
<li>United States - en_US.UTF-8(UTF-8은 가변 길이 유니코드 설정이다), American English 순차적으로 Enter, Enter. -&gt; 해당 옵션은 키보드 설정인 듯.</li>
<li>인트라아이디+42 로 hostname 설정해주고 Domain name은 빈 칸으로 설정</li>
<li>루트 비밀번호 설정 (최소 10자 이상, 숫자와 대문자 포함, 연속적인 3개 이상의 문자 금지, 인트라 아이디 포함 금지, + 루트 계정 관련 비밀번호 옵션 2개)</li>
<li>user명 intra아이디로 설정</li>
<li>user 비밀번호 설정</li>
<li>partition 설정, 과제에서 암호화된 LVM을 사용하라고 했으니 3번째 항목 선택.</li>
<li>partion할 디스크 선택</li>
<li>파티션 분할을 위해 seperate 선택.</li>
<li>LVM구성 : yes -&gt; 비밀번호 설정</li>
<li>8.1gb 설정</li>
<li>파티션 설정을 최종적으로 보여주는 화면이 뜨면 finish.</li>
<li>disk 변경점 알려주는 화면 나오고 yes.</li>
<li>scan으로 읽어올 cd와 dvd 없으므로 NO.</li>
<li>mirroring할 지역과 사이트 설정 -&gt; 한국, deb.devian.org 선택.</li>
<li>proxy 설정 비움 (사용 안 함).</li>
<li>통계자료 NO로 패스.</li>
<li>운영체제에 필요한 소프트웨어 패키지 선택, 최소한의 설정으로 맨 아래 두 개만 설치하고 넘어감.</li>
<li>GRUB 부트로더 설치 화면 (yes해도 되고 NO해도 됨).</li>
</ol>
<p>-&gt;GRUB 부트로더 : Grand Unified Bootloader의 약자, 부트로더란 리눅스가 부팅되기까지 부팅의 전과정을 진행하는 부팅전문프로그램을 의미한다. 설치해도 상관없지만 요즘은 잘 사용되지 않는 편이다.
27. 부트로더 설치 위치를 선택한다. -&gt; 과제에서 /dev/sda로 설치하라고 명시되어있다.
28. continue를 눌러 설치를 마친다.</p>
<p>비밀번호는 모두 똑같이 해도 상관 없는 것 같다.</p>
]]></description>
        </item>
    </channel>
</rss>