네트워크 계층이란?

 

OSI 7계층 중, 3계층에 속하는 내용으로, 망 계층 이라고도 불립니다.

 

네트워크 계층의 대표적인 역할은,

라우팅(Routing) 과 포워딩(Fowarding) 입니다.

 

 

라우팅은

길 찾기에 해당하는 내용이며, 네트워크 계층 내에서 Control Plane 으로 나누기도 합니다.

출발지에서 목적지 까지의 경로 중, 가장 효율적인 경로를 탐색하는 것을 말합니다.

 

포워딩은

네트워크 계층 내에서 Data Plane 으로 나누기도 하며,

최선의 경로를 따라서 실제로 데이터를 전달하는 기능을 말합니다.

 

 

데이터 링크 계층과의 차이

데이터 링크 계층은 인접한 두 노드 간의 전송을 담당하는 것이고, 

네트워크 계층은 전체 네트워크 상에서 최종 수신지 까지의 전송을 담당합니다.

 

 

 

IP (인터넷 프로토콜, Internet Protocol)

목적지 까지 데이터를 전달하는 기능을 수행하고, 주소를 관리하는 기능을 수행합니다.

IP를 통해서 포워딩이 이루어진다 라고 볼 수 있습니다.

IP 주소 라고 불리는 인터넷 상의 고유 식별 번호를 통해 실제 데이터의 출발지와

목적지 등 각각의 주소(위치) 를 정의 할 수 있습니다.

 

 

 

DHCP (Dynamic Host Configuration Protocol)

클라이언트에게 동적으로 IP 주소를 할당하는 방법을 제공하는 프로토콜 입니다.

 

DHCP 의 특징

-주소의 대여시간 갱신 (연장)

-동일한 주소의 재 사용 가능

-네트워크에 접속하고자 하는 이동 사용자 지원

 

 

 

NAT ( Network Address Translation)

라우터 등의 장비에서 사설 IP를 외부IP 로 변환하는 기술입니다.

 

만약, 네트워크 내에 엄청나게 많은 호스트가 있다면, 호스트 마다 IP 주소를 할당하는데에는

너무 많은 IP 주소가 필요할 것 입니다. 하지만 실제로는 외부 인터넷이 내부 인터넷에 존재하는

모든 호스트에게 IP 주소를 직접 할당 해주는 것이 아닌, 라우터에게 IP 주소를 할당해줍니다.

결국 라우터 내부의 네트워크는 사실상 모두 같은 IP 주소를 사용하는것 보일 수 있습니다.

 

하지만, 내부 네트워크에서만 사용할 수 있는 또 다른 IP 주소를 부여받게 되고 이를 사설 IP 라고 부릅니다.

 

결과적으로, 내부 네트워크에 존재하는 사설 IP를 외부 IP 로 변환 및 매핑시켜주는것이 NAT 라고 할 수 있습니다.

 

 

월요일이되고, 가장 먼저 캐릭터 접속 authorization 을

추가로 만든것과 관련한 의견을 튜터님께 물었다.

 

 

주말 간 진행한 내용으로,

결론만 말하면, 결과는 폭망이다.

 

오늘은 그 과정에 대한 내용을 서술하도록 하겠다.

 

로그인만 인증하는 기존의 authorization (미들웨어) 에 더해서 

로그인 + 캐릭터 인증까지 검증하는 authorization 를 추가로 만든 것이고,

 

이렇게 만든 이유는,

1. 캐릭터 특정이 필요한 상황 (아이템 획득, 구매 등등) 에서 매번 캐릭터id를 param으로 받지 않아도 된다.

 -> 캐릭터 인증 (접속) 을 통해 해당 캐릭터 id를 고정할 수 있음

 

2.  param으로 캐릭터Id를 받을 경우 매번 해당 캐릭터Id 가 로그인한 계정에 포함이 되어 있는지 

검증을 해야하는데, 그 과정이 캐릭터 접속API에 포함되며, 

이후로는 JWT 복호화를 통해 검증함으로써 보안에도 유리함.

 

 

단점을 따지면,

 

1. 불필요한 코드일까 ? or 잘못된 접근 방식일까? 하는 막연한 생각 정도?

 

 

결론은

단호하게 '안된다' 였고,

해당 코드를 無로 돌릴것을 권장하셨다.

 

물론, 최대한 부드럽게 말씀해주시긴 했지만,

뭔 쓰잘데기 없는 코드를 만들어왔어? 라고 생각하셨음이 ' 제 6의 감각' 으로 느껴졌다...

 

코드 복잡성도 늘어나고, 불필요한 실행도 늘고, 유지보수에 대한 문제 등등을 지적하셨다.

 

 

납득이 되지 않았다.

 

 

과제에서 요구한 방식인 params 로 캐릭터id를 받아도

기존 JWT인증 + 로그인계정에 있는 캐릭터Id가 맞는지에 대한 검증은 해야하기 때문에,

캐릭터Id params를 받는 모든 API에   로그인계정에 있는 캐릭터Id인가- 에 대한 검증 코드가 필요하다.

 

당연히, 그런 API 는 다수 일 것이기 때문에, 동일한 내용을 복붙을 하거나, 

해당 캐릭터 인증만 하는 미들웨어를 어차피 만들어야 할 것이다.

 

 

실행할 것이 늘어난다? 

1의 내용은 어차피 실행되어야 할 내용 두가지를 합쳤을 뿐이고,

추가적으로 캐릭터 접속 API를 따로 진행해야 한다는 것인데,

그것이 즉시 폐기 라는 대답이 나올 정도로 불필요한 일인지 모르겠다.

 

오히려 캐릭터 접속API는 캐릭터 검증을 위한 데이터 베이스 조회를 한 번만 하고, 

이후로는 JWT 복호화를 통해 진행하므로, 코드 복잡도 면에서 오히려 유리하다.

==> 24.09.10 일 내용 정정

복호화로 얻은 정보를 바탕으로 find를 실행하기 때문에 데이터 베이스 조회 횟수는 같다.

============================================================================

 

심지어 JWT 복호화는 추가적인 작업이 아니라 기존에 계정Id를 복호화 하던것에 더해진 것이기 때문에,

이부분도 크게 차이가 없다.

물론, JWT 코드 자체가 복잡해질 수 있는 문제는 있다.

 

 

 

내가 놓친 부분이 있을까?  

별다른 수가 없다.

일단 피드백 받은 내용대로 갈아엎은 결과와 지금을 비교하는게 최선일듯 하다.

 

 

 

우선 만들어 놓은 아이템 획득 API를 기존의 요구 방식대로 바꿔보자.

 

위 처럼만 해놓으면, 아무계정이나 로그인해서 모든 캐릭터에 접근 할 수 있으니, 

캐릭터 검증 과정이 필요하다.

 

 

해당 코드를 추가하여, 검증을 하여야 한다.

이 과정에서 character.find 조회 과정은 반드시 필요함.

 

결국, 만들었던  캐릭터 접속 과정은, 

기존 로그인 JWT + 저 위에 코드를 합체한 내용일 뿐이다.

 

캐릭터 접속 할때 동일한 검증을 한다.

 

차이는 캐릭터 접속 API를 이용해야 한다는 점...

이 과정 자체가 문제일까?

 

 

JWT에  추가로 characterId 가 들어가기 때문에 JWT 토큰이 무거워진다는 단점도 있을 것이다. 

 

차라리,

늦게 확인한 것은 안타깝지만 해당부분은 과제 요구사항에서 벗어나기 때문에 안된다.

 

라는 대답이 돌아왔다면 머리가 아프진 않았을텐데...

 

심란하다

 

 

 

그렇게 바뀐 아이템 획득 API 이다. 

 

본인 캐릭터 확인 절차는 따로 미들웨로 만들기엔 좀 짧지 않나 생각도 들고...

그냥 복붙 해서 쓰자니, 유지보수가 어려워진다.

 

해결방안?

 

만약, 캐릭터 검증이 필요한 API 의 경우, _auth 가 더해진 param을 받아서

 

 

기존 로그인 미들웨어에 해당 구문을 추가하여, character_id_auth 를 param으로 받은 경우에만

검증을 진행하도록 했다.

 

물론 이 해결방안도 상당히 짜치는 구조이다.

 

굳이 login 검증만 하던 미들웨어에,  조건 한정으로 캐릭터검증을 하는 일을 부여했으니 말이다.

 

말하자면,

때에 따라 캐릭터 검증도 하는 유능한 login 검증 미들웨어가 아니라,

이걸 왜 나한테 시키지? 라는 상황의 login 검증 미들웨어가 된것에 가깝다.

 

login 검증 미들웨어가 실행 되면, 반드시 character_id_auth 의 존재를 체크해야 하고,

필요없다면 undifined를 정의한채로 넘어가는 불필요한 동작이 생긴 것이다.

 

해결방안의 해결방안?

 

1. 비록 긴 코드는 아니지만, 캐릭터 검증만을 위한 미들웨어를 만든다.

 

폐기된 코드가 떠오르는 방안이지만, 엄연히 차이가 있다.

폐기된 코드 => 기존 A 작업 + 새로운 B 작업 을 합쳐놓은 C작업을 만든것이고,

 

해당 방안은 => 새로운 B작업만 만들고, 필요에 따라 A 와 B를 각각 호출하는 것이다.

 

그런데B가 실행되는 상황에서는 반드시 A도 실행되어야 하는 상황인데..?

그래서 A+B 를 합친 C를 만들었던 것인데....??

 

여기서, 의문이 들었던 부분을 추가로 짚고 넘어가보자..

 

JWT 인증 + param으로 characterId를 받는 상황이라면,

해당 API는 전부 캐릭터 검증을 추가로 해야한다는건 이미 여러번 설명한 내용이다.

그렇게 되면 캐릭터 인증만하는 미들웨어를 추가하던지, API 마다 검증하는 코드를 붙여넣던지 간에,

character 데이터 베이스를 조회해야 한다. API가 실행될 때마다 find를 통해, character Id를 찾아 검증해야 하기 때문이다.

 

폐기된 방법 ( 캐릭터 접속 API를 만들고, 로그인검증+캐릭터검증에 해당하는 미들웨어를 추가) 의 경우,

기존에 있던 로그인 검증과 동일하게, 캐릭터 접속 당시에만 데이터 베이스를 조회하고

문제가 없을경우 JWT를 발급해서, 이후 발생한 검증은 발급한 JWT를 복호화를 통해 진행되기 때문에,

보안적인 면에서도 유리하고, DB조회도 접속시 1회만 하면 되기 때문에 복잡도 면에서도 유리하다.

==> 24.09.10일 정정

DB 조회는 미들웨어에서도 진행하기 때문에 미들웨어 호출당 1회의 조회를 하게 됨

 

또, 한번의 접속으로 캐릭터를 계속해서 특정할 수 있기 때문에,

클라이언트가 API 마다,  캐릭터id 를 param으로 전달해줘야 하는 일도 없어진다.

 

 

나는 틀리지 않았다

라는 말이 하고싶은것이 아니다.

어디가 잘못된건지 아직도 모르는것이 화가난다

 

 

폐기된 코드에 대한 이야기는 그만 하도록 하자.

 다시 다음 방안으로 넘어가서,

 

 

2. 튜터님의 피드백에 해당하는 내용으로 ,

login 검증 함수를 호출할 당시에, 미리 캐릭터 검증을 포함하는지, 안하는지 정보를 줘서

검증이 필요 없을때는 본래의 코드만, 정보가 있을 때에는 추가 코드를 실행하게 만들어서

각각 온전하게 필요한 부분만 작동이 가능하도록 하는 방법 에 대한 내용이다.

 

이 방법은 내가 당장 이해하기엔 어렵지 않을까 생각이된다.

 

 

 

분노의  TIL 을 작성하다 보니, 오늘 진행한 내용은 결국 작성하지 못했다.

 

다음 시간에 !!

 

 

 

 

 

 

즐거운 토요일... 코딩을 시작해보자.

 

오늘의 목표는 인벤토리를 어느정도 구현해보는 것이다.

 

구현에 앞서서...

인벤토리란 무엇일까?

흔히, '가방' 으로 표현되는 인게임 기능으로, 캐릭터가 항상 들고 다니는 

아이템 보관함 같은것 이라고 할 수 있다.

어플리케이션 마다 인벤토리의 형식은 많은 차이가 있지만,

내가 생각하는 인벤토리의 가장 기본적인 기능은,

 

아이템의 소유자를 명시하는 테이블 이라고 보고 있다.

 

가질 수 있는 아이템의 한계치, 혹은 수량의 중첩 등 부가적인 기능들이 있지만,

기본적으로 어떤 아이템을 '누가' 소유하고 있고, 그 소유자만이 해당 아이템을 사용 할 수 있게 하는것이

인벤토리의 가장 기초적인 기능이라고 생각된다.

 

 

 

그것을 중점으로 생각하여 우선 테이블을 만들어 보았다.

 

inventoryNumber 는 아직 어떤 기능을 할 지 모르겠지만 왠지 필요할 것 같기에 일단 넣어둔 것이고,

 

결국 캐릭터의 Id 와 아이템의 Id만 있다면 아이템의 소유를 명시 할 수 있을것이라 생각하고 만들어봤다.

 

해당 인벤토리 테이블은 서버 내의 '모든' 캐릭터가 소유한 '모든' 아이템을 담게된다.

 

때문에, 인벤토리는 동일한 캐릭터가 여러번 들어갈 수 있어야 하며, 

같은 아이템 또한 여러개의 소지가 가능하기 때문에,

itemId 와 CharacterId를 모두 1:N 관계로 설정했다.

 

이를 통해 만들어진 인벤토리의 모습 

 

여기서 한가지 문제점 (한가지 만은 아닐테지만)을 발견했다....

 

 

현재로서는,

캐릭터의 Id를 특정 할 수가 없는 상태이다.

 

왜냐하면 지금 구현된 부분은, 계정 로그인만 하고 캐릭터는 따로 접속하는 개념이 없기 때문에,

계정 외에는 특정할 수 있는 부분이 없다.

 

해결 방안..?

 

1. 한 계정 당 캐릭터 하나로 제한하여 사실상 캐릭터===계정으로 만들어버린다.

    -> 제작 의도와 거리가 멀기 때문에 탈락

 

2. /api/:character_id/ item~어쩌구 저쩌구

   - 캐릭터 id를 params 로 받는 방법.

   -> 거의 모든 api 에서 해당 character_id가 계정내 캐릭터가 맞는지 검사해야한다.

   -> 유저와 서버와 개발자 모두 별로인 방법

 

3. 로그인 처럼 캐릭터 접속 기능을 만든다.

   -> 지금 생각나는 방법 중에선 가장 그럴싸하다.

 

 

당장 해보자. 

계정 로그인 API 와 캐릭터 삭제 API의 형식이 묘하게 합쳐져서 캐릭터 접속 API 가 되었다.

 

하지만, 문제를 해결하려 할 수록 오히려 문제가 늘어가고 있다...

 

접속에 성공했다면, 해당 캐릭터Id를 어디에 저장해야 할까?

 

쿠키를 두개 쓴다? 제대로 참고할 수 있을지도 잘 모르겠고,

모든 매 통신마다 보내는 쿠키가 하나 더 늘어나는것은 좋지 않은데다가,

 

 

해당 과제 요구사항이 뜻하는 것이 아직 제대로 이해가 되지 않기 때문에

어떻게 해야할지 모르겠다.

 

진전이 없기 때문에, 일단은 지금의 cookie 를 계속 사용하고 나중에 방법을 다시 알아보기로 했다.

 

다시 돌아가...자 마자 문제 발생 !!

 

authMiddleware 가 로그인정보 + 캐릭터 정보 모두 검증하게 만들 계획이었지만,

 

캐릭터 접속을 하기 위해서는 로그인이 되어야 하기 때문에  authMiddleware  검증이 필요하다.

 

마치,

자물쇠가 걸린 방 내부에 자물쇠 열쇠가 있어서 못들어가는것 과 같은 상황이다.

 

해결방안

1. 캐릭터 접속까지 authMiddleware 검증 없이 어떻게든 해결한다??

   -> 캐릭터 접속, 생성, 삭제 등 로그인 만 하면 되는 기능들을 전부 직접 검증해야한다

 

2. 로그인 시 임의의 캐릭터 정보 값을 부여하여, 캐릭터 접속을 가능하게 한다.

   -> 임의의  값 일때 캐릭터 접속 이외의 검증은 통과해선 안되기 때문에

        검증을 필요로 하는 모든 API 에 추가 조건을 만들어야 할듯..

 

3. authMiddleware 를 2가지로 나눈다 !! (계정 검증 / 계정+캐릭터 검증) 

  -> 정말 내키지 않는 방법인데 일단 이 방법이 그나마....

 

 

생각해보면 

로그인만 해도 가능해야하는 API (캐릭터 생성, 캐릭터 접속, 캐릭터 삭제) 와 

캐릭터 접속까지 해야 가능한 API 어느정도 분리되어야 하기 때문에,

authMiddleware 2가지로 나누는게 아주 나쁜 선택은 아닐것이다.

( 평일이 되면 관련 내용을 튜터님에게 피드백 받아보자 )

 

어렵진 않으니 만들고보자.

 

그냥 복붙으로 두개로 나눈다음에, 필요한 곳에서 각각 불러오고, 적용하면 끝이다.

 

캐릭터 접속까지 한 경우를 검증하는 connect.auth 에서는

추가로 char 값이 이상할 때 에러를 발생하게 하면 된다.

 

이 작은 차이 때문에 복사를 해야한다고?! 생각이 되긴한데 다른 방법이 당장 떠오르지 않는다..

 

 

 

캐릭터 접속까지 요구하는 API
캐릭터에 접속
접속 후 동일한 API가 잘 처리된 모습

 

 

이 타이밍에..... 엄청난 반전이 있었으니...

 

 

 

 

이럴수가 ...?!?!?!?

 

 

도전기능

에 해당하는 부분에서, 캐릭터ID 를 param 으로 받는다는 내용이 기재되어 있었다...

 

필수 과제만으로 벅찰것 같아서 자세히 확인을 안해본 도전과제에

저런 내용이 있었다니 !!

 

사실 이는 해당 글의 상단에 있는

 

 

2번 해결방안에 해당하는 내용이었다.

그 해결 방안이 과제에서 제시한 방법이었던 것이다.

 

음.. 별로인.. 방법 아닐지도 ? ㅎ

태세 전환

 

 

 

 

 

 

시작에 앞서서, 어제 문제가 됐던 부분을 다시 살펴보자.

 

 

이번에 사용한 방법은, findFirst를 where 까지만 우선 실행해서,

character.userId 를 얻어내는 방법이다.

 

그 다음 한번 더 find 하는 효율적인 작업을 해서, 

Money에 삼항 연산자를 적용하는 방법이다.

 

 

의도한 대로 작동하지만, 조회 한번 하는데 쿼리를 3번이나 불러오는 현상이 목격되었다.

where 로 2번, select 로 한번 불러 오는 듯 하다.

 

 

음...

character.userId 를 참조하는 쉬운 방법이 분명히 있을것 같은데,

상당히 기초적인 것을 놓치고 있는 느낌이다.

그게 아니라면, where 와 select 사이에서 뭔가 해 줄 수 있을것 같은데, 아직 잘 모르겠다.

 

 

 

 

우선 어제 계획한대로, 강의 부터 모두 듣고난 뒤에 

계속해서 과제를 진행해 보았다.

 

 

오늘 진행한 부분은 아이템 관련 부분이다.

 

위의 아이템 생성 API 는, 로그인 없이도 아무나... 만들 수 있으며,

만들어진 아이템은 items에 추가된다. 

 

items 테이블

 

 

 

조회 기능도 만들어 보았다.

마찬가지로 아직 error 잡는 부분 등은 미흡

 

itemId 2의 상세 조회 결과

잘 작동하는 모습 !!

 

오늘 구현한 기능은 많지 않지만, 

강의를 모두 들었기 때문에 다음 주 부터는 조금 더 속도가 날 것 같다.

 

 

 

 

 

 

CH 3 아이템 시뮬레이터 과제 !

 

아직 강의를 다 듣지 못했지만

조금 급한 마음으로 과제를 시작해보았다.

 

내가 할 수 있는게 맞는지 아닌지가 궁금했기 때문이다.

 

 

 

강의를 토대로 만든 결과 나름대로 잘 작동하는 모습이다 !

 

아직 error 처리라던지,  디테일한 DB 셋팅 등등  할 일이 많지만...

 

그래도 못할 건 없겠다 라는 생각이 들어서 안심이 됐다.

 

 

그래서.. 

 

오늘의 문제 !!

 

 

캐릭터 조회 API 만들기 ..

 

우선, params 를 입력받아 해당 캐릭터Id를 조회하게끔 구현을 해 보았다.

 

문제는 캐릭터 조회가 아니라...

 

바로 이 두 줄에 해당하는 부분이다.

 

 

 

 

아무리 생각해도, 로그인했을때  기록된  userId 와,

현재 조회하는 character 가 가진 userId 가 같은지를 비교하는게 맞는 방향 같은데,

 

로그인 했을 때 userId 는 req.user 가 가지고 있고,

문제는 조회한 character 가 가진 userId 인데,

 

방법을 모르겠다.

방법을 왜 모르는지 모르겠다 !!

 

위에 코드는,

일단 userId를 select 하고  그 select 한 userId를 토대로 비교해서 

Money를 삭제할지 말지 정하는 if문을 추가한 방식이다.

 

문제가 되는 사항은,

userId는 조회 할 필요가 없다.

그래서, userId를 셀렉트 하지 않으면? 

아래 if 문에서 userId를 참조할 수가 없게된다..

userId select를 : false 로 바꾸면?

 

그럼 애초에 select 하지 않아서인지, 참조가 되지 않는다.

 

 

사실, 이 단계까지 오는데도 너무 오랜 시간이 소요되었기 때문에...

수리를 할 시간이 부족했다.. 

이미 자정이 지나버린 시간

 

 

결국,

userId 까지 select 하면 아무튼 의도대로 작동하는 상태

에서 마무리가 되었다. 수리는 좀 나중으로 미루고...

 

 

내일은 조급한 마음을 가라 앉히고 다시 강의를 듣는것에 집중해야겠다.

 

저번 시간에 배운 1계층 - 물리 계층에 이어서,

오늘은 2계층인 데이터 링크  계층에 대해 알아보자.

 

 

데이터 링크 계층 (Data Link Layer)

OSI 7 계층 중, 2계층에 해당하는 내용으로,

- 직접 연결된 서로 다른 2개의 네트워킹 장치간의 데이터 전송을 담당

- Network 장비 간에 신호를 주고받는 규칙을 정하는 계층

- Network 기기 간에 Data를 전송 + 물리 주소를 결정

- 장치 간 신호를 전달하는 물리 계층을 이용해서, 네트워크 상의 주변 장치들 간에 Data를 전송

- Data들을 Network 전송 방식에 맞게 단위화 해서, 해당 단위를 전송

  .. 이때 전송되는 Data를 Frame 이라고 함 !

- Data Link Layer 에서 최종적으로 이더넷 헤더/ 트레일러가 붙는다.

 

 

주요 기능

프레이밍 (Framing)

- 물리 계층으로부터 받은 신호들을 조합해서 프레임(Frame) 단위의 정해진 크기의 Data Unit으로 변환

 

오류 제어 (Error Control)

- Frame 전송 시 발생하는 오류들을 처리

 

흐름 제어 (Flow Control)

- 송수신 측 간에 Data를 주고 받을 때, 적당한 양의 Data를 송수신 할 수 있도록 Data의 흐름 제어

 

접근 제어 (Access Control)

- 매체 상에 기기가 여러개 존재할 때, 데이터 전송 여부를 결정

 

동기화 (Synchoronization)

- 수신 노드에서 데이터 시작을 알 수있도록 송신 노드가 헤더 부분이 시작되기 전 일정한 비트 패턴 (Frame 구분자)

를 첨가하여 동기화 한다.

 

 

이더넷 (Ethernet)

- LAN 환경에서 Data 를 정상적으로  주고받기 위한 규칙

- Data 충돌을 막기 위해 CSMA / CD 프로토콜을 사용

- 현재에는 UTP 케이블 및 광케이블을 사용

- 프리앰블 / 이더넷 헤더 / Layer 3패킷 / FCS 를 추가해서 Frame 생성

 

 

 

데이터 링크 계층의 링크 종류

Point - to Point 방식 

- PPP, Ethernet 스위치와 호스트 사이의 Point  to Point 

 

Broadcast 방식

- 매체를 공유하는 방식으로 케이블형 Ethernet, 무선 LAN 이 해당

 

 

MAC 주소 (Multiple Access Control)

MAC 주소는 데이터 링크 계층에서 사용하는 주소로, 하드웨어의 생산 당시에 찍혀서 생산되기 때문에,

변경할 수 없는 물리적인 주소이다. MAC 주소는 중복될 수 없고, 한개의 기기에 한개의 MAC 만 존재한다.

 

 

 

입문주차를 끝내고, Node.js숙련 주차에 해당하는 강의 ! 

 

에 대한 이야기를 하기에 앞서서,

 

CH3 의 개인과제에 해당하는 내용이 오늘 공개가 되었다.

과제의 발제 자체는 내일이지만, 어쨌든 해야할 과제가 무엇인지는 공개가 되었다.

 

그 과제는 이름하야...

 

CH 3 아이템 시뮬레이터 과제 !!!

 

 

학습 과제를 끝내고 나면 할 수 있어요!  ==> 이걸 모르면 과제를 할 수 없어요 !

 

발제는 내일이기 때문에 간략하게만 살펴 보았는데,

 

Expreess 로 서버 와 api 구성

AWS RDS -> MySQL 을 사용하여 데이터 베이스를 활용

Prisma를 사용하여 데이터 베이스 컨트롤

AWS EC2 를 이용한 배포

 

와 같은 내용으로 구성되어 있었으며

 

조금 더 구체적으로 들어가면,

1. 회원가입 및 로그인 기능

2. 캐릭터 생성 및 캐릭터의 능력치 설정

3. 캐릭터 상세 조회와 캐릭터 삭제 기능

4. 아이템 생성 

5. 아이템 수정 (강화..?)

6. 아이템 목록 조회 및 아이템의 상세정보 조회

 

도전기능으로는 

1. 아이템 구매 및 판매

2. 인벤토리 기능

3. 장착 아이템 목록 조회

4. 아이템 장착 및 탈착 API

5. 게임머니 획득 API

 

 

이걸 만들라구요 ?!

 

 

어라... 이 감정...?

 

24.08.21 TIL - Roglike 과제를 받은 날

 

 

그렇지만 이번에는 정말로 어려울것 같다는 느낌이 든다...

따로 스켈레톤 코드 같은 것이 지급되지 않는것으로 확인이되어,

더 감이 안잡히는것 같다.

 

 

중요한건 강의를 서둘러서 들어야 한다는거임

 

관계형 데이터베이스 

 

오늘 배운 내용은 전부 여기에 해당한다고 볼 수 있다.

 

저번시간에 학습했던 비관계형 데이터베이스인 mongoDB 와 달리

관계형 데이터 베이스에 해당하는 Mysql을 기반으로 학습을 진행했다.

 

 

관계형 데이터 베이스에 해당하는 Mysql 은 이전에 입문단계에 해당하는 강의를 학습한 적이 있다.

때문에 비교적 이해하는데 큰 어려움은 없었다.

 

차이가 있다면, 입문강의에서 진행했던 내용은  DBeaver 를 통해 이미 작성된 database 를 조회하는

방법 위주로 강의가 진행되었기 때문에, 내용을 따지고 보면 오늘 학습하는 내용과 차이가 꽤 크다고 볼 수 있다.

 

오늘은, 실제로 MySQL 데이터베이스를  AWS RDS 를 통해 직접 구동하여 (대여해서...)

Database 와 table, 그리고 column 에 해당하는 부분을 직접 추가하거나 수정하고, 삭제하는 과정을 학습했다.

VScode 에서  SQL 언어을 사용할 수 있고, 

MYSQL 확장 프로그램을 통해 VScode 내에서 사실 다 할 수 있다.

마이크로 소프트.. 그는 대체...

 

 

 

 

오늘 미약한 공포가 느껴졌던 부분

그것은 바로 Raw Query 라고 할 수 있다.

언뜻 보면, 노드 환경에서 직접 SQL 요청을 보내는 평범한 기능으로 생각이 되지만, 

 

// app.js

/** 테이블 생성 API **/
app.post('/api/tables/', async (req, res, next) => {
  const { tableName } = req.body;

  await connect.promise().query(`
      CREATE TABLE ${tableName}
      (
          id        INT         NOT NULL AUTO_INCREMENT PRIMARY KEY,
          name      VARCHAR(20) NOT NULL,
          createdAt DATETIME    NOT NULL DEFAULT CURRENT_TIMESTAMP
      )`);

  return res.status(201).json({ message: '테이블 생성에 성공하였습니다.' });
});

 

이런식으로 Raw Query 를 작성하다 보면, 이건 좀 아닌것 같다는 생각이 스멀스멀 들기 시작한다.

 

강의에서는 Raw Query 최대 단점을, 유지보수가 어려운 점을 꼽았는데,

예를 들면,

createdAt 라는 칼럼이  createDate 라는 칼럼명으로 바뀌어야 한다고 가정해 보자.

우리는 Raw Query로 작성한 createdAt를 전부 찾아서 createDate 로 수정해야만 할 것이다.

 

그냥 바꾸지 말죠? 에서 부터,

최악의 경우, 한 두가지를 놓치는 바람에 서버가 다운되거나 데이터가 변조될 위험성도 있다.

 

ORM  등장 (Object Relational Mapping) 

 

앞선 문제들에 대한 대책으로 ORM이 개발되었고, 강의에서는 ORM 중 하나인 Prisma 를 소개해 주었다.

 

 

 

 

ORM 의 최대 장점은, 데이터 베이스가 통째로 바뀌더라도 유연한 대처가 가능하며, ( ex MySQL -> Oracle)

위에 언급된 문제인 Table 또는 cullum 의 수정 정도는 어렵지 않게 대처할 수 있다.

 

 

Node에서 Prisma를 설치할 경우,

schema.prisma 을 내용을 토대로 database 를 추가하거나 수정할 수 있게 된다.

Posts 라는 새로운 table 과 각 cullum 의 설정을 작성한다.

 

SQL 과 Raw Query 사이 어딘가....로 보이는 형태의 언어이지만,

성능이 확실하다고 설명했기 때문에 왠지 호감도가 올라간 상태 !!

 

이렇게 schema.prisma에 작성한 내용을 

 

 npx prisma db push 명령을 통해 push 하면,

 

 

node_modules/.prisma/index.d.ts  의 어딘가에

JS 형태로 작성이 되어진다.

(이 외에도 예상했던것 보다 많은것이 기록된다...)

 

 

 

Prisma를 통해 Posts table과 각 컬럼 속성 설정에 성공한 결과 !

 

 

오늘 학습한 내용은 여기까지 인데, 아무래도 과제 발제 까지 시간이 촉박 하기 때문에,

 

며칠정도는  TIL 의 작성시간을 줄이고 강의를 더 듣는다거나, 

주말을 포기한다거나 하는 일이 발생할 것 같다...

 

 

 

 

 

어제에 이어서 2주차의 마지막이자, 입문주차의 마지막 부분을 학습했다.

 

범위로만 따지면 2.9~ 2.11에 해당하는 부분이라 금방 끝날 것으로 예상하고

오늘 숙련주차를 들어갈까 말까를 생각했었는데,

 

입문주차 마무리를 겨우 할 정도로 빠듯했다.

 

 

2.9 코드 서식 정리하기

에 해당하는 부분은, 이전 과제에서 Prettier 를 간단하게 다루고 넘어갔기 때문에 

어느 정도는 알고있던 부분이다. 

{
    "printWidth": 50,
    "tabWidth": 4,
    "singleQuote": false,
    "trailingComma": "es5",
    "semi" : false,
    "arrowParens": "always"
  }

나름대로 원하는 스타일로 prettierrc.json 을 작성 해 보았다.

singleQuote 부분은 기본적으로 single을 사용할 예정이지만,

한글이 포함되거나 포함될 가능성이 있는 문자열만 "" 을 사용해보려고 한다. (최소한의 개성..?)

물론 변동될 수 있지만 말이다

 

 

 

2.10 배포를 위한 Git 학습 

또한 이전 과제와 그 전에 해당하는 최초의 과제를 진행하며 어느정도 학습한 부분이 있다.

다만, 이전에는 Github Desktop 라는 최신식(?) 사용자 친화적 인터페이스를 갖는 보조도구를 사용했기에

이번에는 강의에 나온 것 처럼 터미널을 이용하여 Git 과 연동하는 작업을 해 보았다.

 

 

 

Github desktop가 알아서 처리해줬던 부분인,

Git 명령어를 먼저 알아보았다.

 

 

 

 

간단하게, 이미 알고있던 desktop 의 방식과 비교하며 정리해볼까? 

 

 

git init 은 현재 위치한 경로를 git repository로 지정하는 명령어 이다.

GUI 에서는 create new repository를 통해 init 기능을 사용할 수 있다. 

(디렉토리를 지정하여 새로운 레포지토리 생성)

아마 Add local repository도 비슷한 기능인듯 하다.

오히려 이쪽에 가까울지도?

 

변경 사항을 스테이징 영역에 올리는 명령어로..

이 부분은 아마 Github desktop를 이용한다면 스킵되어버리는 명령어로 확인이 되어진다.

뭔가 생소한 기능인것 같아서 검색을 해보니, Github desktop 에서는 commit 과 동시에 add가 이루어진다 

라고 설명하는 경우가 많다.

 

개인적으로는 gui의 인터페이스에서

레포지토리로 지정한 폴더의 모든 파일이 자동으로 add 된다는 느낌이긴 한데 맞을지 모르겠다.

(일단 모든 파일이 add 되고, 커밋을 할지 ignore 할지 결정하는 느낌?)

 

 

 

깃의 대표적인 기능중 하나로, 변경사항을 기록하는 것이다.

이 시점으로 얼마나 내용이 수정되었는지, 등록이 되기 때문에 

원한다면 등록했던 많은 커밋 중 하나의 시점으로 파일을 복원하는것이 가능하며,

추가적으로 코멘트를 달아서 해당 커밋의 내용을 직관적으로 설명할 수 있다.

저지른다

 

 

 

본격적으로 git과 github 를 연결하는 명령어로, 

정확히 말하면 로컬 저장소에 원격 저장소를 추가하는 기능이라고 설명되어있다.

commit, push 한 데이터가 어디로 날아갈지 지정하는 명령어 이다.

 

Github desktop 에서는 첫 레포지토리 생성 후

commit 등 원격 저장소를 필요로하는 작업을 하기 전에 

Publish resository를 통해 지정하게 되어있다.

github desktop 에 로그인된 github 계정에 자동으로 연결된다.

 

 

 

클론으로 repository를 생성한다면, 

클론 할 원격 저장소의 주소가 바로 필요하기 때문에, 시작과 동시에 

입력을 하게 되어있다.

git clone 과 remote add 기능이 합쳐졌다고 보면 될 듯 하다.

 

push 는 commit 한 내용을 본격적으로 원격 레포지토리에 업로드 하는 명령어 이다.

 

GUI 에서는 commit 을 하면, 자동으로 push 를 진행하는 버튼이 활성화 된다.

 

 

 

 

push 가 업로드 라면, pull 은 다운로드 라고 할 수 있다.

원격 저장소에 push된 최신 변경사항들을  로컬 저장소로 가져오는 작업을 말한다.

GUI 에서는 해당 명령을 통해, 아직 적용되지 않은 최신 사항을 체크 할 수 있으며,

있을 경우 pull 버튼이 활성화 되어, 비교 및  적용이 가능하다.

 

 

 

2. 11    AWS 배포하기 

오늘 가장 난해했던 부분이다.

 

이전 강의를 통해 만든 서버를 본격적으로 배포하기 위한 수단으로...

기본 사양의 서버인 EC2 에 해당하는 서버를 1년 동안 무료로 사용할 수 있는

대인배  클라우드 서비스 라고 할 수 있다. (물론 프리티어라도 막쓰면 1년 내에 요금이 나올수도 있다)

 

 

강의에서 알려주고는 있지만, 기본적으로 사용한 만큼의 요금이 발생하는 유료 서비스 이며,

잘못 사용 할 경우 금전적인 문제 또는 정보 유출 등의 문제가 발생할 수 있기 때문에, 

추가적으로 알아보고 조심조심소심소심; 하며 진행했고....

시간을 많이 잡아 먹었다 ! !

 

아무튼간에 회원가입과 EC2 인스턴스의 생성은 마쳤고,

 

 

pm2 를 활용하여 app.js 를 구동하는것도 성공했다.

 

웹 브라우저로 접근했을때도 정상적으로 작동하는 모습 !!

위에 보이는 5252 는, 같은 조원이 해당 페이지에 접속해서 남긴 글이다. 

다른 사람도 접근이 가능한것을 확인 !

 

 

pm2 log를 통해 로그도 정상적으로 확인 가능하다.

 

 

여기까지 봤으면 그래도 오늘 할 일은 다 한듯 하다..

 

 

 

 

상당히 골치아픈 문제가 나와서 글로 작성해 보았다.

이게 level 1 이라니...

 

얼핏보면 수월하게 풀 수 있을것 같은 문제지만 (그렇게 생각했었는데요...)

제한 사항을 보면 X와 Y의 길이가 최대 300만 으로 정해져 있으며,

이로 인해서 이중 for문 등을 사용하면 시간복잡도가 급증해 시간초과의 결과가 나오게 된다.

 

 

풀이 설계

 

1. X 와 Y 를 내림차 정렬을 한다.

2. X 와 Y를 맨 앞에서부터 비교하여 같을 경우 answer 에 추가

3. X[0] Y[0] 이 같지 않을 경우 둘 중 작은 값의 다음 index를 검사

4. "0" 의 결과와 "-1" 의 결과를 따로 리턴

 

늘 그렇지만, 이번에도 대단히 효율적이진 않을것 같은 풀이 방향이다.

내림차 정렬을 위한 sort 2회, X와 Y의 값을 비교하기 위한 반복문이 기본적으로 필요하기 때문.

 

 

풀이 시작

 

    X= [...X].sort((a,b) => b-a)
    Y= [...Y].sort((a,b) => b-a)

시작은 역순정렬 부터 한다. 큰 값으로 정렬하여 비교하면 유리할 거라는 생각이었으나,

사실, sort 과정 자체가 시간복잡도를 많이 잡아먹어버린다.

어쨌든 그렇게 설계했기 때문에 이렇게 시작해 보도록 하자.

 

이 과정에서 저번 알고리즘 때 배웠던 [...str] 을 이용한 배열화를 사용해 보았다.

 

그 후, X와 Y를 비교하기 위해 반복문을 사용해야 하는데, 

while 문을 사용하기로 했다. 

 

    let i = 0;
    let j = 0;
    while ( X.length > i && Y.length > j) {
        if ( X[i] === Y[j]) {
            answer += X[i]
            j++ 
            i++
        }

X 와 Y의 끝까지 비교를 해야할 것으로 예상되기 때문에, 위 같이 조건을 주었고,

가장 중요한 X[i] ===Y[j] 일 경우 answer에 추가하는 것 까지는 어렵지 않게 진행되었다.

 

 

잘못된 풀이..?

그 전에, 잠깐 뻘짓을 했던 과정을 살펴보자면

    while ( X.length > i && Y.length > j) {
        if ( X[0] === Y[0]) {
            answer += X[0]
            X= X.substring(1)
            Y= Y.substring(1)
        }

위처럼 동일한 조건으로 진행하지만, i j 를 통해 다음 index로 넘어가는것이 아니라,

.substirng(1) 을 통해  X,Y의 0번의 index만 검사하도록 진행해 보려는 시도를 했었는데, 

굳이 substring 이라는 메서드가 사용된다는 점 때문에 폐기 되었다.

 

시도했던 이유와 폐기한 이유??

 

substring 은 문자열의 0번째 ~를 제거하는 메서드이다. 

참조형이 아닌 기본형 타입의 string을 변조하는건 시간복잡도가 1회 일 것으로 예상을 했었으나,

기본적으로 substring() 의 시간 복잡도는 O(n) 에 해당했다.

글 마무리에 substring을 사용했을 때와, index 순번을 사용했을 때의 시간차이를 비교해보도록 하겠다.

 

 

자, 다시 풀이로 돌아와서, 이제 X[i] 와 Y[j] 가 다른 값일 경우만 처리하면 된다.

 

생각이 닿기는 좀 시간이 걸렸지만,

    while ( X.length > i && Y.length > j) {
        if ( X[i] === Y[j]) {
            answer += X[i]
            j++ 
            i++
        } else if (X[i] > Y[j]){
            i++          
        } else { 
            j++}
        
    }

X[i] 와 Y[j] 의  둘 중 큰 값을 비교해서 

큰 값을 가진쪽의 index 를++ 해서  다음 index를 참조하게 하면,

점점 값이 작아지며 서로 같은 값이 나올때 까지 넘어가게 된다.

 

마무리로, 

    if (answer[0] === "0" ) { return "0"}
    return answer === ""? "-1" : answer

"000000" 만 나온 경우와  일치하는값이 하나도 없을경우만 따로 빼주면 정답이 완성된다.

 

답 코드

function solution(X, Y) {
    var answer = '';
    
    X= [...X].sort((a,b) => b-a)
    Y= [...Y].sort((a,b) => b-a)
    let i = 0;
    let j = 0;
    while ( X.length > i && Y.length > j) {
        if ( X[i] === Y[j]) {
            answer += X[i]
            j++ 
            i++
        } else if (X[i] > Y[j]){
            i++          
        } else { 
            j++}
        
    }
    if (answer[0] === "0" ) { return "0"}
    return answer === ""? "-1" : answer
}

 앞서 말한것 처럼, sort와 반복문이 사용되며 결론적으로 그렇게 효율적인 코드는 아니게 되었다.

 

딱 봐도  11~ 15번 case 가 헬 구간 ( X,Y length가 길게 주어지는듯)

 

 

 

 

 

그렇다면, substirng 의 경우 얼마나 차이가 날까?

function solution(X, Y) {
    var answer = '';
    
    X= [...X].sort((a,b) => b-a).join('')
    Y= [...Y].sort((a,b) => b-a).join('')
    while ( X.length > 0 && Y.length > 0) {
        if ( X[0] === Y[0]) {
            answer += X[0]
            X= X.substring(1)
            Y= Y.substring(1)
        } else if (X[0] > Y[0]){
            X= X.substring(1)       
        } else {
            Y= Y.substring(1)}
        
    }
    if (answer[0] === "0" ) { return "0"}
    return answer === ""? "-1" : answer
}

.substirng(1) 이기 때문에 별로 차이가 없지 않을까 하는 생각에서 완성을 해 보았다.

 

 

유의미한 차이가 있다.

substirng(1) 이라고 해도 결코 상수의 처리시간이 걸리는건 아닌것으로 보인다.

물론 X 와 Y를 추가로 join 해주는 과정에서도 추가적인 시간이 소요됐겠지만 말이다.

 

대충 정리를 하자면 

11~15번 케이스에서 걸린 시간은 

기본 답 코드 ( X[i] Y[j] 로 비교) = 약 2300ms 가량 

두 번째 테스트 ( 기본 답 코드에서 X,Y를 join만 해준 경우)  = 약 2500ms 가량 (join에 걸리는 시간 대략적으로 확인)

세 번째 테스트 ( join 이후 substring을 통해 X[0] Y[0] 으로 비교) =  약 2800ms 가량 ( join과 substring에 걸리는 시간 확인)

 

 

당연히~~~~~

X[i] Y[j] 로 비교하는것이 빠르겠지만, 순전히 궁금증이 생겨나서 

테스트를 해 보게 되었다.

결론은.... 애초에 sort 가 제일 오래 걸리는듯 함 (ㅠ)

 

 

 

물리 계층은 OSI의 7 계층 중 가장 아래에 위치한 1계층으로, 하드웨어로 표현된다.

 

네트워크 장치의 전기적, 기계적 속성 및 전송수단을 정의한다고 할 수 있다.

 

 

전기 신호 하면 가장 먼저 떠오르는 0 과 1 을 이용한 통신이 여기에 해당한다.

 

물리 계층 장비

허브 Hub

전기 신호를 증폭시켜서 연결된 장치간의 통신을 가능하게 해준다.

흔히 말하는 랜선이 허브에 해당된다.

허브는 중계기 역할을 하며, 하나의 장치가 허브로 연결된 모든 장치에게 데이터를 전달하고

이것을 브로드 캐스팅 Broad - casting 이라고 한다.

 

이 방식은 여러 장치가 허브로 연결된 상태에서 1:1 통신이라기 보다는

1: 모든 장치로 통신 데이터를 전송하여 송신 대상만 데이터를 처리하고 

나머지는 데이터가 무시된다고 설명되어있다.

 

리피터 Repeater 

신호의 세기를 증포해서 먼거리의 통신을 가능하게 해주는 장치

 

 

 

물리 계층과 물리 계층 간의 통신은 전기, 빛 , 전파를 통해 통신을 하며 

이러한 것들을 Signaling 이라고 한다.

 

전기

UTP, 전화선, 동축 케이블이 해당

 

광섬유 케이블 (빛의 패턴)

 

전파

wi-fi 와 같은 무선인터넷 (마이크로파 패턴을 신호로 사용)

 

전송 단위

 

Signaling의 방식은 위처럼 다양하지만, 어찌됐던 물리 계층이기 때문에

이 모두는 전기 신호인 0, 1 로 데이터를 인코딩해서 전송한다.

 

 

 

+ Recent posts