✅☑️❌
학습과 망각
뱀 풀고 복기하는데 엉망진창인 부분이 너무 많았다.
유용한 테크닉들은 배웠지만 완벽히 흡수하고 실전에서 이대로 나올 수 있을지 모르겠음.
일단 최대한 내 걸로 만들어보려고 이해를 다듬는 과정도 거쳤지만,
머리에 완전히 들어오진 않는 느낌임.
비슷한 거 풀다보면 익혀지려나?
그리고 이렇게 많이 얻어가는 문제들 있으면 잘 못 푼 풀이라도
블로그에 올린 다음 가끔씩 or 코테 전에 봐주면 좋을듯.
우선 cmu 숙제 pdf를 읽어봄.
웹 proxy는 웹 브라우저와 end server 사이의 중계자다.
웹 페이지 얻으려고 end server에 직접 접촉하는 대신, 브라우저는 proxy에 접촉한다.
end server가 proxy에 답신하면 proxy는 답변을 브라우저에 보낸다.
proxy는 방화벽, 익명화, 캐시 서버 등 여러 용도로 쓰일 수 있다.
이 숙제는 간단한 HTTP proxy를 만드는거다.
일단 이 기능들 되는 proxy를 만들어봐라.
1. 들어오는 연결을 수락함
1. 요청을 읽고 수정해서 웹 서버로 보내기
1. 웹 서버의 응답을 클라로 전달하기
이거 하면 HTTP가 어떻게 동작하는지,
소켓을 사용해서 네트워크 연결 어떻게 구현하는지 알게 될거다.
그 다음으론 proxy 업그레이드해서 여러 동시 연결을 받을 수 있게 해봐라.
그거 하면 동시성에 대응하는 방법 알게 될거임.
그것도 다 하면 최근에 접근했던 웹 컨텐츠를 메인 메모리에 cache해서
proxy가 caching할 수 있게 만들어라.
일단 다 만들고 테스트를 해보았으나 실패.
어디서부터 실패했는지 디버깅 시작.
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는 사라지고 다른 오류가 발생했다.
이것도 동일한 함수(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가 떴다.
프록시 실행 후 브라우저로 접속해보았다.
단서 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 정보 넘겨주기
어디에서 문제가 발생하는 건지 확실하게 찍어보려고 디버깅 출력을 넣어봤는데,
터미널에서 보이지 않음.
이전 코드에서 표준 출력이 소켓으로 지정되어버렸거나,
그냥 안 나오는 거일듯?
다른 곳에 찍어봤더니 멀쩡히 나옴. (@@@@ 부분)
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이 터미널에 갑자기 왜 끼어드는거지?
아직 이 변수는 사용도 안 했는데?
라는 건 내 생각이고, 어딘가에서 사용되었으니 오류를 일으키는 거겠지? 생각해보자.
:/8080
이 나오는게 수상함. 이거 분명 내가 parse_uri에서 만져주던 그거임.:/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까지 하고 다시 해봤는데 똑같은 결과.
결론:
코드가 저주에 걸렸다고 판단,
tiny를 가져와서 처음부터 다시 쌓아가보기로 함.
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
위와 같은 블로그의 코드인데,
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차원 배열이라 코드 더러웠었는데 이렇게 하면 확실히 간단할듯.
알고리즘 스터디 발표를 위해 필기한 것.
깔끔하게 정리해서 블로그에 내용 추가하자.