과목 정리

네트워크 프로그래밍 정리

kimkim615 2025. 9. 24. 19:48

 

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랑 다르게, symbolicinode를 공유하지 않음

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 linksymbolic 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 LinkHard 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)

OUIIEEE가 제조사에게 부여하는 고유번호

 

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인지 판별

 

addrx의 주소를 저장하는 포인터

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)

 

UDPTCP의 주요 차이점으로는

UDP는 순서 보장 없음, 전송률 보장 없음

TCPreliable stream delivery (신뢰할 수 있는 스트림 전송)

중복 없이, 데이터 손실 없이 이루어짐을 보장함

 

 

 

TCP virtual circuit 이다. 전화 거는 거랑 비슷하다

송신자가 수신자랑 연결 요청하고, 양쪽이 연결 조건 협의하고 연결이 완료되면 통신 시작 가능

 

어플리케이션 입장에서는 신뢰성 있는 연결이 존재하는 것처럼 보이지만 실제로 그렇지는 않음

IP는 전송 보장 안하지만 TCP 계층이 이를 숨겨서 어플리케이션이 신경 안 써도 되게 만듦

 

Buffered transfer

TCP는 어플리케이션과 독립적으로 전송할 데이터를 묶어 보냄

데이터가 많아질 때까지 기다렸다가 묶어서 전송

송수신단에 각각 버퍼가 존재할 수 있음

TCP는 자동 buffering과 최적의 packet 크기 전송을 통해 효율적 통신 보장 

애플리케이션은 세부 처리에 관여 안 해도 됨

 

Stream Orientation

수신 노드는 데이터 수신 어플리케이션에 보낸 순서 그대로 전달함

 

Full duplex

TCPIP 위에서 양방향 동시 전송 지원

두 개의 독립된 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_STREAMTCP , SOCK_DGRAMUDP )

Protocol : 프로토콜 번호 ( 보통 0 으로 설정하면 기본값 자동 선택 )

 

인터넷용으로 TCP/IP 프로토콜을 쓰는 소켓을 만들어서 소켓 디스크립터를 리턴해달라는 의미이다

 

PF_INETIPv4 인터넷 통신용이고 SOCK_STREAMTCP, 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 ( )

 

sshort 16 비트.      llong 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,2stdin 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 *addrlencall 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 모두 연결을 요구하지만,

SMTPstateful 이고 HTTPconnectionless 이다

 

 

 

Simple Mail Transfer Protocol

 

SMTP는 네트워크를 통해 이메일을 전송하기 위한 기술

컴퓨터와 서버가 하드웨어나 소프트웨어랑 관계없이 데이터 교환 가능

SMTP는 메일 전송 프로토콜이지 검색이 아님

메일을 메일서버로 전송하지만 이메일을 읽기위해서는 POP3 같은 별도 프로토콜 필요

 

 

 

SMTP는 포트 25번을 사용했지만 요즘에는 587,465,2525

포트 25번은 주로 SMTP 서버간의 통신에 사용됨 스팸유의!

포트 465번은 원래 Secure Sockets Layer(SSL) 암호화 사용을 위해 지정되었으나

SSLTransport 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 dataURLCGI 스크립트를 나타내는 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 명령어로 FTPPassive 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_SETSIZEfd_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