Hi yoahn 개발블로그

[네트워크 분석 실습] 네분실 기말 정리 본문

sswu

[네트워크 분석 실습] 네분실 기말 정리

hi._.0seon 2021. 11. 19. 15:13
반응형

1. Link layer

  • nodes: hosts & routers
  • 링크
    • 통신 경로 상의 인접한 노드들을 연결하는 통신 채널
  • 데이터그램을 링크 계층 프레임으로 캡슐화해서 링크로 전송
  • 서로 다른 링크를 통해 서로 다른 링크 프로토콜에 의해 데이터그램을 전송
  • WiFi -> Ethernet
  • datagram을 출발지 호스트에서 목적지 호스트로 이동시키기 위해서는 데이터그램을 종단간 경로의 개별 링크로 이동시켜야만 한다.
    각 링크 프로토콜은 서로 다르지만 데이터 전송 기능 역할은 같음

링크 계층이 제공하는 서비스

  1. 프레임화
    • datagram의 앞부분에 header를 붙이고, 끝에 trailer를 붙여서 프레임을 만든다.
    • MAC 주소는 헤더부분에 들어가서 source, destination의 식별자가 된다.
  2. 신뢰성있는 전송
    • 유선은 신뢰성있는 전달이 되기 때문에 필요 없는 기능
    • 무선 링크같이 오류율이 높은 링크에서 주로 사용됨
    • TCP도 신뢰성있는 전송을 제공하지만, TCP는 TCP 패킷을 책임지는 것이므로 프레임에 대해서는 책임 없음
      Link가 더 큰 영역을 체크한다.
    • 에러방지를 제공할 수도 있고 안할수도 있다
  3. 흐름 제어
  4. 에러 검출
  5. 에러 정정
  6. 전이중/반이중 통신

링크 계층이 구현되는 위치

  • 모든 호스트에 구현되어있다.
  • 칩이나 NIC(네트워크 인터페이스 카드)에 구현될 수 있다.(링크계층, 물리 계층)
  • 호스트의 시스템 버스에 연결된다.

  • 보낼 때
    • datagram을 프레임으로 캡슐화
    • 에러 체크, 신뢰성있는 데이터 전송, 흐름 제어 등
  • 받을 때
    • 에러체크, 신뢰성있는 데이터 전송, 흐름 제어
    • datagram 추출해서 네트워크 계층으로 전달

1.1 MAC 주소

  • 32bit IP address
    • 네트워크 계층 주소, 인터페이스마다 할당되는 주소
    • 네트워크 계층에서 사용
    • 라우터에서는 IP 주소를 보고 작업 (Network layer - 3)
  • MAC address
    • 링크계층에서는 IP 주소를 사용할 수 없다.
    • 48bit MAC address
    • 전역적으로 유일한 주소를 가진다.
      (IP주소는 한 서브넷 안에서는 다른 서브넷과 겹칠 수 있다.)
    • 48bit 중 = 24bit는 제조사마다 할당되고, 나머지 24비트는 제조사에서 겹치지 않게 관리
    • MAC 주소는 어댑터의 위치에 따라 변경되지 않는다. 항상 동일한 MAC 주소를 가진다.

1.2 ARP (Address Resolution Protocol)

네트워크 계층 주소(IP주소)와 링크 계층 주소(MAC 주소)가 있으므로 이들 주소 사이에 변환이 필요하다. 이것을 ARP에서 해준다.

목적지의 MAC 주소를 알 수 있는 프로토콜이다.

  1. ARP table
    1. 각 인터페이스마다 [ IP : MAC : TTL ] 주소 매핑을 유지한다.
    2. 테이블에서 각 매핑이 언제 삭제되는지를 나타내는 값: TTL

ARP 동작

ex) datagram을 A에서 B로 보낸다. (local)

  1. A가 broadcasts ARP query 전송, B의 IP 주소를 포함해서 전송한다.
    destination MAC: FF-FF-FF-FF-FF-FF
    모든 노드(B, C, D)가 ARP query를 전송 받고, C, D는 이 쿼리를 무시한다.
  2. B는 응답에 자신의 맥 주소를 포함해서 응답한다.
    source IP, MAC  => B의 주소
    Target IP, MAC => A의 주소
  3. B의 맥 주소가 A의 ARP 테이블에 없는 경우, A는 B가 보낸 응답에 포함된 B의 MAC 주소를 보고 자신의 ARP 테이블에 B의 IP 주소와 MAC 주소를 저장한다

ex) 다른 서브넷에 있는 맥 주소

라우터를 통해서 A에서 B로 datagram 전송하기

가정

  1. A는 B의 IP 주소를 안다.
  2. A는 첫번째 홈 라우터 R의 IP 주소를 안다.
    DHCP에서 첫번째로 사용할 홉 라우터도 알려준다.
  3. A는 R의 MAC주소를 안다.
    • 라우터의 IP주소가 주어지면 ARP를 통해 얻을 수 있다.

동작

  1. A는 IP주소가 source A, destination B인 IP datagram을 만든다.
    데이터그램을 프레임으로 만들고, R의 MAC주소를 MAC destination 에 담아서 보낸다.
    -> 프레임을 라우터로 전송
  2. frame을 A에서 R로 보낸다.
    frame을 받은 R은 프레임을 제거하고 datagram을 IP (네트워크 계층)로 보낸다.
  3. R은 바깥의 인터페이스를 통해 A에서 받은 데이터그램을 B로 보내야 한다는 것을 결정
    R은 A-to-B 데이터그램을 담은 링크 계층의 프레임을 생성한다. 프레임의 destination주소는 B의 MAC 주소이다.
  4. B가 프레임을 받으면 IP datagram을 추출한다.
    B는 데이터그램을 네트워크 계층으로 전달

1.3 Ethernet

  • preamble
    • receiver, sender의 주파수를 동기화 시키기 위해 사용
    • 앞의 7바이트는 10101010이 계속 이어지다가 마지막 바이트가 10101011로 되어있으면 다음 데이터부터 중요 정보가 있음을 나타냄
    • 실제 패킷에는 존재하지 않는 부분
  • adresses
    • 6바이트의 source, destination MAC 주소
    • 목적지 주소: 목적지 어댑터의 MAC 주소를 포함한다.
      어댑터 B는 목적지 주소가 BB-BB-BB-BB-BB-BB이거나 MAC 브로드캐스트 주소인 프레임을 수신하면 프레임의 데이터 필드의 내용을 네트워크 계층으로 전달하며 그 외의 다른 MAC주소를 가진 프레임은 폐기한다.
    • 출발지 주소: 이 필드는 프레임을 랜으로 전송하는 어댑터의 MAC주소를 포함
  • type 필드
    • 이더넷으로 하여금 네트워크 계층 프로토콜을 다중화하도록 허용
    • 호스트가 IP이외의 다른 네트워크 계층 프로토콜을 사용할 수 있다는 것을 알아야 한다.
    • 네트워크 계층 데이터그램에서 프로토콜 필드와 비슷한 역할
  • CRC
    • 프레임에 오류가 생겼는지 판단하고 손상되면 프레임을 버린다.

Ethernet and ARP Analysis

  • frame의 Destination MAC주소는 gaia.cs.umass.edu의 주소가 아니라 속한 서브넷에서 첫번째 홉 라우터의 MAC 주소가 된다.
  • frame에서 GET이 나타나는 바이트의 시작 위치
    = 이더넷 프레임의 헤더 사이즈 + IP 헤더 사이즈 + TCP 헤더 사이즈
    = (6 + 6 + 2 = 14) + 20 + 20 = 54byte

응답 메시지

  • source MAC 주소 = 내 서브넷의 첫번째 홉 라우터의 맥 주소
  • destination MAC 주소 = 내 컴퓨터의 맥 주소
  • frame에서 OK가 시작 부터 몇바이트 떨어져 있는가
    = 이더넷 프레임 헤더 사이즈 + IP 헤더 사이즈 + TCP 헤더 사이즈 + HTTP에서 HTTP/1.1 200 바이트 수
    = 14 + 20 + 20 + 13 = 67byte
  • type = 0x0800 = IPv4 프로토콜

ARP 캐싱

  • $ arp -a
    IP 주소와 해당 IP 주소 기기의 MAC주소 등이 나타남
    각각의 인터페이스가 캐시하고 있는 인터넷 주소와 맥 주소를 매핑하고 있는 정보들이 나타남
    Type = dynamic: ARP 프로토콜을 통해서 알게 되는 엔트리
                 static : 수동으로 입력된 ARP 엔트리
  •  ARP request message
    Src: 00:d0:59:a9:3d:68
    Dst: ff:ff:ff:ff:ff:ff
  • type = 0x0806 = ARP
  • ARP opcode는 이더넷 프레임의 20번째 바이트부터 시작
    이더넷 프레임 헤더(6 + 6 + 2 = 14) + (2 + 2+ 1 + 1 = 6) = 20 byte 떨어진 부분에 opcode가 존재
  • opcode = (REQUEST = 1 = 0x0001), (REPLY = 2 = 0x0002) 값을 가질 수 있다.
  • ARP 메시지에는 sender/target의 IP 주소와 MAC주소가 포함된다.
  • ARP request에서 Target IP address의 MAC 주소를 질문하기 위해 Target IP address에 값을 넣고, MAC 주소는 모르기 때문에 모든 값이 0으로 설정
  • reply message
  • opcode는 이더넷 프레임의 20번째 바이트부터 시작
  • opcode 값 = reply = 0x0002
  • Sender MAC Address 부분에 요청했던 ip 주소의 맥 주소가 담겨온다.
  • Sender는 내가 물어본 곳의 IP, MAC 주소가 담겨있고, Target IP, MAC에는 내 로컬 컴퓨터의 주소가 담겨있다.

2. Socket Programming

Socket

  • 시스템 간 통신의 말단 부분
  • 여러 소켓 API 가 존재
  • 하나의 호스트 안에서 프로세스 간 통신할 때도 소켓 사용
    (IPC , inter process communication)

소켓 API 헤더 파일

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>

 

 

Byte order ( Big endian / Little endian)

0x1A2B3C4D5E6F7080

0x1A2B3C4D5E6F7080 의 엔디안

  1. Little Endian
    바이트 순서가 가장 마지막에 있는 것이 첫번째 주소에 위치
  2. Big Endian
    바이트 순서가 가장 앞에 있는 것이 첫번째 주소에 위치

Network byte order

  • 서로 다른 아키텍처는 바이트 순서가 다르므로 host간 통신에 문제가 발생한다.
  • Network byte order는 Big Endian 이다.
    host -> network 바이트 변환 htonl() unsigned long integer 호스트 바이트를 네트워크 바이트 순서로 변환
    htons() unsigned short integer 호스트 바이트를 네트워크 바이트 순서로 변환
    network -> host 바이트 변환 ntohl() unsigned long integer 네트워크 바이트를 호스트 바이트 순서로 변환
    ntohs() unsigned short integer 네트워크 바이트를 호스트 바이트 순서로 변환

Connection-oriented vs. connectionless

  • Transmission Control Protocol (TCP)
    • data를 전송한 순서대로 도착함
    • 중복된 data가 두번 도착하는 것을 방지
    • 데이터 받으면 ACK 전송
    • 데이터를 받지 못하면 재전송 해줌
    • 연결이 끊어지면 클라이언트한테 알려줌
    • 혼잡 제어와 흐름 제어를 통해 네트워크 혼잡을 제어
    • 신뢰성 있는 데이터 통신이 필요한 경우에 사용
    • HTTP, FTP, SSH, SMTP
  • User Datagram Protocol (UDP)
    • 각 데이터 패킷이 따로따로 놀아서 프로토콜 관점에서는 보낸 데이터 패킷의 순서 의미가 없음
    • 앞과 뒤의 패킷이 관련 없는 패킷
    • 패킷이 보낸 순서대로 도착하는 것을 보장하지 않음
    • 패킷이 도착하지 않아도 알 수 없음
    • unicast: 1:1 통신. 데이터를 보낼 때 하나의 목적지
    • IP broadcast
      내 서브넷에 있는 모든 애들한테 다 패킷을 보냄
      답장을 하던 말던 관심 있던 없던 다 보냄
    • multicast 지원
      내가 보내는 거에 관심이 있는 애들한테만 전송
      그룹이 존재함
      -> 그룹에 조인한 애들을 대상으로 동작
      IGMP - 그룹 조인 관리 프로토콜
    • broadcast, multicast 는 TCP에서 제공하지 못함
      TCP는 1:1 통신, 끝단 사이에서만 통신이 가능
      전송하면 거기에 대한 답을 받아야 함
    • DNS(하나의 패킷 안에 리퀘스트, 리스폰스 다 집어넣어서 보냄), real-time applications(게임, 오디오/비디오 스트리밍)
      네트워크가 혼잡한 상황에서도 동작, 패킷이 중간에 손실되도 정상 동작
    • TCP가 제공하는 기능이 필요하지 않다면 UDP 사용
      -> TCP가 제공하는 기능이 오버헤드가 되기 때문에
    • UDP에서 TCP가 제공하는 기능을 애플리케이션에서 구현하면 비효율적임

2.1 Socket Function

  • socket()
    • socket을 생성
  • bind()
    • 소켓 --- IP 주소 & port 연결
  • listen()
    • 서버에서 TCP 소켓으로 들어오는 요청을 기다린다.
  • connect()
    • 클라이언트에서 원격 IP 주소 & Port 번호를 가지고 연결 요청
    • TCP 클라이언트가 주로 사용
    • UDP 클라이언트도 사용 가능
      -> 사용 시 recv() send()로 데이터 주고받음
  • accept()
    • 서버에서, 받은 TCP 요청에 대해 새로운 소켓을 생성하고 TCP 연결을 받아들인다.
  • send() / recv()
    • 데이터를 주고받는 용도의 메소드
    • 원격 IP addr, port 가 셋팅되어 있는 상태에서 사용한다.
  • sendto() / recvfrom()
    • 원격 주소, port를 인자로 넘겨주며 데이터 송수신
  • close()
    • 소켓 종료
    • 관련된 커넥션도 모두 종료
  • shutdown()
    • 연결을 한쪽에서 끊고 싶을 때 종료 요청
  • select( )
    • 여러 개의 연결 요청을 받아들일 때 사용
  • getnameinfo() / getaddrinfo()
    • hostname과 address 정보 제공
  • setsockopt() / fcntl()
    • socket의 옵션을 변경할 때 사용
    • socket 옵셥을 가져오거나 변경할 때

2.2 Paradigms

전통적인 Client-Server 모델

  • 서버는 새로운 연결을 수신
  • 클라이언트는 서버주소와 연결 설정
  • 클라이언트와 서버는 서로 데이터를 주고받음

Peer-to-Peer

  • 각 peer는 동일한 책임을 가진다.
  • client - server

TCP socket 흐름

TCP 소켓 프로그램 흐름도

UDP socket 흐름

UDP 소켓 프로그램 흐름도

socket 프로그램

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <time.h>
#include <string.h>

int main() {
	printf("Configuring local address...\n");
	struct addrinfo hints; // 반환 받을 유형에 대해 힌트 제공할 구조체
	memset(&hints,0, sizeof(hints));
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE;

	struct addrinfo *bind_address;
	getaddrinfo(0, "8080", &hints, &bind_address);
	printf("Creating socket...\n");
	int socket_listen;
	socket_listen = socket(bind_address->ai_family, 
						bind_address->ai_socktype, bind_address->ai_protocol);
	if (socket_listen < 0) {
		fprintf(stderr, "socket() failed. (%d)\n", errno);
		return 1;
	}
	printf("Binding socket to local address...\n");
	if (bind(socket_listen, bind_address->ai_addr, bind_address->ai_addrlen)) {
		fprintf(stderr, "bind() failed. (%d)\n", errno);
		return 1;
	}
	freeaddrinfo(bind_address);
	printf("Listening...\n");
	if (listen(socket_listen, 10) < 0) { // 허용할 커넥션 개수 = 10
		fprintf(stderr, "listen() failed. (%d)\n", errno);
		return 1;
	}
	printf("Waiting for connection...\n");
	struct sockaddr_storage client_addr;
	socklen_t client_len = sizeof(client_addr);
	int socket_client = 
		accept(socket_listen, (struct sockaddr*)&client_addr, &client_len);
	if (socket_client < 0) {
		fprintf(stderr, "accept() failed. (%d)\n", errno);
		return 1;
	}
	printf("Client is connected...\n");
	char address_buffer[100];
	getnameinfo((struct sockaddr*)&client_addr, client_len, address_buffer, sizeof(address_buffer),
			0, 0, NI_NUMERICHOST);
	printf("%s\n", address_buffer);
	printf("Reading request...\n");
	char request[1024];
	int bytes_received = recv(socket_client, request, 1024, 0);
	printf("Received %d bytes.\n", bytes_received);
	printf("Sending response...\n");
	const char *response = "HTTP/1.1 200 OK\r\n"
							"Connection: close\r\n"
							"Content-type: text/plain\r\n\r\n"
							"Local time is: ";
	int bytes_sent = send(socket_client, response, strlen(response), 0);
	printf("Sent %d of %d bytes.\n", bytes_sent, (int)strlen(response));
	time_t timer;
	time(&timer);
	char *time_msg = ctime(&timer);
	bytes_sent = send(socket_client, time_msg, strlen(time_msg), 0);
	printf("Sent %d of %d bytes.\n", bytes_sent, (int)strlen(time_msg));
	printf("Closing connection...\n");
	close(socket_client);
	printf("Finished.\n");
	return 0;
}
  • struct addrinfo *hints
    • 반환받을 유형에 대해 어떤 형식으로 반환해달라고 힌트를 제공
    • ai_family
      • AF_INET: IPv4
      • AF_INET6 : IPv6
    • ai_socktype
      • SOCK_STREAM: TCP
      • SCOK_DGRAM: UDP
    • ai_flags
      • AI_PASSIVE: 사용할 수 있는 주소를 받겠다
      • AI_NUMERICHOST: 
  • getaddrinfo(const char *hostname, const char *servname, addrinfo *hints, addrinfo **result)
    주소 정보를 반환하는 함수
    127.0.0.1, 8080 모두 텍스트 형태. 텍스트 형태를 address구조체 형태로 변환
    • hostname
      • 원하는 IP주소
      • 0으로 하면 사용 가능한 주소를 나타냄
      • ex) example.com, 192.168.1.1, ::1 (IPv6)
    • servname
      • 서비스 이름 or 포트 번호
      • ex) http, 80
    • hints
      • 반환될 구조체에 대해 정보 제공
      • 희망하는 유형을 알려준다. (원하는 정보 유형에 대한 힌트)
      • ai_family: AF_INET(IPv4), AF_INET6(IPv6), AF_UNSPEC(IPv4, IPv6 둘 다 지원)
      • ai_socktype: SOCK_STREAM (TCP), SOCK_DGRAM (UDP), 0 (둘 다 상관 없음)
      • ai_protocol: 여러개의 프로토콜 중 명확하게 어떤 것을 수용할지 결정. 0이면 모든 프로토콜을 받아들임
      • ai_flags: 추가적인 옵션
        • AI_NUMERICHOST
          무조건 숫자 형태 주소로 리턴
        • AI_NUMERICSERV
          무조건 포트 번호로 리턴
        • AI_ALL
          IPv4, IPv6 두개 다 요청할 때 사용
        • AI_ADDRCONFIG
          network card가 IPv4만 지원하는 경우 IPv4만 리턴되도록 설정
        • AI_PASSIVE
          명확히 주소를 넘겨주지 않고 이 값으로 설정하면 사용할 수 있는 IP주소를 넘겨줌
          서버에서 많이 사용하는 값
    • result
      • DNS서버로부터 받은 네트워크 주소 정보를 돌려줌 (결과값)
      • 다 쓴 뒤에는 freeaddrinfo(result)로 메모리를 해제해야 함
      • 여러개의 값이 들어올 수 있음
  • int socket_listen = socket(int domain, int type, int protocol)
    연결 요청을 대기하는 용도의 소켓 생성
    • domain = bind_addr->ai_family
      통신에 사용되는 프로토콜을 지정
    • type = bind_addr->ai_socktype
      소켓 유형을 지정 (TCP / UDP)
    • protocol = bind_addr->ai_protocol
      선택된 protocol_family와 소켓 유형에서 선택할 수 있는 프로토콜 지정
  • bind(socket_listen, sockaddr *addr, socklen_t address_len)
    TCP 통신에서 서버 주소를 소켓에 연결시킨다.
    • addr = bind_address->ai_addr
      소켓에 연결할 서버(자신)의 주소 
    • addr_len = bind_address->ai
      addr의 크기
    • 호출 성공: 0, 실패: -1
  • listen(int socket, int backlog)
    socket 이 연결 요청을 받을 수 있도록 한다
    • socket
      연결 요청이 오는 것을 대기할 소켓
    • backlog
      허용할 커넥션의 갯수
      허용할 연결의 대기 큐의 크기
      이 크기 이상의 여러 갯수의 요청이 들어오면 이 크기 이상부터는 거절됨
  • int socket_client = accept(int socket_listen, sockaddr *client, socklen_t addrlen) - Blocking Call
    서버가 클라이언트에서 온 요청을 수락하면서 새로운 소켓(통신용) 생성
    • socket_listen
      연결 요청을 받는 소켓
    • client = (struct sockaddr*) &client_addr
      struct sockaddr_storage client_addr
      • 연결요청을 한 클라이언트의 주소를 가져온다.
      • 클라이언트 주소 필요 없으면 NULL
    • addrlen = sizeof(client_addr)
  • getnameinfo(sockaddr sa, socklen_t salen, char * host, socklen_t hostlen, char *serv, socklen_t servlen, int flags)
    • sa = (struct sockaddr*)&client_addr
      host name을 가져올 주소 (구조체) 정보
      input이 된다.
    • salen = sizeof(client_addr)
      구조체의 길이
    • host = address_buffer
      hostname을 가져올 버퍼
      result
    • hostlen = sizeof(host)
    • serv = 0
      result
    • servlen = 0
    • flags
      • 0 인 경우 들어온 그대로 리턴
      • NI_NAMEREQD
        hostname 반환. address 형태로는 리턴하지 않음
      • NI_DGRAM
        UDP 기반의 서비스를 나타내기 위해 사용
      • NI_NUMERICHOST
        IP addr을 리턴, hostname X
      • NI_NUMERICSERV
        port 번호 리턴, service name X
  • recv(socket_client, void* buffer, size_t length, int flags)
    클라이언트에서 전송한 데이터를 buffer에 담음
    • socket_client
      데이터 송수신할 때 사용하는 소켓
    • buffer = 수신된 데이터를 받는 버퍼
    • flags = 0
  • send(socket_client, void *buffer, size_t length, int flags)
    socket_client 소켓을 통해 전송할 데이터를 버퍼에 담아서 보냄
  • close(socket_client)
    연결 종료

2.3 TCP Connections

2.3.1 Multiplexing TCP Connections

I/O models 종류

  • Synchronous vs Asynchronous
    결과를 돌려받는 시점의 차이
    • Synchronous
      시스템콜을 호출을 했을 때 결과를 바로 가져올 수 있는 경우
    • Asynchronous
      호출을 지금 했는데 결과는 미래에 도착함. 나중에 가져올 수 있음
  • Blocking vs Non-blocking
    호출된 함수가 일을 다 끝내기 전에 제어권을 돌려받는지 여부
    • Blocking
      • 호출된 함수가 일을 다 끝내기 전에는 호출한 곳으로 제어권이 돌아가지 않음
    • Non-Blocking
      • 기다릴 필요 없이 다음 명령을 실행할 수 있는 상태가 되는 것
      • 호출된 함수가 일을 다 끝내기 전에 제어권을 돌려받을 수 있음
  1. Synchronous Blocking I/O
     
    1. 어플리케이션이 Read()를 호출하면 제어권이 터널로 넘어감
    2. I/O가 끝나면 커널이 결과를 알려주고 제어권이 돌아옴
    3. I/O 결과가 올때까지 어플리케이션이 Block됨
  2. Synchronous Non-Blocking I/O
    호출 후 읽기가 종료되지 않았어도 바로 리턴됨
    I/O가 일찍 끝난 경우 호출이 늦어지면 작업이 늦어지므로 비효율 발생 (언제 읽기가 끝날지 모름)
    1. read 시스템 콜 호출 -> 즉시 리턴됨
    2. 읽어온 데이터가 없으면 에러 코드를 리턴함
    3. 다른 일을 실행할 수 있다.
    4. 어느 순간 읽기가 종료되었을 때 read()를 호출하면 데이터를 읽어올 수 있다.
  3. Asynchronous Non-Blocking I/O
    asynchronous -> 함수를 호출했을 때 그 결과를 바로 읽을 거를 기대하지 않음
    -> issue / 결과 확인하는 쪽
    - io_submit()/ aio_read() 요청은 하는데 결과를 바로 기대하지는 않음. 이슈가 된것만 알 수 있음
    - io_getevents()
    read를 호출하고 기다릴 필요 없이 나중에 I/O가 다 처리 됐는지 확인하면 됨
  • Socket API는 blocking이 기본
    • accept
      새로운 연결 요청이 들어오지 않으면 계속 기다림
    • recv()
      데이터가 들어오기 전까지는 Blocking 상태가 된다.
  • Blocking I/O 문제
    • data가 들어오지 않으면 들어올 때까지 아무것도 하지 못하고 대기해야 함
    • 웹 브라우저는 리소스를 병렬적으로 다운로드 받음 -> 여러 클라이언트와 동시에 연결할 수 있는 방법 필요

Polling non-blocking sockets

  • non-blocking 소켓 생성
    • fcntl() 에 소켓 fd와 O_NONBLOCK 플래그를 설정해주면 Non-block 형태로 동작
    • recv()호출 시 데이터가 없는 경우에도 즉시 리턴됨
  • 여러개의 소켓이 있는 경우에는 어느 소켓에 data가 도착했는지 알 수 없음
  • 모든 소켓에 대해 recv를 호출해서 확인해야 함 <= Polling
  • Polling 방식은 data가 없어도 있는지 없는지 계속 체크해야 하므로 CPU 낭비

Forking and multithreading

  • 각 연결마다 새로운 스레드 / 프로세스 생성
  • 어려워서 잘 안씀
    while(1) {
        socket_client = accept(socket_listen, &new_client, &new_client_length);
        int pid = fork();
        if (pid == 0) {
        	close(socket_listen);
            recv(socket_client, ..);
            send(socket_client, ..);
            close(socket_client);
            exit(0);
        }
        close(socket_client); // 서버에서는 필요 없음
    }​
     

select() 함수

  • socket 들의 집합이 주어짐
    • 읽을 준비된 소켓에 대해 알려줌
    • 쓰기 준비된 소켓에 대해 알려줌
    • exception이 발생한 소켓 알려줌
  • int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
    fd_set 들은 input과 동시에 아웃풋. 내용을 바꿔서 다시 돌려줌
    timeout에 지정한 시간이 끝나면 무조건 리턴됨

  • event loop = select
    select()는 계속 커넥션만 처리
  • Workers = Thread pool
    시간이 오래 걸리는 일들은 여기서 처리

2.3.2 Synchronous multiplexing with select()

fd_set > 11주차 11p

typedef struct {
    __ft_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
} fd_set;
  • 1024개의 소켓을 커버하려면
    long은 32 비트 == 4byte == 32개의 소켓을 커버할 수 있다.
    1024 / 32 == 32 => 32개의 비트마스크(한개당 4byte)로 1024개의 소켓을 커버할 수 있다.
  • macros
    • fd_set our_sockets;
    • FD_ZERO(&our_sockets): 초기화
    • FD_SET(socket_listen, &our_sockets)
      our_sockets에서 socket_listen 부분을 1로 설정
    • FD_CLR(socket_a, &our_sockets)
      our_sockets에서 socket_a를 clear -> 0으로 설정?
    • FD_ISSET(socket_listen, our_sockets)
      socket_listen이 1로 체크되어있는지 확인

select()

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  • nfds
    100인 경우 100까지 커버해야 하므로 + 1 해서 나누기로 나누었을 때 + 1 되도록 함
    소켓을 만들 때마다 체크해서 max socket을 업데이트해야 함
  • readfds 에 있는 값들을 읽어서 어떤 소켓을 확인할지 보고, 결과를 다시 넣어줌
  • select 에 넘겨준 fd_set 들은 매번 업데이트되어서 돌아오므로 복사본을 넣어주어야 함
    copy = our_sockets;
  • FD_ISSET(socket_listen, &copy)
    select()에서 리턴이 오면 준비가 된 소켓이 어떤 것인지 확인
    • socket_listen에서 준비된 경우는 커넥션을 받을 준비가 된 것 == accept
    • socket_a 은 read할 준비 == recv
    • socket_b 는 read할 준비 됨 == recv
  • timeval
    struct timeval {
        long tv_sec;
        long tv_usec;
    }
    • struct timeval timeout;
      timeout.tv_sec = 1;
      timeout.tv_usec = 500000; // 0.5s
      select(max_socket +1, &copy, 0,0, &timeout);
  • returns
    • 몇개의 소켓 디스크립터가 체크가 되어 있는지
    • timeout = 0 return
    • error = -1
  • 이런 방식으로도 체크할 수 있다.
for (i = 1; i <=max_socket; ++i) {
	if (FD_ISSET(i, &master)) {
    	close(i);
    }
}

stdin : 0

stdout: 1

stderr: 2

2.3.3 TCP Client

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <time.h>
#include <string.h>

int main(int argc, char *argv[]) {
	if (argc < 3) {
		fprintf(stderr, "usage: tcp_client hostname port\n");
		return 1;
	}
	printf("Configuring remote address...\n");
	struct addrinfo hints;
	memset(&hints, 0, sizeof(hints));
	hints.ai_socktype = SOCK_STREAM;
	struct addrinfo *peer_addr;
	if (getaddrinfo(argv[1], argv[2], &hints, &peer_addr)) {
		fprintf(stderr, "getaddrinfo() failed. (%d)\n", errno);
		return 1;
	}
	printf("Remote address is: ");
	char address_buffer[100];
	char service_buffer[100];
	getnameinfo(peer_addr->ai_addr, peer_addr->ai_addrlen, 
		address_buffer, sizeof(address_buffer),
		service_buffer, sizeof(service_buffer),
		NI_NUMERICHOST);
	printf("%s %s\n", address_buffer, service_buffer);
	printf("Creating socket...\n");
	int socket_peer;
	socket_peer = socket(peer_addr->ai_family, 
		peer_addr->ai_socktype, peer_addr->ai_protocol);
	if (socket_peer < 0) {
		fprintf(stderr, "socket() failed. (%d)\n", errno);
		return 1;
	}
	printf("Connecting...\n");
	if (connect(socket_peer, peer_addr->ai_addr, peer_addr->ai_addrlen)) {
		fprintf(stderr, "connect() failed. (%d)\n", errno);
		return 1;
	}
	freeaddrinfo(peer_addr);
	printf("Connected\n");
	printf("To send data, enter text followed by enter.\n");

	while (1) {
		fd_set reads;
		FD_ZERO(&reads);
		FD_SET(socket_peer, &reads);
		FD_SET(0, &reads);

		struct timeval timeout;
		timeout.tv_sec = 0;
		timeout.tv_usec = 100000;
		if (select(socket_peer + 1, &reads, 0, 0, &timeout) < 0) {
			fprintf(stderr, "select() failed. (%d)\n", errno);
			return 1;
		}
		if (FD_ISSET(socket_peer, &reads)) {
			char read[4096];
			int bytes_received = recv(socket_peer, read, 4096, 0);
			if (bytes_received < 1) {
				printf("Connection closed by peer.\n");
				break;
			}
			printf("Received (%d bytes): %.*s", bytes_received, bytes_received, read);
		}
		if (FD_ISSET(0, &reads)) {
			char read[4096];
			if (!fgets(read, 4096, stdin)) {
				break;
			}
			printf("Sending: %s", read);
			int bytes_sent = send(socket_peer, read, strlen(read), 0);
			printf("Sent %d bytes.\n", bytes_sent);
		}
	}
	printf("Closing socket...\n");
	close(socket_peer);
	printf("Finished.\n");
	return (0);
}

2.3.4 TCP Server

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <ctype.h>

int main() {
	printf("Configuring local address...\n");
	struct addrinfo hints;
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE;

	struct addrinfo *bind_address;
	getaddrinfo(0, "8080", &hints, &bind_address);
	
	printf("Creating socket...\n");
	int socket_listen;
	socket_listen = socket(bind_address->ai_family, bind_address->ai_socktype,
			bind_address->ai_protocol);
	if (socket_listen < 0) {
		fprintf(stderr, "socket() failed. (%d)\n", errno);
		return 1;
	}
	printf("Binding socket to local address...\n");
	if (bind(socket_listen, bind_address->ai_addr, bind_address->ai_addrlen)) {
		fprintf(stderr, "bind() failed. (%d)\n", errno);
		return 1;
	}
	freeaddrinfo(bind_address);

	printf("Listening...\n");
	if (listen(socket_listen, 10) < 0) {
		fprintf(stderr, "listen() failed. (%d)\n", errno);
		return 1;
	}

	fd_set master;
	FD_ZERO(&master);
	FD_SET(socket_listen, &master);
	int max_socket = socket_listen;

	printf("Waiting for connections...\n");
	while(1) {
		fd_set reads;
		reads = master;
		if (select(max_socket + 1, &reads, 0,0,0) < 0) {
			fprintf(stderr, "select() failed. (%d)\n", errno);
			return 1;
		}
		int i;
		for (i = 1;i <= max_socket; i++) {
			if (FD_ISSET(i, &reads)) {
				if (i == socket_listen) {
					struct sockaddr_storage client_addr;
					socklen_t client_len = sizeof(client_addr);
					int socket_client = accept(socket_listen, (struct sockaddr *)&client_addr, &client_len);
					if (socket_client < 0) {
						fprintf(stderr, "accept() failed. (%d)\n", errno);
						return 1;
					}
					FD_SET(socket_client, &master);
					if (socket_client > max_socket) {
						max_socket = socket_client;
					}
					char address_buffer[100];
					getnameinfo((struct sockaddr *)&client_addr, client_len, address_buffer,
							sizeof(address_buffer), 0, 0, NI_NUMERICHOST);
					printf("New Connection from %s\n", address_buffer);
				} else {
					char read[1024];
					int bytes_received = recv(i, read, 1024, 0);
					if (bytes_received < 1) {
						FD_CLR(i, &master);
						close(i);
						continue;
					}
					if (strstr(read, "quit")) {
						FD_CLR(i, &master);
						close(i);
						break ;
					}

					int j;
					for (j = 0;j < bytes_received; j++)
						read[j] = toupper(read[j]);
					send(i, read, bytes_received, 0);
				}
			}
		}
	}
	printf("Closing listening socket...\n");
	close(socket_listen);
	printf("Finished.\n");
	return 0;
}

2.4 UDP Connections

 UDP

  • hints.ai_socktype = (SOCK_STREAM) -> SOCK_DGRAM;
  • 연결 필요 없는 프로토콜
  • sendto()를 사용해서 연결 없이 데이터 전송
  • connect()를 호출해서 send()를 사용할 수도 있다.
  1. TCP
    1. listening socket이 있다.
    2. 클라이언트당 소켓 하나 필요
  2. UDP
    1. 하나의 소켓이 여러 클라이언트 커버
더보기
더보기

udp_server.c

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <ctype.h>

int main() {
	printf("Configuring local address...\n");
	struct addrinfo hints;
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_DGRAM;
	hints.ai_flags = AI_PASSIVE;

	struct addrinfo *bind_addr;
	getaddrinfo(0, "8080", &hints, &bind_addr);

	printf("Creating socket...\n");
	int socket_listen = socket(bind_addr->ai_family, 
			bind_addr->ai_socktype, bind_addr->ai_protocol);
	if (socket_listen < 0) {
		fprintf(stderr, "socket() failed. (%d)\n", errno);
		return 1;
	}
	printf("Binding socket to local address...\n");
	if (bind(socket_listen, bind_addr->ai_addr, bind_addr->ai_addrlen)) {
		fprintf(stderr, "bind() failed. (%d)\n", errno);
		return 1;
	}
	freeaddrinfo(bind_addr);

	fd_set master;
	FD_ZERO(&master);
	FD_SET(socket_listen, &master);
	int max_socket = socket_listen;

	printf("Waiting for connections...\n");
	while (1) {
		fd_set reads;
		reads = master;
		if (select(max_socket + 1, &reads, 0, 0, 0) < 0) {
			fprintf(stderr, "select() failed. (%d)\n", errno);
			return 1;
		}
		if (FD_ISSET(socket_listen, &reads)) {
			struct sockaddr_storage client_addr;
			socklen_t client_len = sizeof(client_addr);
			char read[1024];
			int bytes_received = recvfrom(socket_listen, read, 1024, 0,
					(struct sockaddr *)&client_addr, &client_len);
			if (bytes_received < 1) {
				fprintf(stderr, "Connection closed. (%d)\n", errno);
				return 1;
			}
			for (int j = 0; j < bytes_received; j++)
				read[j] = toupper(read[j]);
			sendto(socket_listen, read, bytes_received, 0,
					(struct sockaddr*)&client_addr, client_len);
		}
	}
	printf("Closing listening socket...\n");
	close(socket_listen);
	printf("Finished.\n");
	return 0;
}

udp_client.c

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <time.h>
#include <string.h>

int main() {
	printf("Configuring remote address...\n");
	struct addrinfo hints;
	memset(&hints, 0, sizeof(hints));
	hints.ai_socktype = SOCK_DGRAM;
	struct addrinfo *peer_addr;
	if (getaddrinfo("127.0.0.1", "8080", &hints, &peer_addr)) {
		fprintf(stderr, "getaddrinfo() failed. (%d)\n", errno);
		return 1;
	}

	printf("Remote address is: ");
	char address_buffer[100];
	char service_buffer[100];
	getnameinfo(peer_addr->ai_addr, peer_addr->ai_addrlen,
			address_buffer, sizeof(address_buffer),
			service_buffer, sizeof(service_buffer),
			NI_NUMERICHOST | NI_NUMERICSERV);
	printf("%s %s\n", address_buffer, service_buffer);

	printf("Creating socket...\n");
	int socket_peer = socket(peer_addr->ai_family,
				peer_addr->ai_socktype, peer_addr->ai_protocol);
	if (socket_peer < 0) {
		fprintf(stderr, "socket() failed. (%d)\n", errno);
		return 1;
	}

	printf("Connecting...\n");
	if (connect(socket_peer, peer_addr->ai_addr, peer_addr->ai_addrlen)) {
		fprintf(stderr, "connect() failed. (%d)\n", errno);
		return 1;
	}
	freeaddrinfo(peer_addr);
	printf("Connected\n");
	printf("To send data, enter text followed by enter.\n");

	while (1) {
		fd_set reads;
		FD_ZERO(&reads);
		FD_SET(socket_peer, &reads);
		FD_SET(0, &reads);

		struct timeval timeout;
		timeout.tv_sec = 0;
		timeout.tv_usec = 100000;
		if (select(socket_peer + 1, &reads, 0, 0, &timeout) < 0) {
			fprintf(stderr, "select() failed. (%d)\n", errno);
			return 1;
		}
		if (FD_ISSET(socket_peer, &reads)) {
			char read[4096];
			int bytes_received = recv(socket_peer, read, 4096, 0);
			if (bytes_received < 1) {
				printf("Connection closed by peer.\n");
				break;
			}
			printf("Received (%d bytes): %.*s", bytes_received, bytes_received, read);
		}
		if (FD_ISSET(0, &reads)) {
			char read[4096];
			if (!fgets(read, 4096, stdin)) {
				break;
			}
			printf("Sending: %s", read);
			int bytes_sent = send(socket_peer, read, strlen(read), 0);
			printf("Sent %d bytes.\n", bytes_sent);
		}
	}

	// const char *message = "Hello World\n";
	// printf("Sending: %s\n", message);
	// int bytes_sent = sendto(socket_peer, message, strlen(message), 0,
	// 		peer_addr->ai_addr, peer_addr->ai_addrlen);
	// printf("Sent %d bytes.\n", bytes_sent);
	freeaddrinfo(peer_addr);
	close(socket_peer);

	printf("Finished\n");
	return 0;
}

3. Hostname Resolution and DNS

3.1 DNS

getaddrinfo()

  • getaddrinfo() 를 통해 DNS 쿼리 요청
  • ex) google.com 을 받으면 IP 주소로 변경하는 작업 필요
    1. OS 가 이 getaddrinfo() 요청을 받으면, google.com에 대한 IP 주소가 로컬 캐시에 저장 되어있는지 확인
    2. 로컬 캐시에 저장되어 있으면 로컬 캐시에 있는 값을 반환 (TTL 기간만큼 저장함)
    3. 로컬 캐시에 저장되어 있지 않으면 DNS 서버에 요청
      1. DNS Server에 있는 로컬 캐시에 정보가 있으면 서버 내부에 있는 로컬 캐시 정보를 반환
      2. root DNS 서버에 요청
      3. com
      4. google.com 에 주소 요청

DNS record types

Record Type Type ID Description
A 1 IPv4
AAAA 28 IPv6
MX 15 Mail 교환 시 사용
TXT 16 Text record
호스트에 대해 관련된 추가적인 정보
CNAME 5 Canonical name
alias, 별명
ex)1 example.com <= 2 www.example.com
2번주소와 A 타입으로 요청한 경우 CNAME 타입으로 옴
alias가 1번이라는 것을 알려주면 클라이언트가 그 응답을 보고 다시 1번 주소로 요청
* 255 All cached records
ALL / ANY
모든 타입에 대해서 리턴
로컬 캐시에 가지고 있는 모든 레코드 타입을 리턴
(요청한 주소와 관련된 캐시를 리턴 -> 캐시 정보만 리턴하기 때문에 모든 정보를 리턴하지 않을 수도 있음)
  • getaddrinfo()
    • 127.0.0.1, 8080 모두 텍스트 형태. 텍스트 형태를 address구조체 형태로 변환
  • getnameinfo()
    • 변환한 address구조체의 내용을 텍스트 형태로 다시 변환

IP Lookup 프로그램

더보기
더보기

./lookup example.com

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

int main(int argc, char *argv[]) {
	if (argc < 2) {
		printf("Usage:\n\tlookup hostname\n");
		printf("Example:\n\tlookup example.com\n");
		exit(0);
	}
	printf("Resolving hostname '%s'\n", argv[1]);
	struct addrinfo hints;
	memset(&hints, 0, sizeof(hints));
	hints.ai_flags = AI_ALL;
	struct addrinfo *peer_addr;
	if (getaddrinfo(argv[1], 0, &hints, &peer_addr)) {
		fprintf(stderr, "getaddrinfo() failed.(%d)\n", errno);
		return 1;
	}
	printf("Remote address is:\n");
	struct addrinfo *address = peer_addr;
	do {
		char address_buffer[100];
		getnameinfo(address->ai_addr, address->ai_addrlen,
			address_buffer, sizeof(address_buffer), 0, 0, NI_NUMERICHOST);
		printf("\t%s\n", address_buffer);
	} while ((address = address->ai_next));
	freeaddrinfo(peer_addr);
	return 0;
}

4. Web client

HTTP 프로토콜

(HTTP/1.0, /1.1, 2.0 : TCP)

4.1 HTTP request Type

  • GET
    클라이언트가 리소스 받을 때
  • HEAD
    클라이언트가 리소스에 관한 정보만 원할 때 사용함.
    리소스는 포함되지 않음
  • POST
    client가 정보를 서버에 보낼 때 사용
  • PUT
    문서를 웹 서버로 보내는데 사용
  • DELETE
    웹 서버에 데이터 삭제할 때 사용. 보통 post를 사용함
  • TRACE
    프록시 서버에게 진단정보를 요청함
  • CONNECT
    프록시 서버에게 대신 HTTP 연결을 해달라는 요청
  • OPTIONS
    웹 서버가 어떤 HTTP 요청 타입을 지원하는지 알려줌
    "Allow: OPTIONS, GET, HEAD, POST"

1) GET 요청 포맷

GET

  1. request line
    request type, path, protocol version
  2. HTTP header fields
    • User-Agent: 어떤 클라이언트가 요청하는지 알려줌
    • Host: 웹 서버에게 클라이언트가 요청하는 웹 호스트를 알려줌
    • Connection: 웹 서버에게 클라이언트가 추가적인 요청을 보낼 것인지 알려줌
      • Keep-Alive 계속해서 요청 보낼 예정. 연결 닫지 말고 기다려라.
      • close : 연결 종료
  3. blank line
    1. HTTP 요청 헤더의 마지막 부분에 \r\n\r\n을 넣는다.
    2. 빈줄을 넣어 헤더 부분이 끝난 것을 알려줌

2) HTTP response format

  1. status line
    1. 프로토콜 버전, response code, 응답 코드 설명
  2. HTTP response header
    1. caching과 관련있는 필드들
      Date, Etag, Expires, Last-modified
    2. Content-Type: 클라이언트에게 보낸 리소스의 타입을 알려줌
    3. Content-Length: HTTP response body의 바이트 사이즈
  3. HTTP body
    1. HTTP response header 부분과 빈 줄로 구분됨.

3) HTTP 응답 코드

  1. 200번대
    클라이언트의 요청을 성공적으로 처리함
  2. 300 번대
    요청된 리소스가 새로운 위치로 이동됨
    1. Location 정보도 같이 알려줌
    2. 301 Moved Permanently
    3. 307 Moved Temporarily
      -> 다음에 요청할 땐 알려준 위치 정보 말고 알고있던 정보로 요청
  3. 400 / 500번대
    Error
    1. 400 Bad Request
    2. 401 Unauthorized
      인증 안됨
      아직 클라이언트의 정체를 모름
    3. 403 Forbidden
      금지된 요청
      클라이언트가 누군지 알기 때문에 해당 유저가 접근할 권한이 없다고 알려주는 것
    4. 500 Internal Server Error

4) Response body length

  1. Content-Length header
    • body의 길이를 알려줘야 어디까지 받을지 결정함
    • 서버가 body length를 결정하지 못한 상태에서 데이터 보내기를 시작할 수도 있음
    • 파일을 순차적으로 읽어서 보내고 있는데 언제 다 읽을지 모르는 경우
      -> 읽은 만큼씩 클라이언트에게 보내는 상황인 경우
  2. Transfer-Encoding header
    • 서버가 보낼 데이터 길이를 미리 알 수 없을 때 사용
    • Transfer-Encoding: chunked 설정
    • 클라이언트에게 내가 보낸 바디는 여러개의 청크드로 나눠져서 보낼거다라고 미리 알려주는 것
    • 각각의 청크 구성
      • chunk length (16진수)
      • chunk data
      • 마지막에는 길이가 0인 chunk를 보내면 response body가 다 도착했다는 것을 알려줄 수 있음

4.2 URL

  • protocol
    • 없으면 브라우저가 기본으로 http / https 로 연결함
  • hostname
    • protocol 뒤 부분
    • http 클라이언트가 연결을 하고자 하는 주소
  • port
    • : 으로 구분
    • 생략 시 프로토콜에 따라 기본 값으로 사용됨
    • http: 80
    • https: 443
  • path
    • /부터 나오는 부분
    • document path
    • ? 뒤에는 쿼리 스트링 (웹서버가 해석할 때 사용)
  • Hash
    • #으로 시작
    • 화면에서 어디 부분 위치를 가리키는데 사용됨
    • 해시는 웹서버로 전달되지 않음
    • 웹 브라우저가 정보를 다 받은 다음에 현재 페이지에서 어느 위치로 이동해서 보여줄지 결정할 때 사용됨

4.3 HTTP Post Request

  • client가 서버한테 데이터를 보낼 때 사용
  • body에 데이터를 포함시켜서 전송
  • Encoding form data
    • URL encoding
      - 스페이스 문자는 인코딩될 때 + 모양으로 인코딩 됨
      - 특수문자는 %로 시작하는 두자리의 16진수 값으로 인코딩
  • File uploads
    • Content-Type: multipart/form-data
    • HTML form 을 사용해서 파일 업로드 할 때 사용
    • boundary 지정자가 포함된다

 

 

 

 

 

 

반응형

'sswu' 카테고리의 다른 글

디자인패턴 중간 정리  (0) 2022.04.03
오픈소스 소프트웨어 중간 정리  (0) 2022.03.20
파이썬 기말 정리  (0) 2021.11.15
[모바일SW] 기말 정리1  (0) 2021.11.08
[네트워크 분석 실습] 실습 정리  (0) 2021.10.22
Comments