크래프톤 정글 TIL : 0820

lazyArtisan·2024년 8월 20일
0

정글 TIL

목록 보기
51/147

🎯 To-do List


⌚ Time Tracking

  • 09:30 - 10:15 : 아침, 라운지 이동, 공부 세팅
  • 10:15 - 11:30 : 3190 뱀 풀기
  • 11:30 - 12:00 : 문제 푼 거 복기
  • 12:00 - 13:00 : 점심, 교육장 이동
  • 13:00 - 13:15 : 알고리즘 스터디 발표 준비
  • 13:15 - 14:00 : 프록시 서버 구현
  • 14:00 - 14:10 : 퀴즈
  • 14:10 - 17:30 : 프록시 서버 구현
  • 17:30 - 18:05 : 저녁
  • 18:05 - 19:00 : 프록시 서버 구현
  • 19:00 - 21:20 : 알고리즘 스터디 발표, 운동, 샤워, 세탁
  • 21:20 - 22:05 : 프록시 서버 구현
  • 22:05 - 22:35 : 빨래 널기, 공부 자리 이동
  • 22:35 - 02:00 : 프록시 서버 구현
  • 02:00 - 02:30 : 교육장 이동
  • 02:30 - (다음 날 TIL) : 프록시 서버 구현

🔆 Today

  1. 알고리즘 스터디 | 3190 뱀 풀기 ✅
  2. 기본적 프록시 구현 ☑️

🍪 Plan

  • 기본적 프록시, 여러 동시 연결 프록시, 캐시 서버 프록시
  • 퀴즈 봤던거 정리 (기술 면접 준비 +알파 느낌으로)
  • CS:APP 읽기 | 6장

✅☑️❌



📚 Journal


학습과 망각

뱀 풀고 복기하는데 엉망진창인 부분이 너무 많았다.
유용한 테크닉들은 배웠지만 완벽히 흡수하고 실전에서 이대로 나올 수 있을지 모르겠음.

일단 최대한 내 걸로 만들어보려고 이해를 다듬는 과정도 거쳤지만,
머리에 완전히 들어오진 않는 느낌임.
비슷한 거 풀다보면 익혀지려나?

그리고 이렇게 많이 얻어가는 문제들 있으면 잘 못 푼 풀이라도
블로그에 올린 다음 가끔씩 or 코테 전에 봐주면 좋을듯.



🌐 Web Server


🔷 Proxy : Sequential

우선 cmu 숙제 pdf를 읽어봄.

0. Introduction

웹 proxy는 웹 브라우저와 end server 사이의 중계자다.
웹 페이지 얻으려고 end server에 직접 접촉하는 대신, 브라우저는 proxy에 접촉한다.
end server가 proxy에 답신하면 proxy는 답변을 브라우저에 보낸다.

proxy는 방화벽, 익명화, 캐시 서버 등 여러 용도로 쓰일 수 있다.

이 숙제는 간단한 HTTP proxy를 만드는거다.

일단 이 기능들 되는 proxy를 만들어봐라.

1. 들어오는 연결을 수락함
1. 요청을 읽고 수정해서 웹 서버로 보내기
1. 웹 서버의 응답을 클라로 전달하기

이거 하면 HTTP가 어떻게 동작하는지,
소켓을 사용해서 네트워크 연결 어떻게 구현하는지 알게 될거다.

그 다음으론 proxy 업그레이드해서 여러 동시 연결을 받을 수 있게 해봐라.

그거 하면 동시성에 대응하는 방법 알게 될거임.

그것도 다 하면 최근에 접근했던 웹 컨텐츠를 메인 메모리에 cache해서
proxy가 caching할 수 있게 만들어라.

1. Segmentation fault : parse_uri

일단 다 만들고 테스트를 해보았으나 실패.
어디서부터 실패했는지 디버깅 시작.

telnet으로 테스트해보았더니 연결까지는 됨.
사실 여기까진 당연.
tiny 그대로 복붙해서 수정한 건데 이 부분은 수정 안 했으니까.

디버깅 툴을 사용하기 전에 일단 코드를 훑어봄.

/* uri에 담긴 정보 추출 */
void parse_uri(char *uri, char *uri_trim, char *host)
{
  // http://www.cmu.edu/hub/index.html 이거를
  // /hub/index.html 이걸로 바꿔서 uri_trim에 넣어야 함
  char *ptr = uri;
  int count = 0;
  
  while ((ptr = strchr(ptr, '/')) != NULL) {
      count++;
      if (count == 3)
          break; // 세 번째 / 찾으면 끝
      ptr++; // 다음 위치로 이동
  }

  strcpy(host, ptr+1); // 앞에 필요없는 것들 빼고 복사
  *ptr = '\0'; // 복사한 후엔 삭제
  strcpy(uri_trim, uri);

  return;
}

segmentation이 일어날 만한 부분들을 눈여겨 봤는데,
딱 봐도 이게 문제였음.

/ 뒤에 뭐가 있으면 상관 없겠지만,
/ 뒤에 아무것도 없으면 바로 오류 일어날 거임.

/* uri에 담긴 정보 추출 */
void parse_uri(char *uri, char *uri_trim, char *host)
{
  // http://www.cmu.edu/hub/index.html 이거를
  // /hub/index.html 이걸로 바꿔서 uri_trim에 넣어야 함
  char *ptr = uri;
  int count = 0;
  
  while ((ptr = strchr(ptr, '/')) != NULL) {
      count++;
      if (count == 3)
          break; // 세 번째 / 찾으면 끝
      ptr++; // 다음 위치로 이동
  }

  if(!(uri[strlen(uri)-1] == '/'))
  {
    strcpy(host, ptr+1); // 앞에 필요없는 것들 빼고 복사
    *ptr = '\0'; // 복사한 후엔 삭제
  }

  strcpy(uri_trim, uri);

  return;
}

/ 가 마지막 문자가 아닐 때만 복사하도록 했더니
segmentation fault는 사라지고 다른 오류가 발생했다.

2. Name or service not known : parse_uri

이것도 동일한 함수(parse_uri)에서 오류가 발생한 것이라고 추측하였다.

  if(!(uri[strlen(uri)-1] == '/'))
  {
    strcpy(host, ptr+1); // 앞에 필요없는 것들 빼고 복사
    *ptr = '\0'; // 복사한 후엔 삭제
  }

  strcpy(uri_trim, uri);

http://www.cmu.edu/hub/index.html이라는 uri를 받았다면

host는 앞 부분이고, trim_uri는 뒷 부분인데 반대로 넣어줬다.

  if(!(uri[strlen(uri)-1] == '/'))
  {
    strcpy(uri_trim, ptr+1); // 앞에 있는 host 빼고 복사
    *ptr = '\0'; // 복사한 후엔 삭제
  }

  strcpy(host, uri); // 앞에 남아있는 host 정보 넘겨주기

고쳐준 뒤에 함수들 전방 선언도 마저 해주고
디버깅 출력 코드도 삽입했더니

다시 segmentation fault가 떴다.

3. Segmentation fault : bug by debug?

프록시 실행 후 브라우저로 접속해보았다.

단서 1. 브라우저로부터의 요청 헤더는 정상적으로 출력되었다.
단서 2. parse_uri에 있는 디버깅 출력은 출력되지도 않았다.

요청 헤더를 읽어오는 read_requesthdrs 뒤에 바로 parse_uri가 있었다.
parse_uri에서 문제가 발생했다는 것은 명확하다는 뜻.

  // printf('@@@@@@@@@@@@@host: %s', host);
  // printf('@@@@@@@@@@@@@uri_trim: %s', uri_trim);

디버깅을 위해 넣어놨던 코드를 주석처리 했더니
다시 Name or service not known 오류로 돌아갔다.

#include <stdio.h>

int main()
{
    char hi[2] = "";
    printf(hi);
}

비어있는 변수를 출력하면 오류가 생기는건가? 해서
테스트 c 코드를 만들어봤는데 딱히 오류가 생기진 않음.

  if(!(uri[strlen(uri)-1] == '/'))
  {
    strcpy(uri_trim, ptr+1); // 앞에 있는 host 빼고 복사
    *ptr = '\0'; // 복사한 후엔 삭제
  }

  printf("it's good until here");

  strcpy(host, uri); // 앞에 남아있는 host 정보 넘겨주기

어디에서 문제가 발생하는 건지 확실하게 찍어보려고 디버깅 출력을 넣어봤는데,
터미널에서 보이지 않음.

이전 코드에서 표준 출력이 소켓으로 지정되어버렸거나,
그냥 안 나오는 거일듯?

다른 곳에 찍어봤더니 멀쩡히 나옴. (@@@@ 부분)

4. Name or service not known : back to parse_uri

getaddrinfo failed (/:8080GET / HTTP/1.1
): Name or service not known

어디서부터 잘못된건지 잘 모르겠어서
일단 터미널에 확실하게 나오는 getaddrinfo 부분부터
다시 조사해보기로 함.

디버깅을 일일이 printf로 찍어보니 read_requesthdrs부터 문제가 됨을 알 수 있었다.

분명히 "출력 안됨"까지는 문제 없이 출력이 돼야 하는데 출력이 안된다.

tiny 서버와 다 똑같은데

char port[4] = "8080";

이 부분 선언한게 문제가 된 것 같음. (왜인지는 모르겠지만)
getaddrinfo에서도 /:8080이 먼저 나오는 것이 수상함.

/* 소켓 정보 가져오게 하고 할 일 하게하는 main 함수 */
int main(int argc, char **argv)
{
  int listenfd, connfd;                  // 듣기 식별자, 연결 식별자 (서버 소켓과 클라이언트 소켓)
  char hostname[MAXLINE], port[MAXLINE]; // 클라 호스트이름, 포트 (혹은 아이피 주소, 서비스 이름)
  socklen_t clientlen;                   // 클라 주소 크기 (sockaddr 구조체 크기)
  struct sockaddr_storage clientaddr;    // 클라 주소 정보 구조체 (IPv4/IPv6 호환)

ctrl + f 해보니 main 함수에서도 똑같은 port가 선언되어 있음.
static 변수가 아니라서 문제가 발생할 일이 없을 것 같긴 한데
어딘가에서 꼬여서 그런건가?

바꾸고 다시 해보았지만 안됨.

아니 애초에 8080이 터미널에 갑자기 왜 끼어드는거지?
아직 이 변수는 사용도 안 했는데?

라는 건 내 생각이고, 어딘가에서 사용되었으니 오류를 일으키는 거겠지? 생각해보자.

  1. getaddrinfo failed 뒤에 :/8080이 나오는게 수상함. 이거 분명 내가 parse_uri에서 만져주던 그거임.
  2. :/8080 뒤에 나오는게 GET / HTTP/1.1이라는 것도 수상함. 주소가 /으로 바뀐 것도 분명 parse_uri임

그니까 read_requesthdrs에서 while문 다 돌고 printf가 안 되는 건 일단 넘기고,
parse_uri로부터 요청 라인까지 가는 과정을 추적해봐야겠음.

현재 8080이 적혀있는 server_port를 사용하는 곳은

open_clientfd밖에 없었음.
open_clientfd에 뭔가 문제가 있어서 그런건가?
Open_clientfd라고 해야되나? 라고 생각해서
대문자로 바꾸고 실행해봤더니 No such file or directory라는 오류가 뜸.

즉, parse_uri까지는 정상 작동하고 있었다는 것을 확인함.

이상한 값이 uri_trim과 host에 넣어져서 그랬던 것임.

int open_clientfd(char *hostname, char *port) {
    int clientfd, rc;
    struct addrinfo hints, *listp, *p;

    /* Get a list of potential server addresses */
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_socktype = SOCK_STREAM;  /* Open a connection */
    hints.ai_flags = AI_NUMERICSERV;  /* ... using a numeric port arg. */
    hints.ai_flags |= AI_ADDRCONFIG;  /* Recommended for connections */
    if ((rc = getaddrinfo(hostname, port, &hints, &listp)) != 0) {
        fprintf(stderr, "getaddrinfo failed (%s:%s): %s\n", hostname, port, gai_strerror(rc));
        return -2;
    }

open_clientfd의 원형을 찾아가보니 저 알수 없었던 오류 출력의 정체도 알 수 있었음.

hostname에 /이 들어가 있고,
port 자리에는 :/8080GET / HTTP/1.1이 들어가 있음.

한 마디로 총체적 난국.
아까 고칠 때 잘못 고친듯.

결국엔 parse_uri가 또 잘못함.

혹시 내가 코드를 잘못 짜서 printf의 출력이 안 나오는건가 싶어서
tiny 서버에 있던 parse_uri에 printf를 갈겨봤다.
이 친구는 정상적으로 나옴.

내 parse_uri에 뭔가 심각한 결함이 있는게 분명함.
모든 단서가 parse_uri를 가리키고 있음.

얘가 소켓 연결도 버퍼도 출력도 모두 망가뜨리고 있음.

라고 생각해서 전부 주석처리 한 다음 접속해보았더니
똑같은 오류가 나왔다.

도저히 못 믿겠어서 몇 번이고 Ctrl+s 눌렀다가 make하고 테스트해봐도 같은 결과.
make clean까지 하고 다시 해봤는데 똑같은 결과.

결론:

1. Restart!

코드가 저주에 걸렸다고 판단,
tiny를 가져와서 처음부터 다시 쌓아가보기로 함.



⚔️ 백준


📌 3190 뱀

import sys
input = sys.stdin.readline

N=int(input())

# 사과 받기
K=int(input())
apple=[[False for _ in range(N)] for _ in range(N)]
for _ in range(K):
    a, b = map(int,input().split())
    apple[a-1][b-1] = True

# 방향 변환 정보 받기
L = int(input())
dir = []
for _ in range(L):
    dir.append(list(input().split()))
dir.append([0,0])

# 오른쪽으로 방향 90도 회전
# (0,1) -> (1,0)
# (1,0) -> (0,-1)
# (0,-1) -> (-1,0)
# (-1,0) -> (0,1)

# 방향 변환 정보는 0이 될 때까지 계속 확인,
# 시간이 일치하면 방향을 바꾸고 다음 인덱스로 넘어감
def changeDirection(direction, time):
    global idxD
    if int(direction[0]) == time:
        # 오른쪽으로 방향 회전
        if direction[1] == 'D':
            idxD += 1
            return 1
        else:
            idxD += 1
            return -1
    else:
        return 0

from collections import deque
# 꼬리는 가장 처음 들어간 값

# heading만큼 좌표를 더해준 후에
# 게임이 끝났는지 확인하고
# 사과를 못 먹었다면 꼬리를 삭제한다
# 시간을 더해준다
# 방향을 바꿔준다

# 방향을 관리하는 정보들
heading = [[0,1],[1,0],[0,-1],[-1,0]]
idxH = 0
idxD = 0
# 꼬리부터 큐로 들어간다
body = deque()
bodyArr = [[False for _ in range(N)] for _ in range(N)]
body.append([0,0])
time = 0

while(1):
    head = [body[-1][0] + heading[idxH%4][0], body[-1][1] + heading[idxH%4][1]]
    body.append(head)
    # 시간을 더해준다
    time += 1
    # 벽에 부딪히면 종료
    if not ((0 <= head[0] < N) and (0 <= head[1] < N)):
        break
    # 자기 몸에 부딪히면 종료
    if bodyArr[head[0]][head[1]] == True:
        break
    bodyArr.append(head)
    bodyArr[head[0]][head[1]] = True
    # 사과가 없으면 꼬리 삭제
    if not apple[head[0]][head[1]]:
        bodyArr[body[0][0]][body[0][1]] = False
        body.popleft()
    else:
        apple[head[0]][head[1]] = False
    # 방향을 바꿔준다
    idxH += changeDirection([dir[idxD][0],dir[idxD][1]],time)
    
print(time)

문제에서 필요로 하는 정보량이 너무 많았다.
일단 1시간 걸려서 꾸역꾸역 풀긴 했는데
이런걸 실전에선 30분만에 풀어야 한다고?
라는 생각에 코울증 올뻔.

게다가 꾸역꾸역 푼 탓에 코드가 상당히 더러워졌다.
문제에서 주어지는 조건이 널널해서 최적화 안 하고 코드 풀어도 됐는데,
그 부분 하나를 놓쳤었음.

어차피 방향 전환의 개수가 별로 안 많아서
def changeDirection(direction, time): 이 함수 안 만들고

 if time in time_dic:
        # 만약 시계방향으로 돈다면
        if time_dic[time] == 'D':
            d = (d + 1) % 4
        else:
            d = (d - 1) % 4

코드 출처 : https://ji-gwang.tistory.com/473

이런 식으로 대충 해버려도 됐을텐데, 괜히 발상하고 함수 쓰는데에 시간 날렸다.

실전에서 문제 빨리 풀 때도 어디까지 최적화를 안해도 되는지를 잘 생각해야겠다.
그러니까, 풀이 작성 시간과 시간 복잡도 사이의 trade-off를 잘 조율해야겠다.

    # 벽에 부딪히거나 자기 몸과 부딪히면
    if x < 0 or x >= N or y < 0 or y >= N or arr[x][y] == 2:
        break
    # 벽이 아니면
    # 사과가 없다면
    if not arr[x][y]:
        # 꼬리 없애주고
        i, j = snake.popleft()
        arr[i][j] = 0

위와 같은 블로그의 코드인데,

  1. arr를 따로 만들지 않고 한 곳에서 정보를 취합한 점, (0,1,2로 정보 구분)
  2. 어차피 자기 자신의 몸이 있는지는 앞에서 확인했으니 뒤에서는 배제하고 if not arr[x][y]만 사용한 점

코드가 깔끔해진 요인인듯.
사실 테크닉들은 알고 있었는데 빨리 풀어야겠다는 조급함이 있었다보니까
시야가 좁아져서 떠올리지도 못했다. 결국 실력 문제긴 함.
긴장하거나 조급해져도 바로바로 떠올릴 수 있어야 함.

# 우하좌상
dx = [0, 1, 0, -1]  # 행
dy = [1, 0, -1, 0]  # 열

...

x += dx[d]
y += dy[d]

이 부분도 아이디어가 좋다.
dx랑 dy를 따로 만들어놓고 같은 인덱스로 접근해버리면
[[0,1],[1,0],[0,-1],[-1,0]] 이랑 같은 효과를 내는구나.
2차원 배열이라 코드 더러웠었는데 이렇게 하면 확실히 간단할듯.

알고리즘 스터디 발표를 위해 필기한 것.
깔끔하게 정리해서 블로그에 내용 추가하자.

0개의 댓글