







CH05 18p부터
Creating Backups
리눅스에서 사용하는 2가지 백업 방법
tar : 파일 단위
dd : 디바이스(디스크) 단위 백업 -> 비트 단위로 전체 디스크 복사
Creating Archive File
tar 명령어는 아카이브(압축) 파일을 생성할 때 사용됨
tar -cvf [ 압축파일 경로 ] [ 백업할 디렉토리 ]
예시1: /home 디렉토리를 /tmp/home.tar로 백업
tar -cvf /tmp/home.tar /home
예시2 : 여러 디렉토리 (/home, /srv, /root, /var)를 /tmp/system-backup.tar로 백업
tar -cvf /tmp/system-backup.tar /home /srv /root /var
-c 는 새 archive 생성
-v 는 처리되는 파일 나열
-f 는 파일 이름 지정
Creating an Compressed Archive File
tar 명령어에 z 옵션 추가시에 gzip으로 압축 가능
tar -czvf home.tar.gz /home
/home 디렉토리를 home.tar.gz라는 압축 파일로 저장
z: gzip 유틸리티로 압축
j: bzip2 유틸리티로 압축 (압축률 높지만, 속도는 느림)
Path Names in tar
1. 절대 경로 포함 아카이브 생성
tar cvf /tmp/old.tar /old
/old 디렉토리를 아카이브
tar는 /로 시작하는 절대 경로를 멤버 이름에서 제거함
/old/hosts면 old/hosts로
2. 상대 경로 포함 아카이브 생성
tar cvf /tmp/old.tar -C /old .
-C 옵션으로 디렉토리를 /old로 이동한 후 현재 디렉토리(.) 기준으로 상대 경로로 저장
아카이브에 ./hosts. ./shadow 등 상대 경로로 포함된다
Extracting an Archive File
파일을 추출할 때는 c (create 옵션) 보다는 x (extract 옵션)을 사용해야 함
1. 기본 압축 해제
tar -xvf /file.tar
file.tar의 내용을 현재 디렉토리로 추출
2. gzip으로 압축된 파일 해제
tar -zxvf /file.tar.gz
압축된 file.tar.gz의 내용을 현재 디렉토리로 추출
3.특정 디렉토리로 추출
tar -xvf / file.tar -C /somedir
/file.tar의 내용을 /somedir 디렉토리에 추출
Moving a Complete Directory
임시 아카이브 파일을 먼저 만들고, 이를 새 디렉토리에 추출해서 수행됨
방법1. 임시 파일 생성 후 추출
tar cvf /tmp.old.tar -C /old .
tar xvf /tmp/old.tar -C /new
/old 디렉토리 내용을 /tmp/old.tar로 백업
그 후 /new로 복원
파일2. 파이프를 이용한 단일 명령
tar -cC /old . | tar -xC /new
/old 에서 바로 /new로 이동 (중간 아카이브 파일 없이)
Making Device Backups Using dd
하드디스크 전체 내용을 외장 USB 하드디스크로 복제할 수 있음
dd if=/dev/sda of =/dev/sdb bs=4096
if=는 입력 디바이스
of=는 출력 디바이스
bs=4096는 블록 크기를 4096 바이트로 설정해서 성능 향상됨
이 작업은 dd 명령어만 할 수 있고, 디스크 전체 복사이므로 매우 조심해서 사용해야 함
Working with Links
링크는 바로가기처럼 동작하며, 다른 파일을 가리키는 포인터 역할을 한다
Hard Link : 원본과 동일. 동일한 inode 공유해서 하나의 파일로 작동
Symbolic Link : 바로가기 개념, 원본 경로만 포함. 다른 inode를 갖고 경로만 저장
원본 사라지면 링크 무효
Working with Symbolic Links
실제 파일이 아닌 파일 이름을 참조하는 링크 (바로가기 개념)
ln -s /etc/hosts ~/computers
/etc/hosts 파일을 가리키는 computers라는 symbolic link 생성
확인 명령어는 ls -l
예시 출력: lrwxrwxrwx 1 root root ... computers -> /etc/hosts
앞에 l(엘) 표시는 symbolic link임을 의미한다
Understading Inodes
모든 리눅스 파일/디렉토리는 inode를 가진다
이 inode는 해당 파일의 모든 메타데이터(파일을 읽기 위해 필요한 관리 정보)를 포함함
inode에 포함된 정보:
파일에 저장된 블록 목록 / 소유자 정보 / 퍼미션 정보 / 기타 파일 속성
파일 = inode 고 파일 이름은 inode를 가리키는 식별자일 뿐, 실제 정보는 inode에 있음
ls -il 은 해당 파일의 inode 번호를 표시한다
예를 들어서 ls -il /etc/hosts -> 15024138 이 inode 번호이다
sudo debugfs /dev/sda1 -> stat <inode 번호>
debugfs 명령어로 디스크 내 inode의 상세 정보 조회 가능
Exercise:
1. 현재 디렉토리에 Hello라는 문자열이 담긴 test 파일 생성
vi test -> :wq
2. test 파일의 inode 번호 확인
ls -i test
3. 디스크에서 test 파일이 저장된 블록(extent 위치 확인
sudo debugfs /dev/sdc
stat <inode번호>
4. 해당 블록을 파일로 덤프하여 내용 ("Hello")이 있는지 확인
sudo blkcat /dev/sdc <extent번호>
5. test 파일 삭제 후, 이전 블록을 다시 덤프하여 데이터가 남아있는지 확인
rm test
sudo blkcat /dev/sdc <same_extent)
남아있다
fsstat /dev/disk 는
파일 시스템에 대한 전반적인 정보를 확인한다
예를 들어서 파일 시스템 구조, 시작 오프셋, 블록 크기 등
debugfs -R "stat <inode number>" /dev/disk는
특정 inode에 대한 상세 정보 조회
debugfs는 디버깅용 RAM 기반 툴
blkcat /dev/disk extent_number > filename는
파일 시스템 블록 내용 추출해서 파일로 저장
blkcat은 특정 블록(extent)의 내용을 그대로 복사한다
:%!xxd (vi 명령어) 는
현재 열려있는 파일을 Hex 모드로 보기
xxd는 헥스 덤프 도구로 바이너리 파일 내용 확인 가능
Understanding the differences between hard and symbolic links
symbolic link의 크기는 참조하는 파일 이름의 바이트 수로 결정됨
hard link랑 다르게, symbolic은 inode를 공유하지 않음
symbolic의 권한은 항상 열려있음 lrwxrwxrwx 이유는 권한은 원본 파일에 의해 관리되므로 링크 자체에는 적용되지 않음
파일 타입이 l(엘) 로 표시되면 이는 symbolic link임을 의미함
Showing the differences between hard and symbolic links
ln -s test symtest : symbolic link 생성
ln test hardtest : hard link 생성
ls -il 명령어로 inode 번호 확인 가능:
hard link는 원본과 같은 inode 번호 공유함
symbolic link는 다른 inode 번호 사용함
하나의 inode가 여러 개의 hard link와 symbolic link에 연결될 수 있음
Exercise:
1. 현재 디렉토리에 "Link Test" 라는 문장이 들어있는 test 라는 파일명으로 파일을 만든다
echo "Link Test" > test
2. test_s 파일명으로 test 파일의 symbolic link 파일을 만든다
ln -s test test_s
3. test_h 파일명으로 test 파일의 hard link 파일을 만든다
ln test test_h
4. test, test_s, test_h 파일의 크기를 확인하고 차이점을 확인한다
ls -l test test_s test_h
→ test_h는 원본과 동일한 크기.
→ test_s는 경로 문자열 길이만큼만 차지(짧음).
5. test, test_s, test_h 파일의 inode 값을 알아낸다
ls -i test test_s test_h
→ test와 test_h는 동일한 inode 번호.
→ test_s는 서로 다른 inode 번호.
6. 디스크에 test, test_s, test_h 파일이 들어있는 block(extent) 위치를 알아낸다
sudo debugfs /dev/sdX
debugfs: stat test
debugfs: stat test_s
debugfs: stat test_h
→ 각각의 파일이 차지한 블록 번호를 확인 가능 (sdX는 실제 디스크 이름으로 바꿔야 함)
7. Symbolic Link와 Hard Link 차이점을 확인한다
cat test
cat test_s
cat test_h
8. test 파일을 지우고 Link된 파일의 결과를 확인한다
rm test
ls -l
cat test_h # ✅ 여전히 내용 출력됨 (inode 공유)
cat test_s # ❌ "No such file or directory" 오류 발생 (경로 끊김)
CH06 Networks and Protocols
Circuits vs Packets
네트워크 통신 방식은 크게 2가지로 나뉜다
1. Circuit-switched 2. Packet-switched
1. circuit-switched 네트워크
두 지점 사이에 전용 링크를 설정해서 통신하는 방식으로 예시에는 옛날 전화 시스템이 있음
장점으로는 guaranteed capacity로 전용선이 연결되어 있으므로 통신하는 당사자한테 일정한 전송 용량이 항상 보장된다
단점으로는 비용이 높다 (회선이 고정으로 연결되어 있으므로)
2. packet-switched 네트워크
circuit-switched와는 다르게 데이터를 여러 개의 작은 패킷으로 나눠서 전송
보통 컴터 간 연결에 사용되고 나눠진 패킷은 공용 네트워크를 통해서 전송됨
장점으로는 모든 노드가 동일한 자원을 함께 사용하고 비용도 부담함
Internetworking
컴퓨터들 사이에 네트워크를 만드는 다양한 기술들 존재
가장 일반적인 네트워크 기술로는 Local Area Network (LAN)이 있음
좁은 범위 내에서 여러 컴퓨터들이 연결되어 서로 통신 가능
Ethernet Frames
Ethernet은 가장 많이 쓰이는 패킷 교환 네트워크이다
frame 이란 패킷 교환 기반의 Ethernet에서는 데이터 패킷 = frame 이다
프레임은 일정한 구조를 가진다
프레임 크기는 최소 64 Bytes ~ 최대 1518 Bytes ( 일부에서는 4096bytes까지 확장 ㄱㄴ)
최신 jumbo frames에서는 최대 9000
IPv6 등에서는 최대 4GB 프레임까지 지원

Addressing
네트워크 상에서 두 대 이상의 컴퓨터는 서로의 실제 거리와 관계없이 통신 가능
이유는 각 패킷에 출발지와 목적지 주소가 포함되어 있기 때문이다
경로만 연결되면 물리적 거리와 상관없다
통신을 하려면, 각 노드(컴퓨터)는 고유한 주소(address)를 가져야 함
이 주소는 전화번호처럼 고유해야함 (지역 코드 + 국가번호 까지 포함되면 유일한 식별자가 되니까)
Ethernet Address
이더넷에서는 각 노드가 고유한 주소를 가지고
모든 노드는 네트워크 상의 모든 통신을 받지만, 목적지 주소랑 비교해서 자신한테 온 통신만 처리함
이더넷은 자원이 공유 되는 구조이므로, 정확한 주소 지정이 중요하다
MAC 주소와 IP 주소는 다르다
MAC 주소는 물리적 주소, 하드웨어에 내장됨
IP 주소는 논리적 주소
이더넷 주소 형식:
48비트 정수값 (보통 16진수 6쌍으로 표현, 예: 00:1A:2B:3C:4D:5E)
제조사들이 IEEE(미국전기전자학회)로부터 주소 블록 배정받아서, 제품마다 고유한 MAC 주소를 순차적 할당함
MAC 주소 확인 가능한 명령어는 /sbin/ifconfig eth0
Organizationally Unique Identifier (OUI)
OUI란 IEEE가 제조사에게 부여하는 고유번호
MAC 주소 구성방식은 총 48비트 = 6옥텟(octet) 으로
앞 3옥텟(24비트) : OUI 즉 어떤 제조사 장비인지를 식별하는 용도
뒤 3옥텟: 제조사가 부여하는 고유 순번
OUI 검색 방법
MAC 주소가 00:E0:29:5E:FC:BE일 경우
→ 앞 3옥텟 = 00-E0-29
하이픈(-)으로 구분해서 입력
Gateways
게이트웨이는 서로 다른 네트워크를 연결해주는 특수장치
어떤 이더넷 노드라도 게이트웨이 역할을 할 수 있지만, 보통 전용 장치를 사용함
두 개 이상의 네트워크 인터페이스를 가진다
이름처럼 데이터를 중계한다
- 다른 네트워크로 가야할 패킷을 전달하고, 자신의 네트워크로 온 패킷을 받아서 전달한다
ping 명령어는 대상 호스트가 응답 가능한지 확인하고
traceroute 명령어는 목적지까지 어떤 경로(라우터)를 거치는지 확인 가능
Internet Addresses
IP 주소는 32비트 (4바이트)로 구성됨
MAC 주소는 6바이트 이므로 MAC 주소보다 짧다
IP 주소는 Network Identifier (어떤 네트워크인지), Host Identifier (네트워크 내의 개별 장치) 이렇게 두 부분으로 구성된다
주소 표기 방식은 Dotted quad notation으로 192.168.0.1 처럼 4부분으로 나눠 점으로 구분한다


Network Address Translation (NAT)
사설 IP 주소들은 외부 인터넷에 직접 노출되지 않음
대신 NAT 기술을 이용해서 공인 IP 주소 하나로 변환됨
사설 -> 공인 주소 변환 기술이다
( 개인 네트워크를 외부에서 숨기고 하나의 공인 IP로 통신 가능)
Loopback Address
= localhost라는 이름으로도 불린다
127.0.0.1 은 자기자신을 가리키는 IP 주소
테스트용으로 자신의 컴퓨터 내에서 네트워크 기능을 확인할 때 사용한다
Ports
포트는 virtual destination port이다
한 노드가 동시에 여러 네트워크 통신을 가능하게 한다
IP주소가 건물 주소 라면, port는 건물 내 방 번호 라고 생각하면 된다
포트를 통해 장치가 어떤 정보(데이터) 를 보낼지/받을지 구분 가능
총 0~65535번 포트 사용 가능
일부 포트는 특정 기능을 위해 예약됨
80번 포트: HTTP (웹 서버)
443번 포트: HTTPS (보안 웹)
25번 포트: SMTP (이메일 전송)
23번 포트: Telnet 서버 (원격 접속)
포트 정보 확인은 리눅스 시스템에서 /etc/services 파일이나 IANA 공식 웹사이트에서 확인 가능

Network Byte Order
네트워크에서 데이터 전송시의 바이트 순서 규칙
바이트 순서란 int (정수)를 저장할 때 어떤 바이트를 먼저 저장할지에 대한 규칙
Little Endian은 가장 낮은 바이트를 먼저 저장
Big Endian은 가장 높은 바이트를 먼저 저장 (사람이 읽는 방향과 동일)
네트워크에서 중요한 이유는 네트워크 통신시 IP 주소, 포트 번호, 패킷 길이 등 숫자가 포함되는데 서로 다른 시스템 간 호환성을 위해서 바이트 순서 통일 필요함
네트워크 바이트 순서는 Big Endian을 사용함 (Most Significant Byte 먼저 전송)
Endian Example - endian.c
메모리에 데이터가 어떤 바이트 순서로 저장되는지 확인
즉, 시스템이 Little Endian인지 Big Endian인지 확인하는 코드

union이란 하나의 메모리 공간을 공유하는 자료형
e.i에 값을 저장하면, 같은 공간을 공유하는 e.c , e.s로 같은 데이터를 다른 크기로 접근 가능함
union을 이용하면 동일 메모리 공간의 저장 순서를 관찰하여 시스템의 Endian 방식을 판별할 수 있음


포인터를 이용해서 메모리에 저장된 각 바이트 값을 직접 확인
시스템이 Little Endian인지 Big Endian인지 판별
addr는 x의 주소를 저장하는 포인터
int* 이므로 4바이트 단위로 접근하는데
우리는 1바이트씩 보고싶으므로 unsigned char*로 캐스팅하여 1바이트씩 접근

보충 설명
- *(unsigned char *)addr: 가장 낮은 주소에 저장된 1바이트
- *((unsigned char *)addr + 1): 그 다음 주소의 1바이트
- ... 이렇게 한 바이트씩 총 4번 출력
Internet Protocol
IP(Internet Protocol)은 여러 개의 독립된 네트워크들을 하나의 통합 네트워크처럼 연결하는 기본 프로토콜
이 프로토콜을 사용해서 연결된 네트워크들을 internets 라고 부른다
하드웨어 독립적 / 패킷 교환 방식 / 비연결형
1. 데이터그램 단위 정의 2. 라우팅 경로 결정 3. 신뢰하지 않는 전송 허용
IP datagram Header Format



Protocol Layering
하나의 프로토콜로는 모든 네트워크 이슈를 처리할 수 없어서
여러개의 기능 특화된 프로토콜을 계층화시켜서 각자 역할을 분담하게 함
Protocol layer models
OSI 모델은 국제표준화기구(ISO)에서 제안한 프로토콜 계층 표준 모델이다
Open Systems Interconnection reference 모델

User Datagram Protocol
TCP/IP 프로토콜에서 포트를 통해 응용 프로그램 간 통신을 가능하게 하는 프로토콜에는
TCP Transmission Control Protocol
UDP User Datagram Protocol
이렇게 TCP, UDP 두 가지가 있다
UDP의 특징으로는
전송 보장 없음 - 패킷 전달을 보장하지 않음
속도는 빠름
그러니까 UDP 를 사용하는 어플리케이션이 메세지 손실, 신뢰성, 연결 손실을 직접 책임져야 함
UDP의 체크섬 계산시 pseudo-header 사용한다
이 헤더는 datagram 앞에 추가된다
뒤에는 16비트 배수를 맞추기 위한 0으로 채워진 옥텟이 추가됨


Transmission Control Protocol (TCP)
UDP와 TCP의 주요 차이점으로는
UDP는 순서 보장 없음, 전송률 보장 없음
TCP는 reliable stream delivery (신뢰할 수 있는 스트림 전송)
중복 없이, 데이터 손실 없이 이루어짐을 보장함

TCP 는 virtual circuit 이다. 전화 거는 거랑 비슷하다
송신자가 수신자랑 연결 요청하고, 양쪽이 연결 조건 협의하고 연결이 완료되면 통신 시작 가능
어플리케이션 입장에서는 신뢰성 있는 연결이 존재하는 것처럼 보이지만 실제로 그렇지는 않음
IP는 전송 보장 안하지만 TCP 계층이 이를 숨겨서 어플리케이션이 신경 안 써도 되게 만듦
Buffered transfer
TCP는 어플리케이션과 독립적으로 전송할 데이터를 묶어 보냄
데이터가 많아질 때까지 기다렸다가 묶어서 전송
송수신단에 각각 버퍼가 존재할 수 있음
TCP는 자동 buffering과 최적의 packet 크기 전송을 통해 효율적 통신 보장
애플리케이션은 세부 처리에 관여 안 해도 됨
Stream Orientation
수신 노드는 데이터 수신 어플리케이션에 보낸 순서 그대로 전달함
Full duplex
TCP는 IP 위에서 양방향 동시 전송 지원
두 개의 독립된 packet stream을 통해서 양방향 데이터 전송 가능
각각의 stream은 데이터를 보내거나 제어 정보를 전달하는데 사용될 수 있음
동시에 데이터 전송 가능
송신과 수신이 각각 독립적인 경로로 동시에
Unstructured Stream
TCP는 데이터를 순서대로 전달해주지만, 데이터의 구조를 보장해주지 않음



The Client-Server Model
Server 는 네트워크 상에서 다른 어플리케이션이 사용할 수 있는 서비스를 제공하는 어플리케이션
accept connections
perform their service
respond with the result
가장 단순한 서버는 하나의 패킷을 받고 그에 대한 하나의 응답을 보냄
일반적인 서버는 하나의 서버가 여러 클라이언트의 요청을 동시에 처리할 수 있음
multiple connections
Client 는 서버에 요청을 보내고 응답을 기다리는 어플리케이션
일반적으로 클라이언트는 특정 서버에 하나의 요청만 한다
그러나 동시 다중 요청도 가능하긴 함
가장 단순한 것들은 UDP를 사용하고 복잡한 것들은 TCP/IP 사용 예:FTP,HTTP,SMTP

The Domain Name System (DNS)
사람이 기억하기 쉬운 이름을 IP 주소에 대응시켜주는 시스템
컴퓨터용 전화번호부처럼 생각할 수 있음
다른 컴퓨터에 연결하면 내 컴터가 먼저 DNS lookup을 수행하면 결과가 상대 컴퓨터 주소임




whois 는 도메인의 이름의 등록 정보를 조회하는 명령어이다
host는 특정 name server에 직접 요청을 보내는 것

host는 옵션 -t (타입 지정) 을 지원함



매뉴얼 명령어 man을 통해서 host 및 whois 에 대한 정보 얻을 수 있음
오래된 시스템에서는 host와 본질적으로 동일한 기능을 수행하는 nslookup이라는 유틸리티 사용
모든 Linux 시스템이 이름 서버 역할을 할 수 있기 때문에 사설 DNS 정보를 가질 수도 있음
많은 회사와 조직에서는 사설(또는 내부) DNS와 공용(또는 외부) DNS를 모두 사용
내부 DNS는 일반에 공개되지 않는 머신에 사용됨
Chapter 7 Functions
Fuctions
TCP/IP 소프트웨어를 UNIX 운영체제에 이식하는 작업을 함
TCP/IP 네트워크와 이를 사용하는 어플리케이션 사이의 인터페이스 구축을 위해서
기존 유닉스 system call이랑 알고리즘을 사용하고 새로운 함수는 ㄹㅇ 필요할때 추가
그래서 만들어진 인터페이스가 Berkeley socket interface 이다
Berkeley UNIX 또는 BSD UNIX로 알려짐
현재 리눅스도 이 인터페이스를 사용중
What is Socket?
socket은 네트워크 통신을 위한 추상화 개념이다
일반적으로 네트워크 입출력을 수행하는 어플리케이션은 5가지 기본 함수를 사용해야됨
open, close, read, write, control

네트워크가 없는 application에서는 파일을 이용한 I/O 작업을 수행함
파일을 열고 / 데이터를 읽거나 쓰고 / 다시 닫는 순서
이러한 함수들은 file descriptor 를 사용해서 동작됨
file descriptor란 함수가 반환하는 작은 정수
시스템은 프로세스마다 file descriptor 테이블을 유지함
Ex #1 Standarad Input, Output, Error
cat < /dev/stdin
표준입력(stdin)을 받아서 cat으로 출력한다
입력을 받는 장치 /dev/stdin 을 사용한다
ls > /dev/stdout
ls 명령의 출력 결과를 표준출력(stdout) 장치에 모아서 전달한다
출력 장치 /dev/stdout으로 보낸다
tty 터미널 정보확인하는 명령어
who : 현재 로그인한 사용자들과 그들의 터미널 정보를 출력. 현재 자신의 터미널 번호 확인
cat < /dev/pts/0 : 현재 사용자가 연결된 가상 터미널과 연결된 특수파일이 /dev/pts/0 이다. pts 는 pseudo terminal slave이고 사용자마다 고유한 가상 터미널 경로로,
이 경로로 stdin, stdout, shell 출력 등 연결된다
Ex #2 File Descriptor
특정 호스트(또는 터미널)을 파일처럼 열고, 데이터를 보내면 실제로 그 호스트로 전달됨
#include <string.h> // 문자열 관련 함수
#include <unistd.h> // write,close 함수 포함
#include <fcntl.h> // open 함수와 파일 열기 옵션(O_RDWR) 정의

/dev/pts/0 경로를 읽기 쓰기 모드 O_RDWR 로 연다
이때 반환되는 fd는 파일 디스크립터 즉 정수값이다
파일 디스크립터 fd를 통해서 데이터를 전송하는데
즉 가상 터미널( /dev/pts/0)으로 문자열 chae-bong-sohn을 전송한다
Ex #3 File Descriptor
특정 터미널 장치(dev/pts/0) 으로부터 데이터를 읽어와서 출력한다

/dev/pts/0 터미널을 읽기 쓰기 모드로 열기
fd는 해당 장치 파일의 파일 descriptor
터미널에서 데이터를 읽어와서 버퍼 buf에 저장
그리고 printf 함수로 버퍼에 저장된 내용을 화면에 출력하고
파일 디스크립터를 닫음
File Number 0 : stdin 키보드로부터 입력
File Number 1 : stdout 콘솔로 출력
File Number 2 : stderr 콘솔로 에러 출력
이 3개는 유닉스에서 자동으로 생성하는 표준 파일 디스크립터
1. 장치 등록
하드웨어 장치는 커널에 등록됨
각 장치는 /dev/input, /dev/tty, /dev/sr0 과 같은 디바이스 파일로 나타남
커널이 장치에 대한 파일 인터페이스 제공
2. open 호출
int fd = open ("/dev/sr0");
/dev/sr0 파일을 열면 커널은 디스크립터 번호 fd 를 할당
이 디스크립터는 해당 장치 파일과 연결
3. read 접근
int ret = read (fd, input, count);
이 디스크립터를 이용한 함수 호출( read,write )
해당 장치 드라이버의 함수로 연결됨


> 는 출력 리다이렉션
< 는 입력 리다이렉션
| 는 파이프로 두 명령어를 연결
ls 는 디렉토리 목록 출력
more 은 입력을 받아서 하나씩 출력
ls의 명령의 출력을 more 명령의 입력으로 넘겨주는 구조
What Is a Socket?
Berkeley socket interface는 파일 디스크립터 개념을 확장해서,
소켓 디스크립터를 도입한 것임
소켓도 파일처럼 정수값(디스크립터) 으로 식별된다
소켓 디스크립터도 파일 디스크립터랑 동일한 방식으로, 동일한 테이블에 저장됨
그렇기 때문에 하나의 어플리케이션은 같은 값의 파일 디스크립터와 소켓 디스크립터를 동시에 가질 수 없다
네트워크 통신을 하려면 먼저 소켓을 열어야한다
소켓이 열리면, 데이터를 읽거나 쓰는 작업 수행
통신이 끝나면, 소켓을 닫고 자원이 해제됨
소켓 = 네트워크 연결을 위한 파일처럼 동작하는 통신 수단
사용 흐름 = open -> read/write -> close
Using Sockets
소켓은 두 가지 방식으로 사용됨. 생성방식은 동일하나 역할이 다름
1. 연결을 기다리거나 (서버)
2. 다른 소켓으로 연결을 시도하거나 (클라이언트)
Active Socket - 클라이언트
클라이언트에서 사용됨
서버에 연결을 먼저 요청함

Passive Socket - 서버
서버에서 사용됨
클라이언트의 요청을 기다림

connection queue : 클라이언트가 요청했으나 서버가 아직 accept( )를 호출하지 않아서 대기중인 상태
이 대기열에 있는 요청 수를 설정할 수 있음
서버는 listen( )을 호출하여 연결 요청을 기다리며
클라이언트가 요청을 보내면, accept( ) 호출로 연결을 수락한다
연결이 수락되면 서버는 read ( ) 와 write ( ) 를 통해서
클라이언트 요청을 읽고, 응답 데이터를 전송한다
통신이 끝나면 close( ) 를 호출해서 연결을 종료하고
다시 accept( )로 돌아가서 다음 연결 요청을 대기한다
Socket Constants
1. protocol type constants
2. address family constants
관련된 include 파일으로는
/usr/include 경로에 있는 #include <sys/types.h> 랑 <sys/socket.h>
address family constants는 프로토콜 계열로도 알려져있음
대표적인 상수로는
AF_INET : 인터넷 통신 (TCP/IP, UDP 포함)
AF_UNIX : 내부 (local machine) 통신 전용
PF = Protocol Family. 예전에 사용 AF= Address Family 현대에 사용
PF_ 와 AF_ 는 거의 동일하게 취급되나
현재에는 AF_ 를 사용함
AF 계열은 PF 계열의 별칭 alias
/usr/include/bits/socket.h 해서 찾았었으나 현재에는 없음
그래서 find /usr/include -name socket.h 해서 찾음


socket ( ) 함수의 3가지 주요 인자
Family : 주소 체계 ( AF_INET 은 IPv4, AF_UNIX는 로컬 통신 )
Type : 통신 방식 ( SOCK_STREAM은 TCP , SOCK_DGRAM은 UDP )
Protocol : 프로토콜 번호 ( 보통 0 으로 설정하면 기본값 자동 선택 )

인터넷용으로 TCP/IP 프로토콜을 쓰는 소켓을 만들어서 소켓 디스크립터를 리턴해달라는 의미이다
PF_INET은 IPv4 인터넷 통신용이고 SOCK_STREAM은 TCP, 0은 기본값 사용
Address Structure - sockaddr_in
가장 중요하게 생각되는 구조체는 struct sockaddr_in 이다
주소 정보와 포트 정보를 담고 있다
struct sockaddr_in {
short sin_family; // 주소 체계 종류 (예: AF_INET)
u_short sin_port; // 포트 번호 (network byte order)
u_long sin_addr; // IP 주소 (network byte order)
char sin_zero[8]; // 구조체 패딩용 (사용 안 함)
};
Byte Order Functions
TCP/IP 프로토콜에서는 바이너리 정수를 전송할 때 가장 큰 바이트 (MSB most significant byte) 를 먼저 전송해야되는데,
이를 Big Endian 방식이라고 한다.
Berkley 팀은 이러한 바이트 순서 차이 처리를 위해서
변환 함수들을 socket 인터페이스에 내장한다
호스트 바이트 순서 <-> 네트워크 바이트 순서 자동 변환
16비트 또는 32비트 정수에 사용됨
host 에서 network로 : htons ( ) , htonl ( )
network 에서 host로 : ntohs ( ) , ntohl ( )
s는 short 16 비트. l은 long 32비트
socket ( )
소켓을 생성하는 socket 함수는 네트워크 통신의 핵심 함수이다
이 함수 없이는 네트워크 통신이 불가능 하다
네트워크의 종단점 endpoint를 생성하며 반환값은 정수값인 소켓 디스크립터
int socket(int domain, int type, int protocol);
domain 은 주소 체계 ( AF_INET , AF_UNIX )
type 은 소켓 타입 ( SOCK_STREAM, SOCK_DGRAM )
protocol 은 프로토콜 번호 ( 0으로 기본 사용 )

netdb.h는 소켓 관련 함수 정의
socket_fd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)
AF_INET 은 IPv4
SOCK_STREAM 은 TCP
IPPROTO_TCP 는 TCP 사용 0을 넣어도 됨
getchar()은 실행 후 종료되지 않도록 입력 대기
make sckt : sckt.c 파일을 컴파일해서 실행파일 sckt로 생성
. / sckt : 프로그램 실행, 소켓 디스크립터 생성
ps -e | grep sckt : 실행 중인 프로세스 중에 sckt 라는 이름의 PID 찾기
ls -al /proc/10149/fd : PID가 10149인 프로세스의 파일 디스크립터 목록 확인
디스크립터 번호별로 어떤 자원에 연결되어있는지 확인
일단 0,1,2는 stdin stdout stderr 임
디렉토리는 해당 프로세스가 열어둔 모든 파일/소켓/장치에 대한 링크를 가지고 있음
Ex #5.

int argc, char *argv [] 명령줄에 인자 처리 argv[1]에 포트번호 기대
사용자 입력을 통해서 포트번호는 argv로 전달
if (2 != argc) 인자수가 부족하면 포트 번호 입력하라는 메세지 출력 후 종료
simpleSocket = socket ( AF_INET, SOCK_STREAM, IPPROTO_TCP ) 는 소켓 생성
if (simpleSocket == -1) 은 소켓 생성 실패시 에러 출력 후 종료
bind( )
bind 함수는 생성된 소켓을 특정 주소 (IP,Port)에 묶는 역할이다
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
sockfd : 소켓 디스크립터
my_addr : 주소 정보가 들어 있는 구조체 포인터 (sockaddr_in)
addrlen : 구조체의 크기 (보통 sizeof(struct sockaddr_in) )

simplePort = atoi (argv[1]) 는 명령줄에서 포트 번호를 입력받아서 정수로 변환
atoi가 convert string to integer 이다
bzero(&simpleServer, sizeof(simpleServer)); 는 구조체를 0으로 초기화
htons( ) 이랑 htonl ( ) 은 host 바이트 순서에서 network 바이트 순서로 변환
INADDR_ANY 는 0.0.0.0 에 해당하는 상수로, 어떤 IP에서 들어오든 다 요청을 수신하겠다는 의미

실제 bind 호출할때 sockaddr_in 구조체를 sockaddr*로 형변환해서 전달
bind는 소켓 address를 원하므로
bind 성공시 0
struct sockaddr vs struct sockaddr_in ( 둘 다 사이즈는 동일 )
struct sockaddr {
u_short sa_family; // 주소 계열 (2 bytes)
char sa_data[14]; // 주소 + 포트 등 정보를 저장 (14 bytes)
};
일반적인 주소 표현 구조체
형변환용 상위 구조체이다
struct sockaddr_in {
short sin_family; // 주소 체계 (예: AF_INET)
u_short sin_port; // 포트 번호 (16비트, network byte order)
struct in_addr sin_addr; // IP 주소 (32비트, network byte order)
char sin_zero[8]; // 구조체 사이즈 맞춤용 더미 공간
};
bind()나 connect()에 실제로 사용되는 상세 구조
sockaddr_in은 실제 값 세팅하고 함수 호출시에 struct sockaddr *로 캐스팅해서 사용
listen ( )
listen 함수는 소켓을 수신 대기 상태로 전환 (서버용 passive socket)
클라이언트의 연결 요청을 받을 준비 함
int listen(int sockfd, int backlog);
backlog는 대기열 길이 (연결 요청 큐의 최대 개수)
즉 동시 대기 가능한 최대 연결 수
일반적으로 5 사용

simpleSocket은 이미 socket()과 bind()를 마친 상태
accept ( )
accept 함수는 클라이언트의 연결 요청을 수락하고 새로운 소켓 디스크립터를 반환한다
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd 는 listen( )까지 완료된 소켓 디스크립터이다
addr 는 클라이언트 주소 정보를 저장할 구조체 포인터
addrlen 은 addr 구조체의 크기 (포인터로 전달, 값 변경 가능)
*addr 랑 *addrlen은 call by reference로 값을 직접 세팅.저장하기 위해서
값을 이 함수가 직접 assign 할 수 있다
bind( ) 에서는 * 없었지만, accept ( ) 에서는 클라이언트 정보를 저장해야하므로 필요
accept는 무한 루프에서 반복 호출됨
클라이언트 연결이 올 때까지 블로킹(대기) 상태가 됨
서버를 중단하고 싶을때는 수동으로 shutdown 필요함

clientName은 클라이언트 IP 및 포트를 저장할 구조체
&clientNameLength는 구조체 크기 전달 (변경 가능)
= {0} 초기화하는건 예기치 않은 값 방지
simpleclient는 클라이언트와 통신하기 위한 파일 디스크립터
클라이언트 1명당 생성되는 전용 소켓
accept()는 블로킹 함수로 연결 요청 있을때까지 대기함
simpleSocket은 listen()까지 완료된 서버용 소켓
(struct sockaddr *) &clientName 은 클라이언트 주소 저장
accept()는 blocking 함수
클라이언트가 접속 요청 보낼때까지 기다림
이 상태에서 멈춰있는거처럼 보인다
accept()는 클라이언트 정보를 저장하는 구조체를 받음
2번째 인자는 클라이언트 주소 저장할 구조체 포인터
3번째 인자는 그 구조체의 크기 (포인터로 전달됨)
전달된 구조체는 accept() 호출 후에 자동으로 채워짐
bind()는 서버 주소 설정이고 accept()는 클라이언트 정보 저장용
accept()는 새로운 소켓 디스크립터를 반환함
반환값은 클라이언트와의 실제 통신에 사용하는 소켓
기존 서버 소켓은 그대로 유지되며 다른 요청을 계속 수신 가능
write ( )
write 함수는 클라이언트에게 문자열을 전송하기 위해 사용
반환 값은 실제로 전송한 바이트 수
ssize_t write(int fd, const void *buf, size_t count);
fd는 쓰기 대상의 파일 스크립터 (여기서는 클라이언트 소켓)
buf 보낼 데이터의 시작 주소
count 보낼 바이트 수
write(simpleChildSocket, APRESSMESSAGE, strlen(APRESSMESSAGE));
simpleChildSocket 은 accept()로 생성된 클라이언트 전용 소켓
APRESSMESSAGE 는 전송할 문자열 메세지
strlen()은 문자열 길이
close ( )
close 함수는 소켓 통신이 끝났을 때 해당 소켓을 명시적으로 닫아주는 함수
int close(int fd);
인자로는 write()까지 완료된 클라이언트 소켓 디스크립터 전달
accept()가 되돌려주는 파일 디스크립터 즉 클라이언트용 소켓
클라이언트에게 데이터 전송(write)이 완료된 후 close 호출
이때 닫는건 child socket (클라이언트 전용 소켓)
accept는 계속 blocking 상태로 다음 연결 수락을 기다림

< Simple Server >





#include <stdio.h> // 표준 입출력 함수 사용 (printf, fprintf 등)
#include <sys/types.h> // 소켓 관련 자료형 정의 (예: size_t, ssize_t)
#include <sys/socket.h> // socket(), bind(), listen(), accept() 함수 정의
#include <netdb.h> // IP 주소 관련 상수 및 구조체 사용
#include <stdlib.h> // exit(), atoi() 등 유틸 함수
#include <string.h> // 문자열 관련 함수 (strlen, bzero 등)
#include <unistd.h> // close() 함수 사용
// 클라이언트에게 보낼 문자열 메시지
const char APRESSMESSAGE[] = "APRESS — For Professionals, By Professionals! \n";
// argc 는 argument count 고 argv는 argument variable[] 포인터 배열
int main(int argc, char *argv[]) {
int simpleSocket = 0; // 서버 listen용 소켓 디스크립터
int simplePort = 0; // 사용할 포트 번호
int returnStatus = 0; // 각 함수들의 성공/실패 상태 저장
struct sockaddr_in simpleServer; // 서버 주소 정보를 담는 구조체
// 인자가 2개가 아닐 경우 (프로그램 이름 + 포트), 사용법 출력 후 종료
if (2 != argc) {
fprintf(stderr, "Usage: %s <port> \n", argv[0]);
exit(1);
}
// TCP/IP용 스트림 소켓 생성 (IPv4, TCP)
simpleSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// 소켓 생성 실패 시 오류 메시지 출력 후 종료
if (simpleSocket == -1) {
fprintf(stderr, "Could not create a socket! \n");
exit(1);
}
else {
fprintf(stderr, "Socket created! \n"); // 소켓 생성 성공 메시지
}
// 명령행 인자(argv[1])로 전달된 포트 번호를 정수로 변환
simplePort = atoi(argv[1]);
// 서버 주소 구조체 초기화 (0으로 채움)
bzero(&simpleServer, sizeof(simpleServer));
// 주소 체계 설정 (IPv4 사용)
simpleServer.sin_family = AF_INET;
// 서버 IP 주소 설정 — INADDR_ANY는 현재 머신의 모든 IP 허용
simpleServer.sin_addr.s_addr = htonl(INADDR_ANY);
// 포트 번호 설정 (네트워크 바이트 순서로 변환)
simpleServer.sin_port = htons(simplePort);
// 소켓을 서버 주소에 바인딩
returnStatus = bind(
simpleSocket, // 바인딩할 소켓
(struct sockaddr *)&simpleServer, // 주소 정보 형변환
sizeof(simpleServer) // 주소 구조체 크기
);
// 바인딩 성공 여부 확인
if (returnStatus == 0) {
fprintf(stderr, "Bind completed! \n"); // 바인딩 성공 메시지
}
else {
// 바인딩 실패 시 오류 출력, 소켓 닫고 종료
fprintf(stderr, "Could not bind to address!\n");
close(simpleSocket);
exit(1);
}
// 클라이언트 연결 요청을 받을 수 있도록 소켓을 수신 대기 상태로 전환
returnStatus = listen(simpleSocket, 5); // 최대 5개까지 대기열 허용
// listen 실패 시 오류 출력 후 종료
if (returnStatus == -1) {
fprintf(stderr, "Cannot listen on socket! \n");
close(simpleSocket);
exit(1);
}
// 클라이언트 연결을 반복해서 처리하기 위한 무한 루프
while (1) {
struct sockaddr_in clientName = { 0 }; // 클라이언트 주소 저장 구조체
int simpleChildSocket = 0; // 클라이언트와 통신할 새 소켓
int clientNameLength = sizeof(clientName); // 구조체 크기 저장
// 클라이언트 연결 수락 (blocking 상태)
simpleChildSocket = accept(
simpleSocket, // 서버 소켓 (listen 상태)
(struct sockaddr *)&clientName, // 클라이언트 주소 저장할 구조체
&clientNameLength // 구조체 크기 전달 (포인터)
);
// 연결 실패 시 오류 출력 후 종료
if (simpleChildSocket == -1) {
fprintf(stderr, "Cannot accept connections! \n");
close(simpleSocket);
exit(1);
}
// 클라이언트에게 문자열 메시지 전송
write(simpleChildSocket, APRESSMESSAGE, strlen(APRESSMESSAGE));
// 클라이언트 전용 소켓 닫기
close(simpleChildSocket);
}
// (이 코드는 무한 루프라 도달하지 않지만) 서버 소켓 닫기
close(simpleSocket);
return 0;
}
connect ( )
connect 함수는 서버에 연결 요청을 보내는 함수이다
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
sockfd는 소켓 디스크립터 (클라이언트 소켓)
serv_addr는 서버 주소(IP/Port 포함)
addrlen은 주소 구조체 크기
bind()와 비슷하지만, 서버가 아니라 서버 주소로 연결을 시도함

read ( )
read 함수는 소켓으로부터 데이터를 읽어들인다
ssize_t read(int d, void *buf, size_t nbytes);
d는 소켓 디스크립터 (데이터를 받을 소켓)
buf는 수신한 데이터를 저장할 버퍼
nbytes는 최대 읽을 바이트 수
반환값은 최대 읽을 바이트 수이다



#include <stdio.h> // 표준 입출력 함수 사용
#include <sys/types.h> // 소켓 관련 자료형 포함
#include <sys/socket.h> // socket(), connect() 등의 함수 포함
#include <netdb.h> // 네트워크 관련 함수와 구조체 포함
#include <stdlib.h> // 일반 유틸 함수 (exit(), atoi() 등)
#include <string.h> // 문자열 처리 함수 포함
#include <unistd.h> // close() 함수 포함
#include <arpa/inet.h> // IP 주소 변환 함수 (inet_addr 등)
int main(int argc, char *argv[]) {
int simpleSocket = 0; // 소켓 디스크립터 초기화
int simplePort = 0; // 포트 번호
int returnStatus = 0; // 함수 리턴값 저장 변수
char buffer[256] = ""; // 서버로부터 받을 데이터를 저장할 버퍼
struct sockaddr_in simpleServer; // 서버 주소 정보를 담을 구조체
if (3 != argc) { // 인자 개수가 3개 (프로그램 이름, IP, 포트) 아니면
fprintf(stderr, "Usage: %s <server> <port> \n", argv[0]); // 사용법 출력
exit(1); // 프로그램 종료
}
// 스트리밍 소켓 생성
simpleSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (simpleSocket == -1) { // 소켓 생성 실패 시
fprintf(stderr, "Could not create a socket! \n");
exit(1);
} else {
fprintf(stderr, "Socket created! \n"); // 소켓 생성 성공 메시지
}
// 포트 번호를 정수로 변환
simplePort = atoi(argv[2]);
// 서버 주소 구조체 설정
bzero(&simpleServer, sizeof(simpleServer)); // 구조체 초기화 (0으로 채움)
simpleServer.sin_family = AF_INET; // IPv4 설정
simpleServer.sin_addr.s_addr = inet_addr(argv[1]); // 문자열 IP를 네트워크 바이트 순서로 변환
simpleServer.sin_port = htons(simplePort); // 포트를 네트워크 바이트 순서로 변환
// 디버깅용 출력
printf(" Port: %s, Address: %s \n", argv[2], argv[1]);
printf("Port: %d, address: %x \n", simplePort, simpleServer.sin_addr.s_addr);
// 서버에 연결 시도
returnStatus = connect(
simpleSocket, // 클라이언트 소켓
(struct sockaddr *) &simpleServer, // 서버 주소
sizeof(simpleServer) // 주소 크기
);
if (returnStatus == 0) {
fprintf(stderr, "Connect successful! \n"); // 연결 성공
} else {
fprintf(stderr, "Could not connect to address! \n"); // 연결 실패
close(simpleSocket); // 소켓 닫기
exit(1); // 종료
}
// 서버에서 메시지 수신
returnStatus = read(simpleSocket, buffer, sizeof(buffer));
if (returnStatus > 0) {
printf("%d: %s", returnStatus, buffer); // 받은 데이터 출력
} else {
fprintf(stderr, "Return Status = %d \n", returnStatus); // 오류 또는 종료 메시지
}
close(simpleSocket); // 소켓 닫기
return 0; // 정상 종료
}

gethostbyaddr( ) gethostbyname( )
gethostbyaddr 는 IP 주소를 넣으면 호스트 이름 반환
struct hostent *gethostbyaddr(const char *addr, int len, int type);
역방향 DNS 조회 ( IP -> 도메인)
gethostbyname 은 호스트 이름을 넣으면 IP 주소를 반환
struct hostent *gethostbyname(const char *name);
정방향 DNS 조회 ( 도메인 -> IP ).


host = gethostbyname() 반환 값은 struct hostent * 포인터 (호스트 정보가 들어있는 구조체)
struct hostent 주요 member : h_name(공식 호스트이름) h_aliases (별칭 리스트) h_addr_list (IP주소 리스트)
IP 주소 추출 코드
addr.s_addr = *(u_long*)host->h_addr_list[0];
첫번째 IP 주소를 in_addr 구조체에 복사
이후에 inet_ntoa()로 사람이 읽을 수 있는 형식으로 변환

gethostname( ) sethostname( )
gethostname은 현재 로컬 호스트의 이름을 반환 / 누구나 사용가능
sethostname은 시스템 호스트 이름 설정할 때 사용 / superuser만 호출가능
외부 네임 서버나 /etc/hosts 같은 룩업테이블 호출 안하고도 서버 이름 확인가능
int gethostname(char *name, int namelen);
int sethostname(const char *name, int namelen);

실행예시 명령어
vi gethostname.c
make gethostname
./gethostname
Host Name : ssl.kw.ac.kr
getservbyname( ) getservbyport( )
getservbyname 는 서비스 이름 -> 포트 번호
servent 구조체에 대한 포인터 반환
getservbyport 는 포트 번호 -> 서비스 이름
보조 함수
struct servent *getservbyname(const char *name, const char *proto);
struct servent *getservbyport(int port, const char *proto);
사용하는 함수에 따라 servent 구조체의 어떤 멤버가 채워지는지 달라짐
포트번호는 network byte order로 입력해야됨



getsockopt( ) setsockopt( )
getsockopt 는 소켓 현재 설정 값 가져올 때
setsockopt 는 소켓 동작 방식 결정하거나 변경
int getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen);
SO_LINGER 는 소켓 옵션 중 하나로, linger 구조체와 함께 사용되며
소켓 종료시 열어둘지 여부를 결정한다
CH08 Socket Programming
User Datagram Protocol
UDP 는 User Datagram Protocol
TCP 는 Transmission Control Protocol
클라이언트와 서버 프로그램은 TCP 사용
대안으로는 UDP가 있는데 datagram 소켓이라고도 함
UDP는 패킷 전달 보장 없고, 패킷 손실 가능, 순서 바뀔 수 있음
통신 예시로는 DNS (domain name service), NTP(network time protocol), TFTP 가 있음
UDP는 지속적인 통신 불필요 클라이언트가 한번 요청하고 응답을 받으면 통신 종료
-z는 zero-I/O 모드 포트 열림 여부만 확인
-v는 verbose 모드 자세한 출력 포트가 열렸는지 출력해줌
nc -z -v 223.194.7.70 1-255 포트 스캔 명령어. TCP 통신
nc -v -u 223.194.7.70 13 daytime 포트 명령어 UDP 통신
-u 안 붙이면 TCP 통신
cat | nc -v -u 223.194.7.70 7 echo 포트 명령어 값을 넘겨주면 똑같이 넘어옴
UDP Server
클라이언트보다 서버가 먼저 있어야 됨 -> UDP 서버부터 생성
필요한 헤더파일들:
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
버퍼 크기 정의: #define MAXBUF 1024
main 함수에서 표준 선언 시작 및 변수 초기화
MAXBUF 크기의 문자 버퍼 선언하여 메세지 저장함
int main(int argc, char* argv[]) {
int udpSocket;
int returnStatus = 0;
int addrlen = 0;
struct sockaddr_in udpServer, udpClient;
char buf[MAXBUF]; // 송수신 메세지 저장하는 버퍼
}
소켓 생성 코드:
udpSocket = socket(AF_INET, SOCK_DGRAM, 0);
if (udpSocket == -1) {
fprintf(stderr, "Could not create a socket!\n");
exit(1);
} else {
printf("Socket created.\n");
}
TCP와 차이점은 SOCK_STREAM 대신에 SOCK_DGRAM 사용함
SOCK_DGRAM 은 UDP 소켓 생성한다
INADDR_ANY 를 사용하면 모든 로컬 주소에 바인딩 된다
포트는 인자로 받은 값을 사용함
포트값 처리시에 htons( ) 호출이 필요하다
udpServer.sin_family = AF_INET;
udpServer.sin_addr.s_addr = htonl(INADDR_ANY);
udpServer.sin_port = htonl(atoi(argv[1]));
bind ( ) 를 사용해서 시스템에 이 소켓을 주소와 포트에 등록한다
바인딩 코드:
returnStatus = bind(udpSocket, (struct sockaddr*)&udpServer, sizeof(udpServer));
if (returnStatus == 0) {
fprintf(stderr, "Bind completed!\n");
} else {
fprintf(stderr, "Could not bind to address!\n");
close(udpSocket);
exit(1);
}
listen ( ), accept ( ), read ( ) 대신에
recvfrom( )을 사용해서 클라이언트 주소도 같이 받아온다
recvfrom( ) 은 blocking function으로 , accept와 유사하다
수신 루프 코드:


소켓 생성 후 sendto( ) 에서 서버 주소를 지정한다
(TCP에서는 connect로 서버 연결하고
연결후 write 또는 send 하는데
UDP에서는 sendto 에서 자동으로 포트가 지정되어서 bind 필요없이 서버 주소 지정하며 데이터 전송)
UDP Client
sockaddr_in 구조체를 설정해서 datagram 소켓 생성
클라이언트는 특정 포트 번호를 명시할 필요 없음
운영체제가 임의의 포트 번호를 자동 할당 ( bind 거의 필요없음 )
서버가 수신한 UDP 패킷에는 클라이언트의 IP 주소와 포트 번호가 포함되어 있음
서버는 응답 보낼 때 이 정보를 그대로 활용 가능함
정보 설정 완료시에 소켓에 bind 하고 전송 준비

전송 전에 메세지를 문자열로 설정한다
sockaddr_in 구조체에 서버 주소 정보를 설정한다
strcpy(buf, "For Professionals, By Professionals.\n");
udpServer.sin_family = AF_INET;
udpServer.sin_addr.s_addr = inet_addr(argv[1]); // IP 주소 받아옴
udpServer.sin_port = htons ( atoi(argv[2]) ); // 포트 번호 받아옴
sendto ( ) 함수로 서버에 메세지를 전송한다
returnStatus = sendto(
udpSocket, buf, strlen(buf)+1, 0,
(struct sockaddr*)&udpServer, sizeof(udpServer)
);
if (returnStatus == -1) {
fprintf(stderr, "Could not send message!\n");
} else {
printf("Message sent.\n");
}
sendto ( ) 호출로 메세지 전송 성공하면
recvfrom ( ) 으로 서버 응답 수신을 대기한다
addrlen = sizeof(udpServer);
returnStatus = recvfrom(
udpSocket, buf, MAXBUF, 0,
(struct sockaddr*)&udpServer, &addrlen
);
if (returnStatus == -1) {
fprintf(stderr, "Did not receive confirmation!\n");
} else {
buf[returnStatus] = 0;
printf("Received: %s\n", buf);
}
< UDP 서버 코드 >
./udpserver 3500으로 실행




< UDP 클라이언트 코드 >
./udpclient 127.0.0.1 3500 으로 실행
실행시키면 끝남 반면에 서버는 기다리고 있음




File Transfer
TCP(transmission control protocol) 또는 스트리밍 소켓을 이용해서 파일 전송 구현
서버는 포트에 바인딩하고, 클라이언트 연결 대기
연결이 생성된다면
1. 클라이언트가 파일 이름 전송
2. 서버가 파일 이름 읽고 -> 파일 디스크에서 읽음 -> 클라이언트에게 전송
다음 예제 코드에서는 이전에 TCP 서버와 달리 소켓을 2개 사용한다
이유로는 하나의 소켓으로는 하나의 클라이언트 요청을 처리 가능하므로
두 소켓을 사용하면 서버는 현재 요청 처리 중에도 다른 클라이언트 연결 수락 가능하다
클라이언트 연결은 First come First served 즉 선착순 방식으로 큐에 저장됨
소켓 1개만 사용시에, 처리 중인 동안 새로운 연결은 connection refused 발생
소켓 2개 사용시에, 현재 응답을 처리하면서 다른 연결을 대기열에 저장함
처리 완료 후에 에러 없이 다음 연결 처리 가능
multithreaded 동시처리 방식과는 다르다
< File Transfer Server 코드 >





< File Transfer Client 코드 >





chdmod 0644로 권한 변경
위 코드들 run
cc -o xferServer xferServer.c 명령어로 컴파일 후 실행 파일 생성
./xferServer 명령어를 실행해서 서버 시작
서버가 시작되면, 모든 로컬 IP 주소의 8888번 포트에서 수신 대기 (listening)
(8888번은 SERVERPORT의 값으로 원하는 대로 변경할 수 있다)
다음으로 cc -o xferClient xferClient.c 명령어로 컴파일 후 실행 파일 생성
./xferClient 127.0.0.1 filename new-filename 명령어로 클라이언트 시작
127.0.0.1은 서버의 IP 주소(여기서는 로컬 호스트), filename은 전송할 원본 파일 이름, new-filename은 서버에 저장될 새로운 파일 이름
Error Handling
일반적으로 함수들은 문제 발생시 음수, 정상일 때 양수를 반환함
하지만 소켓의 경우, 함수 호출이 음수가 아닌 형태로 오류 반환할 수도 있음
오류 코드들은 errno.h 파일에 정의되어있음
대부분 리눅스에는 이 파일이 /usr/include/asm/errno.h 에 있음
Chapter 9 Protocols, Session and State
Protocols, Sessions, and State
Protocol layering 프로토콜 계층화 를 통해서 상호 보완적인 프로토콜을 개발하고
함께 사용해서 복잡한 작업을 처리한다
TCP(Transmission Control Protocol) 는 연결 지향 프로토콜 = connection-oriented
UDP(User Datagram Protocol) 비연결성 프로토콜 = connectionless
Stateful server
Stateful 서버 는 상태를 유지하는 서버로 현재 클라이언트와의 모든 연결에 대한 정보와
그들 사이에 오고 간 통신에 대한 정보를 유지하는 것 / 중앙 집중식
클라이언트 연결에 대한 정보를 유지하면,
서버와 클라이언트 간에 교환되는 메세지 크기를 줄일 수 있어서, 빠르게 서버가 요청 응답 할 수 있음
그러나 손상될 위험이 있다
Stateful 서버 작동 방식
1. 클라이언트 요청 : 클라이언트가 파일 요청
2. 서버 전송 시작 : 서버가 클라이언트로 파일 전송 시작
3. 파일 크기 : 그러나 파일 크기가 상당히 큼
4. 분할 전송 : 파일은 부분적으로 나눠서 순차적 전송이 되어야 함
5. 서버의 상태 유지 : 이 시점에서 서버는 파일의 어느 부분이 클라이언트로 전송되었는지 정보를 유지 해야 함
6. 서버의 파일 상태 관리 : 파일이 서버에 존재하므로 클라이언트는 파일의 실제 크기를 알 수 없음 오직 서버만 알고 서버만이 파일 전송 상태를 유지할 수 있음
예시로는 Post Office Protocol 3 (POP3) , File Transfer Protocol (FTP) , Simple Mail Transfer Protocol (SMTP) 가 있다.
이 프로토콜들은 각 클라이언트 연결 상태를 유지한ㄴㄷ다
서버가 클라이언트의 상태(예: 로그인 여부, 특정 작업 진행 단계)를 알고 있어야만, 올바른 명령을 처리하고 유효성 검사를 수행할 수 있기 때문에 상태 유지가 필수적
Stateless server
Stateless 서버는 상태를 유지하지 않는 서버이다 / 분산형
UDP와 같이 각 클라이언트의 상태와 현재 작업 추적은 어플리케이션 프로토콜에 달려있음
Stateless 서버에서는 클라이언트가 파일 전송을 관리해야 함
클라이언트 어플리케이션은 디스크에서 다음에 읽을 위치를 지정해서
서버가 파일의 어떤 부분을 전송했는지 제어함
클라이언트는 매 요청마다 filename이랑 authentication을 해야 함
(서버가 요청간의 트래킹을 하지않으므로)
stateless 예시로는 Hypertext Transfer Protocol 즉 HTTP 가 있음
웹 서버는 기본적으로 상태 유지를 안 함
HTTP 를 통해서 클라이언트로부터 오는 요청은 원자적 요청으로 간주됨 독립적이여야함
앞서 언급한 프로토콜들 (HTTP ... ) 은 어플리케이션 프로토콜이다
프로토콜이 연결을 요구하는 경우에는 TCP over IP 를 사용함
Domain Name System (DNS) 나 Network TIme Protocol (NTP) 와 같은
비연결성 프로토콜은 UDP 사용
Simple Mail Transfer Protocol이랑 HyperText Transfer Protocol 모두 연결을 요구하지만,
SMTP는 stateful 이고 HTTP는 connectionless 이다
Simple Mail Transfer Protocol
SMTP는 네트워크를 통해 이메일을 전송하기 위한 기술
컴퓨터와 서버가 하드웨어나 소프트웨어랑 관계없이 데이터 교환 가능
SMTP는 메일 전송 프로토콜이지 검색이 아님
메일을 메일서버로 전송하지만 이메일을 읽기위해서는 POP3 같은 별도 프로토콜 필요
SMTP는 포트 25번을 사용했지만 요즘에는 587,465,2525 씀
포트 25번은 주로 SMTP 서버간의 통신에 사용됨 스팸유의!
포트 465번은 원래 Secure Sockets Layer(SSL) 암호화 사용을 위해 지정되었으나
SSL이 Transport Layer Security(TLS)로 대체되면서 사용 안 함
포트 587 은 이제 이메일 제출을 위한 기본 포트임 TLS 암호화 사용
포트 2525는 공식적으로 관련이 없으나 위 포트들 차단의 경우 제공

telnet localhost 25: 로컬 호스트의 25번 포트로 telnet 연결을 시도
HELO localhost : 본인이 localhost 다
VRFY bongbong : 'bongbong'이라는 사용자를 확인하려고 시도
MAIL FROM : <elec@localhost>: 발신자 주소를 'elec@localhost'로 지정
RCPT TO : <bongbong@localhost>: 수신자 주소를 'bongbong@localhost'로 지정
DATA : 메일 내용
QUIT : 클라이언트가 연결 종료

cd /var/mail: 사용자가 /var/mail 디렉토리로 이동. 로컬 사용자들의 이메일이 저장되는 위치
cat bongbong으로 bongbong이라는 파일 내용 출력
nc -v -z 127.0.0.1 25: nc (netcat) 명령어를 사용하여 127.0.0.1의 25번 포트로 연결을 시도
wsl에서 SMTP 설치하는 명령어: sudo apt install sendmail

IMAP 는 어떤 기기에서든 이메일 접근 가능
서버가 이메일을 저장함
오프라인에서 접근할 수 없음
클릭전에는 다운로드되지않음
서버에서 자동삭제 안되므로 더 많은 서버 공간 요구
POP3 는 다운로된 기기에서만 접근 가능
다운로드된다면 서버에서 이메일이 삭제됨
오프라인에서 접근 가능
기본적으로 다운로드되서 오래걸릴수있음
자동삭제되므로 서버 공간 절약
HypterText Transfer Protocol
Uniform Resource Locator (URL)
Hypertext Markup Language (HTML)
HyperText Transfer Protocol (HTTP)
HTTP 1.1 이 오랫동안 사용된 표준 버전이다
5 Request -> 5.1 Request-line -> 5.1.2 Request-URI
요청(Request)은 요청 라인(Request-line)을 포함하고, 요청 라인은 요청 URI(Request-URI)를 포함한다는 것을 의미
Request-URI = "*" | absoluteURI | abs_path | abs_path | authority
Get http://www.example.com/index.html HTTP/1.1
GET 메서드를 사용하여 http://www.example.com/index.html이라는 완전한 URI를 요청하는 HTTP/1.1 형식의 요청
telnet hostname.domainname 80
특정 호스트의 80번 포트(일반적으로 HTTP 기본 포트)로 telnet 연결을 시도
telnet 223.194.7.70 80
telnet 명령어를 사용하여 IP 주소 223.194.7.70의 80번 포트(표준 HTTP 포트)에 연결을 시도
GET / HTTP/1.1
서버의 루트 디렉토리(/)에 대한 HTTP/1.1 GET 요청
Common Gateway Interface ( CGI )
CGI 는 웹서버가 HTTP 또는 HTTPS 사용자 요청을 처리하기 위해
외부 프로그램을 실행할 수 있도록 하는 인터페이스
일반적은 사용사례는 웹페이지에서 웹 폼을 제출할때 발생
form data는 URL에 CGI 스크립트를 나타내는 HTTP 요청과 함께 웹 서버로 전송됨
웹 서버는 새로운 컴퓨터 프로세스에서 스크립트 실행 후 폼 데이터를 스크립트로 전달함
CGI 스크립트는 출력을 생성하고 (HTML 형식으로) 이 출력을 웹 서버로 다시 전달하며,
웹 서버는 이를 브라우저 응답으로 다시 전달함
sudo a2enmod cgi : Apache의 cgi 모듈을 활성화합니다. (CGI 스크립트 실행을 위해 필요)
ls /etc/apache2/mods-enable : 활성화된 Apache 모듈 목록을 확인합니다.
sudo mkdir /var/www/cgi-bin : CGI 스크립트가 저장될 디렉토리(/var/www/cgi-bin)를 생성합니다.
sudo systemctl restart apache2 : Apache 웹 서버를 재시작하여 변경 사항을 적용합니다

웹 브라우저에서 http://223.194.7.90/cgi-bin/add.py?a=10&b=20와 같은 URL로 접근하면, 웹 서버가 /cgi-bin/add.py 스크립트를 실행하고,
쿼리 스트링(?a=10&b=20)을 파이썬 스크립트에 전달하며,
스크립트의 결과가 웹 페이지로 출력됨
sudo cp add.py /var/www/cgi-bin: 작성한 add.py 스크립트를 Apache의 CGI 디렉토리로
복사
sudo chmod +x /var/www/cgi-bin/add.py: 복사된 스크립트에 실행 권한을 부여

http://223.194.7.90/cgi-bin/add.cgi?a=10&b=20
sudo cp add /var/www/cgi-bin/add.cgi: 생성된 실행 파일 add를 /var/www/cgi-bin 디렉토리로 add.cgi라는 이름으로 복사
Common Gate Interface VS JavaServerPage
각 요청마다 새로운 프로세스 시작 / JVM 내에서 실행됨
오버헤드 느림 / 더 빠름
많은 언어 / 자바만
확장성 떨어짐 / 확장성 굳
File Transfer Protocol (FTP)
FTP는 서버에서 클라이언트로 컴퓨터 파일을 전송하는데 사용되는 표준 통신 프로토콜
클라이언트-서버 모델 아키텍처 기반
FTP 사용자는 사용자 이름과 암호 형태인 clear-text 로그인 프로토콜을 사용해서 자신을 인증함
그러나 서버가 허용한다면 익명으로도 연결 ㄱㄴ
암호화하기위해서 SSL/TLS (FTPS) 로 암호화되거나
SSH File Transfer Protocol (SFTP) 로 대체됨
File Transfer Functions
초기 FTP는 그래픽 사용자 인터페이스 (GUI)가 없는 명령줄 프로그램이였음
현재 FTP는 지원 중단됨. 완전 제거

telnet 명령어로 IP주소 의 21번포트에 연결 시도
USER 명령어로 elec 이라는 사용자 이름 입력
PASS 명령어로 비밀번호 comm 입력
HELP 명령어로 서버가 지원하는 FTP 명령 목록 요청

PASV 명령어로 FTP의 Passive Mode을 요청함 즉 채널을 하나 여는 것
클라이언트가 데이터 연결을 위해 접속해야할 IP 주소와 포트 번호를 제공함
LIST 명령어로 현재 디렉토리 파일 및 목록을 요청함
다시 PASV 해서 다른 포트 번호를 부여 받음
RETR 명령어로 txt 파일을 서버로부터 다운로드(retrieve)하라고 요청함
QUIT 명령어로 FTP 세션 종료
netstat 명령어로 Network Status를 알아냄
sudo netstat -an4p
a : 모든 연결과 리스닝 소켓 표시
n : IP주소/포트를 숫자로 표시
4 : IPv4만 출력
p : 연결한 프로세스 (PID) 표시
FTP 의 Listen Port의 경우
telnet 223.194.7.75 56767. : FTP 제어 포트(21번)가 아니라, PASV 명령을 통해 서버로부터 전달받은 특정 데이터 포트(56767)로 직접 telnet 연결을 시도
PASV 명령으로 서버가 특정 포트를 열면,
클라이언트는 이 포트로 데이터 연결을 맺고 하나의 데이터 전송(예: LIST 또는 RETR)을 수행한다.
해당 작업이 완료되면 데이터 연결은 즉시 종료된다
Methods for Maintaining State
stateful 프로토콜은 서버가 클라이언트 연결 상태를 추적해야 함
이의 예시로는 Post Office Protocol 3 (POP3) , File Transfer Protocol (FTP) , SImple Mail Transfer Protocol (SMTP) 가 있음
Storing State on the Server
상태 유지 방법으로는
1. 초기 연결 상태는 인증 상태가 false로 할당되지만 클라이언트가 인증되면 true로 설정됨
2. 클라이언트가 서버에 처음 연결하면 해당 클라이언트를 위한 고유한 세션 생성
3. 서버는 이 세션을 사용해서 여러 클라이언트 요청에 필요한 다양한 값 (서버 내부 데이터 포함 가능) 저장
4. 각 클라이언트 세션은 고유함 다른 세션 접근 불가능
장점으로는 서버가 클라이언트 연결 정보를 유지함으로써, 교환되는 메세지 크기 줄일 수 있음
단점으로는 잘못된 정보가 되어 손실되거나 손상될 수 있음
세션 종료시에 상태 정보 삭제
세션이 존재하므로 서버가 클라이언트와의 연속성을 유지할 수 있음
Session Lifecycle : 세션은 클라이언트 요청과 서버 응답에 따라서 현재 상태 내에서 변경됨
Handling Commands : 서버는 클라이언트 명령이 현재 상태 내에서 유효한지 여부를 평가함
Stateless Protocol and Server : HTTP 같은 프로토콜은 상태 유지를 지원하지 않음
그러므로 stateless로 간주함 = 서버가 각 요청을 독립적으로 처리
이전 요청에 대한 정보를 기억하지 않음
Stateless 서버에서도 세션은 여전히 유지될 수 있음
즉 HTTP 자체는 stateless이지만 그 위에 구축되는 application을 통해서 세션 상태 관리 가능하다
-> PHP,ASP,JSP 같은 기술 사용
웹 서버는 세션 상태 자체를 유지하지 않는다
단순히 요청을 받아서 어플리케이션 서버로 넘겨주는 중계 역할을 하며
실제 세션 관리는 어플리케이션 서버에서 처리됨 !
Session ID 는 stateless 서버와의 통신에서 상태를 유지하는데 매우 중요하다
세션 아이디는 메인 서버와 분리된 어플리케이션에 의해 처리될 때 상태 유지를 가능하게 함
HTTP에서는 암호화가 안되어서 하이재킹 가능성이 있어서 HTTPS와 같은 암호화 프로토콜을 사용해야함
Session Initialization : 각 세션은 고유하게 할당된 ID와 함께 시작
세션 아이디는 모든 요청과 함께 전송됨
클라이언트가 요청을 보낼때마다 세션 아이디를 함께 보내면, 서버는 이 아이디를 사용해서 해당 클라이언트의 현재 세션 정보를 찾아내고 유지할 수 있음
세션 아이디는 쿠키나 URL 파라미터로 전송될 수 있음
쿠키는 브라우저에 저장되어 자동으로 전송되는 방식이고
URL 파라미터는 URL 주소 자체에 세션아이디가 포함되는 방식
세션 아이디가 클라이언트에 의해서 전송되지만,
서버는 어플리케이션 상태를 유지하고 세션 아이디를 할당하는 책임이 있음
Storing State on the Client
트랜잭션의 클라이언트 측에서 상태유지가 가능함
장점은 서버가 세션을 유지해야하는 부담을 덜어줌
그러나 클라이언트가 각 요청마다 상태를 유지해야함
cookie : 클라이언트에 변수의 값을 저장하는데 사용됨
hidden form variables : 값과 정보 보관
URL parameter : 변수 정보 저장
Cookies
쿠키는 클라이언트에 단순 텍스트 파일로 저장되는 정보의 조각들이다
HTTP에서 웹 브라우저에 의해 저장되고 관리됨
쿠키에 포함된 내용
1. 이름
2. 만료시간
3. 도메인
4. 값
장점으로는 최소한의 리소스를 요구하며 관리가 용이
단점으로는 텍스트 파일이므로 컴퓨터와 권한을 가진자에게 다 보임
민감한 데이터 손상 위험성
Form Variables
HTML 폼은 type 매개변수를 hidden으로 설정할 수 있음
숨겨진 폼 필드는 아래와 같이 보임 이렇게 값을 설정해서 상태 유지 가능

장점으로는 쿠키같이 추가 리소스 필요로 하지않음
단점으로는 보안이 부족함. 숨겨져있을뿐임 HTML 소스 검사해서 값을 쉽게 볼 수 있음
URL Parameters
HTTP 표준은 모든 HTTP URL에 변수 정보를 포함하는 쿼리 문자열을 추가하는걸 허용
정보 전달을 위해 name/value 패턴을 따름
http://host.domain.com/script.cgi?variable1=value1&variable2=value2
Chapter10 Client-Server Architecture
Client-Server Architecture
여러 클라이언트를 동시에 관리할 수 있는 능력 필요
다중 클라이언트 처리는 연결 관리, 자원 활용, 서버 응답성
처리 전략은 Multiplexing(여러 연결 관리) , Forking(새로운 프로세스 생성), Threads (동시 처리) 가 있음

입력한 수 만큼 자식 프로세스를 fork( )로 생성하여 각 클라이언트가 서버에 접속해서 메세지를 보내고 응답을 받음
child_func 함수는 각 자식 프로세스가 실행할 클라이언트 로직을 담고 있음
socket (AF_INET, SOCK_STREAM, IPPROTO_TCP) 로 TCP 소켓 생성
sAddr.sin_family = AF_INET : IPv4 주소 패밀리 사용.
sAddr.sin_addr.s_addr = INADDR_ANY : 모든 인터페이스에서 수신
sAddr.sin_port = 0 : 임의의 포트 할당
bind(sock, (const struct sockaddr *)&sAddr, sizeof(sAddr)) 로 소켓 바인딩
send(sock, buffer, strlen(buffer), 0): 서버로 데이터를 전송
recv(sock, buffer, 25, 0): 서버로부터 데이터를 수신 (최대 25바이트)
자식 생성 루프에서는 nchildren 수만큼 반복
if ((pid = fork()) == 0) : fork() 시스템 호출을 사용하여 자식 프로세스를 생성
pid가 0이면 현재 프로세스가 자식 프로세스임을 의미

struct sockaddr_in sAddr: 서버의 주소 정보를 저장하는 구조체
int listensock: 클라이언트의 연결 요청을 들을 소켓 디스크립터
int newsock: 클라이언트와 실제로 통신할 새로운 소켓 디스크립터
AF_INET : IPv4 주소 체계
SOCK_STREAM : TCP
IPPROTO_TCP : TCP
listen(listensock, 5) : 연결 요청 대기열 설정 최대 5개로
Multiplexing
멀티플렉싱은 단일 서버 프로세스에서 여러 클라이언트를 처리하는 것이다
클라이언트들이 서버에 연결하면 서버는 이 클라이언트들을 watch list에 추가한다
이 watch list들은 socket descriptors로 구성된 리스트
notification으로 어떤 클라이언트가 데이터를 보냈는지 혹은 연결되었는지 알려줌
The select( ) Function
select 함수는 디스크립터 (sockets, files, pipes, FIFOs 등) 들의 집합을 지정할 수 있게 해줌
여러 소켓 중 어느 소켓에서 event가 발생했는지 감지할 수 있도록 함
어떤 디스크립터이든 동작함
시스템은 우리 프로그램을 sleep 시키고
소켓들의 활동을 polls 함 ( 변화가 있는지 감지 )
이벤트가 발생하면 우리 프로그램을 wake 시킴
busy loop를 작성해서 CPU 시간 낭비 막아줌
#include <sys/select.h>
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
n: 첫 번째 매개변수. 이 매개변수는 세 가지 소켓 집합(readfds, writefds, exceptfds)을
통틀어 가장 높은 번호의 디스크립터에 1을 더한 값
fd_set *readfds: 읽기 이벤트를 감지할 소켓들
fd_set *writefds: 쓰기 이벤트를 감지할 소켓들
fd_set *exceptfds: 예외 이벤트를 감지할 소켓들
struct timeval *timeout: select 함수의 타임아웃 지정
NULL이면 무한 대기, 0이면 즉시 반환.
select 함수가 실제로 받는 값은 실제로 복사되어야 할 총 디스크립터의 수를 받음
번호 시작점은 0부터 시작함 따라서 전달되는 숫자는 가장 큰 디스크립터 번호에 1을 더한 값
select 함수는 자신에게 전달된 디스크립터 집합 (fd_set)을 수정함
이벤트가 발생하지 않은 디스크립터는 해당 집합에서 제거됨
select ( ) 를 재사용하려면 원본 디스크립터 집합의 복사본을 유지해야함
select 호출될 때마다 fd_set이 변경되기 때문에
select 함수 사용시 필요한 매크로
void FD_SET (int fd, fd_set *set) : 특정 디스크립터 fd를 감시하도록 플래그 설정
void FD_CLR (int fd, fd_set *set) : fd_set에서 특정 디스크립터 fd의 플래그 해제
int FD_ISSET (int fd, fd_set *set) : select 함수 반환된 후 특정 디스크립터 fd에 플래그가 설정되어 있는지 여부 확인
void FD_ZERO (fd_set *set) : fd_set 을 모두 0으로 초기화함 함수 실행전에 항상 이 매크로로 초기화해야함
초기화하는 매크로 제외하고 디스크립터 fd를 인수로 필요로 함
FD_SETSIZE는 fd_set이 표현할 수 있는 최대 디스크립터 개수
플래그가 설정된 디스크립터의 의미는 해당 소켓에서 활동이 발생했음을 나타냄
fd_set sockreadset : 읽기 이벤트를 감시할 변수
FD_ZERO(&sockreadset) : sockreadset을 초기화
FD_SET (sd, &sockreadset) : 소켓 디스크립터 sd를 감시대상으로 만듦
select(FD_SETSIZE, sockreadset, NULL , NULL, NULL) : FD_SETSIZE: 가장 높은 디스크립터 번호 + 1
&sockreadset: 읽기 이벤트를 감시할 fd_set.
NULL, NULL, NULL: 쓰기, 예외 디스크립터 집합과 타임아웃은 사용하지 않음을 의미
즉, 무한 대기

select 함수가 모니터링하는 것들은
새로운 연결 시도, 클라이언트 연결해제, 기존 연결에서의 읽기 이벤트
서버의 리스닝 소켓에서 읽기 이벤트가 감지되었을 때는
새로운 연결로 간주하고 accept( ) 함수가 호출됨
이 새로운 디스크립터는 서버의 watch set에 추가됨
클라이언트 소켓에서 읽기 이벤트가 발생했을 때는
recv 함수가 사용됨 - 들어오는 데이터를 확인하기 위해
데이터가 수신되지 않았을 때는
클라이언트 연결 해제를 나타낸다
서버는 해당 디스크립터를 watch set에서 제거함
데이터가 수신되었을 때는
데이터는 처리된다 그리고 일반적으로 클라이언트에게 다시 에코 됨


Forking
유닉스에서 여러 클라이언트를 처리하는 전통적 방법은 fork( ) 시스템 호출을 사용하는 것이다
fork ( ) 호출시에 부모 프로세스의 정확한 복제본이 생성됨
새로운 자식 프로세스가 이 복제본으로 시작됨
Program Counter (PC)
부모의 heap 영역
stack 영역
데이터 영역
모든 open descriptor 가 모두 복사되는데,
부모의 PID는 복사되지 않는다!
자식의 프로세스는 새로운 고유한 PID 를 가진다
fork는 두번 반환되는데, 부모 프로세스에서는 새로운 자식 프로세스의 PID 를 반환함
자식 프로세스에서는 0을 반환함
이걸로 부모 자식 구분
fork는 호출하는 프로그램의 정확한 복사본을 생성함
자식 프로세스는 부모가 fork( ) 호출 지점에 있었던 바로 그 지점부터 실행을 시작함
Forking에서는
자식 프로세스 생성시에 모든 open된 디스크립터들이 자식에게 복사됨
자식 프로세스 생성시에 각 복사된 디스크립터에 대해 reference count가 1 증가됨
그 결과, 자식 프로세스는 부모가 사용하던 리스닝 소켓 디스크립터 닫아야됨
부모는 자식이 사용하던 클라이언트 소켓 디스크립터 닫아야됨
제대로 안 닫으면 reference count 가 부정확해짐
fork 는 각 클라이언트에 대해 새로운 프로세스를 생성하는게 쉽다
각 클라이언트가 고유한 프로세스에서 실행됨
단일 클라이언트가 서버 독점하는거 방지해줌ㅇㅇ
한 자식 프로세스가 충돌하더라도, 다른 자식에게 영향을 미치지 않음
단점으로는 공유 메모리의 부족
부모 프로세스가 시작시에 공유 메모리 세그먼트를 생성함
각 자식 프로세스는 이 공유 메모리에 대한 연결을 상속받음
공유 메모리 접근시에 semaphore 로 동기화해야함
One Process Per Client
멀티 프로세스 서버를 위해 가장 단순한 아키텍처는
클라이언트당 하나의 프로세스를 사용하는 것
서버는 클라이언트가 연결할 때까지 기다린 다음에
연결을 처리하기 위한 프로세스를 생성한다
A Forking Server
부모 프로세스는 클라이언트가 연결할 때까지 기다린다
클라이언트가 연결되면 fork 호출해서 자식 프로세스 생성하고
자식이 연결을 처리하도록 함
새로운 연결을 계속 listen 함
자식 프로세스는 클라이언트로붙터 데이터를 읽고, 이를 다시 echo 함
데이터 처리 다 하면 연결을 닫고 exit 함
그동안 부모 프로세스는 다시 연결을 기다리기 위해 루프로 돌아감


Preforking: Process Pools
이전의 fork는 실행중인 프로세스를 복사하는건 성능적 단점이 있음
더 많은 클라이언트가 연결될 수록 delay 발생 가능
이러한 startup costs 완화를 위해서,
어플리케이션 시작 지점에 여러 프로세스를 미리 fork ( ) 해서 process pool에 넣어두는 것이다
이를 Preforking 이라고 함
클라이언트가 연결되면, 미리 생성된 프로세스 중 하나가 연결을 처리함
accept 가 부모 프로세스가 아닌 각 자식 프로세스에서 호출됨
리스닝 소켓 디스크립터는 모든 자식 프로세스에서 열린 상태로 유지되므로
동일한 리스닝 소켓에서 accept 호출 가능함
커널은 클라이언트 연결 처리할 미리 실행 중인 자식 프로세스 중 하나를 선택함
Apache Web Server가 프로세스 풀 process pool을 사용한다
아파치 설정 파일에서 지정할 수 있는 항목에는
초기 자식 프로세스 수, 최대 자식 프로세스 수, 최소 idle childern 수, 최대 idle children 수가 있음
부모 프로세스는 자식 프로세스들을 지속적으로 모니터링하여 얼마나 많은 프로세스가 idle 상태인지 확인함
그에 따라 부모는 추가적인 자식 프로세스 종료시키거나 새로운 프로세스를 생성함
Apache 버전2는 쓰레드 풀 thread pools 도입함
쓰레드 풀은 프로세스 풀과 유사하지만, 프로세스 대신 thread를 생성함
클라이언트 연결 처리 핸들러는 어플리케이션 초기화 시점에 생성됨
Preforking Server
부모 서버 프로세스는 루프를 사용해서 자식 프로세스들을 초기화함
fork 할 자식 프로세스 수는 command line으로 지정됨
부모는 wait 함수를 사용해서 모든 자식이 종료될때까지 자신의 종료를 지연시킴
wait 호출이 없으면 부모는 자식을 생성한 직후 즉시 종료될 것임.. zombie 문제 야기
자식은 들어오는 클라이언트 연결 처리를 위해서 동일한 리스닝 소켓에서 accept 호출
선입선출 방식으로 요청을 처리할 자식 프로세스 선택
선택된 자식 프로세스는 클라이언트로붙터 데이터를 읽고, 응답을 다시 보내고, 연결을 닫는다
다음 클라이언트를 기다리기 위해 accept 를 다시 호출함
이 부분이 fork 와 가장 큰 차이점이다
fork 서버는 자식이 통신 후 종료하지만, preforking 자식은 재활용됨


Multithreading
스레드는 경량 프로세스이며 부모 프로세스의 주 메모리 공간을 공유함
더 작은 자원을 사용하고, context-switch 시간을 가짐 전환이 빠름
단점은 덜 안정적이고, 공유 메모리 때문에 충돌 가능
but 견고하다
공유 메모리가 제대로 관리 필요함
thread-local storage TLS 모든 스레드는 전역 변수를 공유한다
클라인트별 정보를 유지하기 위해 스레드 로컬 저장소 를 사용해야 함
사용이유1. 스레드간 데이터 충돌 방지 - 독립된 변수 공간 제공
사용이유2. 클라이언트별 정보 유지
사용이유3. 코드 단순화
사용이유4. 성능 향상
전역 변수가 스레드 간 공유되어야한다면 mutexes 뮤텍스와 같은 동기화 객체를 사용해서 접근을 제어하는게 중요함
뮤텍스는 한번에 하나의 스레드만 공유 자원에 접근하도록해서 데이터 손상 방지함
모든 전역 변수 및 구조체에 대한 접근을 동기화(뮤텍스)하는 것이 필수다
A Multithreaded Server
클라이언트당 하나의 스레드를 사용하는 것이 가장 간단한 형태의 멀티스레드 서버 아키텍처이다
그러나 시스템이 지원할 수 있는 최대 스레드 수는
최대 프로세스 수보다 훨씬 적다
지속적인 연결을 유지해야하기 위해 대안적인 아키텍처(스레드 풀) 필요
멀티 스레드는 멀티 프로세스와 매우 유사하게 작동 - 클라이언트별로 처리하므로
부모는 서버 프로세스는 클라이언트 연결을 기다린다
클라이언트 연결되면, 서버는 새로운 스레드 생성하고
이 스레드한테 새로운 소켓 디스크립터를 넘겨줌
새로운 스레드는 클라이언트로부터 들어오는 데이터 처리하고 다시 클라이언트한테 echo함
통신 완료시에 해당 스레드는 연결을 닫고 종료됨
그동안 부모는 추가적인 클라이언트 연결을 위해 계속 루프를 돔


Thread Pool Server
스레드 풀은 프로세스 풀과 유사하게 동작
어플리케이션 시작될때 특정 개수의 스레드 생성해서 스레드풀을 형성함
하나의 스레드가 충돌하면 공유 메모리 공간이나 파일 디스크립터 같은 자원때문에 위험하다
메모리 손상 위험
하나의 스레드 문제가 다른 스레드에 영향을 미치는걸 방지하기 위해 신중한 설계 필요


Combining Preforking and Prethreading
멀티프로세스는 안정적이지만 context switching이 느리다
멀티스레드는 더 빠른 context switching 을 제공하나, 자식 스레드가 실패하면 충돌하는 경향이 있다
그래서 둘을 병합함
Preforking 모델은 서버는 미리 정해진 수의 프로세스를 fork 함
process 는 클라이언트 연결을 직접 처리하지 않음
각 자식 프로세스는 유한한 수의 스레드를 생성함
스레드가 클라이언트 연결을 처리한다
스레드가 충돌해도 자식 프로세스 내의 다른 스레드에게만 영향을 미침
프로세스가 처리할 수 있는 특정 요청 수를 설정할 수 있도록 허용
요청 제한에 도달하면, 오래된 프로세스는 종료되고 새로운 프로세스 생성됨
Which Method Should You Choose?
Multiplexing Server 고려사항
짧은 수명의 클라이언트 연결을 가진 낮은 볼륨의 서버에 적합
단일 클라이언트가 서버를 독점해서 다른 클라이언트 방해할 수도 있음
SMP 사용 제한. 단일 cpu에서 단일 프로세스로 실행되므로
Single Process/Thread Per Client 고려사항
클라이언트당 하나의 프로세스 또는 스레드를 구현하는 것이 가장 간단함
프로세스는 더 큰 안정성을 제공함
스레드는 더 빠른 컨텍스트 스위칭, 더 적은 자원 사용
Coordination and Memory Sharing 고려사항
웹 서버는 클라이언트가 독립적으로 동작하므로 공유 메모리 중요성이 덜함
enterprise applications은 공유 메모리 필수적
Process Pools 고려사항
컨텍스트 스위칭 오버헤드 줄일 수 있음
공유 메모리 부족으로 여전히 제한됨
'과목 정리' 카테고리의 다른 글
| 멀티미디어공학 정리 (0) | 2025.01.16 |
|---|---|
| 컴퓨터 네트워크 정리 -2 (0) | 2025.01.16 |
| 컴퓨터 네트워크 정리 -1 (0) | 2025.01.16 |
| 시스템 프로그래밍 정리 (2) | 2024.07.17 |
| 리눅스 기초 (0) | 2024.07.17 |