Skip to content

dudcks0994/42seoul

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 

Repository files navigation

Projects of 42Seoul

42Seoul

  • 가르치는사람과 교재 없이 주어진 문제를 동료학습을 통해 능동적으로 해결하는 방법을 배우는 교육기관

Minishell

과제 목표 : C99의 제한된 함수들을 이용해 기본적인 기능들을 가진 리눅스 쉘 프로그램 구현

heredoc

  • 실제 bash는 input 데이터가 파이프 버퍼사이즈보다 작으면 그 안에 저장하는데, 실행파트의 통일성을 위해 임시파일에 저장하는 방법으로 대체
  • heredoc 실행 중 시그널은 부모프로세스에게도 전달이 되면 안되므로 부모프로세스에서 특정시그널 무시처리
  • 입력종료시, heredoc토큰을 리다이렉션으로 변경 및 임시파일이름을 추가하여 처리
  • 여러개 heredoc 실행 중에 종료되는 경우 임시파일들 처리를 위해 따로 파일이름을 리스트에 저장후 관리

환경변수

  • export를 통해 환경변수 추가/수정 가능하게끔 환경변수 네이밍 규칙대로 파싱
  • 환경변수 치환 이후 다시 메타캐릭터를 통한 토큰화가 이루어지는 bash처럼, 파싱단계가 아닌 실행단계 직전에 환경변수 치환
  • 쉘 안에서 프로그램 실행 시, 변경된 환경변수들 적용되게끔 기존 환경변수들을 리스트에따로 담아서 저장후, 실행시 환경변수 새로 만들기
  • 각 토큰을 순회하면서 환경변수가 있다면 새로운 문자열을 만들어 치환 이후의 문자열을 만들어 대체

시그널 처리

  • bash처럼 쉘 내에 Ctrl+C 와 Ctrl+| 등의 동작이 일반 프로그램과 동작이 다르게끔 설정(리턴코드도 변경)
  • $? 구현을 위해 heredoc 및 명령어실행시의 자식프로세스 종료코드를 전역변수로 저장
  • 자식프로세스에서 받는 시그널이 부모프로세스에게 영향을 끼치지 않게 SIG_IGN을 이용해 무시

실행부

  • 만들어진 토큰을 확인하면서 환경변수 치환 후, 다시 토큰화가 된다면 토큰화(bash)
  • 리다이렉션 토큰 확인후 미리 fd확보, 예외처리(권한 및 I/O에러)
  • 파이프가 존재 할 경우, 명령어 세트별로 연결 리스트를 만든 뒤 프로그램, 인자, 리다이렉션 파일 fd 를 저장
  • fork하면서 파이프 명령어 병렬실행, 파이프를 통해 각 프로세스들의 입력과 출력 연결
  • 모든 프로세스들 waitpid로 회수 후 마지막 프로세스의 종료코드를 저장

배운 점

  • 자식프로세스를 생성하는 heredoc이나 cmd실행에서, SIGINT 등을 했을때 다같이 종료되거나 표준입력이 이상해지는 현상
    • 시그널은 부모와 자식 모두에게 전달되기때문에, heredoc은 fork직후, cmd실행은 실행 직전(다른프로세스로 덮어씌워지므로)에 시그널을 무시하게 처리
  • 천개 이상의 명령어가 파이프를 통해 잘 전달되게 하기 위한 IPC 기본 이해
    • ls와 echo같은 출력만 하는 프로그램은 잘 되지만 cat과 같은 입력이 들어가는 프로그램 실행시 제대로 안되는 문제 -> EOF를 통한 SIGPIPE를 이용하여 해결
    • 이전 파이프관련 과제에서는 명령어 갯수 -1 개만큼 파이프를 만들어서 데이터를 공유했으나, 운영체제에서 프로세스별 fd 최대 제한이 존재함
    • fork()시에 부모프로세스의 모든것이 복제되는 특성을 이용해 부모프로세스에서 최대2개의 파이프를 이용해 모든 자식프로세스들의 입출력을 연결함
  • 스페이스 뿐 아니라 파이프 앞뒤로 문자와 붙어있거나 리다이렉션문자가 파일이름과 붙어있더라도 정상적으로 작동하는 이유는 메타캐릭터의 문자들을 기준으로 토큰화가 되어있기 때문
  • heredoc 구현을 임시파일로 했을 때, 파일이름이 이미 존재할 경우 원본파일이 덮어씌워지므로 파일 유실이 될 수 있음
    • 해당 파일이름을 기준으로 뒤에 숫자를 1씩 증가시켜가며 존재유무를 파악해 중복 회피
  • 입력이 존재하는 프로그램에서 상당수의 오류는 입력의 잘못된 파싱으로 인한 것이므로 파싱이 정말 중요함
  • 명령어가 몇개나 들어올지, 들어올 문자열의 길이 등을 알수 없기 때문에 대부분 동적메모리 할당 및 사용완료시 해제로 누수 해결

Webserv

과제목표 : C++98을 이용하여 정적파일 서빙 및 CGI처리 가능한 웹서버 구현

HTTP 리퀘스트 파싱

  • RFC 문서를 읽으면서 기본적인 HTTP 프로토콜에 대해 이해
  • 스타트라인/헤더/바디 각 부분을 파싱해서 정보를 저장
    • 모든 줄이 CR(Carrige return) + LF(Line feed) 로 끝나는지 확인
    • 스타트라인에 들어가야 하는 요소들 유효성 검사, 헤더 유효성 검사 및 중복검사
    • Method에 따라 body의 파싱여부 판단, Content-Encoding의 타입에 따라 chunked 고려
    • 버퍼크기를 1024로 해둠에 따라, 각 리퀘스트의 완성여부와 에러여부 저장

Multiplexing I/O

  • TCP 소켓을 이용하여 클라이언트와 HTTP 프로토콜을 이용해 소통하는데, 여러 클라이언트가 있을 수 있으므로 kqueue를 이용한 Multiplexing 방식으로 구현함
  • kevent 함수를 사용하여 이벤트가 발생해야만 루프를 돌게 해, Non-Blocking 방식으로 errno를 확인하지 않게끔 구현
  • 클라이언트의 연결요청과 리퀘스트, cgi 실행 및 읽기, 리스폰스 보내기 모두 kevent 반복문에서 처리됌
  • 리퀘스트가 chunked일 경우와 리퀘스트가 형식에 맞게 오다 멈출 경우, CGI 처리가 오래걸리거나 리스폰스 보낼때 시간이 오래걸리는 경우 등을 TIMER 이벤트로 kevent 리턴 시 처리 가능

CGI(php)

  • CGI(Common Gateway Interface)에 대한 기본적인 이해와 RFC문서를 통해 구현 필수조건 확인
  • 실행시간이 오래걸리거나 혹은 잘못된 CGI 프로그램으로 무한루프가 일어날 수 있으므로 kevent에 타이머이벤트를 등록
  • 리퀘스트 바디가 있다면 파이프를 통해 CGI프로그램에 전달하고, CGI프로그램의 실행결과를 파이프를 통해 읽어오는 방식으로 구현
  • 파이프의 버퍼사이즈 제한이 있으므로 Read/Write 이벤트를 모두 등록해 Non-Blocking 방식으로 처리
  • cgi실행 처리 확인을 위해 PID도 이벤트에 등록, 이때 한 CGI실행에 파이프 fd 2개와 PID 1개, 결과 string 총 4개가 연관되어 있으므로 맵을 이용해 연결고리를 생성
  • 대용량 데이터의 송수신일 때 복사가 이뤄지면 서버의 속도저하가 일어나므로 최대한 레퍼런스 연산자를 사용하여 복사생성자 호출 방지 및 쓰기용 인덱스 변수 이용
  • 쿼리스트링 및 Method와 PATH_INFO 등 필요한 인자를 환경변수로 직접 만들어 execve 사용시 인자로 넣음
  • 실행 결과에서 리스폰스에 넣어야할 헤더가 있는 경우 파싱해서 헤더를 추가 혹은 변경
  • 프로세스 이벤트가 일어나면 어떠한 식으로든 종료가 된 것이므로 종료코드를 확인 후 Response를 만들고 WRITE 이벤트를 등록

배운 점

  • TCP/IP 소켓 통신에 대한 이해
    • 컴퓨터들끼리 네트워크상에서 소켓을 사용하여 통신하는 방법에 대한 이해
    • 시스템마다 바이트순서가 다른데, 네트워크 상에서는 모두 빅엔디안으로 쓰기에, 이를 바꿔주는 함수가 존재
    • seige를 사용하여 단시간에 많은 연결로 서버부하 테스트 진행시, 어느순간부터 모든연결이 안되는 현상이 발생
      • TCP 소켓 연결 종료시, 원래는 남아있던 데이터를 전송하는데 이로인해 소켓연결이 완전히 닫히지않는 문제가 발생하여 so_linger 구조체로 즉시 데이터를 버리게 처리
    • 테스트 후 껏다가 다시켰을때 같은 포트로 열면 bind error가 발생
      • 소켓의 연결종료를 하더라도, 서로 종료를 확인하고 주고받으며 만약 데이터를 다 보내지 못했을 경우 이를 마저 보내고 종료하도록 되어있음
      • 부하테스터기인 siege를 빠른속도로 실행시, siege에서 비정상 연결종료를 했을 때 아직 커널단에서 관리중인 포트가 있어서 테스터기가 중단됌
      • 마찬가지로 서버도 호스트의 포트를 사용하다가 종료하면 위의 과정을 거쳐야 하므로 시간이 지나서 실행하지 않으면 해당 포트가 사용불가
      • reuseaddr 옵션을 사용해 close()호출 이후 커널단에서 관리중인 포트를 재사용가능하게 처리 / so_linger옵션을 사용하여 클라이언트와의 연결 종료시 즉시 데이터를 버리게끔 처리
  • HTTP 프로토콜에 대한 이해
    • RFC문서(https://datatracker.ietf.org/doc/html/rfc7230)를 통해 프로토콜 이해 및 문법 확인
    • 특정 브라우저로만 접속이 가능하다던가, 브라우저로만 다운로드가 가능하거나, 다운로드 가속기같이 분할 고속다운로드가 가능하거나 막는것이 어떻게 되어있을지 예상가능
    • 템플릿으로 테스트할 때, pending상태의 파일들일 자주 떴는데 Content-Length를 보내주지 않으면 브라우저가 해당 컨텐츠가 어디까지인지를 모름 -> 여러 헤더들이 하는 역할에 대해 이해
  • Multiplexing I/O 모델
    • 서버는 여러 클라이언트(소켓fd)와 소통해야하는데, 멀티프로세스나 멀티쓰레드 방식 사용시 부하가 크고 느리며 컨텍스트 스위칭 비용으로 인해 입출력을 묶어서 관리하는 멀티플렉싱 방식으로 구현
    • Non-Blocking fd를 사용하여 이벤트가 있을때에만 kevent가 리턴되어
  • CGI 지원
    • CGI RFC문서(https://datatracker.ietf.org/doc/html/rfc3875)를 통해 CGI프로그램의 실행방법과 환경변수에 대한 이해
    • 대용량 데이터를 CGI로 파이프를 통해 송신 및 수신하는 과정에서 Blocking/Non-Blocking 차이에 따라 파이프가 broken되거나 대기가 걸리는 문제가 생김
    • 파이프의 버퍼사이즈가 정해져있는데, CGI프로그램 전부가 입력을 전부 받고 출력을 전부 보내는것이 보장되어 있지 않아 생기는 문제로 CGI실행 시에 pid와 읽기용 파이프fd, 쓰기용 파이프fd 모두 이벤트에 등록해 쓰기와 읽기를 바로바로 하게 해 해결
  • 클라이언트 리퀘스트 파싱
    • 스타트라인과 헤더, 바디로 구성되어있고 각각은 CRLF, 헤더와 바디의 구분은 CRLF 두개로 구분되어있어서 CRLF 두개연속을 찾아서 리퀘스트의 구분 혹은 바디 구분이 가능하나 그렇게 하면, 만약 스타트라인-헤더까지의 길이가 한번 읽는 버퍼사이즈보다 클 경우 그 다음 데이터를 읽을때 다시 한번 처음부터 읽어야 하거나 애초에 잘못된 리퀘스트 구조일 경우 오버헤드 발생
    • 모든 라인의 구분이 CRLF이므로 CRLF를 기준으로 한줄씩 읽어서 파싱 및 저장하는 구조로 구현
  • 타임아웃 처리
    • 클라이언트 연결 후 데이터가 오지 않거나, 리퀘스트가 chunk방식 전송일 때 데이터가 오지 않거나, cgi실행이 오래걸릴 때를 대비해 각각의 경우에 모두 TIMER이벤트를 등록
    • 무한루프 CGI를 크롬에서 여러개 탭으로 실행 후, 한두개만 꺼도 나머지가 전부 hang걸리는 현상 -> 기존 코드는 리퀘스트 파싱 및 cgi실행 이후 같은 fd로부터 클라이언트의 write로 인해 read이벤트가 발생할 경우 타이머를 갱신시키는 방식이였는데 크롬은 일정시간 리스폰스가 오지 않을 경우 같은 내용으로 리퀘스트를 요청함
    • 타이머 갱신은 리퀘스트가 완성된 시점에서는 더이상 read를 하지도 않고 타이머이벤트를 새로 등록하지도 않게 하여 하나의 처리당 타이머가 하나가 되도록 수정
    • 보낸 리스폰스를 읽지 않는 클라이언트가 있다면 이는 결국 메모리 누수와 마찬가지이므로 리스폰스의 write이벤트도 타이머를 등록해서 처리해야 함

Transcendence

과제 목표 : VanilaJS와 Django를 이용해 핑퐁 게임 웹사이트 구현

Backend

  • Django와 Django REST framework를 사용하여 API서버 구현
    • Restful API에 대한 기본적인 이해와 Django REST framework 사용
    • Django ORM을 사용하여 기본 유저 모델 커스터마이징 및 친구기능 구현
    • Oauth 2.0을 통한 소셜 로그인 구현 및 Session과 JWT를 이용한 인증
    • 인증을 위한 JWT추가 및 JWT검증하는 커스텀 Middleware 구현
  • Unicorn, Uvicorn을 이용한 웹소켓 사용
    • 웹소켓 프로토콜에 대한 기본적인 이해
    • 비동기 프로그래밍에 대한 기본적인 이해와 Django의 Channels에 대한 이해
    • 친구의 온라인/오프라인 스테이터스를 실시간으로 확인하기 위한 웹소켓 구현
    • 핑퐁게임 토너먼트의 실시간 원격플레이를 위한 로직 구현

Docker

  • Docker를 사용하여 Django 백엔드 서버와 Nginx 프론트엔드 서버, PostgreSQL 데이터베이스를 컨테이너화
    • Dockerfile과 docker-compose.yml을 사용하여 docker compose up으로 서버 실행
    • 바인드 볼륨을 이용해 코드 수정시 서버 재시작 없이 반영되도록하고 데이터베이스 데이터 보존을 위해 볼륨 사용
    • Nginx 컨테이너에 자체 인증서를 통한 HTTPS 적용 및 라우팅 규칙에 따른 백엔드/프론트엔드 프록시 설정
  • ELK 구축
    • Elasticsearch을 사용하기 위한 RDBMS와의 차이 및 구조 이해
    • Django의 로그를 적절하게 수집하기 위한 grok pattern 작성 및 logstash 설정
    • Kibana를 통해 시각화하기 위해 kibana API를 사용하여 대시보드, 인덱스패턴 생성 및 라이프사이클 관리하는 쉘 스크립트 작성
    • TLS적용을 위해 certutil을 사용하여 인증서를 생성하고 세 컨테이너에 적용시키는 쉘 스크립트 작성

배운 점

  • Git을 통한 협업
    • 컨벤션을 지켜가면서 이슈관리 및 커밋, PR 메세지 작성
    • PR을 통한 코드리뷰 및 머지
  • Django
    • 기본적인 Django의 구조 상 원래의 User모델을 기준으로 기능들이 구현되어 있어 이를 커스터마이징 할 경우 어떻게 해야하는지에 대한 이해
    • 미들웨어의 구조를 이해하여 뷰 함수에 들어오기 전에 인증을 처리하고, 편의를 위해 JWT토큰을 request에 넣어주는 미들웨어 구현
    • 다른 팀원이 종종 Django가 데이터베이스 테이블 관련 문제로 에러를 겪음 -> 도커 컨테이너 첫 실행 시에 cache를 지우고 새로 마이그레이션 생성 및 마이그레이트 실행하게 함 -> 제대로 데이터베이스 자체가 안만들어지는 경우가 있는데, 앱 별로 migrations폴더 내에 init.py파일이 없을 경우 마이그레이션 대상이 되지 않는다는것을 알게됌
    • 동시접속이 되면 게임플레이가 비정상적으로 바뀌어 동시접속을 막아야하는데, Django의 기본 Session은 동시접속이 가능하게끔 되어있어, 친구 온라인/오프라인을 보여주는 웹소켓을 이용해 기존유저가 있을 경우 신규접속 유저의 연결 끊고 프론트에서 로그아웃하게끔 변경
    • 닉네임 변경API를 추가하면서, 기존 온라인/오프라인 스테이터스 웹소켓에서 반영되지 않는 문제발생 -> 닉네임 변경시에도 웹소켓으로 변경된 닉네임을 보내서 기존 명단에서 수정하는식으로만 변경
    • 유저 한명 접속시 해당 유저가 친구인 사람에게만 갱신이 이루어지게끔 웹소켓 메세지를 보내기 전에 서버에서 친구를 확인하는 로직 추가
    • 위의 Webserv를 만들 당시 OPTIONS 메소드에 대해알고만 있었는데, 프론트와 합치면서 cors에러를 겪으면서 개발자 도구를 통해 OPTIONS메소드가 무엇인지 Preflight방식에 대해서도 알고, django-cors-headers 패키지를 설치하여 해결함(혹은 수동으로 헤더에 Access-Control-Allow-Origin을 넣어주는 방법도 있음)\

Philosopher

과제 목표 : C98의 제한된 함수들을 이용해 식사하는 철학자 문제 구현

병행성

  • 여러명의 철학자들이 각자 포크를 들거나 먹고 자는것이 동시에 이루어지기 위해서 멀티쓰레드와 멀티쓰레드를 사용하여 구현
  • 순서가 보장되지 않기에 데이터레이스가 일어날수 있어 뮤텍스를 사용해 임계구역을 만들어 원자적 실행 보장(혹은 멀티프로세스 시 세마포어)
  • 시간순으로 각 철학자의 행동을 출력해야 하는데, 누군가 죽었을 때 출력이 멈춰야하고, 시간순서대로 출력이 나와야하므로 모든 출력부도 뮤텍스로 원자성 보장
  • 철학자들의 죽음을 피해야하므로 데드락 발생을 막아야함
    • 각 포크의 사용상태를 변수로 두고 각각 뮤텍스를 만들어 확인할때 반드시 임계구역을 만들어서 확인후 집거나 포기하며, 한개를 들더라도 두개를 들수없다면 내려놓게끔 처리
    • 반드시 왼쪽 포크부터 집게끔 하고, 처음 프로그램 시작 시, 홀수번째 철학자들을 짝수번째 철학자들이 먼저 먹을 수 있게끔 기다리게 처리
  • 철학자 수가 많아지면서 컨텍스트 스위칭 횟수가 늘어날수록 시간 밀림현상이 증가하므로, 이를 위해 밥먹는 시간, 자는시간 등의 지연처리 시 해당 스레드를 확인하지 않게 usleep에 적당한 긴 시간을 줌
  • 멀티프로세스는 힙과 데이터영역이 공유되지 않아서 비교적 프로그래밍이 쉬움 -> 이 과제에서 먹는 횟수를 만족시 종료되어야 하는데, 이를 공유하기 위한 IPC함수가 사용불가능으로 세마포어를 싸이클마다 post하며 카운트를 세게끔 처리

배운점

  • sleep(usleep)의 매개변수 시간은 해당 프로세스(혹은 쓰레드)의 최소 실행유예시간을 보장한다 -> 정확히 그 시간이 지나고서의 실행이 보장되지 않음
  • 컨텍스트 스위칭 자체에 PC,레지스터 값 변경 등에 따른 자원(시간)소모가 있어, 시간 밀림현상을 줄이기 위해 오히려 sleep(usleep)에 조금더 긴 시간을 주어야 함
  • 운영체제에서 CPU가 프로세스를 처리할 때 사용하는 PCB에 대한 개념 습득 및 스케줄링에 대한 이해
  • 컨텍스트 스위칭의 자원소모, 쓰레드 혹은 프로세스를 만드는 것 자체도 시간이 걸리며 공유데이터를 관리하기위한 방법(뮤텍스,세마포어등을 통한 원자적실행과 IPC)의 단점이 명확함
  • 아파치보다 nginx가 더 커넥션 성능이 좋은 이유가, 멀티프로세스 혹은 멀티쓰레드를 사용할 경우 소모되는 자원과 시간으로 인해 싱글스레드 멀티플렉싱 방식을 선택한 것이라는 것
  • 웹서버 과제에서 CGI를 구현할 때 fork를 통한 멀티프로세스 방식을 nginx는 FAST-CGI 방식을 사용한 이유 추측 가능

About

projects of 42seoul inner circle

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published