(28)

HTTP Method의 멱등성(Idempotence) 이해하기

멱등성(Idempotency)멱등성이라는 용어부터 살펴보자. 멱등성은 수학에서 유래된 개념으로, 같은 작업을 여러 번 반복해도 결과가 달라지지 않는 성질을 뜻한다. 이 개념은 HTTP Method에서도 중요한 역할을 한다.예를 들어, 같은 GET 요청을 서버에 1번 보내든 10번 보내든, 서버에서 반환되는 데이터는 항상 동일하다. 하지만 POST 요청은 어떨까? 여러 번 실행하면 새로운 데이터가 계속 추가될 수 있다. 바로 이런 차이가 멱등성과 관련이 있다.  HTTP Method의 멱등성위에서 언급한 것처럼, HTTP Method에도 멱등성을 적용할 수 있다. RFC 7231 문서에는 여러 번 동일한 요청을 보냈을 때 서버에 미치는 의도된 영향이 동일한 경우 멱등성을 가진다고 정의되어있다. 아래 표는,..

[알고리즘] 위상 정렬(Topology Sort)

위상 정렬(Topology Sort)  위의 위키피디아 문서를 참조하면, 위상 정렬이란 정점의 선형 순서 지정을 통해 모든 방향 간선 uv에 대해 정점 u가 v보다 앞에 올 수 있게 정렬하기 위해 사용되는 알고리즘으로, 방향성 비순환 그래프 - DAG(Directed Acyclic Graph)에만 적용할 수 있다고 한다.     쉽게 말하자면 순서가 정해져 있는 작업을 차례로 수행해야할 때 순서를 결정해주는 알고리즘이고,더 쉽게 말하자면, 순서를 찾아주는 알고리즘이다.  특징나열한 정렬 순서만 두 가지 경우가 존재하고 추가로 여러 개의 답이 더 존재할 수 있다. 이처럼 위상 정렬은 여러 개의 답이 존재할 수 있다는 특징이 있다. 순회하는 방법이 한 가지보다 많을 수 있기 때문이다. 또한, 위에서 DAG에..

크루스칼(Kruskal) 알고리즘 (with. 백준 1197번 최소 스패닝 트리)

크루스칼 알고리즘 (Kruskal Algorithm)크루스칼 알고리즘(Kruskal Algorithm)이란 최소 신장 트리(Minimum Spanning Tree)를 찾기 위해 사용되는 알고리즘이다. 신장 트리(Spanning Tree)그래프 내의 모든 정점을 포함하는 트리 최소 신장 트리(Minimum Spanning Tree)신장 트리 중에서 사용된 간선들의 가중치 합이 최소인 트리   최소 신장 트리를 찾기 위해 모든 간선을 가중치에 따라 오름차순으로 정렬하는 아이디어를 사용하여 효과적으로 MST를 구할 수 있게 된다.세부적인 구현 방법은 다음과 같다. 1. 모든 간선을 가중치에 따라 오름차순 정렬한다.2. 간선을 하나씩 선택하여 사이클이 생기지 않을 때 연결하여 모든 노드가 연결될 때 까지 반복한..

[알고리즘] LIS (Longest Increasing Subsequence)

LIS(최장 증가 부분 수열)N크기의 배열이 주어졌을때, 배열에서 일부 원소를 조합하여 만든 부분 수열 중, 오름차순의 조건을 만족하면서 길이가 최대인 부분 수열을 말한다. 아래처럼, {3, 6, 2, 1, 7, 8, 5, 4, 9}의 배열이 있다면, LIS는 {3, 6, 7, 8, 9} 이다  DPLIS 문제는 각 요소를 포함하는 증가하는 부분 수열의 최대 길이를 찾는 문제로 이를 효율적으로 해결하기 위해 동적 계획법을 사용한다.단일 배열 내에서, 이전 인덱스의 숫자와 비교하여 증가시켜주면 되기 때문에 DP로 문제를 해결할 수 있다.단일 배열이라는 점만 제외하면 LCS와 유사한 매커니즘이기에, 점화식도 비슷한 방법으로 구현할 수 있다.   [알고리즘] LCS (Longest Common Subseque..

[알고리즘] 벨만-포드(Bellman-Ford) 알고리즘

벨만-포드 알고리즘은 그래프의 최단 경로를 구하는 알고리즘의 하나로, 다익스트라로 해결하지 못하는 음의 간선이 포함된 문제를 효과적으로 해결할 수 있는 알고리즘이다.  [알고리즘] 다익스트라(Dijkstra) 알고리즘다익스트라(Dijkstra) 알고리즘그래프의 최단 경로를 구하는 알고리즘으로 하나의 정점에서 출발하여 최단 거리를 구하는 알고리즘이다. 탐욕법(Greedy)과 동적 계획법을 사용하는 알고리즘으로,mag1c.tistory.com   다익스트라 vs 벨만-포드백준 웜홀 문제의 2번 예시로 그래프를 그려보았다. 우리가 눈으로 보았을 때, 실제 1에서 1까지의 최단경로는 0이 아니라 한 바퀴를 돌았을 때, 무한히 -2의 가중치를 계속해서 더하는 경로이다. 1에서 3의 최단경로 또한 1에서 3까지의 ..

[네트워크] TCP와 UDP (handshake, tcpdump, HTTP/3.0, QUIC)

네트워크를 부분적으로 공부한 후 간결하게 정리하였습니다.개선점들을 알려주신다면 적극 반영하겠습니다.(개발 입문 시 간단히 공부했던 TCP / UDP에 대해 재정리했습니다.)   TCP (Transmission Control Protocol)TCP(전송 제어 프로토콜)은 IP의 핵심 프로토콜 중 하나로, OSI 4계층인 전송 계층에 위치하는 프로토콜이다.  IP 네트워크에서의 데이터 통신은 데이터 전송의 불확실성, 데이터 손실, 라우팅 과정에서의 패킷 손실 등이 있는데, 이는 우편을 받기까지의 과정을 생각해본다면 쉽게 이해할 수 있다.  편지가 우체국에서 분류될 때, 실수로 잘못된 주소로 보내지거나 운송 중 분실될 수 있다. 이는 IP 네트워크에서 패킷이 전송 중에 손실되거나, 네트워크 경로를 이탈하여 수..

[네트워크] IP, 서브넷(서브넷 마스크, 서브네팅), Public IP와 Private IP

네트워크를 부분적으로 공부한 후 간결하게 정리하였습니다.개선점들을 알려주신다면 적극 반영하겠습니다.   IP(Internet Protocol)인터넷을 통해 데이터를 주고받을 때 사용되는 통신규약으로 네트워크 계층에 위치하는 프로토콜이다.인터넷에 연결된 모든 장치들을 식별할 수 있도록 각각의 장비에 부여되는 고유 주소이다.    IPv6는 128비트로 구성된 IP주소로, 8개의 4자리 16진수로 이루어져있으며, 콜론으로 구분된다.1234:0ae3:85a3:0000:0000:2f3a:0370:7334   구조 아이피는 네트워크 ID와 호스트 ID로 구성되어 있으며 위의 IP주소 예시에서의 네트워크 ID와 호스트 ID는 다음과 같다     IP 클래스 네트워크 ID는, 어떤 네트워크인지 식별하며, 호스트 ID..

[네트워크] LAN과 WAN (허브, 스위치, 라우터, CSMA/CD, ARP, 프레임, 패킷, 홉)

네트워크를 부분적으로 공부한 후 간결하게 정리하였습니다.틀린 정보 혹은 보기에 불편한 점을 알려주신다면 적극 반영하겠습니다.   LAN Local Area Network의 이름에서 알 수 있듯이, 소규모 통신망을 말한다.     HUB근거리 통신에서, 컴퓨터 간의 소통을 위한 연결장치  허브의 단점데이터 전송 시 연결된 모든 컴퓨터에 데이터를 전송하며(그림 1)이미 누군가가 통신망을 사용중이라면 충돌 문제(Collision)가 발생한다.(그림 2)     허브의 단점 개선Collision문제를 CSMA/CD 프로토콜을 사용해 해결하고자 했다.CSMA/CD (Carrier Sense Multiple Access/Collision Detection:: 반송파 감지 다중 접속/충돌 감지)쉽게 말해, 데이터 전..

[알고리즘] LCS (Longest Common Subsequence, Longest Common Substring)

LCSLongest Common Subsequence는 최장 공통 부분 문자열로, Substring의 값을 구하는 것이 아니라연속되지 않은 부분 문자열 중 가장 긴 공통 문자열을 찾는 알고리즘이다. 반대로, Longest Common Substring은 비슷하지만 부분 문자열이 아닌, substring이 되는 문자열이다. 예를들어 ABCDEF, BCDFQQ라는 문자열이 주어지면Longest Common Subsequence는 BCDF가 되고Longest Common Substring은 BCD가 된다.  LCS의 길이를 구할 때는 DP(Dynamic Programming)를 통해 메모제이션으로 효율적인 문제 해결이 가능하다.   점화식char[] w1 = word1.toCharArray();char[] w..

HTTP Method의 멱등성(Idempotence) 이해하기

Tech/C.S. 2025. 1. 14. 22:30
728x90
728x90

멱등성(Idempotency)

멱등성이라는 용어부터 살펴보자. 멱등성은 수학에서 유래된 개념으로, 같은 작업을 여러 번 반복해도 결과가 달라지지 않는 성질을 뜻한다. 이 개념은 HTTP Method에서도 중요한 역할을 한다.


예를 들어, 같은 GET 요청을 서버에 1번 보내든 10번 보내든, 서버에서 반환되는 데이터는 항상 동일하다. 하지만 POST 요청은 어떨까? 여러 번 실행하면 새로운 데이터가 계속 추가될 수 있다. 바로 이런 차이가 멱등성과 관련이 있다.

 

 

HTTP Method의 멱등성

위에서 언급한 것처럼, HTTP Method에도 멱등성을 적용할 수 있다. RFC 7231 문서에는 여러 번 동일한 요청을 보냈을 때 서버에 미치는 의도된 영향이 동일한 경우 멱등성을 가진다고 정의되어있다.

 

아래 표는, RFC 7231에서 정의한 HTTP Method의 멱등성을 정리해보았다.

 

Method 멱등성 안전여부 설명
GET 리소스를 조회한다. 여러 번 호출해도 같은 데이터를 반환한다.
HEAD 리소스의 헤더 정보를 조회한다. GET과 비슷하지만 본문 데이터는 반환하지 않는다.
PUT 리소스를 생성하거나 업데이트한다. 같은 데이터를 여러 번 요청해도 결과가 동일하다.
DELETE 리소스를 삭제한다. 같은 요청을 반복해도 추가로 삭제되지 않는다.
POST 리소스를 생성한다. 반복 호출 시 리소스가 중복으로 생성될 수 있다.
PATCH 리소스의 일부를 업데이트한다. 요청에 따라 결과가 달라질 수 있다.
TRACE 요청을 그대로 반환한다. 요청을 여러 번 보내도 동일한 결과를 가져온다.

 

 

 

여러 번 요청이 전송될 수 있는 네트워크 환경에서는 같은 요청이 중복으로 처리되지 않도록 하는 것이 중요하다. 멱등성을 가진 Method는 이런 문제를 방지하여 시스템의 안정성을 높여준다.

 

예를 들어, 결제 시스템에서 같은 요청이 여러 번 실행되어도 금액이 중복으로 차감되지 않도록 해야하며 데이터 삭제 작업에서 이미 삭제된 리소스에 대해 추가로 요청이 들어오더라도 오류가 발생하지 않아야한다.

 

 

 

 

 

리소스와 표현(Representation)

포스팅 전반에 리소스라는 단어가 계속해서 등장하는데 표현(Representation)이라는 것도 있다.

표현이란 요청마다 변할 수 있는 리소스를 전달하는 형태이다.

 

Resource: /posts/123

Representation(JSON):
{
  "id": 123,
  "title": "REST Representation",
  "content": "This explains representation in REST."
}

Representation(XML):
<post>
  <id>123</id>
  <title>REST Representation</title>
  <content>This explains representation in REST.</content>
</post>

Representation(HTML):
<article>
  <h1>REST Representation</h1>
  <p>This explains representation in REST.</p>
</article>

 

 

REST에서는 표현 방식을 클라이언트와 서버가 콘텐츠 협상(Content Negotiation)을 통해 결정하도록 설계되어있다. 우리가 아는 Accept 헤더를 통해 말이다.

 

# Request 
GET /posts/123 HTTP/1.1
Host: example.com
Accept: application/json

# Response
HTTP/1.1 200 OK
Content-Type: application/json
{
  "id": 123,
  "title": "REST Representation",
  "content": "This explains representation in REST."
}

 

 

 

안전한 메소드란?

RFC 7231 문서에서는 읽기 전용 작업인 메서드를 안전한 메서드로 정의한다. 즉 안전한 메서드란 클라이언트가 서버 상태의 변화를 요청하거나 기대하지 않는다. 또한 합리적으로 사용되었을 때, 서버에 손해를 끼치거나 클라이언트의 손실 혹은 과도한 부하를 발생시키지 않아야 한다고 정의한다.

 

이 개념은 네트워크와 애플리케이션에서 클라이언트 요청이 불필요한 영향을 미치지 않도록 보장하기 위해 매우 중요한 개념이다. 예를 들어 GET은 단순 데이터를 조회할 뿐 서버에 영향을 미치지 않으므로 안전한 메서드로 분류된다.

 

 

 

GET

GET은 서버에서 리소스를 조회하기 위한 요청이다. 같은 요청을 여러 번 보내더라도 서버의 데이터는 변경되지 않기 때문에 멱등하다. 또한 GET은 서버의 상태를 변경하지 않으므로 안전한 요청이다.

 

하지만 외부 요인에 의한 리소스가 변경되어, 조회 결과가 멱등성을 보장하지 않는다는 말은 틀렸다. 외부 요인에 의해 변경되는 경우 멱등성 보장의 범위 밖이기 때문이다.

 

# 1. 최초 GET 요청
GET /posts/123
# 응답
{
  "id": 123,
  "title": "Hello World",
  "content": "This is the original post."
}

# 2. PUT 요청으로 게시물 수정
PUT /posts/123
{
  "id": 123,
  "title": "Updated Title",
  "content": "This is the updated post."
}

# 3. 다시 GET 요청
GET /posts/123
# 응답
{
  "id": 123,
  "title": "Updated Title",
  "content": "This is the updated post."
}

 

 

 

위처럼 PUT 메서드인 클라이언트(외부) 에 의해 데이터가 변경되어 GET 요청을 다시 보냈을 때, 데이터가 변경된 것은 고려하지 않는다. 

 

 

 

POST

POST는 멱등하지도 안전하지도 않은 메서드다. 주로 서버 리소스를 생성하는데 사용되므로, 새로운 리소스가 중복으로 생성되고 서버의 상태를 변경하기 때문이다.

 

POST /posts
{  
  "title": "mag1c",
  "content": "HAPPY 2025~"
}

 

 

새로운 posts가 생성되며 같은 요청을 반복하면 동일한 데이터의 posts가 여러 번 생성된다.

 

 

 

PUT

PUT은 리소스를 생성하거나 업데이트한다. 데이터가 있더라도 덮어 씌운다고 생각하면 쉽다. 같은 요청을 여러 번 보내더라도 최종 상태는 동일하기 때문에 멱등하지만, 서버의 데이터를 변경하므로 안전하지는 않다.

 

PUT /posts/123
{  
  "title": "mag1c",
  "content": "HAPPY 2025~"
}

 

 

내부 정책으로 title에 unique를 적용했다고 가정해보자. 업데이트는 이루어지지 않을 것이다. 그렇다면 이는 멱등하지 않을까? 그렇지 않다. 정책에 의해 동작이 달라질 수 있지만, 이것은 HTTP 메서드의 멱등성과 무관한 서버의 설계나 정책이기 때문이다.

 

 

 

 

PATCH

리소스를 수정하기 때문에 안전하지 않다. 그렇다면 PUT처럼 수정하는 메서드인 PATCH는 멱등할까?

우리는 수정 이라는 키워드를 유심히 보아야한다.

 

PATCH /posts/123
{  
  "status": "published"
}

 

 

위처럼 상태를 변경하는 API는 계속해서 pubilshed로 수정하기 때문에 멱등한 것 같다. 하지만 아래 API를 보자.

 

PATCH /posts/123/views
# 최초 상태
{
  "id": 123,
  "title": "mag1c",
  "views": 1
}
# 2회째
{
  "id": 123,
  "title": "mag1c",
  "views": 1
}
# 3회째
{
  "id": 123,
  "title": "mag1c",
  "views": 2
}
	.
	.
	.

 

 

 

같은 값을 계속 추가해나가는 것도 마찬가지로 수정이다. 이러한 추가의 요청에도 PATCH가 사용된다. 요청을 반복할 때마다 조회수가 증가하므로 최종 결과가 요청 횟수에 따라 달라진다. 이러한 이유들 때문에 문서에서도 멱등하지 않다고 서술되어있는 듯 하다.

 

인프런에 영한님의 PATCH 메서드가 멱등이 아닌 이유에 대한 서술도 있으니 참고하면 좋을 것 같다.

 

 

 

 

 

 

DELETE

리소스를 삭제하기 때문에 안전하지 않고, 이미 삭제된 리소스는 영향을 받지 않기 때문에 멱등하다.

DELETE /posts/123

 

 

엑!? DELETE를 중복해서 보내니 404가 생기던데요??? 라는 질문은 위의 글을 제대로 보지 않은 것이다.

리소스가 있는지 없는지 판별하여 선택적으로 에러를 내뱉는 것은 개발자 또는 누군가의 설계의 몫이다. 멱등성은 서버 리소스가 계속해서 변하냐 안변하냐의 리소스 관점에서 생각한다면 쉽게 판단이 가능하다.

 

 

 

 

 

 

 

 

reference.

https://www.rfc-editor.org/rfc/rfc7230

https://www.rfc-editor.org/rfc/rfc7231

https://developer.mozilla.org/ko/docs/Glossary/Idempotent

https://ko.wikipedia.org/wiki/%EB%A9%B1%EB%93%B1%EB%B2%95%EC%B9%99

https://restfulapi.net/

https://www.lonti.com/blog/the-key-ingredients-of-restful-apis-resources-representations-and-statelessness

https://stackoverflow.com/questions/16273390/different-restful-representations-of-the-same-resource

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[알고리즘] 위상 정렬(Topology Sort)

Tech/C.S. 2024. 7. 22. 22:42
728x90
728x90

 

위상 정렬(Topology Sort)

위키피디아: Topological sorting

 

 

위의 위키피디아 문서를 참조하면, 위상 정렬이란 정점의 선형 순서 지정을 통해 모든 방향 간선 uv에 대해 정점 u가 v보다 앞에 올 수 있게 정렬하기 위해 사용되는 알고리즘으로, 방향성 비순환 그래프 - DAG(Directed Acyclic Graph)에만 적용할 수 있다고 한다.

 

 

 

 

 

쉽게 말하자면 순서가 정해져 있는 작업을 차례로 수행해야할 때 순서를 결정해주는 알고리즘이고,

더 쉽게 말하자면, 순서를 찾아주는 알고리즘이다.

 

 

특징

나열한 정렬 순서만 두 가지 경우가 존재하고 추가로 여러 개의 답이 더 존재할 수 있다. 이처럼 위상 정렬은 여러 개의 답이 존재할 수 있다는 특징이 있다. 순회하는 방법이 한 가지보다 많을 수 있기 때문이다.

 

또한, 위에서 DAG에만 적용할 수 있다고 했다. 이 의미는, 시작점이 반드시 존재하는 사이클이 없는 그래프를 뜻한다. 즉 특정 정점 하나 이상은 반드시 가리키는 대상이 되어서는 안된다. 다시말해 진입 차수가 0이어야만 한다.

 

 

동작 과정

위상 정렬은 다음과 같은 과정을 통해 위상 정렬 가능 여부와, 위상 정렬의 결과를 반환한다.

 

1. 진입 차수가 0인 정점을 스택 / 큐에 삽입한다.

2. 원소를 스택 / 큐에서 꺼내 연결된 모든 간선을 제거한다.

 

1~2번의 과정을 반복하면서, 위상 정렬을 진행한다.

 

정점의 개수만큼 반복이 올바르게 동작한다면 위상 정렬이 가능한 것이며 올바른 위상 정렬 결과를 반환하게 된다. 반대로 모든 정점을 순회하지 못한다면 위상 정렬을 올바르게 수행하지 못한 것이다. 올바른 그래프 구조라고 했을 때, 사이클이 발생한다는 것이다.

 

 

필자는 P.S. 문제에서 bfs와 더불어 위상 정렬이 필요한 경우가 많았기 때문에 자연스레 큐로 구현한다.

위의 출근 그래프를 가지고 위상 정렬을 수행해보자.

 

 

 

이 때, 8번 그림과 같이 5번 정점의 진입차수는 1이기 때문에, 큐에 삽입하지 않는다.

 

 

 

 

 

 

 

 

 

 

 

코드(Java)

원리도 알았고, 동작 과정도 그림으로 표현해봤으니 코드로 작성해보자.

public class Main {
	public static void main(String[] args) {
    	int TOTAL_NODE = 10;
        
        //진입 차수 배열
    	int[] degree = new int[TOTAL_NODE + 1];
        //그래프 정보
        List<List<Integer>> graph = new ArrayList<>();
        for (int i = 0; i <= TOTAL_NODE; i ++) {
        	graph.add(new ArrayList<>());
        }
        
        graph.get(1).add(2);
        graph.get(1).add(7);
        graph.get(2).add(3);
        graph.get(2).add(4);
        graph.get(3).add(5);
        graph.get(4).add(5);
        graph.get(5).add(6);
        graph.get(6).add(9);
        graph.get(7).add(8);
        graph.get(8).add(9);
        graph.get(9).add(10);
        
        degree[2] = 1;
        degree[3] = 1;
        degree[4] = 1;
        degree[5] = 2;
        degree[6] = 1;
        degree[7] = 1;
        degree[8] = 1;
        degree[9] = 2;
        degree[10] = 1;
        
        topologySort(TOTAL_NODE, degree, graph);
    }
    
    public static void topologySort(int N, int[] degree, List<List<Integer>> graph) {
    	Queue<Integer> que = new LinkedList<>();
        int pollCnt = 0;
        
        //진입차수가 0인 정점을 큐에 삽입한다.
        for (int i = 1; i <= N; i ++) {
        	if (degree[i] == 0) que.offer(i);
        }
        
        while(!que.isEmpty()) {
        	//큐에서 원소를 뺀다.
        	int V = que.poll();
            pollCnt++;
           
            //간선을 제거하고 진입차수가 0인 정점을 큐에 삽입한다.
            for (int next: graph.get(V)) {
            	if (--degree[next] == 0) que.offer(next);
            }
        }
        
        //정점의 개수보다 큐에 삽입된 원소가 작다면, 사이클이 발생한 것이다.
        if (pollCnt < N) {
        	System.out.println("CYCLE");
        }
    }
}

 

 

 

 

 

참조

https://en.wikipedia.org/wiki/Topological_sorting

 

Topological sorting - Wikipedia

From Wikipedia, the free encyclopedia Node ordering for directed acyclic graphs In computer science, a topological sort or topological ordering of a directed graph is a linear ordering of its vertices such that for every directed edge (u,v) from vertex u t

en.wikipedia.org

 

https://www.geeksforgeeks.org/topological-sorting/

 

Topological Sorting - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

크루스칼(Kruskal) 알고리즘 (with. 백준 1197번 최소 스패닝 트리)

Tech/C.S. 2024. 7. 16. 16:15
728x90
728x90

 

크루스칼 알고리즘 (Kruskal Algorithm)

크루스칼 알고리즘(Kruskal Algorithm)이란 최소 신장 트리(Minimum Spanning Tree)를 찾기 위해 사용되는 알고리즘이다.

 

신장 트리(Spanning Tree)
그래프 내의 모든 정점을 포함하는 트리

 

최소 신장 트리(Minimum Spanning Tree)
신장 트리 중에서 사용된 간선들의 가중치 합이 최소인 트리

 

 

 

최소 신장 트리를 찾기 위해 모든 간선을 가중치에 따라 오름차순으로 정렬하는 아이디어를 사용하여 효과적으로 MST를 구할 수 있게 된다.

세부적인 구현 방법은 다음과 같다.

 

1. 모든 간선을 가중치에 따라 오름차순 정렬한다.

2. 간선을 하나씩 선택하여 사이클이 생기지 않을 때 연결하여 모든 노드가 연결될 때 까지 반복한다.

 

여기서, 사이클의 생성 여부와 노드의 연결은 Union-Find를 통해 수행된다.

사이클의 생성 여부를 Find를 통해 확인하여 연결이 가능한 상태라면 Union을 수행하는 것이다.

 

 

 

예제를 통한 이해

이해를 돕기 위해 실제 P.S. 예제인 백준의 최소 스패닝 트리 문제로 차근차근 알아가보자.

 

 

 

 

입력 첫 줄에 정점(Vertex)과 간선(Edge)이 주어지고, 간선에 대한 정보들이 주어진다.

각 간선에 대한 정보에는 A와 B의 정점이 가중치가 C인 간선으로 연결되어 있다고 한다.

 

예제 데이터는 위키 백과의 크루스칼 알고리즘 글에서 발췌해서 사용했다.

 

출처: 위키백과 크루스칼 알고리즘

 

위 그래프의 간선들의 정보를 저장하면 다음과 같다. (편의상 A = 1, B = 2와 같이 표현했다.)

 



 

모든 간선의 정보를, 간선을 기준으로 오름차순 정렬하여 하나하나 연결하면 된다.

최소 가중치에 대한 보장은, 이미 오름차순 정렬을 통해 연결되는 간선이 최소 가중치라고 보장받을 수 있게 된다.

또한 위에서 말한 것 처럼 Union-Find를 사용하여 사이클의 여부를 파악하고 연결을 수행하면 되겠다.

 

하나하나 연결해보자.

 

 

 

이제 다음 상황인 2와 5의 연결에서 2의 부모 노드는 1이고 5의 부모 노드는 3이다.

부모 노드가 다름은 곧 연결이 되지 않았음을 의미하기 때문에 Union을 진행한다.

Union 과정에서 5번 노드의 부모 노드가 바뀌는 것이 아니라, 각 노드의 부모 노드인 1, 3번 노드의 연결을 내부적으로 수행하게 된다.

 

위 그림처럼 5번 노드의 부모 노드가 변경되는 것이 아닌, 3번 노드의 부모 노드가 변경된다.

 

 

 

이제 2번 노드와 3번 노드의 차례인데, 이미 같은 부모 노드를 가지고 있다.

위에서 언급한 사이클이라는 것이 바로 이런 경우를 말하는데, 같은 부모 노드를 가질 경우 사이클이 존재한다고 판단한다.

즉, 최소 신장 트리를 구해야 하기 때문에 굳이 추가로 연결하지 않는다.

 

 

 

 

5번과 6번 노드를 확인할 때도 마찬가지다.

5번 노드의 부모 노드를 따라가보면, 5 -> 3 -> 1번 노드가 나오고, 6번 노드 또한 1번 노드이다.

재귀적으로 부모 노드를 탐색하여 결국 5번 노드의 부모 노드는 1번 노드가 되고 부모 노드가 같기 때문에 Union은 진행하지 않는다.

 

 

 

 

 

이와 같은 방식으로, 계속해서 사이클 여부를 판단하여 연결을 진행해준다.

 

 

 

 

시간 복잡도

Union-Find 알고리즘의 시간 복잡도가 상수이기 때문에, 정렬을 수행하는 데 걸리는 시간이 곧 크루스칼 알고리즘의 시간복잡도가 된다.

일반적으로, 정점과 간선이 주어질 때 O(ElogV)의 시간복잡도를 가진다.

 

 

 

 

구현 코드(with Java, 백준 1197번)

위의 백준 1197번을 한 번 풀어보자.

위에서 언급했던 것 처럼, 구현 방법은 정렬 -> 사이클 파악 여부에 따른 연결로 진행된다.

 

 

입력받은 정보를 바탕으로 간선의 가중치를 기준으로 오름차 정렬한다.

int[][] graph = new int[E][3];

for (int i = 0; i < E; i++) {
    StringTokenizer st = new StringTokenizer(br.readLine());
    int v1 = Integer.parseInt(st.nextToken());
    int v2 = Integer.parseInt(st.nextToken());
    int edge = Integer.parseInt(st.nextToken());
    graph[i][0] = v1;
    graph[i][1] = v2;
    graph[i][2] = edge;
}

Arrays.sort(graph, (o1, o2) -> o1[2] - o2[2]);

 

 

 

사이클이 존재하지 않는 경우(부모 노드가 서로 다를 경우) 연결한다.

int mstCost = 0;

for (int i = 0; i < E; i ++)  {
    int v1 = graph[i][0];
    int v2 = graph[i][1];
    if (find(parent, v1) != find(parent, v2)) {
        union(parent, v1, v2);
        mstCost += graph[i][2];
    }
}

 

 

 

전체 코드는 아래와 같다.

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int V = Integer.parseInt(st.nextToken());
        int E = Integer.parseInt(st.nextToken());

        int[][] graph = new int[E][3];

        for (int i = 0; i < E; i++) {
            st = new StringTokenizer(br.readLine());
            int v1 = Integer.parseInt(st.nextToken());
            int v2 = Integer.parseInt(st.nextToken());
            int edge = Integer.parseInt(st.nextToken());
            graph[i][0] = v1;
            graph[i][1] = v2;
            graph[i][2] = edge;
        }
        
        int[] parent = new int[V + 1];

        for (int i = 1; i <= V; i ++) {
            parent[i] = i;
        }
        
        Arrays.sort(graph, (o1, o2) -> o1[2] - o2[2]);
        int mstCost = 0;

        for (int i = 0; i < E; i ++)  {
            int v1 = graph[i][0];
            int v2 = graph[i][1];
            if (find(parent, v1) != find(parent, v2)) {
                union(parent, v1, v2);
                mstCost += graph[i][2];
            }
        }

        bw.write(String.valueOf(mstCost));
        bw.flush();
        bw.close();
    }

    private static int find(int[] parent, int x) {
        if (parent[x] == x) {
            return x;
        }

        return parent[x] = find(parent, parent[x]);
    }

    private static void union(int[] parent, int x, int y) {
        x = find(parent, x);
        y = find(parent, y);

        if (x == y) return;
        
        if (x < y) {
            parent[y] = x;
        }
        else parent[x] = y;
    }
}

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[알고리즘] LIS (Longest Increasing Subsequence)

Tech/C.S. 2024. 6. 21. 13:32
728x90
728x90

LIS(최장 증가 부분 수열)

N크기의 배열이 주어졌을때, 배열에서 일부 원소를 조합하여 만든 부분 수열 중, 오름차순의 조건을 만족하면서 길이가 최대인 부분 수열을 말한다.

 

아래처럼, {3, 6, 2, 1, 7, 8, 5, 4, 9}의 배열이 있다면, LIS는 {3, 6, 7, 8, 9} 이다

 

 

DP

LIS 문제는 각 요소를 포함하는 증가하는 부분 수열의 최대 길이를 찾는 문제로 이를 효율적으로 해결하기 위해 동적 계획법을 사용한다.

단일 배열 내에서, 이전 인덱스의 숫자와 비교하여 증가시켜주면 되기 때문에 DP로 문제를 해결할 수 있다.

단일 배열이라는 점만 제외하면 LCS와 유사한 매커니즘이기에, 점화식도 비슷한 방법으로 구현할 수 있다.

 

 

 

[알고리즘] LCS (Longest Common Subsequence, Longest Common Substring)

LCSLongest Common Subsequence는 최장 공통 부분 문자열로, Substring의 값을 구하는 것이 아니라연속되지 않은 부분 문자열 중 가장 긴 공통 문자열을 찾는 알고리즘이다. 반대로, Longest Common Substring은 비

mag1c.tistory.com

 

 

점화식

for (int i = 0; i < N; i ++) {
    dp[i] = 1;

    for(int j = 0; j < i; j ++) {
        if (line[i][1] > line[j][1]) {
            dp[i] = Math.max(dp[j] + 1, dp[i]);
        }
    }
}

 

위의 코드를 실행하면 dp배열이 각 요소를 포함하는 최장 증가 부분 수열의 길이를 저장하게 되며, 이를통해 최종 LIS를 구할 수 있다.

 

 

 

점화식에서 알 수 있듯이, 시간 복잡도는 O(N^2)로,  시간복잡도를 고려해야하는 문제에서는 시간 초과가 발생할 수 있다.

(해당 문제는 백준의 전깃줄-2 이다.)

 

 

 


 

 

이진 탐색

LIS를 구할 때, O(N logN)의 시간 복잡도를 가진 이진 탐색으로 구할 수 있다.


LIS는 단일 시퀀스 내에서 증가하는 부분 수열을 찾는 문제이기 때문에 이진 탐색으로 해결할 수 있지만
LCS는 두 시퀀스 간의(=두 문자열의 공통부분) 공통 부분을 찾는다. 두 시퀀스를 모두 비교해야하기 때문에 이진 탐색으로는 효율적으로 해결이 불가능하다.

 

 

기본적인 이진 탐색은, 정렬된 배열에서 특정한 값이 배열 내에 어디에 위치하는지를 계속 반으로 나눠서 위치를 특정할 수 있다.

 

 

[알고리즘] 이진탐색(이분탐색) - Binary Search

Binary Search 정렬된 데이터 집합을 이분화 하면서 탐색하는 방법 정렬되어 있어야 한다 보통 세 개의 변수를 지정해 두고 (ex : left, mid, right) 찾고자 하는 값, 즉 mid의 값이 찾아낸 값보다 크면 mid는

mag1c.tistory.com

 

 

LIS에서의 이진 탐색은, 정렬된 배열을 사용하지 않고, LIS를 유지하면서 각 요소의 삽입 위치를 이진 탐색을 통해 업데이트한다.

 

List<Integer> lis = new ArrayList<Intger>;
for (int i = 0; i < N; i++) {
	binarySearch(lis, arr[i]);
}

private static void binarySearch(List<Integer> lis, int current) {
    int start = 0;
    int end = lis.size();

    //current가 들어갈 위치를 찾는다.
    while(start < end) {
        int mid = (start + end) / 2;
        if (lis.get(mid) >= current) {
            end = mid;
        }
        else start = mid + 1;
    }

    //업데이트
    if (start >= lis.size()) lis.add(current);
    else lis.set(start, current);
}

 

 

하지만 이를 출력해보면 {1, 4, 7, 8, 9}를 얻을 수 있는데 이는, 아래와 같은 동작을 거쳐 해당 원소들의 위치를 찾아 강제로 변경하기 때문이다.

 

 

 

 

해당 원소들의 실제 인덱스를 알아야겠구나!!!! 라고 생각이 들었다면, 바로 구현해보자.

 


실제 인덱스를 알아야 하는 이유는 위의 경우처럼 리스트에는 최장 부분 수열의 길이만 확정적으로 알 수 있고
{1, 4, 7, 8, 9}와 같이 전혀 다른 (LIS가 아닌) 부분 수열이 올 수도 있기 때문이다.

 

 

//arr의 인덱스의 숫자들이 들어가는 위치를 저장
int[] realIdx = new int[N];

List<Integer> lis = new ArrayList<>();
for (int i = 0; i < N; i++) {
	realIdx[i] = binarySearch(lis, arr[i]);
}

private static int binarySearch(List<Integer> lis, int current) {
    int start = 0;
    int end = lis.size();

    //current가 들어갈 위치를 찾는다.
    while(start < end) {
        int mid = (start + end) / 2;
        if (lis.get(mid) >= current) {
            end = mid;
        }
        else start = mid + 1;
    }

    //업데이트
    if (start >= lis.size()) lis.add(current);
    else lis.set(start, current);
    
    return start;
}

 

 

 

이제 리스트와, idx배열을 역순 추척하여 해당 요소가 LIS의 일부인지 확인하고, 해당 요소를 리스트에서 제거하는 방식으로 추적할 수 있다. LIS의 각 요소는 이전 요소보다 커야하기 때문에, 역순 추적을 하면 올바른 순서로 추적할 수 있다.

 

StringBuilder sb = new StringBuilder();

int lisLen = lis.size();
for (int i = N - 1, l = lisLen - 1; i >= 0 && l >= 0; i--) {
    if (realIdx[i] == l) {
        sb.append(arr[i] + " ");
        l--;
    }
}

System.out.println(sb.reverse().toString().trim());
//3 6 7 8 9

 

 

 

 


 

참조

 

Longest Increasing Subsequence (LIS) - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

알고리즘 - 최장 증가 부분 수열(LIS) 알고리즘

컴퓨터/IT/알고리즘 정리 블로그

chanhuiseok.github.io

 

 

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[알고리즘] 벨만-포드(Bellman-Ford) 알고리즘

Tech/C.S. 2024. 6. 14. 15:16
728x90
728x90

 

 

벨만-포드 알고리즘은 그래프의 최단 경로를 구하는 알고리즘의 하나로, 다익스트라로 해결하지 못하는 음의 간선이 포함된 문제를 효과적으로 해결할 수 있는 알고리즘이다.

 

 

[알고리즘] 다익스트라(Dijkstra) 알고리즘

다익스트라(Dijkstra) 알고리즘그래프의 최단 경로를 구하는 알고리즘으로 하나의 정점에서 출발하여 최단 거리를 구하는 알고리즘이다. 탐욕법(Greedy)과 동적 계획법을 사용하는 알고리즘으로,

mag1c.tistory.com

 

 

 

다익스트라 vs 벨만-포드

백준 웜홀 문제의 2번 예시로 그래프를 그려보았다.

 

우리가 눈으로 보았을 때, 실제 1에서 1까지의 최단경로는 0이 아니라 한 바퀴를 돌았을 때, 무한히 -2의 가중치를 계속해서 더하는 경로이다. 1에서 3의 최단경로 또한 1에서 3까지의 최단경로는 0이지만, 1에서 1까지의 최단경로가 무한한 음의 가중치를 가지기 때문에 3또한 영향을 받아 무한히 감소하는 음수 사이클을 가지게 된다. 이는 곧 음의 무한대를 의미하며 코드 실행 시 무한히 동작할 것 같은 기대(?)를 갖게 한다.

 

실제로 다익스트라를 구현하여 이 문제를 해결하려고 하면 어떻게 될까?

 

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

public class Main {

    private static class Node implements Comparable<Node> {
        int node; int cost;

        private Node(int node, int cost) {
            this.node = node;
            this.cost = cost;
        }

        @Override
        public int compareTo(Node o) {
            return cost - o.cost;
        }
    }
    private static ArrayList<ArrayList<Node>> nodeList = new ArrayList<>();
    private static int[] dist;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int N = Integer.parseInt(st.nextToken());
        int M = Integer.parseInt(st.nextToken());

        for (int i = 0; i <= N; i ++) {
            nodeList.add(new ArrayList<>());
        }

        for (int i = 0; i < M; i ++) {
            st = new StringTokenizer(br.readLine());
            int A = Integer.parseInt(st.nextToken());
            int B = Integer.parseInt(st.nextToken());
            int C = Integer.parseInt(st.nextToken());

            nodeList.get(A).add(new Node(B, C));
        }

        for (int i = 1; i < nodeList.size(); i ++) {
            for (Node node: nodeList.get(i)) {
                System.out.println(i + " → " + node.node + "(거리 : " + node.cost + ")");
            }
        }

        dist = new int[N + 1];
        Arrays.fill(dist, Integer.MAX_VALUE);
        dijkstra(1);

        for (int i = 1; i <= N; i ++) {
            System.out.println("1에서 " + i + "까지의 거리 : " + dist[i]);
        }
    }

    private static void dijkstra(int start) {
        PriorityQueue<Node> q = new PriorityQueue<>();
        q.offer(new Node(start, 0));
        dist[start] = 0;

        while(!q.isEmpty()) {
            Node cur = q.poll();
            System.out.println("현재 노드: " + cur.node + ", 가중치: " + cur.cost);

            for (Node node: nodeList.get(start)) {
                if (dist[node.node] > dist[cur.node] + node.cost) {
                    dist[node.node] = dist[cur.node] + node.cost;
                    q.offer(new Node(node.node, node.cost));
                    System.out.println("노드 " + node.node + "까지의 가중치 갱신(가중치: " + dist[node.node] + " )");
                }
            }
        }
    }
}

 

 

 

실행이 끝나지 않고 계속 무한사이클을 돌 줄 알았지만, 1에서 1까지의 거리는 0, 1에서 3까지는 3이라는 결과를 보여주었다.

 

이처럼, 최단 경로가 무한히 줄어들 때, 다익스트라 알고리즘으로는 원하는 문제를 해결할 수 없다. 애초에 최단거리를 찾을 수 없기 때문이다. 또한 위의 코드 결과에서 알 수 있듯이 다익스트라 알고리즘으로는 도출된 답이 틀렸는지, 맞았는지 알 수도 없다.

 

 

 

벨만-포드

벨만-포드 알고리즘은 다익스트라 알고리즘 처럼 최단 경로를 구하는 데 사용되지만, 음의 가중치가 있는 간선이 포함된 그래프에서도 올바르게 동작하는 것을 보장할 수 있다.

 

음수 사이클을 감지하여 최단 경로가 무한히 줄어드는 경우를 알 수 있기 때문에, 음의 가중치가 있는 경우에는 벨만-포드 알고리즘을 사용하는 것이 적합하다.

 

벨만-포드 알고리즘의 구현 과정은 N - 1번의 릴렉세이션을 수행한 후, 한번 더 릴렉세이션을 수행하여 음의 가중치가 있는지 확인한다. N - 1번의 릴렉세이션을 수행하는 이유는, 최단 경로가 최대 N - 1개의 간선을 가질 수 있기 때문이다. 모든 간선을 통과하는 경우가 최단 경로일 경우가 존재할 수 있다는 의미이다.

 

 


릴렉세이션(Relaxation)
간선을 통해 더 짧은 경로를 발견하면 해당 경로로 거리를 업데이트 하는 과정.

 

 

벨만-포드 알고리즘은 다익스트라 알고리즘과 달리, 모든 간선을 우선적으로 사용하지 않아도 된다. N - 1번 모든 노드를 거쳐 탐색하기 때문에 간선을 반복적으로 릴렉세이션하지 않아도 된다.

 

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.StringTokenizer;

public class Main {

    private static class Edge {
        int from, to, cost;

        private Edge(int from, int to, int cost) {
            this.from = from;
            this.to = to;
            this.cost = cost;
        }
    }
    
    private static ArrayList<Edge> edgeList = new ArrayList<>();
    private static int[] dist;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int N = Integer.parseInt(st.nextToken());
        int M = Integer.parseInt(st.nextToken());

        for (int i = 0; i < M; i++) {
            st = new StringTokenizer(br.readLine());
            int A = Integer.parseInt(st.nextToken());
            int B = Integer.parseInt(st.nextToken());
            int C = Integer.parseInt(st.nextToken());

            edgeList.add(new Edge(A, B, C));
        }

        for (Edge edge: edgeList) {
            System.out.println(edge.from + " → " + edge.to + "(거리 : " + edge.cost + ")");
        }

        dist = new int[N + 1];
        Arrays.fill(dist, Integer.MAX_VALUE);
        boolean hasNegativeCycle = bellmanFord(1, N);

        if (hasNegativeCycle) {
            System.out.println("음수 사이클 존재");
        } else {
            for (int i = 1; i <= N; i++) {
                System.out.println("1에서 " + i + "까지의 거리 : " + (dist[i] == Integer.MAX_VALUE ? "INF" : dist[i]));
            }
        }
    }

    private static boolean bellmanFord(int start, int N) {
        dist[start] = 0;

        // N-1번의 릴렉세이션(relaxation) 수행
        for (int i = 1; i < N; i++) {
            for (Edge edge: edgeList) {
                if (dist[edge.from] != Integer.MAX_VALUE && dist[edge.to] > dist[edge.from] + edge.cost) {
                    dist[edge.to] = dist[edge.from] + edge.cost;
                }
            }
        }

        // 추가 릴렉세이션으로 음수 사이클 검증
        for (Edge edge: edgeList) {
            if (dist[edge.from] != Integer.MAX_VALUE && dist[edge.to] > dist[edge.from] + edge.cost) {
                return true; // 음수 사이클 존재
            }
        }

        return false; // 음수 사이클 존재하지 않음
    }
}

 

 

 

 

 

관련 문제

해당 문제를 풀어보면, 벨만-포드 알고리즘 구현의 기초를 다질 수 있을 것 같다.

 

https://www.acmicpc.net/problem/11657

https://www.acmicpc.net/problem/1865

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[네트워크] TCP와 UDP (handshake, tcpdump, HTTP/3.0, QUIC)

Tech/C.S. 2024. 6. 10. 12:11
728x90
728x90

 


네트워크를 부분적으로 공부한 후 간결하게 정리하였습니다.

개선점들을 알려주신다면 적극 반영하겠습니다.

(개발 입문 시 간단히 공부했던 TCP / UDP에 대해 재정리했습니다.)

 


 

 

TCP (Transmission Control Protocol)

TCP(전송 제어 프로토콜)은 IP의 핵심 프로토콜 중 하나로, OSI 4계층인 전송 계층에 위치하는 프로토콜이다.

 

 

IP 네트워크에서의 데이터 통신은 데이터 전송의 불확실성, 데이터 손실, 라우팅 과정에서의 패킷 손실 등이 있는데, 이는 우편을 받기까지의 과정을 생각해본다면 쉽게 이해할 수 있다.

 

 

편지가 우체국에서 분류될 때, 실수로 잘못된 주소로 보내지거나 운송 중 분실될 수 있다. 이는 IP 네트워크에서 패킷이 전송 중에 손실되거나, 네트워크 경로를 이탈하여 수신자에게 도달하지 않는 것과 같다.

 

또한 편지가 예상보다 늦게 도착하는 것처럼, IP 네트워크에서도 패킷이 중간의 여러 노드들을 거치며 지연될 수 있다.

 

이와 같이, IP 네트워크에서의 데이터 전송의 불확실성과 손실은 우편 시스템에서 편지를 보내는 과정에서 발생할 수 있는 분실, 지연, 손상의 문제와 유사하다. 이러한 이유로 TCP와 같은 프로토콜이 등장하게 되었다.

 

 


 

 

 

TCP는 이러한 IP의 단점을 보완해 패킷 전송을 제어하여 신뢰성을 보장할 수 있는 프로토콜이다.

IP 위에 TCP를 얹어 신뢰성을 보장할 수 있게 되었다고 보면 되는데 이를 풀어서 얘기하면 다음과 같다.

 

목적지에 도달하게끔 하는 IP위에 TCP를 얹어 목적지에 올바르게 도착했는지, 원활하게 소통이 완료되었는지 등을 확인하고, 데이터가 제대로 전달이 되지 않았다면 다시 요청을 보낸다. 이러한 메커니즘을 통해 데이터 전송의 신뢰성을 보장한다.

 

 

특징

1. 연결형 서비스로 가상 회선 방식을 제공(Handshake, Connection-Oriented) 하여 높은 신뢰성을 보장한다.

  • TCP는 데이터를 보내기 전 수신자와 연결을 확립하고 데이터 전송이 끝날 때 까지 연결을 유지한다.

 

2. 데이터 처리속도를 조절하여(흐름 제어) 수신자의 버퍼 오버플로우를 방지한다.

  • 송신자가 수신자의 처리 능력에 맞춰 데이터 전송 속도를 조절한다.

 

3. 네트워크 내의 패킷 수가 과도하게 증가하지 않도록 방지한다.(혼잡 제어)

  • 네트워크 혼잡 상황을 감지하여 데이터 전송 속도를 조절한다.

 

4. 전이중, 점대점 방식을 사용한다.

  • 전이중(Full-Duplex) 방식 - 전송이 양방향으로 동시에 일어날 수 있다.
  • 점대점(Point to Point)방식 - 각 연결에 2개의 종단점을 가지고 있다.

 

이러한 TCP의 특징들 덕분에 데이터 전송의 신뢰성을 보장하고, 패킷 손실이나 데이터 손실이 발생하더라도 이를 복구할 수 있다.

 

 

 

구조

기존 IP 패킷이 프레임에 포함되어 전송되는것 처럼, TCP또한 세그먼트(Segment)라 불리우는 데이터가 IP 패킷에 포함되어 전송된다.

 

TCP 세그먼트가 IP 패킷에 포함되어 전송되며, TCP 세그먼트를 포함한 IP 패킷은 프레임에 포함되어 물리적으로 전송된다.

 

 

 

아래 그림과 표에서, TCP 세그먼트의 구조와 간단한 설명을 확인할 수 있다.

 

TCP Segment 구조 (출처: https://www.geeksforgeeks.org/tcp-ip-packet-format/)

 

 


SYN(동기화)
시퀀스 번호의 동기화를 위해 사용하는 플래그로, 상대방에게 어떤 시퀀스 번호를 수락해야 하는지 알려주는 데 사용된다.

ACK(승인)
호스트가 성공적으로 수신한 패킷을 승인하는 데 사용된다. 확인 번호 필드에 유효한 확인 번ㅂ호가 포함되어 있으면 플래그가 설정된다.

FIN(Finish)
연결 종료를 요청하는데 사용되며, 발신자가 보내는 마지막 패킷이다. 리소스를 해제하고 연결을 정상 종료한다.

PSH(Push)
데이터를 즉시 전달하기 위해 사용하는 플래그로 수신자가 데이터를 버퍼에 저장하지 않고 즉시 처리하도록 지시한다. 주로 상위 계층의 빠른 데이터 전송이 필요할 때 사용한다.

 

 

 

 

통신 과정

 

TCP의 통신과정은 연결 수립 - 데이터 통신 - 연결 해제의 3단계로 통신이 이루어지는데, 각 단계별로 나눠서 간단히 설명해보자.

 

 

연결 수립(3-way Handshake)


Handshake란?
서로가 손을 내밀고, 악수를 하는 모습과 유사하게 TCP 연결 수립, 종료가 이루어진다.
이에 비유하여 Handshake라는 용어를 사용하며, 수립은 3단계의 HandShake, 종료는 4단계의 Handshake과정이 이루어져 3-way Handshake, 4-way Handshake라고 부른다.

 

1. 연결 요청(SYN)

클라이언트에서 서버(Source Port to Destination Port)로 연결 수립을 위한 요청을 보낸다.

이 때 SYN 플래그를 가진 세그먼트를 보내며, 시퀀스 번호(Sequence Number)는 랜덤하게 선택되지만, 예시로 0으로 두었다.

 

2. 응답 확인(SYN-ACK)

서버에서는 올바른 연결일 경우, 연결 수립이 되었다는 세그먼트를 ACK 플래그를 담아 보낸다.

이 때 응답 번호(Acknowledgement Number)를 보내는데, 이는 다음 요청에 대한 시퀀스 번호의 기대값을 의미한다.

 

3. 연결 확인(ACK)

클라이언트가 서버에게 다시 통신을 하여, 올바르게 연결이 수립되었다는 확인 세그먼트를 보낸다.

서버에서 예상하는 기대값인 1을 시퀀스 번호에 담아 전송한다.

 

 

 

데이터 통신

 

1. 서버로 데이터 전송

클라이언트에서 서버로 데이터를 전송한다. 이 때 데이터는, 단순 요청일 수도 있고, 서버에 적재해야하는 데이터일 수도 있다. 이 때 시퀀스 넘버는 연결 수립 시의 시퀀스 번호 그대로 1이다.

 

2. 서버에서 성공적으로 수신 확인(ACK)

요청을 성공적으로 수신하였다면, 서버는 성공적으로 수신되었다는 확인 응답(ACK)을 보낸다.

이 응답은 기존 시퀀스 넘버에 데이터의 바이트를 합친 값만큼 다음 요청의 기대값이 된다.

 

3. 클라이언트로 데이터 전송

클라이언트에서 데이터를 보낼 때, 새로운 시퀀스 넘버는 이전의 ACK 번호가 된다.

 

4. 클라이언트에서 성공적으로 수신 확인(ACK)

서버가 데이터를 수신하고 새로운 ACK 번호를 보내어 수신 확인을 응답한다.

 

만약 이 과정들에서 ACK를 받지 못하였다면, 데이터 송신을 실패한 것으로 판단하여 데이터를 재전송하게 된다.

 


Data의 Bytes는 제한이 있을까?
데이터가 설정된 MTU(Maximum Transmission Unit)을 초과하면, 데이터를 여러 세그먼트로 분할하여 전송된다.
위의 TCP 구조에서 언급했던 것 처럼, 분할된 세그먼트의 순서를 보장해주는 것이 시퀀스 번호와 응답 번호이다.

MTU와 MSS

MTU(Maximum Transmission Unit)
네트워크 인터페이스에서 전송할 수 있는 최대 패킷 크기. 이더넷의 경우 일반적으로 1500바이트이다.

MSS(Maximum Segment Size)
TCP 계층에서 한 번에 전송할 수 있는 최대 세그먼트 크기로, MTU에서 IP헤더와 TCP헤더의 크기를 뺀 값이다.

 

 

 

연결 종료(4-way Handshake)

 

1. 클라이언트 연결 종료(FIN)

클라이언트가 서버에게 연결 종료 플래그를 설정한 세그먼트를 보낸다. 이 때 시퀀스 번호는 마지막으로 보낸 데이터의 시퀀스 번호다.

 

2. 서버에서 수신 확인(ACK)

서버는 클라이언트의 FIN 세그먼트를 수신하고 이를 확인하는 ACK 응답을 보낸다.

 

3. 서버 연결 종료(FIN)

1과 마찬가지로 FIN 플래그를 설정한 세그먼트를 클라이언트에게 보낸다.

 

4. 클라이언트에서 수신 확인(ACK)

2와 마찬가지로 ACK 응답을 서버로 보낸다.

 

 

 

 

 

 


응답 번호는, 마지막 수신한 시퀀스 번호 + 1이 된다.
데이터의 순서를 보장하며, 데이터의 무결성을 확인할 수 있고, ACK 번호를 통해 흐름제어가 가능하다.
또한 ACK 번호가 예상보다 낮다면 이전 데이터가 손실되었음을 의미하여 데이터를 재전송할 수 있게 해준다.

 

 

 

 

 

TCPdump로 패킷 분석

결국 직접 코드로 보면서 이해한다면, 더 좋을 것이라 생각했다. tcpdump를 사용해 네트워크 트래픽을 실제로 보냈을 때의 패킷을 분석해보자.

 

 

설치 및 셋팅

준비물은 두 개의 터미널이다. 하나의 터미널엔 모니터링 세팅을, 하나의 터미널은 요청을 보내보자.

# 설치
## Linux
apt-get install tcpdump

## Mac
brew install tcpdump

 

# 트래픽 캡처
## 모든 네트워크 인터페이스의 패킷 중 HTTP 트래픽만, 숫자 형식으로 출력
sudo tcpdump -i any -nn port 80

 

 

요청 보내서 응답 받기

# 요청 보내기
## mag1c.tistory.net:80에 success를 전송한다. (TCP연결 테스트)
echo "success" | nc magic.tistory.net 80

 

 

 

 

패킷 분석

캡쳐된 패킷들을 분석해보자.

총 10개의 패킷이 캡처되었고 12개의 패킷이 필터링되어 수신되었으며, 커널에서 드롭된 패킷이 없다고 나와있다.

 

 

 

 

 

TCP의 한계

휴대폰을 로컬 데이터로 사용하다 와이파이 연결 시 기존 이용하던 애플리케이션의 끊김 현상을 본 적이 있을 것이다.

TCP는 네트워크 환경이 변경될때 마다 새로운 커넥션을 맺어 연결을 확립해야하기 때문에 끊김 현상이 발생한다.

 

TCP 통신에서, 시퀀스 넘버와 응답번호를 통해 패킷의 순서를 보장받을 수 있었다. 조금 다르게 말하면 반드시 순서대로 처리되어야 한다는 뜻이다. 처리되는 순서가 정해져있기 때문에 이전에 받은 패킷을 파싱하기 전까지 다음 패킷을 처리할 수 없다.

설정한 시간동안 패킷이 수신되지 않는다면? TCP의 특성 때문에 패킷을 다시 보내서 처리해야한다.

 

이러한 특성 때문에, 병목 현상이 발생할 수 있는데, 이를 HOL Blocking(Head of Line Blocking)이라 한다.

 

 

 

TCP의 한계 때문에, HTTP 3.0에서는 TCP 대신 UDP기반의 새로운 프로토콜인 QUIC가 채택되게 되었다.

 

 

 

 

UDP (User Datagram Protocol)

TCP는 핸드쉐이크 과정을 거쳐 신뢰성 있는 통신을 할 수 있도록 보장했고, 데이터 전달 여부를 확인하는 등 데이터를 전송하기 위한 작업들이 많았다.

 

시대가 변하면서 전송해야하는 데이터도 단순 텍스트를 넘어 동영상이나 웹 스트리밍, 음악 같은 멀티미디어를 전송하면서 데이터의 크기가 점점 커져갔고, TCP의 특징 때문에 속도의 한계가 존재했다. 확실한 전달, 안전성 등을 제공하기 위해 추가적인 오버헤드와 대기 시간이 소요됐던 만큼, 데이터 크기에 비례하여 속도가 느려졌다.

 

UDP는 데이터그램 방식을 사용하는 프로토콜로, 패킷 간의 순서가 존재하지 않는 독립적인 패킷을 사용한다.

하나의 데이터를 전송할 때, 연결 상태를 보장하지 않아 소통이 안전하지 않고, 데이터가 잘 전달됐는지를 확인하지 않는다. 확인하지 않기 때문에 TCP처럼 재전송하지 않는다는 얘기다.

 

이런 단점에도 불구하고, TCP보다 빠르고 가볍다는 장점이 있어서 동영상이나 웹 스트리밍, 실시간 회의, 게임 등의 분야에서 널리 사용된다. TCP처럼 데이터를 전송하기 위한 추가작업들이 없고 단순 전달만 하기 때문에 전송 속도가 빠르며, 헤더 크기또한 8bytes로 20bytes ~ 60bytes인 TCP보다 가볍다.

 

 

 

구조

 

 

 

TCP의 세그먼트 구조에 비해 굉장히 단순하며 이를 통해서도 TCP에 비해 가볍고 빠르다는 걸 유추해볼 수 있다.

 

 

 

 

 

통신 과정

 

TCP는 데이터를 전송하면, ACK 플래그를 통해 데이터 전송의 신뢰성, 순서 보장등을 제공했다. 하지만 UDP는 단순 데이터를 보내줄 뿐이다. 위의 그림을 보면, 데이터 하나를 보내기위해 TCP는 데이터 전송의 확인 과정을 거치기 때문에 한 데이터를 전송하기 위한 과정이 UDP보다 많고 복잡하다.

 

이처럼, UDP는 데이터를 단순히 던져놓기 때문에 빠르고 단순하다.

 

 

TCP의 패킷 분석에서, 총 10줄의 패킷이 발생했던 것과 다르게 UDP는 단 한줄로 마무리된다. 아래 명령어를 이전의 터미널에 입력해보자. 위에서 말한것 처럼추가 과정 없이 단순 데이터를 전송만 한다는 것을 알 수 있다.

 

echo "UDP TEST" | nc -u mag1c.tistory.com 80

 

 

 

 

QUIC (Quick UDP Internet Connections)

TCP의 한계 때문에, HTTP/3 에서는 QUIC라는 새로운 프로토콜이 채택되게 되었다.

 

QUIC는 구글이 개발한 UDP를 기반으로 TCP + TLS + HTTP를 구현한 새로운 OSI 4계층의 프로토콜로, HTTP/3 에서 표준으로 채택되었다.

 

TCP를 사용하는 경우, TCP를 통한 핸드셰이크 과정이 필요하며, TLS또한 자체 핸드셰이크가 필요하다. 각 핸드셰이크는 클라이언트와 서버 사이를 완전히 왕복해야하며, 클라이언트가 서버의 물리적인 거리에 비례해 시간이 길어진다. 하지만 QUIC은 단 한번의 핸드셰이크면 된다.

 

또한 TCP는 방화벽 및 NAT 디바이스와 같은 수많은 장치들에 의해 읽히고 조작된다. 하지만 QUIC는 UDP를 사용하여 완전히 암호화되어 세부 정보를 검사거나 조작할 수 없게 만든다.

출처: aws

 

 

기존의 TCP에서는 IP와 포트로 식별했기 때문에, 네트워크 환경이 바뀌면 연결을 다시 수립했어야하지만 QUIC은 연결을 식별하기 위해 연결 ID를 사용하는데, 이는 네트워크 경로가 변경되더라도 그대로 유지된다. 유튜브를 보면, 네트워크를 와이파이로 변경하거나 로컬 데이터로 돌아오더라도 원활하게 스트리밍이 되는 것을 볼 수 있다.

 

 

 

정리하면, QUIC는 UDP를 기반으로 빠른 연결과 데이터 통신을 가능하게 한다.

 

단일 라운드 트립 또는 0-RTT로 연결을 설정하여 연결 지연을 최소화하였고, TCP의 연결 및 데이터 전송 기능, TLS의 보안 기능, HTTP/2의 멀티플렉싱 기능을 모두 통합하여 단일 프로토콜로 구현한 것이 QUIC이다.

 

 

 

 

 

 

참조

[유튜브] 크래프트맨 멘탈리티님의 네트워크 기초 무료강의

 

TCP와 UDP, handshake / 3-way handshake, 4-way handshake

틀린 부분이 있다면 지적해주시면 감사하겠습니다. 공부하는 데 큰 도움이 됩니다.     WEB과 HTTP / (특징, 구조, 동작 과정 예시)틀린 부분이 있다면 지적해 주시면 감사하겠습니다. 공부에 많

mag1c.tistory.com

 

TCP flags - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

TCP/IP Packet Format - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

TCP - MDN Web Docs 용어 사전: 웹 용어 정의 | MDN

**TCP (전송 제어 프로토콜)**은 두 개의 호스트를 연결하고 데이터 스트림을 교환하게 해주는 중요한 네트워크 프로토콜입니다. TCP는 데이터와 패킷이 보내진 순서대로 전달하는 것을 보장해줍

developer.mozilla.org

 

TCP 핸드셰이크 (TCP handshake) - MDN Web Docs 용어 사전: 웹 용어 정의 | MDN

전송 제어 프로토콜(Transmission Control Protocol)은 3방향 핸드셰이크(three-way handshake) (TCP-handshake, 3개의 메시지 핸드셰이크 및/또는 SYN-SYN-ACK로 알려져 있습니다)를 사용하여 IP 기반 네트워크를 통해 T

developer.mozilla.org

 

웹페이지를 표시한다는 것: 브라우저는 어떻게 동작하는가 - 웹 성능 | MDN

사용자는 로드가 빠르고 상호작용이 원활한 컨텐츠로 이루어진 웹 경험을 원합니다. 따라서 개발자는 이 두 가지 목표를 달성하기 위해서 부단히 노력해야합니다.

developer.mozilla.org

 

전송 제어 프로토콜 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 전송 제어 프로토콜(Transmission Control Protocol, TCP, 문화어: 전송조종규약)은 인터넷 프로토콜 스위트(IP)의 핵심 프로토콜 중 하나로, IP와 함께 TCP/IP라는 명칭으로

ko.wikipedia.org

 

 

 

[네트워크] TCP와 Handshake (tcpdump를 이용한 tcp 패킷분석)

네트워크를 부분적으로 공부한 후 간결하게 정리하였습니다.개선점들을 알려주신다면 적극 반영하겠습니다.(개발 입문 시 간단히 공부했던 TCP / UDP에 대해 재정리했습니다.)   TCP (Transmission Co

mag1c.tistory.com

 

User Datagram Protocol (UDP) - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

UDP 체크섬

UDP Pseudo Header, UDP 가상헤더, TCP Pseudo Header, TCP 가상헤더

www.ktword.co.kr

 

TCP vs. UDP — What's the Difference and Which Protocol is Faster?

If you're getting into computer networking, or if you've dug through the network settings of some applications, you've likely seen these terms: TCP and UDP. TCP, which stands for Transmission Control Protocol, and UDP, or User Datagram Protocol, are part o

www.freecodecamp.org

 

🌐 아직도 모호한 TCP / UDP 개념 ❓ 쉽게 이해하자

HTTP / IP / TCP / UDP 는 모두 프로토콜 프로토콜은 클라이언트와 서버가 정보를 교환할 수 있도록 하는 메시지 형식 대한 규칙 이라고 보면 된다. 수신 호스트가 전송 받은 메시지를 이해하려면 설

inpa.tistory.com

 

Amazon CloudFront, HTTP/3 지원 시작 | Amazon Web Services

Amazon CloudFront는 콘텐츠 전송 네트워크(CDN) 서비스로서, 사용자와 지리적으로 더 가깝고 컴퓨터에 훨씬 빠르게 도달하는 상호 연결된 서버 네트워크입니다. Amazon CloudFront는 자동화된 네트워크 매

aws.amazon.com

 

HTTP/3: 과거, 현재 그리고 미래

오늘 QUIC와 HTTP/3의 엣지 네트워크 지원을 발표하게 되어 기쁘게 생각합니다. 또한 모두를 위해 웹을 빠르고 더 신뢰성 있게 만들고자 하는 우리의 노력에 있어 Google Chrome과 Mozilla Firefox라는 선도

blog.cloudflare.com

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[네트워크] IP, 서브넷(서브넷 마스크, 서브네팅), Public IP와 Private IP

Tech/C.S. 2024. 5. 30. 17:16
728x90
728x90

 

네트워크를 부분적으로 공부한 후 간결하게 정리하였습니다.

개선점들을 알려주신다면 적극 반영하겠습니다.

 


 

 

IP(Internet Protocol)

인터넷을 통해 데이터를 주고받을 때 사용되는 통신규약으로 네트워크 계층에 위치하는 프로토콜이다.

인터넷에 연결된 모든 장치들을 식별할 수 있도록 각각의 장비에 부여되는 고유 주소이다.

 

 

 

 


IPv6는 128비트로 구성된 IP주소로, 8개의 4자리 16진수로 이루어져있으며, 콜론으로 구분된다.
1234:0ae3:85a3:0000:0000:2f3a:0370:7334

 

 

 

구조

 

아이피는 네트워크 ID와 호스트 ID로 구성되어 있으며 위의 IP주소 예시에서의 네트워크 ID와 호스트 ID는 다음과 같다

 

 

 

 

 

IP 클래스

 

네트워크 ID는, 어떤 네트워크인지 식별하며, 호스트 ID는 네트워크에 연결되어있는 디바이스를 식별하며

옥텟 별로 IP의 클래스를 A,B,C로 나눌 수 있다.

호스트의 개수가 많을 수록, 대규모 네트워크에 적합하다.

 

 

 

Class A

 

네트워크 수는 적지만, 많은 디바이스를 연결시킬 수 있어 대규모 네트워크에 적합하다.(2^24 -2, 약 1670만개).


0과 127로 시작하는 주소는 예약 주소로 사용할 수 없다.
0.~ : 미지정 주소
127.~ : 호스트 주소로 OS와 네트워크가 제대로 연결되었는지 확인한다.

 

 

 

Class B

2바이트의 네트워크와 2바이트의 호스트를 가지고있다. (2^16 - 2, 65534개)

많은 인터넷 제공 기업들이 사용한다.

 

 

Class C

총 네트워크의 수가 많은만큼, 적은 디바이스를 연결해야한다. (2^8 - 2, 256개)

작은 기업이나, 개인용으로 많이 사용한다.

 

 


Class D와 Class E는 일반적인 네트워크 장치에 IP주소로 할당되지 않는다.
Class D: 멀티캐스트 그룹 주소로만 사용됨 (224 ~ 239)
Class E : 연구 및 실험 목적으로 예약되어 있음(240 ~ 255)

 

 


 

 

서브넷(Subnet)

IP 주소를 클래스로 구분하여 할당하는 방식은 비효율적이다.

 

예를 들어 100개의 IP 주소를 필요로 하는 중소기업 "매직" 이 있다.

클래스 C를 할당해야하지만, 256개 중 156개의 IP는 사용되지 않아 절반 이상의 낭비가 발생한다.

 

 

 

반대로, 중견기업 "마술"이 있다.

이 기업에서는 5000개의 IP주소를 필요로한다. 클래스 B를 할당하면 나머지 60,536개는 사용하지 않게 되어 엄청난 낭비가 발생한다.

 

반면에 클래스 C를 여러개 할당하자니 20개의 클래스 C 네트워크가 필요한데, 이는 관리와 라우팅 측면에서 매우 복잡해질 수 있다.

 

 

 

 

 

이러한 문제를 해결하고 IP 주소 자원을 효율적으로 관리하며 네트워크 성능을 향상시키기 위해 서브넷이 등장하게 되었다.

 

서브넷은 하나의 IP 네트워크를 더 작은 여러 개의 네트워크로 나누는 방법이다.

IP 네트워크를 서브넷으로 만드는 작업을 서브네팅(Subnetting)이라고 하며, 서브네팅을 통해 네트워크의 크기를 줄여 더 작은 네트워크로 나눌 수 있다. 이는 네트워크 주소의 일부 비트를 호스트 주소에서 가져와 서브넷 마스크를 변경함으로써 이루어진다.

이를 구분하기 위해 서브넷 마스크(Subnet Mask)를 사용한다.

 

 

 

서브넷 마스크(Subnet Mask)

 

 

서브넷 마스크를 더 간소화해서 표현할 수 있는 CIDR 표기법이 있다.

서브넷 마스크에서, 네트워크 ID에 해당하는 영역을 슬래시(/)로 표기하는 방식이다.

 

 

 

서브네팅(Subnetting)

서브네팅의 과정을 한번 그림으로 표현해보자.

기존의 192.168.100.0/24는 2^8 - 2= 256개의 IP주소를 가지고 있다.

여기서, 서브네팅을 통해 호스트 주소의 1비트를 빌려왔다.

 

 

 

이 때, 사용할 수 있는 호스트 IP는 총 7비트가되었으며, 2^7 - 2인 126개가 된다.

이를 CIDR로 표기하면 192.168.100.0/25, 192.168.100.128/25가 되고

서브넷 마스크는 255.255.255.128이 된다.

 

마찬가지로, 2비트 호스트 주소를 빌려온다면, 총 4개의 서브넷이 생성되며, 각 서브넷마다 2^6 - 2인 62개의 IP를 할당받을 수 있으며

이러한 규칙만 잘 이해한다면 서브네팅에 따른 서브넷, 호스트 범위, 호스트 수, 서브넷 마스크 등을 유추할 수 있다.

 

 

 

그렇다면, 위의 예시에서 "매직" 기업의 서브넷은 어떻게 구성해야할까?

단순 C 클래스 네트워크를 그대로 사용하는 것 보다, 서브네팅을 통해 1개 이상의 비트를 가져와서 구성하는 것이 더 효율적일 것이다.


더 잘게 쪼개진 네트워크를 가진 서브넷으로 나눌 경우의 이점
1. 서브넷에서 발생하는 브로드캐스트 트래픽이 절반씩 감소한다. 이는 운영 비용 감소에 긍정적이다.
2. 분리된 서브넷별로 보안 규칙등을 세분화할 수 있고, 보안 사고 발생 시 다른 네트워크로의 확산을 방지할 수 있다.
3. 서브넷 별로 관리하기 용이하고 확장에 유연하다.

 

 

 

Public IP / Private IP

IPv4 주소의 고갈로, 효율적으로 주소를 나눠 사용할 필요가 생겼다.

이러한 이유에서, 공인 주소와, 사설 주소를 구분하여 사용하는 방식이 도입되었다.

 

 

아래는, Public IP와 Private IP의 특징을 정리한 표와, Private IP의 클래스 별 범위이다.

 

 

출처: 위키피디아 - Private network

 

 

 

보통의 LAN 단위에서는, Private IP를 통해 소통을 하며, 외부 소통(인터넷 등)을 위한 라우터는 Public IP를 가지게된다.

 


이전 포스팅(LAN/WAN) 과 지금의 내용들을 정리했을 때, 사설 네트워크에서는 외부와의 통신이 되지 않는다.

외부와의 데이터 통신을 LAN에서 보낼 때, ARP를 통해 목적지 IP 주소를 IP 패킷에 담아 라우터에 전송하여, 목적지 IP가 있는 라우터까지 라우팅 테이블을 따라 계속 이동한 뒤, 목적지 라우터에서 ARP를 통해 다시 목적지 MAC 주소를 받아 전달해준다고 머리속에 정리가 되어있었는데, 사설 IP 주소밖에 없는 현 상황에서 어떻게 외부와의 소통을 할 수 있을까? 에 대한 의문이 생겨 다음과 같이 통신 과정을 정리해보았다.

 

기준이 되는 네트워크는 이전 포스팅의 내 네트워크 환경이다.

 

 

 

 

 

참조

[유튜브] 크래프트맨 멘탈리티님의 네트워크 기초 무료강의

 

Private network - Wikipedia

From Wikipedia, the free encyclopedia Network using private IP addresses In Internet networking, a private network is a computer network that uses a private address space of IP addresses. These addresses are commonly used for local area networks (LANs) in

en.wikipedia.org

 

 

서브넷마스크(subnet mask)와 서브넷팅(subnetting)이란??

서브넷 마스크(Subnet Mask)와 서브넷팅(Subnetting)?? 서브넷팅(Subnetting)이란 ?? 서브넷팅이란 IP주소 낭비를 방지하기 위해 네트워크를 분할하여 효율적으로 사용하는 개념입니다. IPv4 주소(32bit)의 고

devoong2.tistory.com

 

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[네트워크] LAN과 WAN (허브, 스위치, 라우터, CSMA/CD, ARP, 프레임, 패킷, 홉)

Tech/C.S. 2024. 5. 26. 18:06
728x90
728x90

 
네트워크를 부분적으로 공부한 후 간결하게 정리하였습니다.
틀린 정보 혹은 보기에 불편한 점을 알려주신다면 적극 반영하겠습니다.
 


 
 

LAN

 
Local Area Network의 이름에서 알 수 있듯이, 소규모 통신망을 말한다.
 
 
 
 
 

HUB

근거리 통신에서, 컴퓨터 간의 소통을 위한 연결장치
 
 

허브의 단점

데이터 전송 시 연결된 모든 컴퓨터에 데이터를 전송하며(그림 1)
이미 누군가가 통신망을 사용중이라면 충돌 문제(Collision)가 발생한다.(그림 2)
 
 

 

 
 

허브의 단점 개선

Collision문제를 CSMA/CD 프로토콜을 사용해 해결하고자 했다.


CSMA/CD (Carrier Sense Multiple Access/Collision Detection:: 반송파 감지 다중 접속/충돌 감지)
쉽게 말해, 데이터 전송 전에 네트워크가 사용 중인지 확인하는 과정을 통해 사용 중이지 않다면 데이터를 전송하는 방법이다.

 
 
허브로 연결된 모든 장치에 데이터를 보낼 때, 프레임MAC 주소를 넣어 식별하였다.
데이터를 받은 장치에서는, 프레임의 수신 MAC 주소를 확인하여 수신지를 식별할 수 있었고 장치 내부에서 데이터를 수신 혹은 비수신처리 하였다.
 


MAC(Medium Access Control) 주소
NIC(Network Interface Card:: 랜카드)에 각인된 고유한 물리적 주소
6바이트 구조의 16진수로 구성되어 있으며(fe:1b:63:84:45:e6) 첫 3바이트는 IEEE에서 기업이나 단체 단위로 할당해준다.
프레임
데이터 링크 계층에서 전송되는 단위
크게 목적지 MAC 주소, 소스 MAC 주소, 데이터로 구분되며 자세한 프레임 형식은 여기에서 확인할 수 있다.

 
 

(출처: geeksforgeeks)

 
 
 
 
 

SWITCH

허브의 단점들이 보완된 연결 장치이다.
 
1.
스위치가 직접 프레임의 MAC 주소를 파악하여 데이터를 전송한다.
모든 장치에 데이터가 전송되지 않으며, 장치에서 불필요하게 데이터 수신에 대한 의사결정을 하지 않게 되었다.
 
2.
Collision이 발생하던 기존 문제를, 케이블이 송신용, 수신용으로 분리하여 더이상 충돌이 발생하지 않게 되었다.
 
 
 
 


 
 
 
 

WAN

WAN(Wide Area Network)는 먼 거리에 있는 장치들을 연결하는 대규모 네트워크다.
LAN-LAN, LAN-WAN 혹은 WAN-WAN을 라우터(Router)를 통해 연결한다.
 
인터넷을 생각해보면 쉽다. KT, SK등 업체에서 인터넷 연결을 위해 방문한 경험이 있을 것이다. 우리는 기사가 설치해준 랜선을 통해 인터넷에 연결되어 인터넷을 이용한다.
 
이 때 우리의 집이나 사무실에 컴퓨터들이 공유기를 통해 연결된 상태는 LAN이다. 이 LAN이 인터넷이라는 WAN을 통해 전 세계의 다른 LAN들과 연결된다.
 
 
 
 
 

Router

라우터는 WAN과의 연결을 위한 연결 장치로, 라우터끼리의 연결도 가능하다.
 
이 라우터는, MAC주소를 알 수 없고, IP주소만 할당되어 있는 상태이다.
 

 

 
위에서 설명한 LAN의 데이터 송,수신 방식에서 MAC주소를 언급했었다.
하지만, 이 라우터에는 MAC 주소는 없는데 어떻게 데이터를 전송할 수 있는 것일까?
 
 
 
 

ARP(Address Resolution Protocol:: 주소 결정 프로토콜)

주소 결정 프로토콜은 이처럼, 라우터와 같은 네트워크 계층에서 목적지의 MAC 주소를 알아내기 위해서 사용되는 프로토콜로
프레임의 데이터 안에 IP 패킷을 넣어 요청 - 응답 을 통해 MAC 주소를 알아낼 수 있다.

 
위의 구조에서, Target Hardware Address는, 목적지 MAC 주소를 모르기 때문에 0으로 채워진다.
 


ARP Cache
매 요청마다 무조건적으로 ARP를 브로드캐스팅하게되면, 트래픽이 과하게 발생할 수 있다.
그렇기 때문에, 새로 생긴 MAC 주소에 대해 20분의 유효기간을 가지는 캐시가 생성되어 ARP 요청 없이 사용할 수 있다.

 
 
 


패킷(Packet)
네트워크 레이어에서 사용하는 전송 단위로 서로 다른 LAN 간에 데이터를 주고 받을 때 사용된다.
패킷도 프레임과 마찬가지로 목적지 주소, 소스 주소, 데이터를 가지고있는데, 이 때 주소는 IP 주소를 말한다.

 
 
 
 
 

그렇다면, ARP로 어떻게 MAC 주소를 알아낼 수 있는걸까?

 

 
 

192.168.0.3을 나라고 가정하고, 목적지인 192.168.30.1로의 데이터 전송과정을 살펴보자.

 
 
 

ARP request

 

LAN영역에서, 데이터 전송을 위한 패킷이 생성되었지만, 목적지의 MAC 주소는 알지 못하고 있는 상태이다.
 
 

그림 9. ARP Packet Broadcast

 
이럴 때, 목적지의 MAC 주소를 알아내기 위해 LAN의 모든 장치에 브로드캐스팅을 하는데,
 

그림 10. 프레임 + ARP Request Packet

 
이 때, 프레임의 목적지 MAC 주소는 브로드캐스팅에 사용되는 MAC 주소로 세팅되며
데이터는 ARP에 사용될 IP Packet이 들어가게 된다.
 
ARP Request Packet에는, 위의 그림 5번의 패킷 구조에서
받는 쪽의 MAC 주소를 모르기 때문에, 받는 쪽의 MAC 주소는 00:00:00:00:00:00로 세팅된다.
 
 
 

ARP reply

위의 ARP 요청에서, 같은 LAN에 목적지의 IP가 있다면, 바로 MAC 주소를 받을 수 있지만
만약 브로드캐스팅 후에 LAN에서 응답이 없을 경우 Default Gateway, 즉 LAN에 연결된 라우터의 MAC 주소를 가져온다.
 
 
 

라우터간 데이터 전송

요청이 라우터에 도달되면, 수신받은 프레임에서 IP 패킷을 추출한다. (디캡슐레이션)
이 때 라우터는, 다음 목적지를 알아내기 위해 라우팅 테이블을 확인하고, 다음 의 라우터를 결정한다.
라우터는 현재 라우터의 MAC 주소와, 다음 홉의 라우터의 MAC 주소 및 데이터에는 추출했던 IP 패킷을 담은 새로운 프레임을 생성하여 전송한다.
 
이 과정을, 목적지 네트워크에 도달할 때 까지 반복하게된다.


홉(hop)
컴퓨터 네트워크에서 출발지와 목적지 사이의 경로의 한 부분

 
 
 

그림 11. 라우팅 과정의 일부와 라우팅 테이블

 
 
위 그림은, 라우팅 과정에서, 라우터 간의 데이터 전송의 시작지점이다.
 

1.

위에서 셋팅했던 프레임에서 IP 패킷을 디캡슐레이션하여 목적지 IP 주소인 192.168.30.1를 추출한다.
 

2.

목적지에 해당하는 IP를 라우팅 테이블에서, 다음 홉을 결정한다.
 

3.

다음 홉의 위치를 확인했으니, 해당 홉의 라우터의 MAC 주소를 송신지로 하는 새로운 프레임을 만들어 IP 패킷을 감싼다.
 

4.

1.2.3.2 라우터에 전송한다.
 

5.

이 과정을, 다음 홉이 직접 연결된 곳인 라우터까지 반복한다.
 


그림 7에서, 만약 현재 홉이 1.2.3.2 라우터라면, 다음 홉은 1.2.3.5가 될 것이다.
1.2.3.2의 라우팅 테이블은 아래와 같다
192.168.0.0/24 - 1.2.3.1
192.168.10.0/24 - 직접 연결됨
192.168.20.0/24 - 1.2.3.5
192.168.30.0/24 - 1.2.3.5

 
 

6.

다시 ARP request를 보내 IP 주소에 해당하는 MAC 주소 요청을 브로드캐스팅하고
 
7.
ARP reply를 통해 받은 MAC 주소를 프레임에 담아 전송한다.
 
8.
LAN의 스위치 등의 연결장치에서 MAC 주소에 맞는 장치로 데이터를 보낸다.
 
 
 

 

 

참조

 

Ethernet Frame Format - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

 

WAN(Wide Area Network)의 특징, WAN 구성기술, WAN 프로토콜, PPP의 과정, PPP의 인증

1. WAN(Wide Area Network)         1) WAN의 특징   1. LAN과...

blog.naver.com

 

Packet - MDN Web Docs Glossary: Definitions of Web-related terms | MDN

A packet, or network packet, is a formatted chunk of data sent over a network. The main components of a network packet are the user data and control information. The user data is known as the payload. The control information is the information for deliveri

developer.mozilla.org

[유튜브] 크래프트맨 멘탈리티님의 네트워크 기초 무료강의

[인프런] 널널한 개발자님의 넓고 얕게 외워서 컴공 전공자 되기

 

 

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

[알고리즘] LCS (Longest Common Subsequence, Longest Common Substring)

Tech/C.S. 2024. 5. 26. 13:06
728x90
728x90

 

 

LCS

Longest Common Subsequence는 최장 공통 부분 문자열로, Substring의 값을 구하는 것이 아니라

연속되지 않은 부분 문자열 중 가장 긴 공통 문자열을 찾는 알고리즘이다.

 

반대로, Longest Common Substring은 비슷하지만 부분 문자열이 아닌, substring이 되는 문자열이다.

 


예를들어 ABCDEF, BCDFQQ라는 문자열이 주어지면
Longest Common Subsequence는 BCDF가 되고
Longest Common Substring은 BCD가 된다.

 

 

LCS의 길이를 구할 때DP(Dynamic Programming)를 통해 메모제이션으로 효율적인 문제 해결이 가능하다.

 

 

 

점화식

char[] w1 = word1.toCharArray();
char[] w2 = word2.toCharArray();

 

 

위와 같이, 문자열을 배열로 변환했다고 가정하고, 아래와 같이 점화식을 작성할 수 있다.

Substring과 Subsequence의 차이는 공통 문자열이 이어지느냐 이어지지 않느냐의 차이이다.

//Longest Common Substring
if (w1[i - 1] == w2[j - 1]) {
    dp[i][j] = dp[i - 1][j - 1] + 1;
}
else {
    //공통 문자열이 끝났기 때문에 초기화
    dp[i][j] = 0;
}

//Longest Common Subsequence
if (w1[i - 1] == w2[j - 1]) {
    dp[i][j] = dp[i - 1][j - 1] + 1;
}
else {
    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}

 

 

 

점화식의 이해를 돕기위해 각각의 상황을 위의 예시인 ABCDEF / BCDEFQQ를 가지고 그림으로 그려보았다.

 

 

Longest Common Substring

 

 

 

 

 

 

 

Longest Common Subsquence

부분 문자열에서는, substring과 다르게 계속해서 이전 메모된 값들 중 Max값을 가져가야한다.

(연속된 문자열이 아니기 때문)

 

 

 

 

 

 

 

 

 

예제 - 백준 9251번: LCS

https://www.acmicpc.net/problem/9251

 

 

위에서 설명했던 것 처럼 점화식은 아래와 같다.

if (w1[i - 1] == w2[j - 1]) {
    dp[i][j] = dp[i - 1][j - 1] + 1;
}
else {
    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}

 

 

단순 최대 길이를 출력하는 것이기 때문에 dp배열의 가장 끝 idx에 최대길이가 저장되게 된다.

 

import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String word1 = br.readLine();
        String word2 = br.readLine();
        int[][] dp = new int[word1.length() + 1][word2.length() + 1];

        char[] w1 = word1.toCharArray();
        char[] w2 = word2.toCharArray();
        for (int i = 1; i <= word1.length(); i ++) {
            for (int j = 1; j <= word2.length(); j ++) {
                if (w1[i - 1] == w2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                }
                else {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }

        System.out.println(dp[word1.length()][word2.length()]);
    }

}

 

 

 

 

 

 

관련 문제

https://leetcode.com/problems/longest-common-subsequence/description/

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

방명록