소켓 (Socket)
더보기TCP/IP 의 implementation이 커널 수준에서 되어있음
이걸 user mode application이 접근할 수 있도록 인터페이스 제공
기본적인 본질은 File 이지만, 이때 프로토콜을 추상화했기 때문에 Socket이라고 한다.
Socket 이란??? => TCP라는 요소를 User mode application process가 접근할 수 있도록 추상화환 인터페이스
The socket interface is a set of functions that are used in conjunction with the Unix I/O functions to build network applications.(소켓 인터페이스는 네트워크 응용을 만들기 위한 Unix I/O 함수들과 함께 사용되는 함수들의 집합이다.)
소켓 주소 구조체 1.소켓 주소는 'IP+포트'이다.
2.즉, 목적지에 가려면 IP주소만 있어서는 안 되고 포트 정보도 필요
3.IP 주소도 IPv4는 32비트 주소 체계를 사용, IPv6는 128비트 주소 체계 사용
4.=> 어떤 프로토콜을 사용하느냐에 따라 주소 정보를 나타내는 데이터 타입이 달라짐
5.so, 이를 사용하기 편하게 주소 프로토콜 체계에 따라 사용하기 쉽게 틀을 만든 것
1.네트워크 프로그램에서 필요한 주소 정보를 담고 있는 구조체
2.프로토콜 체계에 따라 주소 지정 방식이 다르기 떄문에 다양한 소켓 주소 구조체가 존재한다.
sockaddr 구조체는 소켓의 주소를 담는 기본 구조체 틀의 역할을 한다.
/* Generic socket address structure (for connect, bind, and accept */
struct sockaddr {
uint16_t sa_family; // 주소 체계
char sa_data[14]; //해당 주소 체계에서 사용하는 주소 정보(IP정보+포트정보)
}
sockaddr 사용 예시
그래서 보통 connect(연결 요청)과 같은 함수들이 인자 타입으로 sockaddr을 받는다.
sockaddr_in으로 했던 sockadde_un으로 했던 sockaddr 타입으로 형변환 값이 매개변수로 들어가게 된다.
https://jhnyang.tistory.com/261
빨간색 부분을 보면 선언은 sockaddr_in으로 해줬지만, 인자로 넣을 때는 sockaddr* 타입으로 형변환을 해준다. (서버 프로그램에서 bind 함수에서도 마찬가지) 코드를 유연하게 만들기 위해 모든것을 받아주는 generic 틀이라고 생각하면 쉽다.
결론: sockaddr 구조체는 일반적인(범용적으로 사용 가능한) 구조체이다.
Q. 근데 void 타입을 사용하면 되지 sockaddr*을 사용해야만 하는가? (궁금증해결💡)여러 종류 주소 정보 구조체 포인터를 받기 위해 sockaddr 을 사용해야만 하는가? void 을 쓰면 되는거 아닌가?
=> 함수의 매개변수 데이터 타입을 void 포인터(void)로 선언하는 경우, 어떤 변수의 포인터이든지 인자 값으로 받을 수 있기 때문에 sockaddr을 선언하는 것보다 의미전달이 명확해진다. 역사적인 이유는 그 당시에 void 포인터가 표준으로 존재하지 않았다.
void 보다 sockaddr 를 사용하게 의미전달에 맞고, void* 를 쓰면 정말 아무거나 올 수 있기에 어떤 계열의 타입이 와야하는지 파악하기 쉽지 않고, 또 역참조가 불가능하다는 단점도 있다.
결론: 현재는 주소체계 인자가 필요한 경우 sockaddr*로 형변환을 맞춰서 넣어주게끔 되어 있다.
sockaddr_in 구조체는 IPv4를 저장하는 구조체이다.
/* IP socket address structure */
struct sockaddr_in {
uint16_t sin_family; // 주소 체계를 저장하는 필드, IPv4를 위한 주소체계이기에 AF_INET 넣어줌
uint16_t sin_port; // 포트 정보 저장
struct in_addr; // IPv4 정보 저장, 타입은 in_addr 구조체
unsigned char sin_zero[8]; // 사용하지 않는 필드, 0으로 채워줘야함
}
sockaddr_in 사용 예시
sockaddr_in 구조체에다가 사용하려는 주소 정보를 할당해보자~
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET; // sockaddr_in이니깐 AF_INET을 할당해줌
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
serv_addr.sin_port = htons(3030); // htos: host 바이트 순서 => 네트워크 바이트 순서
1.inet_addr: 문자열 + 점으로 이루어진 IP 주소를 32bit 주소체계로 변환해줄뿐만 아니라 네트워크 바이트 순서에 맞게끔 변환도 해줌
2.htons: host 바이트 순서에서 네트워크 바이트 순서로 변환해주는 함수 (host to network, short형)
참고 자료: https://jhnyang.tistory.com/261
accept 함수서버는 accept 함수를 호출해서 클라이언트로부터의 연결 요청을 기다린다.
#include <sys/socket.h>
int accept(int listenfd, struct sockaddr *addr, int *addrlen);
// Return: nonnegative connected descriptor if OK, -1 on error
1.클라이언트로부터 연결 요청이 듣기 식별자 listenfd에 도달하기를 기다리고, 그 후에 addr 내의 클라이언트의 소켓 주소를 채우고, Unix I/O 함수들을 사용해서 클라이언트와 통신하기 위해 사용될수 있는 연결 식별자 connfd 를 리턴한다.
2.듣기 식별자 listenfd
- 클라이언트 연결 요청에 대해 끝점으로서의 역할을 함
- 대개 한 번만 생성, 서버가 살아있는 동안 계속 존재
3.연결 식별자 connfd
- 클라이언트와 서버 사이에 성립된 연결의 끝점
- 서버가 연결 요청을 수락할 때마다 생성되며, 서버가 클라이언트에 서비스하는 동안에만 존재함
[듣기 식별자와 연결 식별자의 역할]
1.서버 accept 호출, 연결 요청이 listenfd에 도달하길 기다림
2.클라이언트 connect 호출 즉, listenfd로 연결 요청 보냄
3.accept 함수는 새로운 연결 식별자 connfd를 오픈하고, clientfd와 connfd 사이의 연결을 수립하고 connfd를 리턴한다.
#include <sys/socket.h>
#include <netdb.h>
int getnameinfo(const struct sockaddr *sa, socklent_t salen,
char *host, size_t hostlen,
char *service, size_t servlen, int flags);
// Returns: 0 if OK, nonzero error code on error
클라이언트-서버 프로그래밍 모델 (Client-Server Programming Model)
더보기IT에서는 네트워크에 연결되어 있는 컴퓨터들을 호스트(Host)라고 부른다.
인터넷은 TCP/IP 프로토콜을 이용하여 통신을 하는데, 통신을 하려고 해도 목적지와 출발지가 없으면 어디로 데이터를 보낼지 받을지 모른다. IP라는 고유한 주소를 통해 목적지와 출발지를 구할 수 있으며 호스트는 IP 주소를 갖는다.
호스트 = IP를 가지고 있는 양방향 통신이 가능한 컴퓨터
URL VS URI에코 서버 (Echo Server)
더보기
HTTP(HyperText Transfer Protocal)
HTML(HyperText Markup Language)
HTML은 태그(명령!)을 포함하고 있어서 브라우저에게 여러가지 텍스트와 그래픽 객체를 페이지에 어떻게 표시할지를 알려줌
HTML의 진정한 강점은 페이지가 인터넷 호스트에 저장된 컨텐츠로의 포인터(하이퍼링크)를 포함할 수 있다는 것이다.
웹 클라이언트(브라우저)와 서버에게, 컨텐츠는 연관된 MIME(Multipurpose Internet Extensions) 타입을 갖는 바이트 배열이다.
MIME type | Description |
---|---|
text/html | HTML page |
text/plain | Unformatted text |
application/postscript | Postscript document |
image/gif | Binary image encoded in GIF format |
image/png | Binary image encoded in PNG format |
image/jpeg | Binary image encoded in JPEG format |
웹 서버는 두 가지 방법으로 클라이언트에게 컨텐츠를 제공
웹 서버가 리턴하는 모든 내용들을 서버가 관리하는 파일에 연관됨.
이 파일 각각은 URI(universal Resource Locator)라고 하는 고유의 이름을 가
URI 설명 💡💡💡💡http://www.google.com:80/index.html
http://jungle.com:8000/cgi-bin/adder?7&3
리눅스 TELNET 프로그램을 사용해서 인터넷 상의 모든 웹 서버와 트랜잭션을 실행 가능.
TELNET(텔넷)이란?telnet을 리눅스 쉘에서 실행, aol 웹 서버와 연결 오픈 요청 ~
telnet은 터미널에 세 개의 출력 인쇄 & 연결 오픈, HTTP 요청 기다림
HTTP 요청 => GET / HTTP/1.1 우리가 HTTP 요청을 입력하고 enter 키를 누를 때마다 telnet은 이 줄을 읽고, carriage return(\r)과 line feed(\n), 이 라인을 서버로 보냄 => "\r\n"
HTTP 표준은 매 텍스트 라인이 "\r\n"으로 종료될 것을 요구함
[과정]
1) 트랙잭션을 개시 위해 HTTP 요청을 입력함(5~7번줄)
=> 요청 라인(5), 요청 헤더(6)
2) 서버는 HTTP 응답으로 회신하며(8~17줄)
=> 응답 라인(8), 요청 헤더(9~13), 응답 바디(15~17)
3) 연결을 닫는다.(18번줄)
HTTP 요청 => 요청 라인(5번 줄), 여러개의 요청 헤더(6번 줄), 헤더리스트를 종료하는 빈 텍스트줄(7번 줄)
요청 라인요청라인은 다음과 같은 형태를 갖는다.
method URI version
method
URI
version
요청 라인의 version 필드 => 요청이 준수하는 HTTP 버전을 나타냄
HTTP/1.1
1. 커넥션 유지
2. 호스트 헤더
3. 강력한 인증 절차
요약: 5번 줄의 HTTP 요청 라인은 서버에게 HTML 파일 index.html을 가져와서 리턴할 것을 요구한다. 또한, 서버에게 나머지 요청들이 HTTP/1.1 포맷으로 되어있을 것이라고 알려준다.
요청 헤더는 다음과 같은 형태를 갖는다.
요청 헤더는 서버에 브라우저의 이름이나 브라우저가 이해하는 MIME 타입 같은 추가적인 정보를 제공한다.
Header-name: header-data
우리의 목적을 위해서 신경을 쓰는 유일한 헤더는 Host 헤더(6번줄)로, 이것은 HTTP/1.1 요청에서 요구되었으며, HTTP/1.0 요청에서는 아니다. Host 헤더는 프록시 캐시에 의해 사용되며, 브라우저와 요청된 파일을 관리하는 본래의 서버 사이의 중간자 역할을 한다.
5번 줄의 요청 라인, 6번 줄의 요청 헤더 이후, 키보드의 엔터 키를 눌러서 만든 헤더를 종료하고, 서버에게 요청한 HTML 파일을 보낼 것을 명령한다.
HTTP 응답HTTP 응답은 HTTP 요청과 비슷하다.
HTTP 응답 => 응답 라인(8번 줄),응답 헤더들(9~13번 줄), 헤더를 종료하는 빈 줄(14번줄), 응답 본체(15~17번 줄)
응답 라인version status-code status-message
9~13번줄 => 응답에 대한 추가적인 정보 제공, 두 개의 가장 중요한 헤더는 !
Q. 어떻게 클라이언트는 프로그램 인자들을 서버에 전달하는가?
Q. 어떻게 서버가 자식에게 다른 정보를 전달하는가?
Q. 자식은 자신의 출력을 어디로 보내는가?
=> 이러한 질문들은 CGI(Common Gateway Interface)라고 부르는 사실상의 표준으로 설명할 수 있다.
서버가 다음과 같은 HTTP 요청을 받은 후에
GET /cgi-bin/adder?15000&213 HTTP/1.1
fork을 호출해서 자식 프로세스를 생성하고,
execve를 호출해서 /cgi-bin/adder 프로그램을 자식의 컨텍스트에서 실행한다.
execve를 호출하기 전에 자식 프로세스는 CGI 환경변수 QUERY_STRING을 "15000&213"으로 설정하고, adder 프로그램은 런 타임에 리눅스 getenv 함수를 사용해서 이 값을 참조가능하다.
CGI 프로그램은 자신의 동적 컨텐츠를 표준 출력으로 보낸다.
자식 프로세스가 CGI 프로그램을 로드하고 실행하기 전에 리눅스 dup2 함수를 사용해서 표준 출력을 클라이언트와 연계된 연결 식별자로 재지정한다. 이를 통해서 CGI 프로그램이 표준 출력으로 쓰는 모든 것은 클라이언트로 직접 가게 된다.
부모가 자식이 생성한 컨텐츠의 종류와 크기를 알지 못 하기 때문에 자식은 Content-type과 Content-length 응답 헤더와 헤더를 종료하는 빈줄까지 생성할 책임이 있다.
Tiny Web Server
더보기RIO: Robust I/O
=> RIO 패키지는 짧은 카운트가 발생할 수 있는 네트워크 프로그램 같은 응용에서 편리하고, 안정적이고 효율적인 I/O를 제공한다.
rio_readinitb: read 버퍼를 초기화하는 함수
rio_readlineb: 파일에서 텍스트 라인을 읽어 버퍼에 담는 함수
rio_readnb: 파일에서 지정한 바이트 크기를 버퍼에 담는다.
rio_writen: 버퍼에서 파일로 지정한 바이트를 전송하는 함수
main: 웹 서버 메인 로직
프록시 서버 (Proxy Server)
더보기프록시 서버란, proxy 란 대리 혹은 중계 Agent로서의 의미이며, 프록시 서버는 클라이언트의 요청을 받아 중계하는 서버이다.
즉, 클라이언트가 요청을 보냈을 때 proxy 서버에서 웹 서버에 접근하여 요청과 응답을 처리한 후 proxy서버에서 다시 클라이언트에게 응답을 한다.
익명성으로 보안의 목적으로 사용
캐시를 이용한 요청 속도 개선
차단된 사이트를 우회하여 접속
원하지 않는 사이트를 차단
웹 캐시(web cache) 또는 HTTP 캐시(HTTP cache)는 서버 지연을 줄이기 위해 웹 페이지, 이미지, 기타 유형의 웹 멀티미디어 등의 웹 문서들을 임시 저장한 후
동일한 요청 시 프록시 서버의 웹 캐시에 저장된 정보를 불러오므로 트래픽이나 서버의 부하를 줄여 클라이언트가 원하는 정보를 빠르게 응답할 수 있다.
프록시 서버가 서비스 서버에 작업하는 위치와 네트워크 구성에 따라서 크게 Forward Proxy/Reverse Proxy 로 구분된다.
Forward Proxy (포워드 프록시)
일반적인 프록시 서버를 말하며, 클라이언트와 웹 서버의 중계역할로 클라이언트가 요청 시 Proxy서버는 해당 요청을 웹 서버로 중계해 자원을 가져오는 개념이다.
프록시 서버는 클라이언트가 요청하기 전까지 웹 서버의 주소를 알 수 없다.
Reverse Proxy (리버스 프록시)
클라이언트와 내부망(Private Network) 서버 사이에(앞에) 위치하여 제어 역할을 한다. 그래서 클라이언트가 요청을 하면 프록시 서버가 내부망 서버에 요청 후 응답 받은 자원을 클라이언트에게 전달해주는 개념이다.
리버스 프록시 서버는 실제 서버들에 대한 주소를 매핑하고 있어야 한다. 그리고 내부망에 서버에 대해 보안적으로나 로드밸런싱을 위해 사용되기도 한다.
익명으로 컴퓨터를 유지할 수 있다. 프록시 서버를 통해 한 단계의 보안을 더 할 수 있기 때문에 컴퓨터 보안을 유지할 수 있다.
프록시 서버에 요청된 내용들을 캐시를 이용하여 저장하면 전송시간도 절약할 수 있음은 물론 동시에 불필요하게 외부와의 연결을 하지 않아도 된다는 장점을 가지게 된다.
네트워크 서비스나 콘텐츠로의 접근 정책을 적용하기 위해 사용한다. 또한 사용률을 기록하고 검사하기 위해 사용할 수 있다.
보안 및 통제를 뚫고 나가기 위해 사용할 수 있다. 또한 역으로 IP 추적을 당하지 않을 목적으로 사용한다. 역기능이긴 하지만 우회를 할 수 있도록 한다.
밖으로 나가는 콘텐츠를 검사하기 위해 사용한다. 중계 서버인 프록시 서버를 거치기 때문에 콘텐츠를 검사할 수 있다. 지역 제한을 우회하기 위해 사용할 수 있다.
driver.sh
1.proxy 터미널 => make 후 ./driver.sh 실행
2.자동채점(autograde)이 되면서 proxy 점수 파악 가능
3.기본 기능이 문제 없이 동작: 40점 (자동채점)
4.동시성 : 15점 (자동채점)
5.캐시 : 15점 (자동채점)
curl
1.tiny 터미널 => ./tiny 8000 실행, proxy 터미널 => ./proxy 7777 실행
2.webproxy 폴더가 있는 경로에서 mkdir testing
3.cd testing
4.tiny 터미널과 proxy 터미널이 아닌 새로운 터미널을 하나 열어줌
5.현재 위치가 testing 폴더일 때 curl --proxy http://localhost:7777 --output home.html http://localhost:8000/home.html
6.실행 후, client에서 요청된 파일이 proxy를 거쳐 서버에 요청이 잘 됨과 동시에 서버의 응답이 proxy를 거쳐 client에 왔다는 것을 testing에 home.html 파일이 만들어진 것으로 확인할 수 있다.
My Github 코드: https://github.com/kelvin3476/webproxy-lab/blob/main/proxy.c