구현하기

Web Proxy #1 : 이론

pwerty 2025. 5. 3. 21:46

2025.05.02 - [분류 전체보기] - [CS:APP] 11 : 웹 서버

 

[CS:APP] 11 : 웹 서버

모든 네트워크 애플리케이션은 클라이언트-서버 모델을 기반으로 한다.여기서 이야기하는 모델에 따르면, 애플리케이션은 한 개의 서버와 한 개 이상의 클라이언트로 구성된다.서버는 특정 자

hyeonistic.tistory.com

여기 마지막 단원 11.5, 11.6은 실습 내용을 담고 있다. 그래서 차례로 해보려고 한다.
여러 사람들의 도움을 주고 받는데 그것이 지나치지 않은 선에서 아주 좋다고 느낀다.

Echo 서버 만들기

  • 진짜 간단한 서버이다. 기능으로, 클라이언트가 연결되면 클라이언트는 8192자까지 메시지를 적어서 보낼 수 있다.
  • 서버는 그 내용을 받아서 Echo를 수행한다.
    • 여기서 Echo는 말 그대로 울림, 메아리이다. 즉 클라이언트는 자신이 보낸 메시지를 다시 돌려받게 된다.
    • 그리고 이 과정을 진행하기 위해서는 클라이언트와 서버 단위의 코드 작성이 필요하다.

Echo : Client

클라이언트 역할 수행을 위해 필수적인 파일 가져오기
클라이언트 역할을 위해 쓸 파일 디스크럽터를 가져오는 함수를 사전 선언
메인(매개변수 갯수, 매개변수 내용)
{
    사전 선언 : 소켓(파일 디스크럽터) 저장 할 곳.
    사전 선언 : 연결 할 곳, 포트번호, 실제 내용
    사전 선언 : robust I/O 사용을 위한 기초 변수 한 개
    만약 : 매개변수 갯수가 안맞으면 (여기선 2개가 필요하다.)
    {
        
        매개변수는 [프로그램 이름] [연결 할 곳] [포트 번호] 순으로 적어주세요. 라고 출력한다.
        
        프로그램 종료한다.
    }
    매개변수 중 첫 번째는 프로그램 내에 "연결 할 곳" 에 저장한다.
    매개변수 중 두 번째는 프로그램 내에 "포트번호"에 저장한다.


    디스크럽터를 저장 할 변수에 디스크럽터를 만드는 함수를 호출한 결과를 넣는다.
        이 함수 사용을 위해서는 "연결 할 곳", "포트번호"라는 정보가 필요하다.


    RIO라는 안전 중개 장치 사용을 위해 아까 얻은 디스크럽터를 rio에 연결한다.
    
    반복하기 : 사용자에게 입력받은 문자열의 내용이 있는 경우
        {
            중개 장치를 사용하여 buf 내용이 전부 보내질때까지 전송한다. (clientfd에 도착지, buf와 strlen(buf)는 안전포장 내에 있는 내용에 대한 내용이다. )
            중개 장치를 사용하여 줄 단위로 내용을 읽어온다. (rio에 접근해서, buf에 MAXLINE만큼 받아온다.)
            받아온걸 내 컴퓨터의 화면에 출력하도록한다.
        }
    디스크럽터에 대한 사용을 명시적으로 종료한다.
    프로그램 종료한다.
}


디스크럽터를 만드는 함수이다. 재료로 호스트에 대한 정보와 포트 번호가 필요하다.
{
    디스크럽터 저장할 지역변수 선언한다.
    더 많은 함수를 불러오게 될텐데, 이 내용들을 저장할 구조체를 몇 개 미리 선언한다.
    hints 구조체의 내용을 0으로 초기화한다. 명시적으로 NULL 초기화하는거랑 같은 이유이다.
        hints는 일종의 검색 범위, 필터 같은 역할이다. 그래서 내가 뭘 원하는지를 이제부터 지정해주어야 한다.
    hints 내용 중, 소켓 형태를 연결 지향형 소켓으로 하겠다는 설정으로 전환한다.
        
    hints 내용 중, port 인자가 문자열로 된 숫자임을 보장하겠다고 사전에 설정한다.

    hints 내용 중, IPv4, IPv6에 부합하는 애들만 결과로 받아들이도록 사전에 설정한다.
    주소 목록을 받아오는 함수를 호출한다.
        * hostname, port에 대응하는 주소 정보를 linked list 형태로 받아온다.
        * hints는 여기서 검색 조건으로써 쓰이고, listp는 결과값을 받아오는 포인터로써 사용된다.


    listp의 모든 아이템에 대해 한 번씩 시도한다:
    {

        만약 : 디스크럽터 저장할 곳에 소켓 함수를 호출한 결과가 없다면,
            이 아이템에 대한 반복문을 여기서 종료하고 바로 다음 아이템에 대해 시도한다.
            
        [그럼 위의 만약을 통과했다면 소켓 디스크럽터를 얻어오긴 했다는게 된다.]
        만약 : 얻어온 디스크럽터에 연결에 성공한다면
            이 반복문을 즉시 빠져나간다.

        [위 만약을 통과했다면 연결시도에서 문제가 생겼으니 명시적으로 닫아줘야한다.]
        디스크럽터에 대한 사용을 명시적으로 종료한다.
    }

    listp를 더 쓸 이유가 없으니 메모리 해제를 한다.

    만약 (!p) 라면 (반복문을 다 돌면 p는 NULL이 된다. 반복문을 다 돌았다는건 접속에 성공한게 없다는 것이다.)
        -1을 반환하여 실패를 알린다.
    그렇지 않다면
        성공한 디스크럽터 번호를 알린다.

}

Echo : Server

서버에서 역할 수행을 위해 필수적인 파일 가져오기
이 서버에서 할 일인 echo를 사전 선언한다.
클라이언트 역할을 위해 쓸 파일 디스크럽터를 가져오는 함수를 사전 선언한다.
이 서버 할 일인 에코 내용(연결된 클라이언트를 매개변수를 통해 받아온다)
{
    사전선언 : 사이즈를 다루는 변수
    사전선언 : 실제 내용을 담을 변수
    사전선언 : 안전 중개 장치
    매개변수로 받아온 연결된 클라이언트 디스크럽터를 방금 만든 안전 중개 장치에 이어준다.
    반복한다 : 안전 중개 장치를 통해 받아온 내용이 있다면
    {
        서버가 "몇" 바이트를 받았습니다!! 라고 출력한다.
        클라이언트를 향해 전송받은 내용을 다시 보낸다.
    }
}
메인(매개변수 갯수, 매개변수 내용)
{
    사전 선언 : 소켓(파일 디스크럽터) 저장 할 곳 2개
    사전 선언 : 연결 될 클라이언트의 정보 담을 변수
    사전 선언 : Accept 함수 쓰는데 필요한 변수 선언 : 솔직히 잘 모르겠다
    사전 선언 : 호스트 이름과 포트 저장 할 곳 알아두기
    만약 : 매개변수 갯수가 안맞으면 (여기선 1개가 필요하다.)
    {
        
        매개변수는 [프로그램 이름] [포트 번호] 순으로 적어주세요. 라고 출력한다.
        프로그램 종료한다.
    }
    
    받아온 매개변수를 통해 listen 목적의 디스크럽터를 만들고 저장한다.

    무한 반복!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    {
        클라이언트의 신상정보가 저장될 곳을 초기화한다.

            [아래 함수가 실행되면 clientaddr, clientlen, connfd가 전부 유의미한 값이 된다. 사실상 리턴이 세 개나 있는 변수]
        Accept를 함수를 35번 줄에서 얻어왔던 디스크립터, 그리고 얻은 정보를 저장할 변수들을 던진다. 이 줄에서 클라가 연결된다.
        Getnameinfo에 지금까지 얻은 정보를 다 재료로 던진다. 정확히하면 사람이 읽을 수 있는 형태로 변환하는 함수이다.
        서버에서 할 일인 echo를 연결된 컴퓨터에 대해 수행한다.
        연결을 명시적으로 종료한다. 이래야 안전하게 다음 반복을 수행 할 수 있다.
    }
}
디스크럽터를 만드는 함수이다. 포트번호 정보가 있어야한다.
{
    더 많은 함수를 불러오게 될텐데, 이 내용들을 저장할 구조체를 몇 개 미리 선언한다.
    디스크럽터 저장할 지역변수 선언한다.
    hints 구조체의 내용을 0으로 초기화한다. 명시적으로 NULL 초기화하는거랑 같은 이유이다.
        hints는 일종의 검색 범위, 필터 같은 역할이다. 그래서 내가 뭘 원하는지를 이제부터 지정해주어야 한다.

    hints 내용 중, 소켓 형태를 연결 지향형 소켓으로 하겠다는 설정으로 전환한다.
    hints 내용 중, 모든 인터페이스에 대해 수신 가능하되, IPv4, IPv6에 부합하는 애들만 결과로 받아들이도록 사전에 설정한다.
    hints 내용 중, port 인자가 문자열로 된 숫자임을 보장하겠다고 사전에 설정한다.
    주소 목록을 받아오는 함수를 호출한다.
        * port에 대응하는 주소 정보를 linked list 형태로 받아온다.
        * hints는 여기서 검색 조건으로써 쓰이고, listp는 결과값을 받아오는 포인터로써 사용된다.
        * 여기선 hostname이 필요가 없다.

    listp의 모든 아이템에 대해 한 번씩 시도한다:
    {

        만약 : 디스크럽터 저장할 곳에 소켓 함수를 호출한 결과가 없다면,
            이 아이템에 대한 반복문을 여기서 종료하고 바로 다음 아이템에 대해 시도한다.
            
    서버 프로그램을 재실행할 때 이전 연결에서 사용된 주소와 포트를 바로 재사용할 수 있도록 합니다.
        [만약 이전 연결이 TIME_WAIT 상태에 있어도 주소 재바인딩이 가능하게 됩니다.]
        [위의 만약을 통과했다면 소켓 디스크럽터를 얻어오긴 했다는게 된다.]
        만약 : 얻어온 디스크럽터에 바인딩에 성공한다면
            이 반복문을 즉시 빠져나간다.
        [위 만약을 통과했다면 연결시도에서 문제가 생겼으니 명시적으로 닫아줘야한다.] 디스크럽터에 대한 사용을 명시적으로 종료한다.
    }

    listp를 더 쓸 이유가 없으니 메모리 해제를 한다.
    만약 (!p) 라면 (반복문을 다 돌면 p는 NULL이 된다. 반복문을 다 돌았다는건 접속에 성공한게 없다는 것이다.)
        -1을 반환하여 실패를 알린다.
    [위의 만약을 통과했다면 실패는 아니다.]
    만약 : 디스크럽터에 listen을 시도 했을 때의 결과가 불량하면
        해당 디스크럽터에 대한 연결을 종료한다.
        연결이 불량한 것을 알리기 위해 -1로 반환한다.
    연결 결과가 양호한 상황의 listenfd를 반환한다.
}

 

다음 챕터는 이 코드가 실제 적용된 형태를 확인해보겠다.