Section 01. 전송 계층 관련 프로토콜 분석
01. TCP 헤더 분석
TCP는 전송 계층이 해야 하는 대표적인 작업을 담당하는 프로토콜이다.
전송 계층이 해야하는 인터페이스 제공, 데이터 무결성 보장, 혼잡 제어(흐름 제어), 연결 설정 및 해제는 모두 TCP 헤더에 포함되어 수행된다.
응용 프로그램이 소켓을 통해 인터넷으로 전달할 데이터를 보내면 TCP가 TCP 헤더를 붙여 IP로 내려 보낸다. IP는 IP 헤더를 붙여 이더넷을 내려보내는 이처럼 캡슐화 과정을 거쳐 이더넷이 데이터를 전송한다.
- (IP헤더와 마찬가지로) 32bit 기준으로 나눈다.
- 필수 부분은 4Byte (5 * 4)
- TCP를 사용하는 모든 응용 프로그램들은 포트를 사용해 구분
- 포트번호의 길이는 16bit → 0 ~ 65,535까지의 포트번호를 갖는다.
- 받는 쪽의 응용 프로그램이 웹 데몬(HTTPD)와 같이 잘 알려진 소프트웨어라면, 0 ~ 1023까지의 well-known 포트 번호를 가지고 있다.
- 일반적인 응용 프로그램이라면 1024 ~ 65,535 이하의 임의의 값을 할당 받는다.
- 데이터 전송 과정에서 순서가 뒤바뀌거나 사라지는 문제
- Sequence Number(일련번호), Acknowledge Number(ACK 번호)
- 컴퓨터 작동하는 시간동안 무수히 많은 수의 데이터를 전송하기 때문에 충분히 커야 한다. → 32bit 씩 할당 ⇒ 약 42억개의 번호 사용 가능
- THL(TCP Header Length): 옵션을 포함한 TCP 헤더의 길이 (데이터 포함 X)
- 4Byte 단위의 줄의 길이를 의미. (옵션이 없는 경우) 5가 기본값
- ECE(ECN-Echo): IP 헤더에서의 ECN 필드와 연동된다.
- ECN은 IP 헤더에서 혼잡제어에 사용 → 네트워크에 혼잡이 발생하여 패킷이 제대로 전달되지 않을 경우 활성화 (1)
- IP의 ECN이 네트워크 크기가 혼잡해 윈도우 크기를 줄인다고 결정하면 TCP의 ECE가 1로 바뀐다. (ECN-Echo라고 하는 이유)
- 라우터의 혼잡한 상태 확인: 네트워크 계층
- 윈도우 크기를 줄여 혼잡을 완화하는 작업 수행: TCP
- CWR: Congestion Window Reduced, ECE를 받아 윈도우 크기를 줄였다는 것을 확인. → ECE를 중복적으로 보내지 않아도 된다는 의미
- URG: Urgent, 긴급하게 처리해야 할 데이터가 있는 경우 → 1
- ACK: TCP 헤더의 ACK 번호가 의미가 있는 경우 → 1, 0이면 ACK 번호 무시
- PSH: push, 1이 되면 순서가 맞지 않아도 버퍼에 있는 데이터를 응용 계층으로 무조건 올려 보낸다.
- SYN: Synchronize, 연결 설정에 사용되는 CR. 연결 설정 시 1로
- FIN: Finalize, 연결 해제에 사용되는 DR. 연결 해제 시 1로
- RST: Reset, 연결 설정 혹은 해제가 원만히 이루어지지 않을 경우 1로 표시해 리셋하자는 의미
- Window Size: 슬라이딩 윈도우 프로토콜의 윈도우 크기. 16bit로 윈도우의 최대 크기(ACK 없이 보낼 수 있는 데이터의 개수)는 65,536개
- Checksum: 에러 검사 코드. 16bit. TCP의 검사합은 헤더와 데이터 모두 검사
- Urgent Pointer: URG가 1이 되면 의미가 생긴다. 통신 중 긴급하게 처리해야 하는 데이터가 있으면 URG가 1이 되고, 이럴 경우 데이터 중 긴급하게 처리 되어야 하는 위치를 Urgent Pointer가 갖고 있다.
02. UDP
TCP는 데이터 전송 시 정확해 안정성이 높지만, 수행하는 작업이 많아 느리다.
화상회의나 유튜브와 같이 실시간으로 많은 양의 데이터를 전송해야하는 경우, TCP의 작업이 부담스러울 수 있다. 이때, TCP 대신 UDP(User Datagram Protocol)를 많이 사용한다.
→ 안정성보다는 속도를 요구하는 네트워크 통신에서 UDP/IP를 사용
IP에서 올라온 패킷을 거의 그대로 응용 프로그램에게 전달한다.
- UDP 헤더는 TCP 헤더에 비해 아주 간단하다.
- 1번 줄은 TCP와 마찬가지로 보내는 쪽 포트와 받는 쪽 포트
- 2번 줄 UDP Length는 데이터를 포함한 UDP의 전체 길이를 Byte로 표시한다.
- UDP는 헤더를 포함한 전체에 대해 에러검출 코드로 검사합(checksum)을 사용한다.
03. RTP
- RTP는 스트리밍 데이터 전송을 위해 설계된 실시간 전송 프로토콜(Real-time Transport Protocol)이다.
- 인터넷 전화(VoIP), 동영상 스트리밍, 화상통신과 같은 멀티미디어 통신에 사용
- TCP나 UDP는 운영체제가 인터넷을 사용하는 응용 프로그램에게 제공하는 통신 서비스
- RTP는 UDP를 사용하여 빠르게 데이터를 전송하도록 설계되었다.
- 다양한 비디오 및 오디오 포켓을 지원하기 위해 헤더에서 기능을 추가하거나 뺄 수 있도록 설계되었다.
- RTP는 UDP/IP를 활용하기에 RTP 헤더 - UDP 헤더 - IP 헤더 - 이더넷 헤더들이 차례대로 붙는다.
- 지터 보상, 패킷 손실, 느린 전송 감지 기능을 제공
- 일대일 통신, 멀티캐스트(특정 다수, 화상회의)를 통해 여러 곳에 데이터를 전송할 수 있도록 한다.
04. 혼잡제어
혼잡제어를 위해 TCP 헤더에는 ECE와 CWR 필드가 사용되고, IP에서는 ECN이 사용된다.
- IP가 혼잡하다고 발견하면 TCP에서 윈도우 크기를 조절한다.
혼잡제어를 위해서는 TCP의 연결설정 단계에서 양쪽이 ECN을 사용할 것인지 합의한다.
-
혼잡제어를 사용을 원하는 호스트 A는 연결설정에서 SYN, CWR, ECE 필드를 1로 만들어 보낸다.
→ 받은 호스트 B가 동의하지 않을 경우 SYN, ACK만 보낸다. ⇒ 혼잡제어 무시
→ 받은 호스트 B가 동의할 경우 SYN, ACK, ECE 필드를 1로 만들어 보낸다.
-
IP의 ECN 필드는 TCP에서 윈도우 크기를 줄이도록 결정한다.
- ECN은 ECT(ECN Capable Transport) 비트와 CE(Congestion Experienced) 비트 두 개로 구성된다.
- ECN 필드가 (0, 0)으로 설정되면 혼잡제어를 하지 않는다는 의미
- (0, 1) 혹은 (1, 0)으로 결정되면 라우터에게 혼잡제어를 하라는 의미 ⇒ ECT 상태
-
라우터에서 혼잡이 발견되면 호스트 B는 TCP의 ECE를 1로 만들어 호스트 A에게 보낸다. ECE를 받은 호스트 A는 슬라이딩 윈도우의 크기를 줄이고, ECE에 대한 확인으로 CWR을 1로 만들어 보낸다.
Section 02. 소켓 프로그래밍
01. 소켓 프로그래밍 개요
1) 클라이언트와 서버의 연결
운영체제는 0에서 65,535까지의 포트 번호 중 비어있는 번호를 응용 프로그램에게 제공한다. 서버 쪽 포트는 여러 개의 소켓이 연결되고, 클라이언트들은 포트에 연결된 멀티 소켓에 하나씩 연결 된다.
→ 응용 프로그램들이 통신을 하려면 소켓에 접속해야 한다.
→ 네트워크에서 데이터를 보낸다는 것은 클라이언트 소켓이 서버 소켓으로 데이터를 보낸다는 것.
⇒ 네트워크 프로그래밍 = 소켓 프로그래밍
- 클라이언트 소켓은 connect()를 이용해 서버 쪽 소켓과의 연결 시도 → 서버 쪽의 accept()가 connect()를 받아준다.
- connect() 이전에 accept()가 준비되어야 하기에 서버 쪽 코드 실행 후, 클라이언트 코드를 실행한다.
2) 파일에 접근하는 방법
소켓 사용 방법은 파일에서 데이터를 읽거나 쓰는 작업과 유사하다.
-
파일을 사용하기 위해서는 해당 하일이 존재하는지, 존재한다면 파일에 접근할 권한이 있는지를 먼저 살펴봐야 한다.
-
open(): 파일을 사용하기 위한 준비 단계.
-
fd = open(”파일이름”) 과 같이 open()이 성공적으로 실행되었다면, 파일에 접근할 수 있는 열쇠(fd)를 준다.
→ 이 열쇠를 통해 파일에 있는 데이터를 읽거나 쓸 수 있다.
-
close(fd): 작업한 뒤 열쇠를 반환하는 작업
-
open() → read() / write() → close() 순으로 파일 작성이 이루어진다.
- unix.txt에 “Test” 라는 데이터를 쓰는 코드
#include <unistd.h>
#include <fctl.h>
void main() {
int fd;
fd = open("unix.txt", 0_RDWR)
write(fd, "Test", 5);
close(fd);
}
3) 소켓에 접근하는 방법
소켓 통신에서는 파일 작업처럼 socket() → send() / recv() → close() 순으로 이루어진다.
- send(cs): 소켓에 쓰기(write) 연산을 하면 데이터를 보낸다는 의미이다.
- recv(cs): 소켓에서 읽기(read) 연산을 하면 데이터를 가져온다는 의미이다.
- cs = socket() 이 후에 클라이언트 소켓이 서버에 접속하는 과정(connect())과 서버가 이를 받아들이는 과정(accept())이 추가된다.
클라이언트 쪽의 경우, socket() → connect() → send() / recv() → close() 순으로 작업이 이루어진다.
- cs = socket()이 성공하면 열쇠(cs)를 주는데, 이를 소켓 기술자(socket descriptor)라고 부른다. 이후 모든 작업은 소켓 기술자(cs)를 통해 이루어진다.
- 열쇠를 얻은 뒤, connect()가 서버와의 접속을 시도한다. 이후 서버에게 데이터를 보내는 경우 send()를 사용하고, 서버로부터 데이터를 받을 때는 recv()를 사용한다.
- 소켓 사용을 마치면 close(cs)를 사용하여 소켓 기술자를 반환한다.
먼저 실행되어야 할 서버 쪽은 socket() → accepr() → send() / recv() → close() 순으로 작업이 이루어진다.
- 서버 쪽은 bind()와 listen()이 추가된다.
- bind(): 서버는 포트 하나에 여러 개의 소켓이 연결된다. 이때 포트에 여러 개 의 소켓을 연결하는 단계
- listen(): 여러 개의 소켓을 사용할 수 있는 상태로 만든다. listen()은 accept()와 쌍으로 사용한다. 소켓을 듣고 있다(listen)가 클라이언트가 접속을 시도하면 그 중의 하나를 받아들이는(accept) 형태다.
02. 클라이언트-서버 코드 분석
01. 클라이언트 코드
02. 서버 코드
03. 실행과 종료
유닉스 상에서 서버와 클라이언트 소켓 코드를 실행하는 방법
- 서버와 클라이언트 코드 컴파일 → gcc -o server server.c 를 사용해 server라는 실행 파일을 만들고, gcc -o client.c를 사용해 client 실행 파일을 만든다.
- 서버는 백그라운드로 실행
- 클라이언트 프로그램을 실행 시키면 서버로부터 “test”를 받아 Receive [test]가 출력된다.
- 앞서 서버 코드에서 여러번 연결돼도 수신받는 것이 이 이야기