<B3D7C6AEBFF6C5A9B1B3C0E75FB1E8C5C2C8A32E687770>

Similar documents
Microsoft Word - ntasFrameBuilderInstallGuide2.5.doc

Microsoft PowerPoint - chap02-C프로그램시작하기.pptx

작성자 : 기술지원부 김 삼 수

gyuha.com/doku/doku.php?id=vim:vim_%EB%AA%85%EB%A0%B9%EC

리눅스 프로세스 관리

[ 컴퓨터시스템 ] 3 주차 1 차시. 디렉토리사이의이동 3 주차 1 차시디렉토리사이의이동 학습목표 1. pwd 명령을사용하여현재디렉토리를확인할수있다. 2. cd 명령을사용하여다른디렉토리로이동할수있다. 3. ls 명령을사용하여디렉토리내의파일목록을옵션에따라다양하게확인할수

Microsoft Word - windows server 2003 수동설치_non pro support_.doc

System Recovery 사용자 매뉴얼

Microsoft PowerPoint Android-SDK설치.HelloAndroid(1.0h).pptx

PowerPoint 프레젠테이션

Chapter 05. 파일접근권한관리하기

PowerPoint 프레젠테이션

운영체제실습_명령어

Microsoft PowerPoint 통신 및 압축 명령어.ppt

Microsoft PowerPoint - chap01-C언어개요.pptx

경우 1) 80GB( 원본 ) => 2TB( 복사본 ), 원본 80GB 는 MBR 로디스크초기화하고 NTFS 로포맷한경우 복사본 HDD 도 MBR 로디스크초기화되고 80GB 만큼포맷되고나머지영역 (80GB~ 나머지부분 ) 은할당되지않음 으로나온다. A. Window P

Studuino소프트웨어 설치

untitled

01장

Windows 8에서 BioStar 1 설치하기

Microsoft PowerPoint - u4.pptx

6. 설치가시작되는동안 USB 드라이버가자동으로로드됩니다. USB 드라이버가성공적으로로드되면 Setup is starting( 설치가시작되는중 )... 화면이표시됩니다. 7. 화면지침에따라 Windows 7 설치를완료합니다. 방법 2: 수정된 Windows 7 ISO

Microsoft Word - src.doc

Microsoft Word - 3부A windows 환경 IVF + visual studio.doc

1) 인증서만들기 ssl]# cat > // 설명 : 발급받은인증서 / 개인키파일을한파일로저장합니다. ( 저장방법 : cat [ 개인키

PowerPoint 프레젠테이션

네이버블로그 :: 포스트내용 Print VMw are 에서 Linux 설치하기 (Centos 6.3, 리눅스 ) Linux 2013/02/23 22:52 /carrena/ VMware 에서 l

Snort Install Manual Ad2m VMware libnet tar.gz DebianOS libpcap tar.gz Putty snort tar.gz WinSCP snort rules 1. 첫번째로네트워크설정 1) ifconf

ISP and CodeVisionAVR C Compiler.hwp

아이콘의 정의 본 사용자 설명서에서는 다음 아이콘을 사용합니다. 참고 참고는 발생할 수 있는 상황에 대처하는 방법을 알려 주거나 다른 기능과 함께 작동하는 방법에 대한 요령을 제공합니다. 상표 Brother 로고는 Brother Industries, Ltd.의 등록 상

제2장 리눅스 사용

chap2

JDK이클립스

PowerPoint 프레젠테이션

The Pocket Guide to TCP/IP Sockets: C Version

Microsoft PowerPoint - comp_prac_081223_2.pptx

PowerPoint Template

PowerPoint 프레젠테이션

벤처연구사업(전동휠체어) 평가

PowerPoint 프레젠테이션

tiawPlot ac 사용방법

목차 윈도우드라이버 1. 매뉴얼안내 운영체제 (OS) 환경 윈도우드라이버준비 윈도우드라이버설치 Windows XP/Server 2003 에서설치 Serial 또는 Parallel 포트의경우.

1) 인증서만들기 ssl]# cat > // 설명 : 발급받은인증서 / 개인키파일을한파일로저장합니다. ( 저장방법 : cat [ 개인키

Microsoft PowerPoint - 02_Linux_Fedora_Core_8_Vmware_Installation [호환 모드]

Microsoft PowerPoint SDK설치.HelloAndroid(1.5h).pptx

Discrete Mathematics

시스템 사용자 계정 관리

Install stm32cubemx and st-link utility

<4D F736F F F696E74202D C61645FB3EDB8AEC7D5BCBA20B9D720C5F8BBE7BFEBB9FD2E BC8A3C8AF20B8F0B5E55D>

Microsoft Word - PLC제어응용-2차시.doc

IP 심화 라우팅프로토콜적용시 라우팅테이블에서 이니셜이있는네트워크를설정하는것 : onnected 직접연결된네트워크를의미한다. 그러므로라우팅은 나는이런네트워크와연결되어있다. 를직접연결된라우터들에게알려주는것 1>en 1#conf t 1(config)#router rip 1

Microsoft Word - Armjtag_문서1.doc

제1장 Unix란 무엇인가?

PowerPoint 프레젠테이션

iii. Design Tab 을 Click 하여 WindowBuilder 가자동으로생성한 GUI 프로그래밍환경을확인한다.

NTD36HD Manual

/chroot/lib/ /chroot/etc/

3) MySQL data 백업백업방법 : dump 파일로저장저장위치 : /backup/mysqldump 백업주기 : 시간별 (/etc/cron.hourly) 또는일별 (/etc/cron.daily) 보관기간 : 7 일백업스크립트 : 아래예제 6-1). 참조 4) 웹데이

Microsoft PowerPoint 웹 연동 기술.pptx

PowerPoint 프레젠테이션

1

금오공대 컴퓨터공학전공 강의자료

Microsoft PowerPoint - chap06-2pointer.ppt

<4D F736F F F696E74202D20C1A632C0E520C7C1B7CEB1D7B7A5B0B3B9DFB0FAC1A4>

PowerPoint 프레젠테이션

PowerPoint 프레젠테이션

Microsoft PowerPoint - Lecture_Note_5.ppt [Compatibility Mode]

메뉴얼41페이지-2

Adobe Flash 취약점 분석 (CVE )

Mango-IMX6Q mfgtool을 이용한 이미지 Write하기

Microsoft Word - CAE 클러스터 환경 구축-ABAQUS.doc

Microsoft PowerPoint - chap05-제어문.pptx

CD 무결성체크는 SKIP 을해도좋습니다. Next 버튼을누릅니다. Next 버튼을누릅니다.

vi 사용법

사용자계정관리 1. 사용자계정관리 사용자 (user), 그룹 (group) u 다중사용자시스템 (Multi-User System) - 1 대의시스템을동시에여러사람이접속하여쓸수있게하는시스템 u 사용자 (user) - 시스템관리자 : root (=Super user) -

PowerPoint 프레젠테이션

Slide 1

목차 1. 시스템요구사항 암호및힌트설정 ( 윈도우 ) JetFlash Vault 시작하기 ( 윈도우 ) JetFlash Vault 옵션 ( 윈도우 )... 9 JetFlash Vault 설정... 9 JetFlash Vault

슬라이드 1

View Licenses and Services (customer)

ABC 11장

페도라 코어 5 설치 가이드

PowerPoint 프레젠테이션

PowerPoint 프레젠테이션

문서의 제목 나눔고딕B, 54pt

Microsoft PowerPoint - chap06-1Array.ppt

10 강. 쉘스크립트 l 쉘스크립트 Ÿ 쉘은명령어들을연속적으로실행하는인터프리터환경을제공 Ÿ 쉘스크립트는제어문과변수선언등이가능하며프로그래밍언어와유사 Ÿ 프로그래밍언어와스크립트언어 -프로그래밍언어를사용하는경우소스코드를컴파일하여실행가능한파일로만들어야함 -일반적으로실행파일은다

SIGIL 완벽입문

아래 항목은 최신( ) 이미지를 모두 제대로 설치하였을 때를 가정한다

SQL Developer Connect to TimesTen 유니원아이앤씨 DB 기술지원팀 2010 년 07 월 28 일 문서정보 프로젝트명 SQL Developer Connect to TimesTen 서브시스템명 버전 1.0 문서명 작성일 작성자

1. What is AX1 AX1 Program은 WIZnet 사의 Hardwired TCP/IP Chip인 iinchip 들의성능평가및 Test를위해제작된 Windows 기반의 PC Program이다. AX1은 Internet을통해 iinchip Evaluation

버퍼오버플로우-왕기초편 10. 메모리를 Hex dump 뜨기 앞서우리는버퍼오버플로우로인해리턴어드레스 (return address) 가변조될수있음을알았습니다. 이제곧리턴어드레스를원하는값으로변경하는실습을해볼것인데요, 그전에앞서, 메모리에저장된값들을살펴보는방법에대해배워보겠습

1장. 유닉스 시스템 프로그래밍 개요

커알못의 커널 탐방기 이 세상의 모든 커알못을 위해서

Keil Flexlm 라이선스 설명서

Windows Server 2012

Data Sync Manager(DSM) Example Guide Data Sync Manager (DSM) Example Guide DSM Copyright 2003 Ari System, Inc. All Rights reserved. Data Sync Manager

슬라이드 1

Microsoft PowerPoint - Lecture_Note_7.ppt [Compatibility Mode]

<443A5C4C C4B48555C B3E25C32C7D0B1E25CBCB3B0E8C7C1B7CEC1A7C6AE425CBED0C3E0C7C1B7CEB1D7B7A55C4C656D70656C2D5A69762E637070>

Transcription:

네트워크프로그래밍및실습 2007. 12. 1. 정유진 한국외국어대학교컴퓨터공학전공

본교재는 2007 년도정보통신부 NEXT 사업의지원을받아서네트워크프로그래밍및실습과목의실습교안으로작성되었음

< 차례> 1. 리눅스개괄 Chapter 1. 리눅스설치 1. 리눅스란? 9 2. 리눅스설치 1. 설치준비 10 2. 파티션준비 11 3. CD-ROM 부팅 11 4. 설치모드선택 12 5. Startup 12 6. 설치언어선택 12 7. 사용자동의 13 8. 키보드선택 13 9. 파티션설정 14 10. 부트로더설정 17 11. 네트워크설정 18 12. 지역시간대설정 18 13. 관리자암호설정 18 14. 설치패키지선택 19 15. 설치시작 20 16. 로그인유형선택 21 17. 재부팅 21 chapter 2. 기본명령어 1. 명령어매뉴얼과도움말얻기 1. man 24 2. apropos 25 3. info 26 2. 파일관련명령어 1. ls 27 2. rm 28 3. cd 29 4. cp 29 5. mv 30 6. ln 31 7. mkdir 32 8. rmdir 32

9. chmod 33 10. chown 35 11. 파일내용확인명령어 - cat, head, tail, more, less 36 12. df 38 13. wc 39 14. du 39 15. find 39 16. touch 40 17. 명령어찾기 - which, whatis, whereis 41 3. 프로세스관련명령어 1. ps 42 2. kill 43 3. top 44 4. nohup 45 4. 압축명령어 1. tar 46 2. gzip 48 1. vi 개괄 Chapter 3. 에디팅 1. vi 의역사 51 2. vi 의장점 51 2. 기본적인 vim 사용법 1. vim 의모드이해 52 2. 파일열기, 저장, 종료 54 3. 입력 56 4. 이동 57 5. 편집 59 6. 되돌리기와되살리기 63 7. 문자열탐색 63 8. 문자열치환 64 3. vim 을강력하게하는고급테크닉 1. 여러파일을편집하는방법 69 2. 반복되는문자열을저장해서쓰기 72 3. 매크로의사용 74 4. 다중창사용하기 76

5. 마킹으로이동하기 79 6. 셸명령어사용 82 1. 네트워크환경 Chapter 4. 리눅스프로그래밍환경 1. 네트워크서비스시작 85 2. 네트워크관련명령어 1. ping 87 2. traceroute 88 3. route 88 4. netstat 89 3. gcc 를사용해컴파일하기 1. 가장단순한컴파일명령 92 2. gcc 옵션 93 1. 소켓개요 Chapter 5. 소켓프로그래밍기초 1. 소켓의기본동작방식 96 2. 서버/ 클라이언트개념 99 3. 소켓으로작성하는서버/ 클라이언트프로그램의동작방법 101 2. 바이트순서 1. 호스트바이트순서 104 2. 네트워크바이트순서 104 3. 바이트순서바꾸기프로그램 105 4. 호스트바이트순서확인프로그램 109 3. 도메인이름과 IP주소상호변환하기 1. IP 주소변환 111 2. 도메인주소변환 113 3. gethostbyname() 113 4. gethostbyaddr() 115 4. TCP 클라이언트프로그램 1. TCP 클라이언트프로그램작성절차 117 2. TCP 클라이언트예제프로그램 120

5. TCP 서버프로그램 1. TCP 서버프로그램작성절차 128 2. TCP 에코서버프로그램 131 3. 연습문제풀이 132 6. UDP 프로그램 1. UDP 프로그램작성절차 138 2. UDP 에코프로그램 139 3. UDP 에코서버 140 1. 소켓의동작모드 Chapter 6. 고급소켓프로그래밍 1. 블록모드 144 2. 넌블록모드 144 3. 비동기보드 144 2. 다중처리기술 1. 멀티태스킹 145 2. 다중화 145 3. 비동기형채팅프로그램 1. 채팅서버프로그램구조 147 2. select() 148 3. 채팅서버프로그램 149 4. 통신용소켓구분 152 5. 채팅클라이언트프로그램 153 4. 폴링형채팅프로그램 1. fcntl() 156 2. 폴링형채팅서버 157 3. 폴링형채팅서버프로그램 157 4. 연습문제풀이 161 Chapter 7. 소켓옵션 1. 소켓옵션종류 1. SO_KEEPALIVE 171 2. SO_LINGER 171 3. shutdown() 173

4. SO_RCVBUF와 SO_SNDBUF 173 5. SO_REUSEADDR 174 6. 기타옵션 175 2. 소켓옵션변경 1. 소켓옵션변경함수 177 2. 소켓옵션변경예제프로그램 178 3. 연습문제풀이 179 1. 프로세스의이해 Chapter 8. 프로세스 1. 프로세스 191 2. 프로세스식별자 191 3. 프로세스식별자확인 191 4. 프로세스그룹 191 2. 프로세스의생성과종료 1. 프로세스의생성 - fork() 192 2. 프로세스의종료 192 3. 데몬서버구축방법 1. 데몬프로세스 193 2. 데몬서버종류 194 3. 연습문제풀이 196 1. 시그널종류 Chapter 9. 시그널 1. 시그널의종류 202 2. 시그널처리 1. 시그널처리기본동작 204 2. 시그널핸들러 204 3. 시그널처리예 206 3. SIGCHLD 와프로세스종료 1. 프로세스의종료 208 2. 프로세스의종료처리 209 3. 연습문제풀이 213

1. 파이프 Chapter 9. 프로세스간통신 1. 파이프의정의 226 2. pipe() 226 3. fork() 호출 226 4. 불필요한파일디스크립터해제 227 5. 파이프를이용한에코서버프로그램 227 2. FIFO 1. FIFO 의정의 232 2. mkfifo() 232 3. FIFO 를이용한프로세스간통신 232 4. FIFO 를이용한에코서버프로그램 232 3. 메시지큐 1. 메시지큐의정의 236 2. 타입이있는메시지큐 236 3. 메시지큐생성 237 4. 메시지송수신 238 5. 메시지큐제어 239 6. 메시지큐이용예 239 4. 공유메모리 1. 공유메모리의정의 244 2. 공유메모리생성 244 3. 공유메모리첨부 245 4. 공유메모리의분리 245 5. 공유메모리제어 246 6. 공유메모리정보얻기 246 7. 공유메모리삭제 247 8. 공유메모리의동기화문제 247 5. 세마포어 1. 세마포어의정의 250 2. 세마포어생성 250 3. 플래그사용예 250 4. 세마포어객체 251 5. 세마포어연산 251 6. 세미포어제어 252 7. 세마포어이용예 254

8. 공유메모리의동기화문제처리 256 9. 연습문제풀이 259 1. 스레드의생성과종료 Chapter 10. 멀티스레드프로그래밍 1. 스레드의정의 275 2. 스레드생성과종료 275 3. 스레드의사용예 276 4. 스레드의상태 278 5. 스레드의분류 278 2. 스레드동기화 1. 동기화문제 279 2. 동기화문제예 279 3. 플래그사용예 280 4. 뮤텍스 281 5. 뮤텍스사용예 284 3. 스레드간통신 1. 조건변수사용방법 286 2. 조건변수의동작 286 3. 조건변수의생성과삭제 287 4. 조건변수사용예 287 4. 멀티스레드에코서버프로그램 1. 멀티스레드에코서버프로그램 291 2. 연습문제풀이 293

Chapter 1. 리눅스설치

Chapter 1. 리눅스설치 9 S E C T I O N 01 리눅스개괄 1. 리눅스란? 리눅스는 1989 년핀란드헬싱키대학에재학중이던리누스토르발스(Linus Torvalds) 가유닉스를기 반으로개발한공개용오퍼레이팅시스템(OS) 으로, 1991년 11월버전 0.02이일반에공개되면서확대 보급되기시작하였다. 유닉스(Unix) 가중대형컴퓨터에서주로사용되는것과는달리, 리눅스는워크 스테이션이나개인용컴퓨터에서주로활용된다. 리눅스는소스코드를완전무료로공개하여전세계적으로약 5백만명이넘는프로그램개발자그 룹을형성하게되었으며, 이들에의해단일운영체제의독점이아닌다수를위한공개라는원칙하에 지속적인업그레이드가이루어지고있다. 파일구성이나시스템기능의일부는유닉스를기반으로하면서, 핵심커널부분은유닉스와다르게 작성되어있다. 인터넷프로토콜인 TCP/IP를강력하게지원하는등네트워킹에특히강점을지니고 있으며, 유닉스와거의유사한환경을제공하면서무료라는장점때문에프로그램개발자및학교 등을중심으로급속히사용이확대되고있다. 리눅스는각종주변기기에따라혹은사용하는시스템의특성에맞게소스를변경할수있으므로다 양한변종이출현하고있다. 한국외국어대학교컴퓨터공학과

10 네트워크프로그래밍및실습 S E C T I O N 02 리눅스설치 리눅스를설치하는방법에는여러가지방법이있다. CD 또는 DVD 를사용한그래픽모드, 텍스트모드 의설치가있으며, 하드디스크및네트워크를이용한방식도사용된다. 여기서는 CD를이용한그래픽 모드설치방법에대해알아보도록하겠다. 1. 설치준비 가. BIOS 설정 시스템이 CD-ROM으로부팅되게하려면우선시스템부팅시 BIOS 셋업에서부팅순서를 "CD-ROM 우선 으로변경해주어야한다. 컴퓨터전원을켜고초기화면의지시에따라 [Delete] 키또는 [F2] 키 를누른다. BIOS 셋업으로들어가는방법은메인보드에사용된롬바이오스의종류에따라다르므로, 각자시스템에맞는방식으로 BIOS 셋업을실행한다. 그림 1 AMI BIOS Setup 그림 2 AWARD BIOS Setup

Chapter 1. 리눅스설치 11 그림 3 Phoenix BIOS Setup 2. 파티션준비 파티션이란분할영역이라고도하는데, 하나의하드디스크를몇개의논리적공간으로분리하는것을말한다. 파티션분할을하면하드디스크의자료가모두지워지기때문에포맷을하기전에먼저파티션을나누어주어야한다. 이렇게나누어진파티션에운영체제를설치하여사용할수있으며, 하드디스크가 2개이상의파티션 으로나누어져있다면, 각파티션별로운영체제를설치하여여러운영체제를멀티부팅방식으로사 용할수도있다. 현재의시스템에리눅스만을설치하여사용하려는경우또는리눅스설치를위한여분의파티션이 있는상태라면, 바로다음단계인 설치단계 로넘어가면된다. 그러나현재의시스템에하나의파 티션만이존재하고여기에이미 MS Windows 와같은다른운영체제가설치되어있다면, 리눅스설 치시에리눅스를설치할파티션이없기에, 기존파티션에설치를하기된다. 이경우기존의운영체제는삭제되기때문에기존데이터를보존해야할필요가있거나, 2개이상의 운영체제를멀티부팅으로사용해야한다면, 리눅스설치이전에별도의파티션프로그램( 파티션매직 등) 을이용하여파티션을분리해두어야한다. 리눅스를설치하기위해서는별도의여유파티션공간 이필요하다는것을기억하기바란다. 3. CD-ROM 부팅 이제 CD-ROM 드라이브에리눅스설치 CD 를넣고부팅을한다. 그러면아래와같은화면이나타나 며, [Enter] 키를누르면그래픽환경의리눅스설치프로그램인 아나콘다 가구동된다. 한국외국어대학교컴퓨터공학과

12 네트워크프로그래밍및실습 그림 4 CD 부팅화면 4. 설치모드선택 리눅스를설치하는방식중가장많이사용되는방식이그래픽모드의설치방식이다. 방식은앞서언급한대로위의화면에서 [Enter] 키를입력하여진행할수있다. 그래픽모드 그러나그래픽카드및마우스와같은 GUI도구가인식되지않는경우그래픽모드로의설치가수행 되지않을수있다. 이경우에는텍스트모드의설치방식을통해리눅스를설치해야한다. 텍스트모 드로설치하기위해서는위의화면에서 linux text" 라고입력하고 [Enter] 키를누른다. 이외에도 HDD, HTTP, FTP, NFS 등을이용한설치방법이있으나, 여기서는가장일반적인방법인 그래픽모드 의 설치에대해알아본다. 5. Startup 그래픽모드로설치를진행하게되면, Startup" 페이지를만날수있다. 잠시후아래그림과같이로디화면과설치첫페이지인 그림 5 리눅스설치프로그램로딩화면그림 6 설치첫단계(Startup) 화면 6. 설치언어선택

Chapter 1. 리눅스설치 13 앞화면에서 Next 버튼을눌러진행하면아래그림과같이설치시사용할언어를선택하는화면이 나타난다. Korean( 한국어) 를선택하고 Next 버튼을누르면이후에보이는모든페이지들은한글환 경으로나오게된다. 그림 7 언어선택화면 7. 사용자동의 사용자동의서의내용을확인한다음해당사항에동의하는경우에만설치를계속진행할수있다. 그림 8 사용자동의확인화면 8. 키보드선택 현재시스템의키보드를설정한다. 대부분자동으로키보드를인식하므로기본선택된값으로설치 를계속진행하면된다. ( 예: U.S. 영어) 한국외국어대학교컴퓨터공학과

14 네트워크프로그래밍및실습 그림 9 키보드선택화면 9. 파티션설정 리눅스설치중가장중요한파티션설정부분이다. 다른설치사항들은대부분윈도우즈설치와비슷하기에큰어려움없이진행할수있지만, 파티션설정부분은윈도우즈와조금달라서처음리눅스를접하는이들이가장어려워하는부분이기도하다. 아래그림을보면 [ 자동파티션분할] 과 [Disk Druid 를통한수동파티션설정] 의 2가지항목이존재 한다. 그림 10 파티션설정방법선택화면 [ 자동파티션분할] 은현재시스템에있는하드디스크의파티션분할상태를무시하고, 리눅스에서 제공하는가장최적화된구조호파티션을재조정하는것을말한다. 즉, 최적화된환경으로리눅스만 을사용하는경우라면이옵션으로쉽게설치할수있는장점이있으나, 기존에사용하던파티션은 모두초기화되므로, 윈도우즈가이미설치되어있어같이사용하기를원하거나기존데이터의보존 이필요한경우에는이옵션을사용하지않도록해야한다. 반면 [Disk Druid 를통한수동파티션설정] 은사용자가직접파티션을조정할수있으며, 기존의데 이터를보존하기위해서는이옵션으로설치를진행해야한다. 이후설명되는내용은 [Disk Druid를 통한수동파티션설정] 을기준으로설명한다.

Chapter 1. 리눅스설치 15 가. 현재파티션현황시스템에장착되어있는하드디스크가파티션설정이되어있지않은빈하드디스크라면아래와같이하드디스크드라이브를초가화한다는메시지가나타난다. [ 예] 를선택하고계속진행한다. 그림 11 하드디스크초기화 다음으로파티션을설정하는메인화면이나타난다. 그림 12 파티션설정메인화면 현재하드디스크의파티션현황과용량을한눈에볼수있도록도표화되어있다. 리눅스에서의파 티션명칭은윈도우즈화면조금다르다. 윈도우즈에서는앞쪽에있는파티션부터순서대로 C, D, E... 등의명칭을사용하지만, 리눅스에서는 hda, hdb 등과같이하드웨어적인세부사항을파티션명칭으 로나타내므로더명확한파티션구분이가능하다. 위의그림은 10GB의 IDC 하드디스크(hda) 를인식한모습이며, 아직아무런파티션이설정되어있지 않은초기상태이다. 윈도우즈에서는하나의파티션에 OS 가설치되지만, 리눅스는하나의 OS를여러 개의파티션으로분할하여설치할수있다. 가장일반적인리눅스설치방법은아래와같이 2개의 파티션으로구성하는방법이다. 루트(/) 파티션스왑(swap) 파티션 루트(/) 파티션은윈도우즈에서와같이운영체제(OS) 가설치되는최상위파티션으로서, 그하단에각 각의디렉토리및파일들이생성된다. 윈도우즈에서는 Documents and Settings, Program Files, Windows 등의디렉토리들이생성되며, 리눅스에서는 /boot, /usr, /root, /home 등의디렉토리들이 한국외국어대학교컴퓨터공학과

16 네트워크프로그래밍및실습 생성된다. 스왑(swap) 파티션은윈도우즈의가상메모리와같은역할을하는파티션으로, 윈도우즈에서는하드디스크의여유공간을가상메모리공간으로할당하여사용하지만, 리눅스에서는별도의파티션으로나누어서사용하게된다. 리눅스에서는루트(/) 아래에있는각각의폴더들을별도의파티션으로설정하여설치가가능하다. 서버시스템에는보안성향상과관리의편의를위해주요디렉토리를별도의파티션으로구현하기도한다. 그리고사상메모리(swap) 를별도의고정된파티션으로사용함으로써윈도우즈보다안정적이고효과적인메모리관리가가능하다. 나. 파티션추가 / 편집 / 삭제 앞서말한바와같이루트(/) 파티션과스왑(swap) 파티션을생성해보도록하겠다. 화면하단에있는 버튼들중에서 [ 새로생성] 버튼을클릭하면아래그림과같이파티션을추가할수있는창이나타난 다. 그림 13 스왑(swap) 파티션추가화면 먼저스왑(swap) 파티션생성을위해 [ 파일시스템유형] 에서 swap 을선택한다. [ 용량] 부분에는현 재시스템메모리의 2 배정도를입력한다. ( 예: 현재시스템의메모리가 256MB 이면, 512 라고입력 한다.) 그런다음확인을누르면화면에스왑(swap) 파티션이생성된것을확인할수있다. 그림 14 루트(/) 파티션추가화면

Chapter 1. 리눅스설치 17 루트(/) 파티션생성을위해다시한번 [ 새로생성] 버튼을클릭한다. [ 마운트지점] 부분을 / 로선 택하고 [ 파일시스템유형] 에서 ext3" 를선택한다. 그다음 [ 용량] 부분에서사용될적절한용량을지 정하는데, 남은공간모두를루트(/) 로지정하여사용할경우에는하단에있는 [ 최대가능한용량으로 채움] 에체크를하면자동으로최대용량을지정해준다. [ 확인] 버튼을눌러현재상태를확인한다. 그림 15 생성된파티션확인화면 위그림과같이루트(/) 파티션과스왑(swap) 파티션이모두만들어지면, [ 다음] 버튼을눌러다음단 계로계속진행한다. 파티션작업도중실수를하여파티션을잘못생성하였다면 [ 편집] 및 [ 삭제] 버튼을이용하여적정 히수정한다. 파티션설정부분에서파티션을구성하더라도실제파티션수정작업은추후본격적인 설치과정에가서야진행되므로현단계에서는마음껏수정하여작업할수있다. 10. 부트로더설정 하나의시스템에여러개의운영체제를설치하여사용경우, 부팅시원하는운영체제를선택할수있게해주는도구가부트로더이다. 예를들면현재시스템에윈도우즈가같이설치되어있다면이단계에서윈도우즈와리눅스둘중하나를선택해서먼저부팅시킬수있다. 그림 16 부트로더설정화면 위의그림은현재시스템에다른운영체제가설치되어있지않으므로리눅스의항목만이나타난경 한국외국어대학교컴퓨터공학과

18 네트워크프로그래밍및실습 우다. 우측의 [ 편집] 버튼을눌러각항목의이름을변경하거나, 기본적으로부팅되도록선택되어질 항목을수정할수있다. 11. 네트워크설정 네트워크를설정하는단계다. 일반적으로 DHCP( 자동 IP 할당) 방식의네트워크를많이사용하므로기 본설정값그대로 [ 다음] 버튼을눌러진행한다. 만약인터넷서비스회사로부터별도의고정IP를할 당받아사용하는경우라면, 상단의 [ 편집] 버튼을눌러 IP 관련설정을진행한다. 그림 17 네트워크설정화면 12. 지역시간대설정 지역시간대를설정하는화면이다. 자신이속한시간대에가장근접한도시를지도에서찾아클릭하 거나하단의리스트에서찾아설정할수있다. 한글환경으로설치를진행하고있다면자동으로대 한민국의시간대인 아시아/ 서울(Seoul)" 이설정되어있다. 그림 18 지역시간대설정화면 13. 관리자암호설정 관리자(root 계정) 암호를설정한다. root 는리눅스시스템을관리하는최고권한을가진계정이다.

Chapter 1. 리눅스설치 19 그러므로암호설정에신중을기하고외부에알려지지않도록주의해야한다. 설치가완료된이후에관리자로로그인하여생성해줄수있다. 일반사용자계정은 그림 19 관리자암호설정화면 14. 설치패키지선택 패키지를설치하는범위를지정한다. 크게 최소설치 와 전부설치" 로나누어지며사용자정의형식으 로패키지를하나하나선택하여설치하고싶은경우에는마지막에있는 접선택합니다. 에체크를하고다음으로넘어간다. 설치할패키지리스트를직 일반적으로는리눅스관련패키지들을모두설치하여안정적으로리눅스를운영할수있는 치 방식을권장한다. 전부설 그림 20 전부설치를선택한화면 사용자정의형태로진행하면아래와같이패키지들을직접선택하여설치할수있는화면이나타난 다. 그룹으로나누어선택할수있으며, 자세한정보 를클릭하면해당그룹에포함되어있는패키 지들도상세하게선택할수있다. 한국외국어대학교컴퓨터공학과

20 네트워크프로그래밍및실습 그림 21 패키지정보화면 15. 설치시작 지금까지설정한내용을바탕으로본격적인설치를진행한다. 그림 22 설치초기화면 설치초기에는아래그림들과같이파일시스템포맷, 설치이미지전송, RPM 행한후설치과정이이어진다. 트랜잭션준비등을진 그림 23 설치진행화면

Chapter 1. 리눅스설치 21 설치중간 CD를교체하라는메시지가나오면 CD 를바꾼후계속설치를진행한다. 16. 로그인유형선택 설치가완료되면시스템에 부트로더 를적재하고아래그림과같이로그인유형을선택하는화면이 나타난다. 기본로그인유형은텍스트환경이나, 그래픽환경의로그인을원하는경우 [ 그래픽환경] 을체크하고적절한화면해상도와색상수를지정한다. 그림 24 로그인환경지정화면 17. 재부팅 설치가완료되면재부팅을하여바로리눅스를사용할수있다. 그림 25 설치완료화면 그림 26 부트로더화면 시스템이재부팅되면위의그림과같이부트로더화면이나타나고, 된다. 곧이어리눅스부팅과정이진행 로그인유형을그래픽모드로하였다면아래와같이로그인화면이나타난다. ( 텍스트모드인경우콘솔상태에서의로그인화면이나타난다.) 한국외국어대학교컴퓨터공학과

22 네트워크프로그래밍및실습 그림 27 로그인화면 설치시에입력했던 root 관리자암호를이용하여로그인을진행하면아래그림과같이바탕화면이나타난다. ( 그래픽모드의경우) 그림 28 리눅스데스크톱기본화면

Chapter 2. 기본명령어

24 네트워크프로그래밍및실습 S E C T I O N 01 명령어매뉴얼과도움말얻기 리눅스에는외우기힘들정도로많은명령어가있다. 이들모두를알고있으면좋겠지만그것은사실상불가능하다. 사실어떤명령어가있다는것과어떤상황에서무엇을확인하고자할때는어떤명령어를사용해야한다는것정도만알아도충분하다. 1. man 영한사전에서는어떤단어를비슷한단어와함께소개하여쉽게외울수있도록한다. 또한단어하나에많게는수십가지뜻이있기도한다. 우리가일반적으로사용하는간단한명령어역시다른명령어와연계하여쉽게외울수있는경우가많으며수십가지의기능으로확장하기위해옵션을추가할수있다. 이런모든기능을잘사용하기위해서는영한사전과같은존재, 다시말해도움말기능이있어야할것이다. 물론 MS 윈도우에도움말기능이있듯이리눅스에도맨페이지라는도움말기능이있다. 어떤명령어의설명을보고싶다면다음과같이입력한다. # man [ 명령어] 그러면 cron 이라는명령어에대해설명을보는경우를살펴보자. # man cron 그림 29 man 명령어

Chapter 2. 기본명령어 25 이런맨페이지는 /usr/share/man 디렉토리에섹션별로저장되어있으며, 이온라인도움말시스템은 각명령과용어에대해다음과같은내용을포함한다. 명령어이름 (name) 개요(synopsis) 와설명 환경(environment) 와매개변수(parameter) 등 또한각섹션은다음과같이분류한다. 물론같은단어로된명령어가여러섹션에걸쳐있을수있 는데, 이경우는특별히옵션을주지않으면섹션의숫자가적은쪽을출력한다. crontab은 1번과 5 번섹션에서소개된다. 명령어에대한맨페이지를보고싶다면 man 1 crontab, 파일의포맷에대해 알고싶다면 man 5 crontab 이라입력하면된다. 그냥 man crontab이라고입력하면 1번섹션의내 용을볼수있다. 섹션 1 2 3 4 5 6 7 8 9 n 표 1 man 설명 User Command ls, grep, finder와같은일반명령 System calls 리눅스프로그래밍을위한시스템호출과관련된내용 C library functions 리눅스라이브러리함수와관련된내용 File formats(syntax) 특수파일(FIFO, 소켓등) 에대한문서 File formats(syntax) 중요한설정파일에대한정보 Game descriptions 시스템테스팅프로그램에대한설명 Cover text, text format, etc. 표준과규칙에대한정보, 프로토콜, 문자세트, 시그널목록에대한정보 System administration mount, fsck 등시스템관리자가사용하는명령에대한설명 Linux kernel routines 커널프로그래밍을위한정보 "New" or commands that didn't fit elsewhere 새로운명령에대비한공간. 조로 Tcl/Tk 프로그래밍에대한내용섹션별설명 2. apropos 물론 man 을이용하여명령어를사용하는방법을확인할수있겠지만, 명령어이름을정확히알지 못할경우에는다음과같이키워드를이용하여해당키워드가포함된맨페이지를찾아낼수있다. 이명령은 아준다. whatis DB라고하는맨페이지의내용의데이터베이스를검색하여빠르게해당명령을찾 # apropos [ 키워드] 한국외국어대학교컴퓨터공학과

26 네트워크프로그래밍및실습 그림 30 apropos 명령어 3. info info는최신버전의 GNU 맨페이지유틸리티이다. 이것은맨페이지의발전된형태로기획되었지만 아직맨페이지만큼널리사용되지는못하고있다. 최신의정보가많으며, 하이퍼텍스트링크와같 은강력한기능을사용할수도있다. # info [ 명령어] 그림 31 info 명령어

Chapter 2. 기본명령어 27 S E C T I O N 02 파일관련명령어 어떤환경에서라도컴퓨터를사용하는작업의기본은파일을읽고쓰는것에서출발한다. 인작업을할수있는파일관련명령어에대해알아보자. 가장기본적 1. ls ls 명령은디렉토리내파일의목록을보기위해사용한다. # ls [- 옵션] ㅣ 그림 32 ls 명령어 ls -a 모든파일보기 ls -l 자세히보기( 파일의유형, 허가권, 링크카운트, 파일소유권등) ls -c 시간순서대로정렬해서보기 ls -d 디렉토리만보기 ls -f 디스크에저장된순서로보기( 정렬안하기) ls -i 고유번호(inode) 와함께보기 ls -k kb 단위로보기 ls -m 파일이름을가로로나열하기 ls -r 내림차순정렬로보기 ls -t 시간순서대로정렬해서보기 ls -R 하위디렉토리안의파일까지보여주기 ls --color 파일의형태에따라색깔을다르게보여주기 ls -F 파일과디렉토리를심볼로구분하여보여주기 표 2 ls 기본옵션 점(.) 으로시작하는파일과디렉토리는 ls 명령만으로는볼수없으며 -a 옵션을함꼐사용해야한다. 이와같은파일을숨김파일이라한다. 한국외국어대학교컴퓨터공학과

28 네트워크프로그래밍및실습 그림 33 ls -al 숨김파일은주로다른사용자들이함부로수정해선안되거나일반적으로는변경할일이거의없는 환경파일로많이사용된다. 가령, 넷스케이프웹브라우저를처음실행하면사용자의홈디렉토리에.netscape 라는디렉토리가생성되고사용자의브라우저설정과관련된파일들이복사된다. 또한처음 사용자를생성할때복사되는설정파일역시파일이름앞에점(.) 이붙는다. 2. rm 불필요한파일을삭제하기위해 rm 이라는명령어를사용한다. DOS의 del명령어를사용하는것과같 은것으로 remove 의약어이다. # rm [ 파일이름] 다음은 rm 명령과함께사용될수있는옵션이다. 이중 rm -i는사용자가리눅스를안전하게사용할 수있도록앨리어스(alias) 로설정되어있는경우가대부분이다. rm -r rm -f rm -i rm -v 표 3 ls 기본옵션 디렉토리까지지워버리는옵션삭제하기전에확인과정없이강제로지워버리는옵션사용자에게정말로지울것인지확인파일지우는정보를자세히보여줌 그림 34 rm 명령어

Chapter 2. 기본명령어 29 -rf 옵션을사용할때에는디렉토리와함께그안에들어있는파일및서브디렉토리까지한마디말도 없이모두삭제한다. 편리한명령이기는하지만주의해야한다. 3. cd 이명령은디렉토리이동명령이다. 그림 35 cd 명령어 디렉토리를이동할때경로맨앞에슬래시(/) 가있는경우가있고, 없는경우도있다. 슬래시(/) 가있 는경로를절대경로라하며, 없으면상대경로라한다. 다음은 cd 명령과함께사용될수있는옵션에대한설명이다. cd cd ~/ cd - 표 4 cd 기본옵션 사용자의홈디렉토리로이동사용자의홈디렉토리의서브디렉토리로이동방금전에있던디렉토리로이동 [root@localhost /]# cd aaa 현재디렉토리에존재하는 aaa라는디렉토리로이동 [root@localhost /]# cd./aaa 현재디렉토리에존재하는 aaa라는디렉토리로이동 [root@localhost /]# cd../bbb 상위디렉토리에존재하는 bbb라는디렉토리로이동 [root@localhost /]# cd ~ 홈디렉토리로이동 4. cp cp 는파일을복사하는명령어로, DOS의 copy 명령어와같다. 복사할파일의경오롸복사할위치혹 은새이름을입력하면복사된다. 이경우두파일은별개의파일이되어다른 inode를사용하게된 다. 한국외국어대학교컴퓨터공학과

30 네트워크프로그래밍및실습 # cp [ 복사할이름] [ 복사되어생성될이름] 그림 36 cp 명령어 다음은 cp 명령과함께사용될수있는옵션이다. cp -a cp -b cp -f cp -i cp -l cp -P cp -p cp -r 표 5 cp 기본옵션 파일의속성, 링크정보등을유지하면서복사이미파일이존재할경우백업파일생성기존의파일을강제로지우고복사복사할때물어봄하드링크형식으로복사원본파일에경로와함께지정했을경우그경로를그대로복사원본파일의소유주, 그룹, 권한, 시간정보복사경로와함께경로안의파일들을모두복사 5. mv mv 명령은파일명을변경하거나현재위치한디렉토리에서다른디렉토리로파일을이동시킬때사용되는명령으로, move 에서자음만따온것이다. # mv [ 옮길파일명] [ 이동할디렉토리나파일명] 그림 37 mv 명령어 다음은 mv 명령과함께사용될수있는옵션에대한설명이다.

Chapter 2. 기본명령어 31 mv -b mv -f mv -i mv -u mv -v 표 6 mv 기본옵션 백업파일을만든다강제로이동사용자에게확인시킨다업데이트한다과정을자세히보여준다 6. ln ln 은파일사이에링크를만드는명령이다. 이런링크는원본파일을보호하며환경변수를변경하지 않고도새로설치한프로그램을쉽게사용할수있는등다양한방법으로유용하게사용할수있다. # ln [ 옵션] [ 링크시킬파일이나폴더] [ 링크로만들어질파일] 다음은 ln 명령과함께사용될수있는옵션에대한설명이다. ln -s ln -n ln -f 표 7 mv 기본옵션 심볼릭링크파일이존재하면겹쳐쓰기를하지않는다파일이존재해도겹쳐쓴다 가. 하드링크 (Hard links) 하드링크는하나의파일을여러곳으로연결하는데사용한다. 예제를보면 test.c와 hello.c는항상 같은내용을지닌다. 다른이름을갖는두개의파일이지만하나의 inode로생성되므로결국은같은 파일이다. 주의할점은같은 inode를사용하기때문에서로다른파일시스템사이에는링크를만들 수없다는점이다. 그림 38 ln 명령어 나. 심볼릭링크 (Symbloic links) 겉보기에는보통파일로보이지만사실원본파일이어디를가리키고있는지알려주는역할을할뿐인파일을심볼릭링크라고말한다. 심볼릭링크의경우링크를다른곳으로이동시키면링크가깨져서사용할수없다. 한국외국어대학교컴퓨터공학과

32 네트워크프로그래밍및실습 그림 39 링크의확인 7. mkdir 디렉토리를만들때 mkdir 을사용한다. 그림 40 mkdir 명령어 # mkdir [ 디렉토리명] # mkdir -p aaa/bbb -p 옵션은여러단계의하부디렉토리까지만들어준다. 8. rmdir rmdir 은디렉토리를삭제하는명령이다. rmdir로지울때는반드시해당디렉토리가비어있어야한 다. 파일이나다른디렉토리가있는디렉토리를지울때는에러메시지가나타난다. 일반적으로 rmdir 은다음과같은형식으로사용한다. # rmdir [ 디렉토리명]

Chapter 2. 기본명령어 33 그림 41 rmdir 명령어 mkdir과마찬가지로디렉토리내부의디렉토리까지한번에삭제하기위해 -p 옵션을사용할수있지만, 디렉토리는그대로있고내부에파일이없는상태에서만사용할수있기때문에많이쓰이는것은아니다. 9. chmod chmod 는파일의사용권한을조정하는명령어이다. # chmod [option] [ 파일] 그림 42 파일권한확인 예문에서보면 -rw-r--r-- 이라는부분이있다. 이부분은이파일(test.c) 의권한을설정한것으로, 총 10 칸으로되어있다. 구간 의미 앞 1칸 디렉토리(d), 링크파일(l), 스티키비트(s) 등파일의특성 그다음 3 칸(owver = 주인) 파일소유자의읽고쓰고실행하기 (rwx) 그다음 3 칸(group = 그룹) 파일소유그룹의읽고쓰고실행하기 (rwx) 그다음 3 칸(others = 다른이) 그외사람들의읽고쓰고실행하기 (rwx) 표 8 파일권한보기 그림에서나타난대시(-) 가의미하는것은허가권이없음을나타내는것으로, 여기서는주인은읽고 쓰기만할수있고, 그외의사람들은읽을수는있지만쓰거나실행할수는없다. 파일의권한은 8 진수의숫자더하기방식으로나타낼수있다. 읽기는 4(read), 쓰기는 2(write), 실행 한국외국어대학교컴퓨터공학과

34 네트워크프로그래밍및실습 은 1(execute) 이다. 이들숫자의조합으로생길수있는조합은다음과같다. 1 = 실행만 (1) 2 = 쓰기만 (2) 3 = 쓰기와실행만 (2+1) 4 = 읽기만 (4) 5 = 읽기와실행 (4+1) 6 = 읽기와쓰기 (4+2) 7 = 읽기와쓰기와실행 (4+2+1) 즉, 8진수를이용하는방법은각각 4, 2, 1의숫자를더한값을 100 단위는소유자, 10 단위는그룹, 1 단위는타인으로지정하여사용한다. 편의를위하여다음에표를하나정리해보겠다. 사용자범위 허가 상징적인방법 절대적인방법 읽기 r 400 주인 쓰기 w 200 실행 x 100 읽기 r 40 그룹 쓰기 w 20 실행 x 10 읽기 r 4 다른이 쓰기 w 2 실행 x 1 표 9 chmod 옵션(1) 실제의경우에는다음과같이사용된다. 명령 주인 그룹 다른이 chmod 640 file1 rw- r-- --- chmod 754 file1 rwx r-x r-- chmod 664 file1 rw- rw- r-- 표 10 chmod 옵션(2) 절대적인방법으로표시하는법은해당부분의숫자들의합으로허가를변경하는것이다. 예를들어 소유자( 주인) 범주에서읽기와쓰기그리고실행을허용하면 400+200+100=700 이고, 그룹모드에서 읽기와실행만허용하면 40+10=50, 마지막으로타인모드에서도역시읽기와실행을허용하면 4+1=5 가된다. 이를모두더하게되면 700+50+5=755 가된다. 이와같은방식으로예문의권한 (644) 을모든사람에게실행권한만허용하도록하려면다음과같이실행한다. # chmod 111 file1

Chapter 2. 기본명령어 35 숫자가나오는것을보기만해도머리가아픈사람도있을것이다. 이와같이 8진수로나타내는방 법이널리사용되기는하지만, 다음과같은방법으로도사용할수있다. 기호 의미 기호 의미 + 허가권한부여 u 주인( 소유자) - 허가권한제거 g 그룹권한 = 허가권한유지 o 다른이( 타인) S 소유자와그룹만실행 a 수유자, 그룹, 다른이모두 표 11 chmod 옵션(3) 위의예에서그룹과다른이에게쓰기권한을주려면다음과같이추가로입력한다. # chmod g+w,o+w test.c hello.c 파일을모든사용자가읽도록할때는다음과같이입력한다. # chmod a+r hello.c 10. chown chown 은파일의소유권을바꾸어주는명령이다. # chown [owner] [ 파일] # chown [owner:group] [ 파일] # chown [owner.group] [ 파일] # chown [.owner] [ 파일] 다른리눅스시스템에서복사한파일의소유권이자신의것과일치하지않을때그파일의소유권을자신의소유권으로바꾸거나혹은보안을위해특정사용자의소유권을사용하도록해야할때가있다. 이렇게소유권을변경할때사용하는명령어가 chown 이다. chown 명령은셸명령행에서지정하는순서에따라서소유권이변경된다. 앞의예처럼하면된다. 그럴경우그룹은변하지않고소유권과바뀌게된다. 밑의그림과같이 chown 명령뒤에변경하고 자하는소유자와그룹사이에점(.) 또는콜론(;) 을사용하면소유자와그룹모두를변경할수있다. 또한디렉토리에하위디렉토리를포함한수많은파일들에대해서일일이소유권을변경해야한다면 -R 옵션을사용하여한번에소유권을변경할수있다. 한국외국어대학교컴퓨터공학과

36 네트워크프로그래밍및실습 11. 파일내용확인명령어 - cat, head, tail, more, less 가. cat cat 은파일내용을보는가장기본적인명령어이다. 당연한이야기를하나하자면보고자하는파일이텍스트파일일경우그내용을알아볼수있게출력하지만, 바이너리파일일경우에는내용이깨져보일것이다. # cat [ 옵션] [ 파일] 그림 43 cat 명령어 위의예처럼 cat 111.c 222.c > 333.c 라고했을때, 333.c 라는파일이없었던파일이라면당연히 111.c 와 222.c 라는두파일의내용이합쳐진결과가나타나겠지만, 만약 333.c 가기존에다른내 용이저장되었던파일이라면기존에있었던내용이모두사라지고생성된결과만이들어갈것이다. >> 은파일의끝부분에이어서쓰라는뜻이다이렇게입출력의방향을바꾸어파일에저장하거나파 일에서불러오는것을리디렉션이라한다. cat -b 행번호를앞에붙여서출력한다 ( 빈행은번호를붙이지않는다) cat -n 행번호를앞에붙여서출력한다 ( 빈행도번호를붙인다) 표 12 cat 기본옵션 나. head 파일내용의첫부분을기준으로출력하는명령어이다. 특별히옵션을사용하지않으면맨첫번쨰

Chapter 2. 기본명령어 37 줄부터 10 번째줄까지출력한다. # head [ 옵션] [ 파일] 여기서 test.c 파일의처음 3 줄만보기위해서는다음과같이입력한다. # head -3 test.c 다. tail 파일내용의마지막부분을기준으로출력하는명령어이다. # tail [ 옵션] [ 파일] 라. more, less more와 less 는한화면단위로넘어가며텍스트파일을보여준다. cat의경우실행한즉시파일의끝 까지한번에출력해버려내용이많은파일을보기쉽지않았다. 그러나 more를사용하면한화면 단위로내용이넘어가므로내용을자세히볼수있으며, vi를사용하는것과같은방식으로간단한 검색을할수도있다. # more [ 파일] # less [ 파일] 그림 44 more 명령어 [SpaceBar] 를누르면한페이지단위로이동하고 [Enter] 키를누르면한행단위로페이지가넘어간 다. more를실행하던중 e를누르면환경변수에설정된기본편집기를사용하여파일을수정할수 도있다. 그러나이전화면을다시볼수는없다. 한국외국어대학교컴퓨터공학과

38 네트워크프로그래밍및실습 그림 45 less 명령어 less의경우 more 와같이한페이지이동시 [SpaceBar] 를이용하고, 뒤로이동하려면 b키를누르면 된다. 백분율로표시되는특정위치로가려면 p 키를누르고 : 프롬프트에백분율을지정하면움직일 수있는등자유롭게이동할수있다. 많은경우 less 명령을 more라는이름으로심볼릭링크를만 들어사용하기도한다. 12. df 이명령은각파일시스템별로남은공간을확인하여보여준다. # df [ 옵션] df -a 모든정보를출력한다 df -k 용량을 kb로보여준다표 13 df 기본옵션 그림 46 df 명령어 이명령어는디스크의남은공간을보여준다. 시스템이멈춰버리거나일반사용자가로그인되지않을떄꼭확인해보아야한다. 파티션이일정이상채워지고나면일반사용자는시스템에접근할수없다.

Chapter 2. 기본명령어 39 13. wc wc 명령어는파일안의글자수, 단어수, 줄수를세어주는명령어이다. 예컨대, 우리나라에서는원 고료를원고지몇장, A4용지몇장과같은식으로매기지만외국에서는 A4용지몇장과같은식으 로계산하는것은물론한단어당얼마정도식으로계산하는경우도있다. 이럴때일일이단어 수를계산하는것보다훨씬능력적으로사용할수있다. # wc [ 옵션] [ 파일] 예컨대어떤파일에대해 wc 를실행했을때의결과가다음는같다. 46 230 2018 이결과에서이파일은 46줄 230단어 2018 자로이루어졌음을알수있다. 몇줄인지만확인할때에 는 wc -l 명령을사용한다. 바로 46 이라는결과를볼수있다. 14. du Disk Usage 의약어로해당디렉토리의사용량을출력한다. 소유자는자신의사용량만을체크하지만 root 는모든사용자각각에대해서디스크사용량을출력한다. # du [ 옵션] [ 디렉토리또는파일] du -rm 용량표시를 MB로표시 du -a 해당경로에대한사용한디스크용량표시 du -s 전체사용량을간단히표시 du -k 용량표기단위를 KB로표시 표 14 df 기본옵션 du 명령어는사용한디스크용량을확인할경우에사용되는명령어로디스크에남아있는용량을확 인하는 df 명령어와는다소차이가있다. du 명령에서일반적으로디스크용량을확인하기위해서는 -h 옵션을지정하여사용하는것이편리하다. 왜냐하면디스크용량단위를우리가알고있는 MB 또는 GB 단위로표시하기때문에그크기를쉽게알아볼수있다. 15. find 윈도우에서파일을찾을때에서는 [ 시작] [ 찾기] 에서파일의이름이나형식, 마지막으로변경한날짜 등을입력하여간단히찾을수있다. 리눅스에서는 find 명령에다양한옵션을주어같은효과를볼 한국외국어대학교컴퓨터공학과

40 네트워크프로그래밍및실습 수있다. # find [ 찾을디렉토리경로] [ 찾기옵션] [ 찾은후행할작업] [ 찾을디렉토리경로] 에는다음과같은것들이있다.. : 현재디렉토리 / : 루트디렉토리이하 ( 파일시스템전체) ~ID : 특정 ID의홈디렉토리이하 [ 찾기옵션] 에는다음과같은것들이있다. -empty : 비어있는파일 -gid [n] : 특정 gid 을갖는파일 (n : 특정 gid) -group [gname] : 특정 group 에속한파일 (gname : group 명) -name : -newer : -perm : 지정한형식을갖는파일이름 특정파일이후에생성된파일 특정허가모드를가지고있는파일 -uid [n] : 특정 uid 를갖는파일 (n : 특정 uid) -used [n] : 최근에 n 일이후에변경된파일 (n : 일수) -user : 특정파일을소유하고있는소유자의파일 [ 찾은후행할작업] 에수행할내용은다음과같은것들이있다. -print : 가장많이쓰는옵션으로찾은파일을보여준다. -exec : 찾은파일들에대한특정명령을수행한다. 예컨대다음과같이실행하면전체경로에서 root의소유로되어있는 netbook이라는파일을찾는 다. # find / -user root -name netbook 16. touch 비어있는파일을만들때는 touch 라는명령어를사용한다. # touch abcd.txt

Chapter 2. 기본명령어 41 abcd.txt라는파일이생성되었으며이파일의크기는 0 이다. 또한파일의시간을변경할수도있다. 17. 명령어찾기 - which, whatis, whereis find가특정파일을찾아주는명령어인데비해 which라는명령어는사용자의 PATH 범위에서특정 명령어의위치가어디인지를찾아주는명령어이다. 리눅스에서는명령어의취리를모두기억하기어 려우므로이명령어를사용하여찾고자하는명령어의위치를확인할수있다. # which [ 명령어] whereis 라는명령어도이와비슷한일을한다. which와다른점은모든디렉토리를뒤져서해당명령어를찾아준다는것이다. # whereis [ 명령어] whatis라는명령어가있는데 which나 whereis가명령어의위치를찾아주는데비해 whatis는해당명 령어가무엇을하는것인지간단하게보여준다. 이것은명령어자체를찾는것이기보다는앞서설 명한 man이나 info 와비슷한기능을수행한다. # whateis [ 명령어] 언뜻보기에는 whereis가 which보다성능이뛰어나다고생각할수도있으나 whereis는개인의 PATH 변수와명령어그자체만을놓고검색하는것이아닌, 더넓은범위에서맨페이지의경로까지도검 색해주기때문이다. 그러나검색속도에서는 which 가우월하다. 또한대부분의실행파일은개인의 PATH 변수가찾을수있는경로안에있기때문에많은경우 which 를사용하여검색한다. 한국외국어대학교컴퓨터공학과

42 네트워크프로그래밍및실습 S E C T I O N 03 프로세스명령어 시스템의여러가지상황에대해서알아볼수있는명령어를살펴본다. 현상황을확인하고이상이있을경우적잘한조치를취하게된다. 이들명령어를통하여시스템의 프로세스란명령어에해당하는실행파일이메모리에적재되어실제로실행되는상태가된것을뜻한 다. 모든프로세스에는각각여러가지프로그램을실제실행하는데필요한정보가있다. 이를먼저간 단히살펴보자. 프로세스식별번호 (process ID, PID) : 프로세스가시작될때할당되는번호를말한다. 프로세스ID 는오직하나만할당된다. ( 할당될때이미죽은프로세스의식별번호를재사용할가능성은있지만 두개이상의프로세스가같은프로세스의식별번호를가질수는없다.) 사용자식별번호 (user IS, UIS) : 그프로세스가어떤사용자에게속해있는지를나타낸다. 이것은 누가그프로세스를죽일( 혹은중단) 할수있는지를결정하고, 그프로세스가어떤파일과어떤디 렉토리에대해읽거나쓰기권한이있는지결정한다. 그룹식별번호 (group ID, GID) : 프로세스가어떤그룹에속하여있는지를나타낸다. 파일기술자 (file descriptor) : 프로세스가읽기혹은쓰기를위해어떤파일을열고있는지, 또그 각각의파일내에서어떤위치에도달해있는지를기록한다. 1. ps 지금실행하고있는프로세스를볼수있는명령이다. # ps [ 옵션] 그림 47 ps 명령어 이화면의윗줄을살펴보겠다. 각항목의내용은다음과같다.

Chapter 2. 기본명령어 43 항목 내용 USER 프로세스소유자의사용자이름 PID 프로세스의식별번호 %CPU 프로세스가사용하는 CPU의백분율 %MEM 프로세스가사용하는메모리의백분율 VSZ 페이지단위의가상메모리용량(KB 단위) RSS 실제메모리사용량 TTY 프로세스와연결된터미널포트? 는시스템부팅시데몬에서실행되었다는것을의미 현재프로세스의상태 R(Runnable) - 실행대기상태, S(Sleeping) 은수면상태, STAT D(in Disk wait) - 입출력을기다리는인터럽트가불가능한상태, T(sTopped) - 멈춰있거나흔적이남아있는상태, Z(Zombie) - 완전히죽어있는상태 START 프로그램이시작된시간 TIME 프로세스가사용한 CPU 총사용시간 COMMEND 프로세스의실행명령어 표 15 ps 항목별내용 다음은 ps 명령과함께사용할수있는옵션에대한설명이다. ps -a ps -u ps -x ps -e ps -f ps -l ps -j 표 16 ps 기본옵션 다른사용자의프로세스현황도표시실행한유저와실행시간으로표시터미널제어없이프로세스현황표시모든프로세스선택하여표시실행되는모든것을표시긴포맷으로출력 job" 형식으로표시 2. kill 일반적으로프로세스들은다음과같은세가지상황에서활동을중지한다. 상호작용하는프로세스에 [Ctrl]+z 가입력되었을때사용자나프로그램의특별한요청이있을때우선순위가낮은프로세스가제어터미널에접근하려할때 그중가장잘쓰이는상황이사용자나프로그램에특별한요청을하는것으로써, 바로 kill 명령이다. kill 명령은지정한프로세스에지정한시그널(signal) 을보내어프로세스를종료시키도록한다. # kill [- 시그널번호또는시그널] PID 한국외국어대학교컴퓨터공학과

44 네트워크프로그래밍및실습 그림 48 kill 명령어 kill 명령이가지고있는시그널(signal) 종류를알아보기위해서는 kill -l 을입력한다. 그림 49 시그널의종류 프로세스를다시실행하려면 -SIGHUP 또는 -1 옵션을사용한다. 주로이러한경우는시스템의환경 을바꾸고나서데몬을다시띄워야할상황에서시스템을재시작하지않고데몬만재시작해주는 경우유용하다. 시그널에대해서는추후책의뒷부분에자세히언급된다. 3. top CPU 부하, 프로세스로드, 메모리사용량같은다양한시스템자원정보를실시간으로제공한다. # top [ 옵션] 아래그림을해석해보면다음과같다. 23:25:53은 top 을실행한시간으로써, 여기에서는오후 11시 25분에 top 프로그램을실행했다는뜻이다. up 27min이라는메시지는시스템을부팅한지 27분지났 다는뜻이며, 3 명이접속해있고, 평균로드수(load average) 는보이는대로 0.07 이다. Task: 82

Chapter 2. 기본명령어 45 total, 2 running, 80 sleeping, 0 stopped, 1 zombie라는메시지는 83개의프로세스가메모리에적재 되어있으며 80개는활동이잠시멈춰진상태이며 2 개는계속활동하고있다는사실을알려준다. 정 지된프로세스는없고좀비프로세스가 1 개있다. 또한실제메모리와가상메모리상태를보여주며, 실행중인프로세스중많은자원을소모하는상위몇개의프로세스의정보도보여준다. 그림 50 top 명령어 top을실행한상태에서 k를누른다음 PID를입력하면해당프로세스를간단히 kill 할수있다. top 실행을종료할시에는 q 를누르기만하면된다. top -d 스크린을갱신할때의시간출력 top -p 주어진 PID에대한프롯세스만모니터링 top -q 지연시간없이스크린갱신 top -s 보안모드로실행 top -i idle 및 zombie 프로세스무시 top -c 명령라인을모두보여줌 표 17 top 기본옵션 4. nohup # nohup [ 명령] & nohup 명령은부모프로세스가죽거나종료되더라도자식프로세스는계속동작하도록백그라운드 모드에서프로세스를실행하는명령이다. 일반적으로부모프로세스가종료되면자식프로세스도종 료되는데이명령을실행하면사용자가로그아웃을하더라도자식프로세스는죽지않고실행되며, 이에대한정보는 nohup.out 파일에기록된다. 한국외국어대학교컴퓨터공학과

46 네트워크프로그래밍및실습 S E C T I O N 04 압축명령어 리눅스에서사용하는많은프로그램과문서는대개의경우각상황에맞는적당한압축형식으로구할 수있다. 이부분에서는리눅스를사용하기위해꼭필요한압축명령어들을살펴본다. 1. tar tar 는여러개의파일을하나의파일로묶고묶여진파일을다시푸는유틸리티다. 또한묶여진파일 을풀기전에어떤파일들이들어있는지를확인해볼수있다. 이명령어는파일을압축하는것이아니라, 디렉토리를포함한모든파일을단지하나의파일로묶 어주는기능만을한다. 처음에는테이프에백업할데이터를묶기위해사용되었지만, 요즘엔대부분 의소스코드와바이너리를배포할때이포맷이사용되고있다. 기본적인사용방법은다음과같다. # tar [ 옵션] [ 파일이름] [ 대상디렉토리및파일] 다음은 tar 명령과함께사용되는옵션에대한설명이다. tar c 새로운파일을생성함 tar d tar 파일과해당파일시스템간의차이점출력 tar r tar 파일에다른파일들을추가 tar t 묶여져있는 tar 파일의내용을확인 tar f 읽거나쓸파일이름이연속일경우붙여서쓰게함( 반드시사용) tar p tar 파일을생성할당시의파일퍼미션을그대로하여풀어줌 tar v 묶을때나풀어줄때파일들의내용을자세히출력 tar Z 파일을묶거나풀때압축프로그램으로압축함 tar z 파일을묶어나풀때 gzip으로압축함 표 18 tar 기본옵션 일반적으로 tar로작업을하는경우에는다음의 3 가지경우가있다. 첫번째는여러개의파일및디 렉토리를하나의 tar 파일로묶는경우이고, 두번째는이렇게묶은 tar 파일을다시풀어준경우이 며, 세번째가묶어져파일의내용을확인하는것이다. # tar cvf [ 압축파일.tar] [ 압축대상파일및디렉토리] 묶을때 # tar vcf [ 압축파일.tar] 확인할때 # tar xvf [ 압축파일.tar] 풀때

Chapter 2. 기본명령어 47 그림 51 tar로파일묶기 위그림은현재디렉토리에있는모든파일과디렉토리를 project.tar라는파일하나로묶는모습을 보여주고있다. 원본파일은그대로존재하고 project.tar라는파일이새로생성되어그파일에지정 한모든파일및디렉토리가들어가게된다. c 옵션을사용해야한다. 그림 52 tar 파일의내용 위그림은 project.tar라는 tar 파일에어떤파일들이묶여져있는지확인해본것이다. t 옵션을사용 한다. 그림 53 tar 파일풀기 위그림은 project.tar 에묶여져있는파일들을현재디렉토리에풀어준것이다. x 옵션을사용한다. 한국외국어대학교컴퓨터공학과

48 네트워크프로그래밍및실습 tar 로만들어진파일은압축한상태가아니므로파일크기가상당히클수있다. 효율적인압축과백 업을위해보다작은크기의파일을만들어야하는데, 이경우 z 옵션을사용하면된다. z 옵션을주 면 tar 자체가압축을하는것이아니라 gzip 명령을불러압축을시도한다. 그림 54 z 옵션사용 z 옵션을주어서압축을하면용량이많이줄어든다. 그러나이파일을풀어놓으려할때, 앞서설 명한일반적인방법을사용하면다음과같은에러메시지가뜬다. 그림 55 tar 에러메시지 위의예에서압축을풀려면 tar xvfz project.tar 로하면된다. 통상적으로 z 옵션으로압축하여사용 할때는식별을위해 tar cvfz project.tar.gz과같이파일명뒤에 gz 를붙여주는것이좋다. 2. gzip 리눅스환경에서가장많이사용하는압축유틸리티이다. 파일의크기를작게만들어다른네트워크 로전송할때시간을절약해준다. 압축할때에는다음과같이입력한다. # gzip [ 압축할파일명]

Chapter 2. 기본명령어 49 압축된내용을풀어줄때에는다음과같이입력한다. # gzip -d [ 압축된파일명] 그림 56 gzip 명령어 압축해제의경우에는다음과같이사용할수도있다. # gunzip [ 압축된파일명] gzip 명령은옵션을주어압축할수있다. gzip -? [ 압축할파일명] 의형식으로? 에는 1에서 9까지의 숫자가들어간다. 1은가장빠르게압축하는대신파일의크기에는거의변화가없고 9는압축속도 는느리지만가장작은크기로압축한다. 그사이의숫자들은각각숫자가커질수록더좋은효율로 압축하게된다. 옵션을사용하지않을경우에는기본값은 6 으로압축된다. 한국외국어대학교컴퓨터공학과

Chapter 3. 에디팅

Chapter 3. 에디팅 51 S E C T I O N 01 vi 개괄 1. vi의역사 vi는 BSD 의씨셸(C shell) 을개발한빌조이가 1976년에 ed의기능을확장시킨 ex(extended editor) 편집기를개발하고이를확장시켜서만들었다. vi 이전에사용되던에디터로서 ed와 ex라는라인에디터가있는데 ed를사용해본독자라면라인에 디터가얼마나불편한지알수있을것이다. ed나 ex 같은라인에디터는근대의에디터와는인터페 이스가많이틀리다. 라인에디터는마치명령을내리듯이문서를편집하기때문에사용법이복잡하 고다루기힘든편집기였다. 행단위로문서를편집하던시기에마치종이에글을쓰듯이문서를작성한다는일은매우고무적인 일이였을것이다. 믿겨지지않겠지만그래서 vi가 VIsual edit 의준말이다. vi 는기존의에디터에대한새로운인터페이스뿐만아니라에디터의통합을가지고왔다. vi 이전에 는각벤드의터미널마다각각의제어코다가틀려서독립적인에디터가존재할수밖에없었다. 그 러나각각의서로다른터미널의특성을제어하기위한 termcap이라는데이터베이스를이용하여동 일한인터페이스로제어를할수있게되었다. 세상의많은유닉스시스템에서 vi를표준으로선택한 것은당연한일이었다. 2. vi의장점 필자가생각하는 vi 의최대장점은바로키보드로모든것이가능하다는것이다. vi를사용하면마우 스를만질필요가없다. 키보드로모든기능을다사용할수있기때문에일에집중할수도있고또 한매우빠르게일을처리할수있다. vi는마우스가없는시대에만들어졌기때문에현대에나오는어떤 GUI 에디터보다독특한인터페이스를가지고있다. 이러한독특한인터페이스로인해 vi를처음접하는사용자는많은불편함을느낀다. 그러나익숙해지고나면 vi 가정말강력한에디터라는것을알수있다. 한국외국어대학교컴퓨터공학과

52 네트워크프로그래밍및실습 S E C T I O N 02 기본적인 vim 사용법 vi 를자유자재로다룰수있기를원한다면각인해두어야할말이있다. vi 는배우는것이아니라몸으로익히는것이다 vi 는습관이다. vi 의많은명령들은매우단순하며일관성이있다. 먼저그러한일관성을파악해야되고, 일관성을파악한후에는문서의어떤부분을편집하려고먹었다면머릿속의생각보다빠르게번개와같 은손놀림으로이미편집하고있을때까지부단히사용해야한다. 인터넷게시판에서글을다쓰고습관적으로 vi 의저장명령인 [ESC]+:wq! 를눌러서그동안작성한내용 이전자먼지로사라져가는것을경험할때쯤되면 지 vi 는단지독특한그리고불편한에디터일뿐이다. vi 가얼마나괜찮은에디터인지알수있다. 그전까 이번장역시눈으로보고이해하는장이아니라손으로따라하는장이라는것을기억하고예제가나오면무조건따라해보기바란다. 다시한번말하지만 vi 는배우는것이아니라몸이익히는것이다. vi는 30년된공룡시대에디터인만큼오랜진화과정을거치면서많은클론을낳았는데앞으로실습에 서사용할 vim 이바로그것이다. vim은 Vi IMproved의준말로써 vi의기본기능에충실하면서 vi에는없 는편리한기능들이추가되었다. 유닉스시스템은벤더에따라 vi 또는 vim을탑재한시스템이있는데 리눅스는거의 vim 이탑재되어있다. 전통적인 vi 보다는 vim이인터페이스나기능면에서훨씬월등하기 때문에 vim 의사용을권한다. 그러므로앞으로 vim 을기준으로설명한다. 1. vim의모드이해 vim에대해서이해하고자한다면먼저 vim 의여러가지모드에대해서이해해야한다. vim은 3가지 모드를가지고있는데명령모드, 입력모드, ex 모드가바로그것이다. 가. 명령모드 명령모드는키입력을통해 vim 에게명령을내리는모드다. 명령모드에서커서를이동하거나, 삭제, 복사, 붙이기등의작업을수행할수있다. 이명령모드때문에대부분처음 vim을접하는사람들은 생소함을느낀다. 왜냐하면 vim을실행하면명령보드부터시작하는데명령모드에서는아무리타이핑 해도글자가입력되지않기때문이다. 명령보드는 vim에게명령을내리기위한모드지편집을위한 모드가아니라는것을기억하기바란다. 나. 입력모드입력보드는실제로문서를편집하기위한모드다. 입력모드에서글자를타이핑하게되면실제로화

Chapter 3. 에디팅 53 면에출력되면서글자의입력이가능하게된다. 다시한번강조한다. vim은명령모드와입력모드가 있는데명령모드는키입력으로삭제, 복사, 붙이기등의작업을수행하는모드고입력모드가실제로 문서를타이핑하여편집하기위한모드라는것을기억하기바란다. vim은일반에디터와틀리게명 령모드와입력모드가분리되어있다. 이러한특징을이해하지못하고접근하면 vim은낯설고이해하 기힘든에디터가된다. 다. ex모드 ex모드는라인에디터인 ex 에디터의기능을사용하는모드다. vim은 ex 에디터를기반으로만들어 진에디터다. 그래서 ex 에디터의기능을그대로사용할수있다. ex모드를사용하면특정패턴들을 특정문자열로대체한다는것과같이일괄적으로처리해야할적업에매우효율적이다. 그리고 ex모 드에서 ex 라인에디터의기능만구현되어있는것은아니다. vim을더욱강력하게하는많은기능들 이숨어있는모드가바로 ex 모드다. 그림 57 모드전환 각모드의전환은위그림과같다. vim 을처음실행시키면명령모드에서부터시작한다. 명령모드에서 I, i, A, a, O, o, s 중한키를누르면편집이가능한입력모드로전환된다.( 입력모드전환키를다기 억할필요는없다.) 입력보드에서복사, 붙이기등과같은작업을수행하기위해서는명령모드로다시전환해야한다. 입 력모드에서명령모드로의전환은 [Esc] 키를누르면된다. 그림에서보는바와같이입력모드에서 ex 모드로바로전환하는것은불가능하다. 일단명령모드로전환한후 ex 모드로전환해야한다. 명령모 드에서 ex 모드로전환하기위해서는 :, /,? 키중한키를누르면된다. : 키는 ex 편집기명령과 vi의 ex 모드명령을입력할때사용하고 /,? 는특정한패턴을찾을때사용 한다. / 키를누른후찾고자하는패턴을입력하면패턴이나오는곳으로커서가이동한다. / 는정 방향탐색이고? 는역방향탐색을할때이용된다. vi 의기본모드에대해서알았다면이제는에디터로써의기본기능에대해서알아보자. 에디터로써의 기본기능에는입력, 이동, 삭제, 복사, 붙이기, 찾기그리고치환, 저장및종료등이있다. 먼저실행 방법과저장, 종료에대해서알아보자. 한국외국어대학교컴퓨터공학과

54 네트워크프로그래밍및실습 2. 파일열기, 저장, 종료 # vi hello.c 리눅스셸프롬프트상에서위명령을내리면 vim 이실행된다. '$' 는셸프롬프트를의미한다. 실행시 이미 hello.c 파일이존재한다면 hello.c 파일이열리게되고 hello.c 파일이존재하지않으면새로파 일을생성하게된다. 만약 hello.c 파일이있어서기존의파일을편집하게된다면 vim은 hello.c 파일에대응하는.hello.c.swp 파일을생성한다. 그림 58.hello.c.swp 파일.hello.c.swp 파일에는파일의소유자, 사용자이름, 파일명과경로, 수정시간등과같은각종제어 정보와함께파일의내용이들어있다. 화면에일단편집하게되면이.hello.c.swp 파일에기록된내 용이 hello.c 파일에반영된다. 파일이존재하지않아새로생성하는경우생성하는파일의임시파일 에일단기록하고저장명령이내려졌을때임시파일에기록된내용을새로생성할파일에다기록 하게된다. 이러한.swp 임시파일을이용한기록방법은정전상황과같이시스템이예상하지못한상황이발생할때를대비하기위함이다. 누구나중대한보고서나소스를작성하다가갑자기정전혹은예상치못한시스템종료가발생하여비명을질러본경험이있을것이다. 만약 vim 을사용하다가정전이되었다면걱정할필요가없다..swp 파일에이미작성한내용이다 남아있기때문이다. 가끔은.swp 파일이남아있어서 vim을실행할때그림과같은메시지가출력되 곤한다. 주로 vim으로파일을편집하던중에 vim을정상종료하지않고터미널을종료하거나 vim이이상종 료했을때, 혹은다른곳에서해당파일을편집하고있을때.swp 파일이남아서그림과같은메시 지가출력된다. 이런경우에는 rm 명령어로.swp 파일을수동으로지워야한다.

Chapter 3. 에디팅 55 그림 59 기존의.swp 파일때문에생긴주의메시지 이제따라해보자. vi hello.c 명령으로파일을열었다면기본적으로명령모드에서시작한다. i 키를누 르면명령모드에서입력모드로전환되어텍스트를편집할수있게된다. i 키를누른후입력한다. Esc 키를누른후 :w 를누른다. 그림 60 입력모드에서텍스트입력후저장 텍스트입력이끝나면 [Esc] 키를눌러입력모드에서명령모드로전환한다. 현재까지는 hello.c 파일에 입력내용이반영되지않고단지.hello.c.swp 파일에만입력한내용이있는상태다. hello.c 파일에저장하기위해서는 [Esc] 키를누르고 : 키를눌러 ex모드로전환하여그림과같이 w 키를누르고 [Enter] 키를치면된다. 종료는명령모드에서 : 키를눌러일단 ex모드로전환하고 q 키를누르게되면종료하게된다.([Esc] : [Enter] 키를순차적으로누르면종료된다.) 일반적으로저장후종료하는것은 ex모드에서다음과같 은형식으로사용한다. 한국외국어대학교컴퓨터공학과

56 네트워크프로그래밍및실습 :wq! wq 는저장후종료를의미하고! 는강제저장, 종료를의미한다. 만약사용자가자신의 hello.c 파일 을수정중이었는데 hello.c 파일의퍼미션이읽기전용이었다면저장하려고할때읽기전용파일이 라는경고메시지가뜨면서저장할수없다. 그때는! 를붙여서강제저장해야한다. 마찬가지로문서에수정을가하고나서저장하지않고종료하기위해서 되지않는다. 그럴때도 :q! 를입력해서강제로종료하면된다. :q 키를누르게되면종료가 명령어 설명 :w 저장 :w file.txt file.txt 파일로저장 :w >> file.txt file.txt 파일에덧붙여서저장 :q vi 종료 ZZ 저장후종료 :wq! 강제저장후종료 :e file.txt file.txt 파일을불러옴 :e 현재파일을불러옴 표 19 저장및종료 일단 vim 을처음시작하는사람은다음명령만반드시기억하기로하자. 저장 : [Esc] + :w 저장후종료 : [Esc] + :wq! 저장하지않고종료 : [Esc] + :q! file.txt 를불러옴 : [Esc] + :e file.txt 3. 입력 입력은입력보드에서가능하다. 명령모드에서입력모드로전환하기위해서는 a, A, i, I, o, O, s, cc 등의키입력으로가능하다. 독자들은입력모드키가너무많은것이의아스러울것이다. 각키에대 한설명은다음표와같다. a 커서위치다음칸부터입력 A 커서행의맨마지막부터입력 i 커서의위치에입력 I 커서행의맨앞에서부터입력 o 커서의다음행에입력 O 커서의이전행에입력 s 커서위치의한글자를지우고입력 cc 커서위치의한행을지우고입력 표 20 입력모드전환키 위키는대표적인키를나열한것이고위에서나열한키말고도몇가지키가더있다. 히많이사용하는키의경우에는음영으로처리했다. 위키중특 위키들을모두다외우고쓸필요는없다. 물론많이알면그만큼더편하겠지만 vim을처음시작

Chapter 3. 에디팅 57 하는지금으로써는 i(insert) 키만기억하도록하다. 안그러면앞으로나올수많은키들로인해꿈 속에서키보드괴물과싸우게될지도모른다. 그리고믿기지않겠지만나중에익숙해지면저많은 키들을모두골고루사용하게될것이다. 4. 이동 vim 은커서이동과관련하여매우재미있는방식을사용한다. 입력모드에서이동은일반편집기와 같이화살표키를이용하면된다. 명령모드에서는화살표키로이동할수도있고 h, j, k, l 키를이용 할수도있다. h, j, k, l 키는다음표와같이대응된다. h j k l 표 21 명령모드이동키 처음엔 h, j, k, l 키를이용한커서이동이어딘가모르게어색할것이다. 그러나 h, j, k, l 키에익숙 해지고나면정말괜찮은키맵핑이라는것을알수있다. h, j, k, l 키를이용함으로써손이키보드 의중심에서벗어나지않고이동이가능하기때문이다. 명령모드에서단어단위의이동은 w 키와 b 키로가능하다. w 키를누르면다음단어의첫글자로 이동하고 b 키를누르면이전단어의첫글자로이동하게된다. 같은행내에서제일처음글자와마지막글자로이동하는것은 ^ 키와 $ 키를사용한다. ^ 키를누 르게되면행의제일첫글자로이동하고 $ 를누르게되면행의제일마지막글자로커서가이동하 게된다. + 키와 - 키는행을변경할때이용된다. + 키는다음행의첫글자로이동하고 - 키는이 전행의첫글자로이동한다. ( 키와 ) 키는문장의이동에이용된다. vi 에서문장의끝이란마침표(.) 가나오는지점을의미한다. { 키와 키는문단단위의이동에이용된다. 문단은공백인행을기준으 로나누어진다. 한국외국어대학교컴퓨터공학과

58 네트워크프로그래밍및실습 문장범위 문단범위 그림 61 vim에서문장과문단범위 gg키는문서의맨처음으로이동할때사용되고 G 키는문서의마지막위치로이동할때이용된다. 지금까지자주사용되는커서이동키에대한설명이었다. 모든커서이동과화면이동에관련된키는다음과같다. h 왼쪽으로이동 l 오른쪽으로이동 j 아래행으로이동 k 위행으로이동 w or W 다음단어의첫글자로이동 b or B 이전단어의첫글자로이동 e or E 단어의마지막글자로이동 <CR> 다음행의첫글자로이동 ^ 그행의첫글자로이동 $ 그행의마지막글자로이동 + 다음행의첫글자로이동 - 위행의첫글자로이동 ( 이전문장의첫글자로이동 ) 다음문장의첫글자로이동 { 이전문단으로이동 다음문단으로이동 H 커서를화면의맨위로이동 z<cr> 현재행을화면의맨위로이동 M 커서를화면의중앙으로이동 z. 현재행을화면의중앙으로이동 L 커서를화면최하단으로이동 z- 현재행을화면의최하단으로이동 [n]h 커서를위에서 n 행으로이동 [n]l 커서를아래에서 n행으로이동 Ctrl + u 반화면위로스크롤 Ctrl + d 반화면아래로스크롤 Ctrl + b 한화면위로스크롤 Ctrl + f 한화면아래로스크롤 gg, 1G 문서의맨첫행으로이동 G 문서의맨마지막행으로이동 [n]g n 행으로이동 :[n] n행으로이동 표 22 명령모드이동키종합 (<CR> 은 vim 내에서 [Enter] 키의의미, [n] 은임의의번호) 굉장히많은키들로인해기가질릴지도모르겠다. 하지만역시다익힐필요는없다. 사실위에나 오는키를하나도사용하지않고단지화살표키만으로사용할수도있다. 지금은 h, j, k, l 키와단 어단위의이동키인 w와 b, 문단단위의이동키인 { 와 그리고행의시작과끝으로이동하는 ^ 와 $, 특정한행으로이동하는 :[n], 마지막으로문서의시작과끝으로이동하는 gg와 G 정도만기억하기로 하자. 그리고나머지는이책의부록부분에있는 vim 명령어요약을컴퓨터옆에붙여놓고필요할

Chapter 3. 에디팅 59 때하나씩익히면된다. 한글자씩이동 : h, j, k, l 단어단위이동 : w, b 문단단위이동 : {, 행의시작과끝 : ^, $ n 행으로이동 : :[n] 문서의시작과끝 : gg, G 5. 편집 가. 삭제 명령모드에서 x 를누르면현재커서위치의한글자를삭제한다. dw는커서위치의한단어를삭제 하고 dd 는한행을삭제한다. vi의명령에는규칙이하나있는데명령앞에숫자를넣게되면그명령 을앞에누른숫자만큼반복한다는의미가있다. 그래서 10x를누르게되면현재의커서부터 10 개의글자가삭제된다. 마찬가지로 10dw하게되면 10 개의단어가, 10dd하게되면 10 개의행이삭제된다. 원본 4dd 명령 그림 62 4dd 명령화면 주로많이사용하는삭제명령은위에서설명한 법은다음표와같다. x, dw, dd 정도가있고기타삭제와관련된다른방 x, dl 커서위치의글자삭제 X, dh 커서바로앞의글자삭제 dw 한단어를삭제 d0 커서위치부터행의처음까지삭제 D, d$ 커서위치부터행의끝까지삭제 dd 커서가있는행을삭제 dj 커서가있는행과그다음행을삭제 dk 커서가있는행과그앞행을삭제 표 23 삭제와관련된키 한국외국어대학교컴퓨터공학과

60 네트워크프로그래밍및실습 나. 복사 & 붙이기 yw 는현재커서위치의한단어를복사한다. yy 는현재커서위치의한행을복사한다. 이렇게복사 한문자들은 p 로붙여넣을수있다. 앞서설명한 vi의명령규칙대로 10yw는현재커서위치에서 10 개의단어를복사하고, 10yy는 10 개의행을복사한다. 마찬가지로 2p를하게되면복사한내용을 2 번붙여넣게된다. 그림 63 22yy로두행을복사한후 p로붙여넣 기 다. 잘라내기 잘라내기에대해서이해하려면먼저 vi 의레지스터에대해서알아야한다. vi는총 17개의레지스터 를가지고있는데일단 dd 명령어로한행을지웠다면화면에지워짐과동시에그내용은 1 레지스 터에들어가게된다. 그리고다시 dd 명령어로한행을지우게되면 1 레지스터에들어있던내용 은 2 레지스터로옮겨가고 1 레지스터에새로지운내용이들어가게된다. 1 2 3 4 5 6 7 8 9 aaa 1 2 3 4 5 6 7 8 9 bbb aaa 1 2 3 4 5 6 7 8 9 ccc bbb aaa 그림 64 레지스터에들어가는형식 이러한레지스터는 ex 모드에서 :reg 명령으로볼수있다. 위그림은레지스터의내용을본것이고 아래그램은레지스터의이름을나타낸것이다. 0 1 2 3 4 5 6 7 8 9 -. : % # / 그림 65 레지스터버퍼 레지스터는바로이전에지워진내용이항상들어간다. 1 부터 9까지레지스터는지워지는내용

Chapter 3. 에디팅 61 이큐형식으로들어가게된다. 1 부터 9 외에나머지레지스터는특수한목적이있다.. 레지스터에는최근까지타이핑한내용이 들어간다. 그래서아래그림을통해최근에 'ABC' 를타이핑했다는것을알수있다. % 레지스터버 퍼에는현재편집하는파일명이들어간다. 그리고마지막으로 / 레지스터에는가장최근에검색한 문자열이들어간다. 그림 66 :reg 명령어로본레지스터의내용 바로이전에지워레지스터에들어있는내용은 p 명령으로붙여넣기할수있다. 복사역시마찬가 지다. yy 를눌러한행을복사했다면그내용은일단레지스터에들어가게된다. 그리고 p 명령어를 통해서레지스터에있는내용을다시출력할수있다. 그래서 vi에서잘라내기란일단 dw나 dd로지 우고 p 명령어로붙여넣기하면되는것이다. p 명령으로붙여넣기할때특정레지스터를선택할수있다. 2p하게되면 2번레지스터에있는내 용이붙여넣어진다. 만약이전에지우긴했는데그내용이어떤레지스터에있는지모르겠다면 ex 모드에서 :reg 명령을입력하여확인할수있고또다른방법으로는 1pu.u.u. 명령으로각레지스터 에있는내용을하나씩되돌리면서붙여넣기할것을선택할수있다. 1p는 1번레지스터의내용을 붙여는명령이고 u 는되돌리기명령이다. 그리고. 은바로이전에했던작업을반복하는명령이다. 라. 블록지정 지금까지여러단어혹은여러행을지우거나복사하기위해서명령앞에숫자를붙였다. 예를들면 4개의단어를지우기위해서는 5dw를사용했고 5행을복사하기위해서는 5yy 를사용했다. 그러나 여러단어나행을지우거나복사하기위해서일일이단어의개수나행수를세기는매우귀찮은일 일것이다. 그럴때는블록을지정하여직접개수를세지않고도쉽게원하는작업을수행할수있 다. 다음그림과같이원하는위치에서 v를누르고 j 키로커서를아래로이동시키면역상으로볼록이 형성되는것을볼수있다. 일단원하는범위만큼블록을지정했다면, 지우고싶을경우에는 d를누 한국외국어대학교컴퓨터공학과

62 네트워크프로그래밍및실습 르면지워진다. 또한지정된블록을복사하고싶은경우 y를누르면지정된블록에대한복사가일 어난다. 그림 67 블록지정모습 만약다음그림과같은문자열에서 uid 부분만지우고자한다면일반블록지정으로는지우기가힘들 다. 이때는 Ctrl + v 키를사용한다. Ctrl 키를누른상태에서 v 키를누르게되면커서의위치를기 점으로사각형의블록을형성할수있다. 원하는만큼지정한후삭제(d), 복사(y) 를하면된다. 그림 68 블록지정모습 2 ~ 대소문자전환 d 삭제 y 복사 c 치환 > 행앞에탭삽입 < 행앞에탭제거 : 선택된영역에대하여 ex 명령 J 행을합침 U 대문자로만듦 u 소문자로만듦 표 24 블록지정후사용가능한키

Chapter 3. 에디팅 63 6. 되돌리기와되살리기 되돌리기와되살리기기능이 vim에없었다면 vim 은정말정신건강에해로운에디터였을것이다. 혹 은있다고하더라도기능을사용할줄모른다면정신건강을해치는것은마찬가지다. vim에서되돌 리기기능은 u 명령을사용한다. 그리고되살리기는 Ctrl + r 키를이용하면된다. 원래 u 명령의의 미는되돌리기가아니라명령취소의의미다. 그러나결국되돌리기가되는것은사실이기때문에 되돌리기기능이라고말할수있다. 7. 문자열탐색 vim 에서특정한문자열을찾는방법은매우쉽다. 그냥단순히한문자열만찾기위해서는아래와 같이명령모드에서 / 키( 역방향탐색은? 키) 를누른후착기원하는문자열을입력하기만하면된 다. /[ 찾고자하는문자열] 또는?[ 찾고자하는문자열] 다음은 vim 에서파일을열어 to' 라는문자열을탐색한예다. 그림 69 문자열탐색모습 위화면에서 n 키를누르면커서가다음에매칭되는 to 문자열로이동하고대문자 N키를누르면이 전에매칭한 to' 문자열로이동한다. 간단한문자열의경우위와같은 /,? 를사용한명령형식으로찾을수있다. 그러나보다복잡한문 자열의경우정규표현식을사용해야한다. 한국외국어대학교컴퓨터공학과

64 네트워크프로그래밍및실습 8. 문자열치환 문자열치환역시매우쉽다. ex모드에서아래명령을내리면문서에있는모든 old 문자열을 new로 바꾼다. :%s/old/new/g 그림 70 치환전과치환후 위명령은슬레쉬(/) 를구분자로총 4 개의부분으로나누어져있다. :[ 범위]/[ 매칭문자열]/[ 치환문자열]/[ 행범위] 먼저범위는매칭문자를치환할문서상의범위를의미한다. %s 는문서전체를의미한다. 그래서 :%s/old/new/g하게되면문서전체에있는 old 문자열이 new 문자열로치환되는것이다. 아래예제 들은범위지정의예다. 문서전체에서제일처음매칭되는행의문자열만치환한다. :s/old/new/g

Chapter 3. 에디팅 65 그림 71 치환전과치환후 2행에서 4 행사이에매칭되는문자열을치환한다. :2,4s/old/new/g 그림 72 치환전과치환후 현재커서의위치에서위로 1행아래로 3 행되는범위에매칭되는문자열을치환한다. :-1,+3s/old/new/g 그림 73 치환전과치환후 한국외국어대학교컴퓨터공학과

66 네트워크프로그래밍및실습 치환문자열은매칭문자열을만났을때치환할문자열을의미하고마지막으로행범위는치환될행 의범위를의미한다. g는행전체에걸쳐서치환하라는의미고만약행범위에 g를쓰지않고명령 을내리면한행에서여러개의매칭문자열이존재한다고해도한번만치환이일어나게된다. :%s/old/new 그림 74 치환전과치환후 행범위에아래와같이 c 를두게되면매칭되는문자열에대해치환할것인지사용자에게물어본다. :%s/old/new/gc 그림 75 치환명령질의 만약에특정한패턴이있는행에대해서만치환되게하려면다음과같은형식으로사용한다. :[ 범위]/[ 패턴]/[ 매칭문자열]/[ 치환문자열]/[ 행범위] 위명령어는매칭문자에대한치환을조절할수있기때문에유용하다. 예를들면다음예제와같

Chapter 3. 에디팅 67 은소스코드가있을때주석부분은손대지않고실제코드부분만 test 변수를 str 변수로전부바 꾸고자한다면이전에배운내용대로라면주석의 test 도치환된다. 예제 1 테스트용소스코드내용 #include <stdio.h> ////////////////////// // test source code ////////////////////// int main() { char test[] = "Network"; printf( "%s", test ); return 0; 그러나아래처럼명령을내리면주석부분은건드리지않고변수명을바꿀수있다. :g/\(^[^//].*test\ ^test\)/s/test/str/g 그림 76 치환전과치환후 \(^[^//].*test\ ^test\) 는정규표현식1) 으로써 // 으로시작하지않는 test 문자열과 test로시작하는 문자열이라는의미를갖는다. 그래서위명령을내리게되면 // 으로시작하는주석에있는 test는문 자열치환이되지않고실제소스코드부분만치환이일어난것을볼수있다. 이러한치환명령을 요약해서정리해보면아래표와같다. 1 ) 정규표현식 (R e g u la r Ex p re ssio n s) 참고문헌 : 정규표현식완전해부와실습 - 제프리프리들저, 서환수역, 한빛미디어정규표현식기초 - http ://eldercrow.i-i.st/tem p tem p /reg exp.htm 한국외국어대학교컴퓨터공학과

68 네트워크프로그래밍및실습 :s/old/new 현재행의처음 old를 new로교체 :s/old/new/g 현재행의모든 old를 new로교체 :10,20s/old/new/g 10행부터 20행까지모든 old를 new로교체 :-3,+4s/old/new/g 현재커서위치에서 3행위부터 4행아래까지 old를 new로교체 :%s/old/new/g 문서전체에서 old를 new로교체 :%s/old/new/gc 문서전체에서 old를 new로확인하며교체 :g/pattern/s/old/new/g pattern이있는모든행의 old를 new로교체 :/pattern/s//new/g :%s/old/new/g와동일 표 25 치환명령요약

Chapter 3. 에디팅 69 S E C T I O N 03 vim을강력하게하는고급테크닉 지금까지는에디터로써기본적인기능을설명했다. 지금까지의내용만으로는 vim이다른에디터에비해 많이다르다는것은알수있었겠지만어째서 것이다. vim 의색다른기능들과특징을알아본다. vim이다른에디터와비교해서강력한지는알수없었을 1. 여러파일을편집하는방법 보통한프로젝트의소스파일은하나의파일로이루어져있지않다. 코딩을하다보면이파일에서 저파일로옮겨가면서수정해야되는일이대부분이다. 보통 GUI 에디터는여러파일의편집을지원 하기위해서여러탭으로구분하여파일을편집하는형식으로이루어져있다. 지금수정하는파일에 서다른파일을수정하기위해마우스나단축키를사용해서다른파일의탭을열고수정하면된다. vim 도여러파일을편집하는모드를지원한다. 여러파일을편집하는데있어서 vim 만큼편리한에 디터도드물다. 그렇지만 vim 의기본설정상태에서는편집하기가다소불편하다. 여러파일을편리 하게편집하기위해서는몇가지설정을해야한다. 먼저여러파일을여러는방법을알아보자. 다음그림과같이현재작업디렉토리에 file1.c, file2.c, file3.c 세개의파일이있다. 각파일이프로젝트에서각부분을담당한다고생각하면된다. 그림 77 현재디렉토리의파일 이렇게여러파일들이있을때한꺼번에여러파일을여는방법은아래명령어로가능하다. $vi file1.c file2.c file3.c 위명령어와같이여러파일명을일일이지정해도되고아래와같이셸메타기호를사용해서한꺼 번에지정해도된다. 한국외국어대학교컴퓨터공학과

70 네트워크프로그래밍및실습 $vi * 또는 vi *.c 위명령을내리면다음그림과같이 file1.c 파일이열린것을확인할수있다. 그림 78 file1.c의내용 보기에는 file1.c 파일밖에열리지않은것으로보인다. 그렇지만 vim 내부적으로는세개의파일이 다열려있는상태다. 열려있는각파일은 vim 의파일버퍼에들어있게된다. 각파일버퍼에들어 있는내용은 :ls 명령어로확인할수있다. 다음그림과같이 file1.c 파일이열려있는화면을파일 버퍼라고부르기도한다. 그럼파일버퍼간전환을해보자. :b2 ex모드에서위명령을내리게되면열려있는 2 번파일로전환한다. :b2 라는명령어의의미는 파일 버퍼 2 번 의의미다. 그림 79 file2.c의내용 마찬가지로 3 번파일버퍼로전환하기위해서는 :b3 이라고명령을내리면된다. 열려있는파일을닫 는명령은 :bw 다. :bw 하게되면현재의버퍼를닫게된다. 버퍼에관련된명령은다음과같다.

Chapter 3. 에디팅 71 :buffers 버퍼의내용을나열 :files 또는 :ls 버퍼의내용을나열 :b[n] n번버퍼로이동 :bd[n] n 번버퍼를삭제(n 이없으면현재의버퍼를완전삭제) :bw[n] n 번버퍼를완전삭제(n 이없으면현재의버퍼를완전삭제) :bp[n] 이전버퍼로이동. n을붙이면 n번만큼이전버퍼로이동 :bn[n] 다음버퍼로이동. n을붙이면 n번만큼다음버퍼로이동 :sb[n] 창을수평분할하여 n번버퍼를로드 :bf 첫번째버퍼로이동 :bl 마지막버퍼로이동 :al 현재열려있는모든버퍼를수평분할창에로드 표 26 파일버퍼관련명령요약 위에서사용한방법으로파일을전환하는것은코딩과정내내빈번하게발생하는파일전환을고려해볼때다소불편한면이있다. 이제 vim 의설정을변경하여보다편지하게단축키를지정해놓자. :map,1 :b!1<cr> :map,2 :b!2<cr> :map,3 :b!3<cr> map 명령은단축키를매칭(mapping) 할때사용된다. 위명령을 ex모드에서차례로내린후명령모 드에서 1 키를누르게되면 1 번버퍼로전환하고, 2를누르게되면 2 번버퍼로전환한다. 이때타이 밍이중요하다., 키를누르고뗀후숫자키를눌러야하는데, 키를누른후한참있다가 1번을누 르게되면정상적인명령어로인식하지않는다. b1이라고하지않고 b!1이라고한이유는만약 2번버퍼에편집작업을수행한후파일을저장하지 않고는 1 번버퍼로이동하려고할때, vim이파일이편집되었는데저장하지않았다고경고를발생시 키는데, 이를무시하고강제로버퍼를전환하기위해서다. <CR> 은 vim 내에서 [Enter] 키의의미가있 다. 현재 vim에매칭되어있는모든매핑리스트는단순히 ex 모드에서 :map이라고입력하면화면에 출력된다. 이제파일버퍼를전환할때는,[ 숫자] 를누르면된다. 그런데매번위와같이 :map 명령어로키를 매핑해야한다면정말불편한일이아닐수없다. 보다편리하게하기위해서는 vim 설정파일에 map 명령을등록해야한다. 가. vim 설정파일에키매핑추가 사용자의홈디렉토리에.vimrc 파일을생성하게되면 vim 은실행할때 ~/.vimrc 파일을읽어서실행 하게된다..vimrc 파일은 vim 설정파일로써 vim 이실행될때 ~/.vimrc 파일에적힌명령을차례로 수행하게되어있다. ~/.vimrc 파일을열거나생성하여다음내용을추가해보자. 한국외국어대학교컴퓨터공학과

72 네트워크프로그래밍및실습 예제 2 ~/.vimrc 파일내용 map,1 :b!1<cr> map,2 :b!2<cr> map,3 :b!3<cr> map,4 :b!4<cr> map,5 :b!5<cr> map,6 :b!6<cr> map,7 :b!7<cr> map,8 :b!8<cr> map,9 :b!9<cr> map,0 :b!0<cr> map,w :bw<cr> 그림 80 ~/.vimrc 파일의내용 그리고다시여러파일을열었다면명령모드에서,1 또는,2와같은단축키로파일버퍼를전환할수 있다. 지금설정에서는 10 개의파일버퍼를단축키로지정했다. 그러나 vim에서사용가능한파일버 퍼는이보다훨씬많다.(1,000 단위가넘어갈정도다.) 10개이상의파일버퍼를지정하고싶다면추 가적으로지정하면된다. map 명령으로단축키를매핑할때는주의할점이있는데, map 명령어로 매핑할때는명령모드에서사용하지않는단축키를사용해야한다는것이다. 위.vimrc 파일에서 ( 큰 따옴표) 는주석을의미한다. 2. 반복되는문자열을저장해서쓰기 이전에잘라내기에서레지스터에대해잠깐언급했다. 이전에설명한 17개의레지스터외에 vim은 a 터 z까지 26 개의레지스터를더지원한다. 이러한레지스터를네임레지스터라고하는데이레지스 터는반복되는문자열을저장해서쓰기에편리하다. 가령 C 코딩을하다보면다음예제와같이 printf 로디버깅메시지를찍어보는경우가빈번하다.

Chapter 3. 에디팅 73 예제 3 int main() { int i; i = 2; printf로디버깅메시지를찍어보는예제소스 #ifdef DEBUG printf( "DEBUG : i = %d\n", i ); #endif return 0; 이러한디버깅메시지를네임레지스터에저장하고사용하면된다. 아래같이하면된다. 저장하는명령은명령모드에서 a3yy a는 a레지스터에저장하라는의미고 3yy는현재커서위치에서세행을복사해서저장하라는의미 다. 이렇게명령을내리면현재의커서위치에서아래로 3행을복사하여 a 레지스터에저장하게된 다. 저장된내용을붙여넣는방법은원하는위치에서아래의명령을내리면된다. ap "a로 a 레지스터를지정하고 p 로붙여넣기했다. 그림 81 "a3yy 명령을내린화면 한국외국어대학교컴퓨터공학과

74 네트워크프로그래밍및실습 그림 82 "ap 명령을내린화면 a부터 z까지총 26개의네임레지스터를지원함으로넉넉하게원하는문자열을저장해서사용할수 있다. 3. 매크로의사용 vim 에서매크로란키입력을네임레지스트에저장해서사용하는방법을말한다. 제를보면서설명하겠다. 이해하기쉽게예 그림 83 매크로사용설명을위한예제소스

Chapter 3. 에디팅 75 가령위그림과같은소스파일이있다고가정했을때현재커서가있는행에주석을넣기위해서는 키입력으로 ^i // [SpaceBar] [Esc] [Enter] 키를순차적으로누르면된다. ^ 기호는커서를현재행 의처음으로이동시키는명령이다. i 키를사용하여입력모드로전환한후 // 와공백을입력한다. 그 리고 [Esc] 키를사용하여명령모드로전환한후 [Enter] 키를눌러다음행의처음으로커서를이동하 는것이다. 이러한키입을저장해서필요할때사용하 기능이있다면반복적으로키를눌러야하는작업에 있어서편리할것이다. 이런경우사용하는기능이바로매크로기능이다. 매크로를사용하는방법은 다음과같다. 먼저 q[ 네임레지스터명] 을눌러매크로를등록할레지스터를지정한다. 가령 b 레지스터를사용하고 자한다면 qb 라고입력하면된다. qb qb 라고입력하면화면하단에 기록중 이란글자가나온다. 이후에누르는키들은 b 레지스터에저 장되게된다. ^ i // [SpaceBar] [Esc] [Enter] 키를차례로누른다. 그리고 q 를누르면화면하단에 기록중 이란메 시지가사라지면서 b 레지스터에이제까지누른키들이등록된다. 이제 b 레지스터에등록된매크로는 @b 를누름으로써사용할수있따. @b를누르게되면 ^ i // [SpaceBar] [Esc] [Enter] 키를순차적으로누르는것과같은결과를가져오게된다. 7@b 하게되면 @b 를일곱번사용한결과가나타난다. 7@b 다음그림은 b 레지스터에 ^ i // [SpaceBar] [Esc] [Enter] 키를등록하여 7@b 명령어로일곱번반 복했을때의결과다. 한국외국어대학교컴퓨터공학과

76 네트워크프로그래밍및실습 그림 84 7@b 명령후 마찬가지로행주석을없애는매크로를 c 레지스터에저장하기위해서는 qc를입력해서매크로를저 장할레지스터를지정한후 ^3x [Enter] 키를입력한후 q 를누르면된다. ^ 기호는행의처음을의미하고 3x 를사용하여현재커서의위치에서세글자를지운후 [Enter] 키를 눌러다음행의처음으로이동한다는의미다. 4. 다중창사용하기 프로그래밍을하거나소스를분석하다보면다른파일에작성된구조체의타입이나소스를참조해야 되는경우가많이발생한다. 이런경우다중창기능을사용하면유용하다. 다중창기능이란 vim의 화면을여러개로분할하여사용하는방법을말한다. 명령모드에서 Ctrl + w n(ctrl 키를누른상태에서 w를누르고 Ctrl 키를떼고 n 키를누른다. n은 new 의의미) 키를누르면 vim 창이다음그림과같이가로로분할되는것을볼수있다. 그림 85 Ctrl + w n 입력후모습

Chapter 3. 에디팅 77 이렇게창이분할되면 :e [ 파일명] 명령어로파일을불러올수있다. 다음그림은파일을불러오는모 습을나타내었다. 그림 86 :e struct1.c 명령어로새로열린창에 struct1.c 파일을불러온모습 창간커서전환은 Ctrl + ww(ctrl을누른상태에서 w 를두번누름) 키로할수있따. 반복해서 Ctrl + w n 키를누르면창을계속분할할수있다. 그리고이렇게분할한창은 Ctrl + w q(quit) 키를 누르거나 ex 모드에서 :q! 를입력함으로써닫을수있다. Ctrl + w s 키는현재의파일을가지고분할한다. 무슨말이냐하면현재 file1.c 파일이열려있는 창에서 Ctrl + w s(split) 키를누르게되면창이수평분할되면서 file1.c의내용이분할된창에들어 가게된다. Ctrl + w v(vertical split) 키를누르게되면창이수직분할한다. 다음그림은 Ctrl + w v 키로창을수직분할한예다. 그림 87 Ctrl + w v 키로수직분할한모습 한국외국어대학교컴퓨터공학과

78 네트워크프로그래밍및실습 수직분할역시창간전환은 Ctrl + ww 키로하며주로두파일을비교할때사용된다. 분할한창의 사이즈를조절하고싶을때가있다. 가령헤더파일에구조체를참고하다가당장은필요가없어졌지 만빈번하게참고해야할때가있을것이다. 이런경우창의크기를조정하면된다. 수정중인소스 파일의창은최대로하고헤더파일의창은최소로만들면되는것이다. 현재작업중인창을최대 로하는명령은수평분할일경우 Ctrl + w _ 키고수직분할일경우 Ctrl + w 키다. 그림 88 Ctrl + w _ 키로최대화한모습 위와같이최대화한창을원래대로균등하게분할된상태로만들고싶을때는 용하면된다. 최대화되었던창이원래의균등분할된상태로돌아온다. 다음은이러한창분할과관련된명령들을요약해놓은표다. Ctrl + w = 키를사 명령모드 ex모드 결과창생성 Ctrl + w s :sp[plit] 현재파일을두개의수평창으로나눔 Ctrl + w v :[n]vs[plit] 새로운수직창생성, n이붙으면 n칸크기의창분할 Ctrl + w n :new 새로운수평창생성 Ctrl + w ^ 수평창으로나누고이전파일오픈 Ctrl + w f 창을수평으로나누고커서위치의파일을오픈 Ctrl + w i 커서위치의단어가정의된파일을오픈 창삭제 Ctrl + w q :q[uit]! 현재커서의창을종료 Ctrl + w c :clo[se] 현재커서의창을닫기 Ctrl + w o :on[ly] 현재커서의창만남기고모든창삭제 Ctrl + wr Ctrl + wx 창위치바꾸기순차적으로창의위치를순환이전창과위치를바꿈

Chapter 3. 에디팅 79 Ctrl + wh Ctrl + wj Ctrl + wk Ctrl + wl Ctrl + ww Ctrl + wp Ctrl + wt Ctrl + wb 창이동왼쪽창으로커서이동아래쪽창으로커서이동위쪽창으로커서이동오른쪽창으로커서이동창을순차적으로이동가장최근에이동한방향으로이동최상위창으로이동최하위창으로이동 창크기조정 Ctrl + w = 창의크기를모두균등하게함 Ctrl + w _ 수평분할에서창의크기를최대화 Ctrl + w 수직분할에서창의크기를최대화 Ctrl + w [n]+ 창의크기를 n 행만큼증가 (Ctrl + w + 는 1 행만큼증가) Ctrl + w [n]- 창의크기를 n 행만큼감소 (Ctrl + w + 는 1 행만큼감소) Ctrl + w [n]> 창의크기를오른쪽으로 n 칸만큼증가 ( 수직창에한함) Ctrl + w [n]< 창의크기를왼쪽으로 n 칸만큼증가 ( 수직창에한함) 표 27 다중창명령요약 (ex 모드에서 [] 내에문자는생략가능함을의미) 위명령들을자세히살펴보면규칙성이있다는것을발견할수있다. 일단 Ctrl + w를누르게되어 있다. 그리고창생성의경우분할(split), 새창(new), 수직분할(vertical split) 의영문자첫글자로이 루어져있따. 창삭제의경우도마찬가지다. 일단 Ctrl + w 를누른다. 그리고종료(quit), 닫기(close), 단일(only) 의영문자첫자를누르면된다. 창이동의경우기본이동키인 h, j, k, l 키와연관이있는것을알수있고(h, j, k, l 키대신화살표 키를사용해도된다.) 최상위(top), 최하위(bottom) 등도첫자로이루어진것을알수있다. 이러한일관성만파악한다면아무리많은창관련명령도외우기쉬울것이다. 그리고항상말하듯 이 vi 는배우는것이아니라익히는것이기때문에이해되었다고해서따라해보지않으면안된다. 몸 이기억할수있게여러번반복해서사용해야한다. 5. 마킹으로이동하기 지금까지 vim 의기본적인커서이동에관해서알아보았다. 이제는조금더고차원적인마킹으로이 동하는방법에대해서알아보자. 마킹은의미대로커서의특정위치를문자로마킹하여나중에한번 에표시한위치로돌아가기위한방법이다. 이러한마킹기법은소스가매우긴파일이거나여러곳 을동시에수정할때많이사용된다. 그리고특히커널과같이거대한프로젝트소트를분석할때 사용하면유용하다. 마킹은다음명령어로가능하다. m[ 임의의알파벳]: 마킹할때 [ 마킹한알파벳]: 마킹한위치로돌아갈때 한국외국어대학교컴퓨터공학과

80 네트워크프로그래밍및실습 마킹에는세가지종류가있다. A-Z까지대문자로마킹한전역마킹과 a-z까지소문자로마킹한지 역마킹그리고 ~/.viminfo 파일에서자동으로지정한 0-9 로이루어진파일마킹이그것이다. 알파벳 으로마킹하는전역, 지역마칭은언제든지사용가능하나숫자로이루어진파일마킹은 vim이실행 될때나혹은종료될때자동으로지정되기때문에사용할수없다. 파일마킹은이전에 vim으로편 집한파일의경로와파일내에위치를 vim 이알아서마킹하게된다. 전역마킹과지역마킹의차이 점은다음과같다. 전역마킹 (A-Z) 지역마킹 (a-z) : 현재파일을포함한다른파일간의마킹이가능하다. : 현재파일내에서만마킹이가능하다. 즉, 대문자로마킹하게되면파일이틀리더라도마킹한위치로이동할수있지만소문자로마킹하게되면현재파일내에서만마킹이유효하게된다. 위그림과같은소스가있을때 main() 함수에 do_print() 함수가사용되었는데이전에 do_print() 함 수의원형이선언되지않았다. 그래서위소스는컴파일되긴하나컴파일할때경고메시지가발생 하게된다. ma 를눌러현재커서 위치를 a로마킹한다 그림 89 마킹사용법예제 1 경고메시지는결코달가운것이아니기에 main() 함수앞에 do_print() 함수의원형을선언하여경 고메시지를없애기로했다. do_print() 함수는물론현재파일의아래부분에있고필자는 do_print() 함수를모르는상태라고가정하자. 명령모드에서탐색명령 /do_print를사용하면 do_printf() 함수를찾을수있을것이고 yy 명령어로 do_printf() 함수의정의부분에서원형을복사할수있다. 하지만돌아오는것이문제다. 파일의소스 가매우길경우원하는위치로돌아오는것이만만치않을것이다. 물론위소스는매우짧고그리 고돌아오기원하는위치가파일의최상단에있기때문에 gg( 파일의처음으로이동) 명령으로돌아 오면될것이다. 그러나만약돌아오기원하는위치가파일의임의의위치라면마킹을사용하면한

Chapter 3. 에디팅 81 결수월하다. 먼저현재위치를마킹해야한다. 현재위치에서 ma(maring a) 라고입력하게되면햔재위치가 a로 마킹된다. 그러다음 /do_print 명령어로 do_print() 함수가정의되어있는곳을찾는다. 찾았으면 yy 로복사한다. 그림 90 마킹사용법예제 2 그리고다시 a 로마킹된곳으로돌아가면된다. a(' 기호는 backtick 을의미) 를입력하면 a로마킹된 지점으로돌아간다. 그리고 p 명령어로 do_print() 함수의원형을붙여넣고편집하면된다. 그림 91 마킹사용법예제 3 vim 에서마킹된모든정보는 같다. :marks 명령을통해서확인할수있고마킹과관련된명령은다음표와 한국외국어대학교컴퓨터공학과

82 네트워크프로그래밍및실습 명령어 내용 명령어 내용 ma a 로마킹, mb는 b로마킹 `a a 로마킹된위치로돌아감 a a로마킹된행의처음으로돌아감 ' ' 바로전에커서가위치하던행의처음 ` ` 바로전의커서위치로이동 ' " 이전에 vim으로현재파일을닫았을때이전에 vim으로현재파일을닫았을때 ` " 커서가있던행의처음으로이동커서가있던위치로이동 6. 셸명령어사용 vim 에서작업을하다가잠깐셸로돌아가어떤명령을실행해야할때가있다. 예를들면 ifconfig 명령을입력하여작업 PC의 IP를알아내거나아니면현재의프로그램을컴파일하고실행시켜결과를 알아보고싶은경우가있다. 그럴때마다매번 vim을종료하고셸로돌아가서명령을내린후다시 vim 을실행한다면작업이비효율적일것이다. 이렇게셸명령어를입력하거나아니면셸로잠깐빠져나가기위한방법이있다. ex 모드에서 :![ 명령 어] 를입력하면잠시 vim으로빠져나갔다가명령이수행된후다시 vim 으로돌아오게된다. 예를들 면 :!ls를입력하게되면 vim에서 ls 명령이수행되고다시 vim 으로돌아올수있다. :![ 명령어] 그림 92 :!ls 명령어수행결과 마찬가지로셸을수행할수도있다. :!bash라고입력하면새로운셸이열리면서 vim을빠져나가게 된다. 셸에서원하는명령을수행하고다시 vim으로돌아오고자한다면셸에서 exit 명령을내려셸 을종료하게되면다시 vim 으로돌아오게된다. 명령을내리고난후화면에출력되는결과가필요할때에는 :r![ 명령어] 와같은형식으로명령을내 리면명령이수행된결과를현재커서의위치에끼워넣게된다. 가령 ls 명령을내렸을때화면에출 력되는결과를현재커서위치에끼워넣고싶다면 :r!ls 라고명령을내리면된다. :!r[ 명령어]

Chapter 4. 컴파일과링킹 83 그림 93 :r!ls 명령어수행결과 원래 :r[ 파일명] 명령은현재커서의위치에파일의내용을끼워넣기위해서사용한다. 그래서 file1.c 파일수정중에 :r file2.c 명령을내리게되면현재커서의위치에 file2.c 의내용을끼워넣게된다. :!r[ 파일명] 한국외국어대학교컴퓨터공학과

Chapter 4. 리눅스프로그래밍환경

Chapter 4. 리눅스프로그래밍환경 85 S E C T I O N 01 네트워크환경 자신의 IP 주소, 도메인네임서버정보, 게이트웨이주소, 다른호스트와의연결확인등현재네트워크 정보를알아내는데사용하는유닉스명령문들을소개한다. 리눅스에서는네트워크환경을시스템관리 자(root) 만이변경할수있는경우가많다. 1. 네트워크서비스시작 자신이사용하는호스트가인터넷에연결되어동작중인지확인하려면 ifconfig 명령을사용한다. #ifconfig [ 옵션] ifconifg 명령(interface configuration) 은네트워크디바이스의상태를보여주며, -a 옵션은설치되어 있는모든네트워크디바이스의상태를보여준다. 그림 94 ifconfig 명령어 자신의호스트가통신이가능한상태인지를확인하려면위의출력결과에서 UP 이보이지않는다면호스트가통신불가능한상태임을나타낸다. UP 을찾아보면된다. 만일호스트가네트워크를지원하지않고있으면다음과같이네트워크설정파일들의내용을적절 히작성한후 network 명령으로네트워크를활성화시킨다. 수정해야할환경설정파일들은아래와 같다. 한국외국어대학교컴퓨터공학과

86 네트워크프로그래밍및실습 /etc/sysconfig/network /etc/sysconfig/network-script/ifcfg-eth0 /etc/resolv.conf 이파일들을아래와같이호스트환경에맞추어각각수정한후에 을사용하여네트워크서비스를재시작한다. /etc/init.d/network restart" 명령 그림 95 /etc/sysconfig/network 설정파일 그림 96 /etc/sysconfig/network-scripts/ifcfg-eth0 설정파일 그림 97 /etc/resolv.conf 설정파일 그림 98 /etc/init.d/network restart 명령수행결과

Chapter 4. 리눅스프로그래밍환경 87 S E C T I O N 02 네트워크명령어 리눅스는강력한네트워크기능을제공한다. 본격적인사용이나다양한네트워크서비스는뒤에다루겠 지만, 네트워크상태확인에필요한기본명령어를숙지하고사용해보도록한다. 1. ping 원격시스템이제대로네트워크에연결되어있는지확인하는명령어이다. # ping [hostname 또는 ip 주소] 네트워크관련명령어중가장흔하게사용하는것이다. 이명령어는지정한대상호스트로 ICMP 패 킷을보내어그응답을통해통신상태를점검한다. 네트워크연결이느려질때병목현상이일어나 는곳을알아야바르게조치를취할수있기때문에, 네트워크가느려질때흔히사용하는명령어이 다. 또한시스템의네트워크부분을설치한후제대로설정되었는지확인할때에도많이사용한다. ping 이라는네트워크명령어를통하여알수있는것은대상호스트의통신가능여부와이테스트 를하기위해 ICMP 프로토콜을사용한다는것이다. 즉, ICMP 에코요구를네트워크를통해목표호 스트에게보내는데, ping은목표호스트와의통신가능여부와데이터패킷이목표호스트까지갔다 오는데걸리는시간을제공한다. ping 다음에테스트하려는서버의도메인이나 IP주소를입력하면해당주소로 ICMP 패킷을보내게 된다. ping 테스트를끝낼때는 [Ctrl]+c 를입력한다. 그림 99 ping 명령어 위그림과같이 [Ctrl]+c를입력하고나면 ping 테스트의결과를볼수있다. 테스트할서버로보낸 한국외국어대학교컴퓨터공학과

88 네트워크프로그래밍및실습 패킷이 3 개, 응답으로답은패킷도 3 개, 손실된패킷은없는것을볼수있다. ping 명령도다양한 옵션을조합하여사용할수있다. ping -s ping 테스트시에사용할패킷사이즈설정 ping -q ping 테스트결과를지속적으로보여주지않고종합결과만을출력 ping -i ping 테스트시사용할 interval( 지연시간) 설정 ping -b ping 테스트를하는서버와동일한네트워크에있는모든호스트로패킷을보냄 ping -c ping 테스트시에보낼패킷수를지정 ping -t ping 테스트를멈추지않고연속하여실행 표 29 ping 기본옵션 2. traceroute 패킷이목적호스트까지전달되는경로를조사하여화면에출력하는명령어가 traceroute 이다. traceroute 를이용하여패킷이어느경로에서유실되는지를확인할수있으며, 어느네트워크에서트 래픽이발생하는지를점검할수있다. traceroute 의일반적인사용법은다음과같다. # traceroute [ 테스트할주소] 예를들어내컴퓨터에서 daum.net 으로가는과정을추적해보자. 내컴퓨터에서출발한패킷은여 러호스트를거쳐서원하는서버로전송된다. 이명령을실행하면네트워크상태가좋지않거나특 정사이트에접속이잘되지않을때어디에서문제가생겼는지확인할수있다. 테스트한결과에서 다음과같은내용을파악할수있다. 1. 대상호스트와의통신은잘이루어지고있는가? 2. 대상호스트까지몇개의 hop 수를거쳐서통신이되고있는가? 3. 대상호스트까지의통신에있어서지연되는구간은없는가? 만약, 지연되는구간이있다면어 느구간인가? 4. 대상호스트까지의통신경로에있는특정구간의정보확인. 네트워크인터페이스( 랜카드등) 가하나만있을때에는문제가없지만, 여러개의랜카드를사용할때에는경로추적이잘되지않을때가있다. 이때는패킷을전달하는인터페이스를지정해주면정상적으로작동한다. 여러개의인터페이스에서기본라우터로설정된인터페이스를통하여경로추적을할때는다음과같은명령을내린다. # traceroute -i [ 인터페이스이름또는호스트주소] 3. route

Chapter 4. 리눅스프로그래밍환경 89 route 는현재사용중인서버의라우팅경로를설정하기위한것으로특정네트워크인터페이스에 라우팅을설정하는명령어이다. # route 그림 100 route 명령어 특정네트워크인터페이스의기본게이트웨이를설정하는명령어형식은다음과같다. # route add default gw [ 게이트웨이 IP 주소] dev [ 네트워크인터페이스장치] 다음은네트워크인터페이스에외부와통신이가능하도록하기위하여기본게이트웨이를설정하는 명령어형식이다. # route add -net [ 게이트웨이 IP 주소] netmask [ 넷마스크] dev [ 네트워크인터페이스장치] 4. netstat netstat은 route 명령보다더자세한라우팅테이블정보와네트워크상태를체크하는도구이다. 일 반적으로 route 명령보다는 netstat 명령을많이사용한다. netstat 명령의일반적인사용법은다음과 같다. # netstat -nr 다음은 netstat 에서쓸수있는옵션들이다. 한국외국어대학교컴퓨터공학과

90 네트워크프로그래밍및실습 netstat -a --all과같으며 listen되는소켓정보와 listen되지않는소켓정보를모두출력 netstat -n --numeric과같으며 10진수의수치정보로결과를출력 netstat -r --route와같으며설정된라우팅정보를출력 netstat -p --program과같으며실행되고있는각프로그램과 PID정보를출력 netstat -i --interface=iface 와같으며모든네트워크인터페이스정보를출력하거나특정네트워크인터페이스정보를출력 netstat -c --continous 와같으며 netstat 결과를연속적으로출력 netstat -l --listening과같으며현재 listen 되고있는소켓정보를출력 netstat -s --statistic과같으며각프로토콜에대한통계정보를출력 표 30 netstat 기본옵션 netstat 은라우팅테이블정보를조회하는목적외에도서버에서사용중인포트와그포트를사용하 는프로그램에대한정보를얻는데에도사용된다. 그림 101 netstat -s 명령어 위그림을보면 s 옵션을사용한것으로리눅스서버에서사용할수있는프로토콜에대한통계정보를확인할수있다.

Chapter 4. 리눅스프로그래밍환경 91 그림 102 netstat -atp 명령어 atp 옵션을사용하여현재응답할수있는( 서버에서열려있는) 포트번호와각제몬들, 그리고그포 트에서사용하는프로그램에대한정보를상세히점검해볼수있다. 한국외국어대학교컴퓨터공학과

92 네트워크프로그래밍및실습 S E C T I O N 03 gcc를사용해컴파일하기 GCC는원래 GNU C compiler 의줄임말로사용되었지만, 1999년 4 월에 "GNU Compiler Collection" 로 바뀌었다. GNU C 컴파일러는리처드스톨먼등에의해만들어졌는데, 이컴파일러는품질이매우좋으 며, 이식성이좋은 C, C++ 컴파일러이다. C, C++, JAVA, FORTRAN 등여러가지컴파일러들이포함되 어있으나이책에서는 C 컴파일러를이용해모든소스를다루고실행한다. 1. 가장단순한컴파일명령 가장단순한컴파일명령은다음과같다. # gcc hello.c 위와같이명령을내리면 hello.c가 a.out 이라는이름으로컴파일된다. 그림 103 gcc hello.c로컴파일한모습 가장단순하게출력파일의이름조차지정하지않은컴파일방법이다. 위와같은컴파일방법은간단한소스테스트에서자주사용하고보통은아래와같은형식으로사용한다. # gcc -W -Wall -O2 -o hello hello.c -Wall 옵션은모든모호한코딩에대해서경고를보내는옵션이고 -W 옵션은합법적이지만모호한 코딩에대해서경고를보내는옵션이다. 그래서 -W -Wall 옵션을붙이게되면아주사소한모호성에 대해서도경고가발생한다. -O2는최적화레벨을 2 로설정한다. 최적화레벨 2 는거의대부분의최적화를시도한다. -o hello는 컴파일된파일명을 hello 로하라는의미다. 위와같은기본컴파일명령에다음부터설명하는옵션들

Chapter 4. 리눅스프로그래밍환경 93 을바탕으로필요한다른옵션들을추가하면된다. 2. gcc 옵션 가. gcc 전역옵션 옵션 설명 -E 전처리과정을화면에출력 -S 어셈블리파일생성 -c 오브젝트파일생성 -v 컴파일과정을화면에출력 --save-temps 컴파일시생성되는중간파일저장 -da 컴파일과정에서생성되는중간코드생성(RTL 파일등) 표 31 gcc 전역옵션 나. 전처리기(cpp0) 옵션 옵션 설명 -I[ 경로] 헤더파일을탐색할디렉토리지정예 ) -I/opt/include -include [ 헤더파일경로] 해당헤더파일을모든소스내에추가예 ) -include /root/my.h -D[ 매크로] 외부에서 #define 지정예) -DDEBUG -D[ 매크로]=[ 매크로값] 외부에서해당매크로를정의하고값을지정예 ) -DDEBUG=1 -U[ 매크로] 외부에서 #undef 지정예) -UDEBUG -M 또는 -MM make 기술파일을위한소스파일의종속항목출력 -nostdinc 표준 C 헤더파일을포함하지않음 -C 전처리과정에서주석을제거하지않음 -Wp,[ 옵션리스트] 옵션리스트를전처리기에바로전달 표 32 전처리가(cpp0) 옵션 다. C 컴파일러(cc1) 옵션 옵션 설명 -ansi ANSI C 문법으로문법검사 -std=[c 표준] 지정한 C 표준으로문법검사(c89, c99, gnu89, gnu99 등) -traditional K&R C 문법으로문법검사 -fno-asm asm, inline, typeof 키워드를사용하지않음(gnu89 문법기준) 표 33 C 언어옵션 옵션 설명 -Wall -W 모든경고메시지출력 -w 모든경고메시지제거 -Werror 모든경고를오류로취급하여컴파일중단 -pedantic C89 표준에서요구하는모든경고메시지를표시 -pedantic-errors C89 표준에서요구하는모든오류메시지를표시 -Wtraditional ANSI C와 K&R C 간에서로다른결과를가져올수있는부분경고 표 34 경고옵션 한국외국어대학교컴퓨터공학과

94 네트워크프로그래밍및실습 옵션 설명 -O0 아무런최적화를수행치않음 -O1 또는 -O 최적화레벨 1 수행 -O2 최적화레벨 2 수행 -O3 최적화레벨 3 수행 -Os 사이즈최적화수행 표 35 최적화옵션 옵션 설명 -g 바이너리파일에디버깅정보삽입 -pg 프로파일을위한코드삽입 표 36 디버깅옵션 라. 어셈블리(as) 옵션 옵션 설명 -Wa,[ 옵션리스트] 어셈블러에게옵션리스트를바로전달 -Wa,-al 어셈블된코드와인스트럭션을보임 -Wa-as 정의된심볼을보임 표 37 어셈블리(as) 옵션 마. 링크(ld) 옵션 옵션 설명 -L[ 경로] 라이브러리탐색디렉토리지정예 ) -L/opt/lib -l[ 라이브러리이름] 해당라이브러리를링크예 ) -lm -shared 공유라이브러리를우선하여링크 -static 정적라이브러리를우선하여링크 -nostdlib 표준 C 라이브러리를사용하지않음 -M 또는 -MM make 기술파일을위한소스파일의종속항목출력 -nostdinc 표준 C 헤더파일을포함하지않음 -Wl,[ 옵션리스트] 옵션리스트를링크에바로전달 표 38 링크(ld) 옵션

Chapter 5. 소켓프로그래밍기초

96 네트워크프로그래밍및실습 S E C T I O N 01 소켓의동작원리와이해 1. 소켓의기본동작방식 먼저, 소켓이무엇인지에대한정의부터알아보자. 소켓을간단히말하면 프로그래머가네트워크프 로그램을조금더쉽게작성할수있도록도와주기위한도구 (API) 인데, 운영체제에서는네트워크 송수신에관련된함수들을만들어놓고시스템콜형태로제공해주고있다. 이런시스템콜은 C 언 어에서함수를호출하듯이호출하기만하면된다. 그러므로네트워크에대해서잘모르는초보자라도 운영체제에서제공하는네트워크와관련된시스템콜의사용방법만알고있다면네트워크프로그래 밍을할수있다. 전화통화와비유해서설명하면쉽게이해할수있을것이다. 우리들은친구들과연락을하기위해서 전화를많이사용하지만전화기가어떻게만들어지는지전화라인을타고음성데이터가어떻게전송 되는지에대한것은모르고있다. 단지전화기의버튼을누르는것만으로친구와전화통화를하면서 친구에게음성데이터를보내게된다. 소켓은이렇게네트워크에대한자세한지식이없더라도데이 터를쉽게송수신할수있게도와주는역할을한다. 이처럼소켓은 통신 을한다는점에서그리고 사용하는방법에있어서전화기와매우유사하므로일상에서자주사용하는전화기와소켓의사용 방법을비교하면서소켓의사용법에대해서알아볼것이다. 전화기를이용해서 A와 B가통화를한다고가정할때다음과같은전형적인과정을통해서통화가 이루어질것이다. 1. A와 B 사이에는우선전화기가준비되어있어야한다. 2. A: 수화기를들고버튼을눌러서연결을시도한다. 3. B: 신호가울리고수화기를들어서연결을받아들인다. 4. A: B 씨가맞나요? ( 상대방을확인한다) B: 네맞습니다. 실례지만누구시죠? (B 역시상대방을확인해야한다) A: 아, 전 A 입니다반갑습니다. 5. 이제서로에대한확인도되었으니이런저런얘기를나눈다 ( 잠시후모든얘기가다끝났다). 6. B: 그럼다음에뵙도록하겠습니다. 안녕히계세요. A: 네안녕히계세요. 7. 수화기를내려놓고전화를끊는다. 위의과정과소켓을통한데이터통신과정의 3 단계를비교해보면다음과같다.

Chapter 5. 소켓프로그래밍기초 97 전화 소켓 1. 한쪽에서연결을시도하고, 1. 한쪽에서는연결을시도하고, 다른쪽에서는연결을대기한다. 다른한쪽에서는연결을받아들인다. 2. 연결이이루어진후통화를한다. 2. 데이터통신을한다. 3. 연결을종료한다. 3. 통신을끊는다. 표 39 전화와소켓의통신과정 3단계비교 이제부터위의과정을구현하는데필요한소켓함수에대해서알아보겠다. 여기에서는단순히함수를소개하는선에서그칠것이며자세한내용은이장의마지막부분에서예제를통해실제함수를이용해서익힌다. 가. 전화기와비교한소켓의통신과정 1) 일단전화기를준비한다 전화기가있어야한다. 앞서말했듯이전화기는 소켓 이다. 통화를위한다양한서비스를이용하려면 전화기가있어야하듯이네트워크통신을위한다양한기능을이용하려면먼저소켓을만들어야한 다. 그다음연결, 통신, 종료와관련된모든일은이소켓을통해서이루어진다. 그림 104 전화기와 유사한소켓 소켓은 socket 함수를호출해서생성하는데, 자세한함수사용방법은추후예제를보면서설명하겠 다. 2) 상대방에게연결하기위해서전화번호를준비한다 수화기를들었다면이제전화번호를눌러주어야한다. 전화번호를눌러야원하는상대방의전화번호 로정확하게신호를보낼수있기때문이다. 인터넷에서는통신할상대방컴퓨터의위치를찾기위 해서 IP 주소를사용한다는것을이미배웠다. 그림 105 인터넷의전화번호 = 인터넷주소 한국외국어대학교컴퓨터공학과

98 네트워크프로그래밍및실습 소켓에서는이러한인터넷주소를부여하기위해서 bind 함수를사용한다. 이함수를이용해서우리 가통신하고자하는컴퓨터의인터넷주소를설정할수있게된다. 3) 전화번호를누르고연결을시도한다 이제수화기도들었고, 전화번호도준비했으니버튼을누르고신호를보내야할것이다. 이를테면 지금당신에게전할말이있으니수화기를들어주세요 라고상대방에게요청하는과정이다. 소켓에 서는 connect 청할수있다. 함수를통해서인터넷내에서멀리떨어져있는다른컴퓨터에연결을받아줄것을요 4) 연결을기다리고, 연결을받아들인다 통신을하려면연결되는상대가있어야하므로최소한 2 명의인원이필요한데, 보통한명은전화를 기다리고한명은전화를거는형식을취하게된다. 전화를기다리는쪽에서는상대방으로부터신호 가도착하면수화기를들어서연결을받아들이게된다. 그림 106 소켓함수를이용한컴퓨터간의연결 지금까지는소켓에서연결을요청하는쪽을기준으로 connect 함수를이용해서연결을요청하는과 정을설명했다. 그렇지만 connect 함수로연결을요청했다고무조건연결이되는것은아니다. 상대 편소켓에서도기다리고있다가연결요청이들어오면받아들이는부분이있어야한다. 이러한작업 을위해서소켓은 listen 함수와 accept 함수를제공한다. listen 함수는연결요청을기다리기위해, accept 함수는연결요청이들어왔을때요청을받아들이기위해서사용한다. 5) 연결이되었다면이제대화를한다 전화벨소리를확인해서양쪽모두수화기를들었다면, 이제본격적인통화가이루어지게된다. 이 때서로간에주고받는정보는 언어 로이루어져있을것이다. 소켓으로되돌아가보면 connect 함 수를이용해서연결을요청하면반대쪽은 accept 함수를통해서연결요청을받아들이게되고이것 으로컴퓨터간의통신을위한준비가끝나게된다. 컴퓨터간에주고받는데이터는컴퓨터에서사용 하는것과동일하게비트로된일련의연속된값들이다. 이제통신을해야하는데, 수화기역할을할수있는함수를사용해야할것이다. 제대로된통화를 원한다면전송과수신을모두필요로하므로최소한두개의함수가준비되어야한다. 보통송신( 쓰 기) 을위해서는 write 함수를, 수신( 읽기) 을위해서는 read 함수를사용한다. 그리고read/write 함수 외에도송신을위한 sendto 함수와수신을위한 recvfrom 함수등다른함수들도있다. 유닉스프로 그램을해본독자들은 read/write가네트워크데이터를송수신하는데사용한다고하면이상하다고 생각할수도있을것이다. 우리가유닉스프로그램에서는 read/write를파일입출력함수라고생각하

Chapter 5. 소켓프로그래밍기초 99 기때문이다. 하지만 read/write는특정기능에한정된함수가아니라운영체제에서제공하는시스템 콜이다. 때문에어떤상황에서사용하느냐에따라서동작하는내부루틴이달라지게된다. 그림 107 비트로이루어진데이터교환을위한 컴퓨터간의통신 6) 모든대화를마쳤다면통화를끊어야한다 필요한모든대화를나눴다면서로인사를하고전화를끊게된다. 이인사에는 이제통화를끊겠습 니다 란의미도함께전달하게된다. 만약, 이러한과정이없이그냥수화기를내려놓게된다면상대 방은분명 기본적인예의도모르는사람 이라고생각할것이다. 그림 108 close 함수를이용한컴퓨터간통신종료 소켓을통한네트워크통신에서도이러한과정이필요하다. 모든통신이끝났다면상대방에게연결을끊을것임을알려주고자신도연결을끊어야한다. 이처럼두번의연결종료작업( 수화기내려놓기) 이이루어짐을알수있다. 이러한연결종료의작업을위해서소켓은 close 함수를제공한다. 2. 서버/ 클라이언트개념 소켓을통해통신이어떻게이루어지는지대략적인과정을전화통화의예를통해알아보았는데, 전 화통화의경우든소켓을통한인터넷통신의경우든하나의공통된모델을따른다는것을알수있 다. 즉, 무엇인가요청하는쪽( 전화를거는쪽) 과요청을받아서처리하는쪽( 전화를받는쪽) 의존재 다. 이처럼요청을하는객체와요청을처리하는객체가하나의쌍을이루는모델을서버/ 클라이언트 (server/client 줄여서 c/s) 모델이라고부른다. 우리나라말로번역해보자면 고객/ 일꾼모델 정도가 될것이다. 마찬가지로일상생활의예를통해서버/ 클라이언트의관계를살펴보자. 서버/ 클라이언트모델이적용 되는가장확실한예는전화를통한고객지원센터다. 한국외국어대학교컴퓨터공학과

100 네트워크프로그래밍및실습 그림 109 서버/ 클라이언트모델의예 인터넷에서도위의모델은그대로적용된다. 즉, 어떤데이터를요청하는호스트와데이터의요청을 받아서처리하는호스트의쌍으로이루어지는서버/ 클라이언트모델을그대로따르게된다. 인터넷 내에서데이터를요청하는호스트는 클라이언트호스트, 요청을처리하는호스트는 서버호스트 라 고부르며, 보통은호스트글자를빼고 클라이언트 와 서버 로부른다. 여기서호스트는컴퓨터라는하드웨어를나타내며 ( 한마디로껍데기다) 실제로요청을하고처리를 하는것은하드웨어에설치되어있는프로그램이맡아서한다. 클라이언트에설치되어요청하는프 로그램을클라이언트프로그램, 부른다. 서버에설치되어요청을처리하는프로그램을서버프로그램이라고 인터넷에서서버/ 클라이언트모델을따르는가장유명한서비스는 WWW 다. 여기에서클라이언트프로그램은인터넷익스플로러, 넷스케이프, 모질라와같은웹브라우저이며, 서버프로그램은 IIS, 아파치와같은웹서버프로그램이다. 아래의그림을보면웹브라우저와웹서버의관계를쉽게이해할수있을것이다. 그림 110 인터넷에서적용되는 서버/ 클라이언트모델 클라이언트는보통일반 PC 가될것이다. WWW 서비스를이용하기위해서우리는클라이언트프로 그램인익스플로러를실행하고, 서버에웹페이지를보여달라고요청하게된다. 서버는서버웹프로 그램인아파치등이설치되어있어서클라이언트프로그램인익스플로러의요청을받으면웹페이지 를읽어서이를서버에전달하게된다. 응답을받은익스플로러는보기좋은화면으로만들어서모니 터에뿌려주게된다.

Chapter 5. 소켓프로그래밍기초 101 이러한서버/ 클라이언트모델은웹서버인 WWW에서뿐만아니라 FTP, 이메일등을비롯한많은수 의온라인게임등의서비스를위해널리사용되고있다. 서버/ 클라이언트모델외에도몇가지다른 모델도있긴하지만네트워크프로그램의 90% 이상이서버/ 클라이언트모델을사용하고있으며, 우 리가흔히네트워크프로그램을만든다고하면서버/ 클라이언트모델을따르는프로그램을작성한다 고생각하면된다. 3. 소켓으로작성하는서버/ 클라이언트프로그램의동작방법 지금부터는개념적으로만설명했던서버/ 클라이언트프로그램의작성방법을함수별로설명하면서 자세히알아보겠다. 우선서버프로그램동작순서를단계별로정리하면다음과같다. 1. 소켓생성(socket 함수) 2. 생성된소켓에인터넷주소부여(bind 함수) 3. 데이터수신대기(listen 함수) 4. 데이터수신(accept 함수) 5. 데이터읽기(read 함수) 6. 데이터쓰기(write 함수) 7.3 번으로돌아간다(listen 함수) 서버프로그램이하는역할은 AS 센터에서전화상담원이하는일과같다. 전화상담원은전화를준 비한후(socket), 전화번호를부여받는다(bind). 전화기가준비가되면고객이전화를해서전화벨이 울릴때까지전화앞에서대기한다(listen). 전화벨이울리면전화를받고(accept) 고객의불만사항을 접수한다(read). 고객의불만사항을접수한전화상담원은고객의불만을처리한후고객에게처리결과 를알려준다(write). 그리고다시전화벨을기다린다(listen). 이처럼서버프로그램은수동적인입장에서클라이언트프로그램의접속을기다리고클라이언트프 로그램이접속을하면데이터를수신한후에처리한결과데이터를다시클라이언트에게전송해주 는역할을하게된다. 이런서버/ 클라이언트프로그램의구조를간단하게구성해보면다음과같은구 조가된다. 그림 111 서버/ 클라이언트구조 한국외국어대학교컴퓨터공학과

102 네트워크프로그래밍및실습 위그림을보고짐작하는독자도있겠지만서버/ 클라이언트구조는서버에여러대의클라이언트가 연결하는구조로되어있다. 그렇기때문에서버는여러클라이언트의동시접속을빠른시간내에 처리하기위해서하드웨어적인성능이뛰어나야한다. 그리고앞으로여러분이서버프로그램을하 게되면얼마나빠른시간과적은컴퓨터자원을사용해서접속하는클라이언트의요구를처리할것 인지에대해서무수한밤을지새우면서고민하게될것이다. 이제클라이언트프로그램의순서를알아보자. 클라이언트프로그램은위에서설명했던것처럼서버에접속해서데이터를요청하고처리된데이터를받는프로그램이다. 다음은클라이언트프로그램이서버에접속하기위한순서를단계별로정리한것이다. 1. 소켓생성(socket 함수) 2. 서버에연결(connect 함수) 3. 데이터쓰기(write 함수) 4. 데이터읽기(read 함수) 5. 연결종료(close 함수) 클라이언트프로그램이하는일은 AS 센터에전화를하는고객이하는일과같다. 우선 AS 센터에 전화하기위해서전화기를준비한다(socket). AS 센터의전화번호를눌러서연결을하고(connect) 전 화상담원이전화를받으면불만사항을접수하고(write) (read). 전화상담원이처리한내용을듣게된다 서버프로그램과비교하면클라이언트프로그램에서하는일은비교적간단하다고할수있다. 클라이언트프로그램은서버프로그램과다르게요청을기다리는것이아니라서버에연결한다음에자신이필요로하는요청을보내고원하는데이터를받는역할만하면되기때문이다. 클라이언트프로그램에서신경써야하는부분은서버프로그램에서의많은연결을처리하기위한알고리즘이나구조가아니라사용자에게얼마나더편리한그래픽사용자인터페이스(GUI) 를제공할것인지가될것이다. 지금까지의내용을정리하자면클라이언트프로그램은다음과같은순서를따른다. 소켓생성 서버의인터넷주소설정 연결요청 통신( 읽기/ 쓰기) 연결종료 서버프로그램은위의과정에서연결을기다리는 listen 과정과연결되었을때받아들이는 과정이추가된다. 정리해보면서버프로그램은다음과같은순서를따른다. accept 소켓생성 인터넷주소부여 연결수신대기 연결수신 통신( 읽기/ 쓰기) 연결수신대기( 이후과정반복)

Chapter 5. 소켓프로그래밍기초 103 그림 112 클라이언트프로그램작성을 위한프로그램의흐름 위그림을보면왼쪽에있는클라이언트프로그램은 socket, connect, read, write, close와같이다섯 개의함수가사용되고있다. 정말다섯개의소켓함수만으로클라이언트프로그램을작성할수있 을까 라고반문할수도있을것이다. 하지만정말위의다섯개의함수만이용하면제대로동작하는 클라이언트프로그램을작성할수있다. 모든클라이언트프로그램의뼈대는위의구조를유지하며 나머지작업은여기에살을가져다붙이는작업(GUI 화면을입히고, 데이터를처리하는작업등) 이기 때문이다. 정리해보면클라이언트프로그램은다음과같은소켓함수의흐름을따르게된다. socket connect read/write close 서버프로그램의경우에는클라이언트프로그램에서필요없는 연결을기다리고, 연결을받아들이 는 부분이추가된다. 나머지부분은클라이언트프로그램과전혀다를게없다. 그렇지만클라이언트 프로그램의작성에서사용되었던다섯개의함수들외에 listen과 accept가추가되어있음을알수 있다. 나머지구성이나전반적인흐름은클라이언트와동일하다. 정리하면서버프로그램은다음과 같은소켓함수의흐름으로작성된다. socket bind listen accept read/write listen ( 반복) 굵게표시된함수는클라이언트프로그램과비교해서추가된함수다. 이들함수가추가된외에는클 라이언트프로그램과구성이동일하다. 다음장에서위의뼈대를토대로실제간단한클라이언트프 로그램을만들어서작동시킬것인데, 충분히기대해도좋을것이다. 한국외국어대학교컴퓨터공학과

104 네트워크프로그래밍및실습 S E C T I O N 02 바이트순서바꾸기 1. 호스트바이트순서 바이트순서(byte order) 에는호스트바이트순서와네트워크바이트순서두가지가있다. 호스트 바이트순서는컴퓨터가내부메모리에숫자를저장하는순서를말하는것으로써, CPU 에따라다르다. 예를들어, 두바이트로구성된십진수 50146의경우 16진수로표현하면 0xC3E2인데이것은 80x86 계열의 CPU에서는 E2, C3 의순서로( 즉, 하위바이트부터) 메모리에저장되고 MC68000 계열의 CPU 에서는 C3, E2 의순서로메모리에저장된다. 전자를하위바이트( 즉 little end) 가메모리에먼저저장된다고하여 little-endian이라고하고후자를 상위바이트( 즉 big end) 가메모리에먼저저장된다고하며 big-endian 이라고부른다. 그림 113 0xC3E2( 십진수 50146) 의호스트바이트순서비교 2. 네트워크바이트순서 네트워크바이트순서는포트번호나 IP 주소와같은정보를바이트단위로네트워크로전송하는순 서를말한다. 네트워크바이트순서는 high-order 바이트부터전송한다.( 즉 big-endian 이다.) 예를들어, 2 바이트수 0xC3E2의경우 C3, E2 의순서로상위바이트부터전송한다. 이는 80x86 계 열의 CPU가사용하는호스트바이트순서는네트워크바이트순서와다르기때문에 80x86 계열의 컴퓨터에서아무런처리없이네트워크를통하여전송한숫자를 트순서가바뀌게된다. MC68000 계열에서수신하면바이 이러한문제를해결하기위하여컴퓨터내부에서사용하던숫자(sockaddr_in 구조체내의포트번호 등) 를네트워크로전송하기전에 htons() 함수2) 를사용하여모두네트워크바이트순서로바꾸어야 하고, 반대로네트워크로부터수신한숫자는 ntohs() 함수를사용하여자신의호스트바이트순서로 바꾸어야한다. 모든컴퓨터가네트워크바이트순서를지켜숫자를전송하도록하는것이다. 2 ) ho st to netw ork sho rt 의약자이다.

Chapter 5. 소켓프로그래밍기초 105 위에서 MC68000 계열의 CPU에서는호스트바이트순서와네트워크바이트순서가같았으므로이 러한호스트에서의 htons() 와 ntohs() 함수는실제로아무런작업도하지않게된다. 바이트순서를바꾸는함수에는변환할바이트길이가 2 바이트인경우또는 4 바이트인경우에따 라다음과같이두가지종류가있다. unsigned short integer 변환 (2 바이트크기) - htons() : host-to-network 바이트변환 - ntohs() : network-to-host 바이트변환 unsigned long integer 변환 (4 바이트크기) - htonl() : host-to-network 바이트변환 - ntohl() : network-to-host 바이트변환 주의할것은, 바이트순서를맞추는같은포트번호와같이어떤숫자를네트워크로전송할때이며 텍스트문서나바이너리파일등일반데이터는바이트변환이필요없다는것이다. 일변데이터는 메모리( 버퍼) 에저장되었다가바이트단위로메모리앞주소부터차례대로전송되며수신측에서도수 신된데이터를메모리에바이트순서대로차례로저장하기때문이다. 3. 바이트순서바꾸기프로그램 4 바이트로표현된숫자의바이트순서를바꾸는프로그램을작성해보자. 예를들어 16진수 0x12345678을입력하면 0x78563412 가출력되도록하는것이다. 가. 커맨드라인인수사용법 C로제작된프로그램의시작지점인 main() 함수에서커맨드라인명령을얻어오는방법을알아보자. 커맨드창에서입력한명령을얻어오려면 main() 함수의인자를아래와같이정의하면된다. int main( int argc, char* argv[] ); argc 에는실행파일명을포함한인자의개수가저장된다. 아무런인자가없이실행되었을때에는 1이 저장된다. argv는실행파일명을포함한인자가 char* 형식의스트링이배열로저장된다. argv[0] 은실 행파일명에대한스트링을가리킨다. 예를들어커맨드창에서 은값이입력되게된다. cp srcfilename destfilename" 명령을내리게되면각변수에는아래와같 한국외국어대학교컴퓨터공학과

106 네트워크프로그래밍및실습 argc == 3 argv[0] == "cp" argv[1] == "srcfilename" argv[2] == "destfilename" 나. 문자열변환함수 커맨드창을통해입력된 16진수는문자열이기때문에 unsigned long형변수를인자로취하는 htonl() 함수에서는바로사용될수없다. 따라서 char* 형문자열을 unsigned long형숫자로바꿔주 는함수를만들어사용하도록한다. 예제 4 char* 형문자열을 unsigned long형으로바꿔주는함수 int str2num( char* str ) { int nint = 0; int nseed = 0; char cchar; int i; for( i = 0; str[i]; i++ ) { nint = nint << 4; switch( str[i] ) { case 'F': nseed = 15; break; case 'E': nseed = 14; break; case 'D': nseed = 13; break; case 'C': nseed = 12; break; case 'B': nseed = 11; break; case 'A': nseed = 10; break; default: cchar = str[i]; nseed = cchar 48; break; nint += nseed; return nint; 다. 실제 htonl() 함수를이용해바이트순서변환 htonl() 함수를이용해바이트순서를변환하여반환하는함수이다.

Chapter 5. 소켓프로그래밍기초 107 예제 5 htonl() 함수를이용해바이트순서를변환하는함수 unsigned int convert_order32( unsigned int before ) { unsigned int nreturn = htonl( before ); return nreturn; 라. 전체소스 위의 str2num() 함수와 convert_order32() 함수를이용한전체소스이다. 예제 6 바이트순서변환전체소스 // 포함파일 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> // 함수프로토타입 unsigned int convert_order32( unsigned int before ); int str2num( char* str ); // 메인함수 int main( int argc, char* argv[] ) { int nchanged = convert_order32( str2num(argv[1]) ); printf( "%x\n", nchanged ); return 0; // char* 형문자열을 unsigned long 형으로바꿔주는함수 int str2num( char* str ) { int nint = 0; int nseed = 0; char cchar; int i; for( i = 0; str[i]; i++ ) { nint = nint << 4; switch( str[i] ) { case 'F': nseed = 15; break; case 'E': nseed = 14; 한국외국어대학교컴퓨터공학과

108 네트워크프로그래밍및실습 break; case 'D': nseed = 13; break; case 'C': nseed = 12; break; case 'B': nseed = 11; break; case 'A': nseed = 10; break; default: cchar = str[i]; nseed = cchar 48; break; nint += nseed; return nint; // htonl() 함수를이용해바이트순서를변환하는함수 unsigned int convert_order32( unsigned int before ) { unsigned int nreturn = htonl( before ); return nreturn; 마. 실행화면

Chapter 5. 소켓프로그래밍기초 109 4. 호스트바이트순서확인프로그램 위의바이트순서바꾸기프로그램을응용하면호스트컴퓨터의바이트순서를확인할수있는프로 그램의작성이가능하다. 네트워크바이트순서는 big-endian이기때문에임의의수를 htonl() 함수의 인자로입력하여같은수가반환되면호스트컴퓨터의바이트순서또한 big-endian 이고, 다른수가 반환되면 little-endian 으로판단한다. 가. 전체소스 예제 7 바이트순서확인전체소스 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> unsigned int convert_order32( unsigned int before ); int str2num( char* str ); int main( int argc, char* argv[] ) { int before = str2num( "12345678" ); int after = convert_order32( before ); if( before == after ) printf( " 이컴퓨터는 big endian 을사용합니다.\n" ); else printf( " 이컴퓨터는 little endian 을사용합니다.\n" ); return 0; int str2num( char* str ) { int nint = 0; int nseed = 0; char cchar; int i; for( i = 0; str[i]; i++ ) { nint = nint << 4; switch( str[i] ) { case 'F': nseed = 15; break; case 'E': nseed = 14; break; case 'D': 한국외국어대학교컴퓨터공학과

110 네트워크프로그래밍및실습 nseed = 13; break; case 'C': nseed = 12; break; case 'B': nseed = 11; break; case 'A': nseed = 10; break; default: cchar = str[i]; nseed = cchar+48; break; nint += nseed; return nint; unsigned int convert_order32( unsigned int before ) { unsigned int nreturn = htonl( before ); return nreturn; 나. 실행화면

Chapter 5. 소켓프로그래밍기초 111 S E C T I O N 03 도메인이름과 IP주소상호변환하기 1. IP 주소변환 4 바이트(32 비트) 의 IP 주소는편의를위해 cse.hufs.ac.kr과같은도메인이름또는 203.253.64.1과같 은 dotted decimal 방식으로표현되어사용되어진다. dotted decimal 표현은 4개의숫자변수가아 니라 15 개의문자로구성된문자열변수가사용된다. 소켓프로그램에서는이들주소표현법을상호 변환할수있는함수를제공하고있는데아래그림은이들세가지인터넷주소표현방법과이들을 상호변환해주는네개의주소변환함수들을나타낸다. 실제소켓프로그램에서는 IP 데이터그램을 네트워크로전송할때 IP 헤더의 4바이트바이너리 IP 주소만사용한다는점을유의해야한다. 그림 116 IP주소표현의세가지방법및이들의상호변환함수 예를들어, 위그림에서 dotted decimal로표현된 192.203.144.11을 32비트의 IP주소로변환하려면 inet_pton() 함수를사용하고, IP주소를 dotted decimal로변환하려면 inet_ntop() 를사용한다. ntop은 numerical to presentation 의약자이다. 다음은이함수들의사용문법이다. #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> const char* inet_ntop( int af, const void* src, char* dst, size_t cnt ); int inet_pton( int af, const char* src, void* dst ); 위와비슷한기능을수행하는함수로 inet_addr(), inet_ntoa(), inet_aton() 등이과거에사용되었는데, inet_addr() 은 255.255.255.255 주소를처리하지못하는문제가있고 inet_ntoa() 는멀티스레딩환경에 서스레드가정지된후재진입이불가능하다는단점이있다. 또한 inet_addr(), inet_ntoa(), inet_aton() 한국외국어대학교컴퓨터공학과

112 네트워크프로그래밍및실습 등은 IPv4에서만사용할수있지만 inet_ntop(), inet_pton() 은 IPv6 의주소까지처리할수있다. 아래의예제는 dotted decimal 로표현된주소( 예: 210.115.36.23) 를명령문인자로입력하면이것을 inet_pton() 함수를이용하여 4바이트의 IP 주소(16진수로는 E72473D2) 로바꾸어화면에출력하고, 이 IP 주소로부터 inet_ntop() 를호출하여다시 dotted decimal 주소를얻는프로그램이다. 가. 소스코드 예제 8 ASCII(dotted decimal) 로표현된주소를 4바이트 IP주소로변환 #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> int main(int argc, char *argv[]) { struct in_addr inaddr; // 32비트 IP char buf[20]; 주소구조체 if(argc < 2) { printf(" 사용법 : %s IP 주소(dotted decimal) \n", argv[0]); exit(0); printf("* 입력한 dotted decimal IP 주소: %s\n", argv[1]); inet_pton(af_inet, argv[1], &inaddr.s_addr); printf(" inet_pton(%s) = 0x%X\n", argv[1], inaddr.s_addr); inet_ntop(af_inet, &inaddr.s_addr, buf, sizeof(buf)); printf(" inet_ntop(0x%x) = %s\n", inaddr.s_addr,buf); return 0; 나. 실행화면

Chapter 5. 소켓프로그래밍기초 113 2. 도메인주소변환 앞에서사용한주소변환함수들은단순히계산만하는함수이다. 즉, 십진수의 dotted decimal 주소 와 4바이트의이진수를계산에의하여바꾸는작업만하면되며이러한함수는즉시처리가가능하 다. 그러나도메인네임으로부터 IP 주소를얻거나 IP 주소로부터해당호스트의도메인네임을얻으 려면 DNS(Domain Name Service) 서버의도움을받아야하며이러한함수는처리가즉시이루어지 지않을수도있다. gethostbyname() 은도메인네임으로부터 IP 주소를얻는함수이고, gethostbyaddr() 은반대로 IP 주소로부터도메인네임을얻는함수이다. 이들두함수는호스트의각 종정보를저장한구조체 hostent 의포인터를리턴하는데이두함수의사용문법은아래와같다. #include <netdb.h> struct hostent* gethostbyname( const char* hname ); struct hostent* gethostbyaddr( const char* in_addr, int len, int family ); 3. gethostbyname() 위에서 gethostbyname() 은도메인네임 hname을스트링형태로입력받고그이름에해당하는호스 트의각종정보를가지고있는 hostent 구조체의포인터를리턴한다. gethostbyaddr() 은 IP 주소를포함하고있는구조체 in_addr 의포인터와이주소의길이, 주소타입을 입력하여해당호스트의정보를가지고있는 hostent 구조체의포인터를리턴한다. 이와같이 IP 주 소로부터호스트정보( 도메인네임등) 를얻기위해서는 Reverse DNS가수행되어야하는데최근에는 보안문제로대부분의 DNS 서버가 Reverse DNS 기능을제공하지않고있다. 구조체 hostent 의정의는다음과같다. h_addr은호스트의첫번째 IP 주소즉, 호스트의대표주소 인 h_addr_list[0] 을가리킨다. struct hostend { char* h_name; // 호스트이름 char** h_aliases; // 호스트별명들 int h_addrtype; // 호스트주소의종류 (AF_INET=2 등) int h_length; // 주소의크기 ( 바이트단위이며 IPv4에서는 4 임) char** h_addr_list; // IP 주소리스트 ; #define h_addr h_addrlist[0] // 첫번째( 대표) 주소 도메인네임으로부터해당호스트의 hostent 구조체정보를구한후 hostent 구조체의내용을모두 한국외국어대학교컴퓨터공학과

114 네트워크프로그래밍및실습 출력해보는예제프로그램을소개한다. 가. 소스코드 예제 9 도메인이름을 IP 주소로변환 #include <stdio.h> #include <stdlib.h> #include <string.h> // memcpy #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <netdb.h> #include <errno.h> 함수선언 int main(int argc, char *argv[]) { struct hostent *hp; struct in_addr in; int i; char buf[20]; if(argc<2) { printf("usage: %s hostname\n",argv[0]); exit(1); hp = gethostbyname(argv[1]); if(hp==null) { printf("gethostbyname fail\n"); exit(0); printf(" 호스트이름 : %s\n", hp >h_name); printf(" 호스트주소타입번호: %d\n", hp >h_addrtype); printf(" 호스트주소의길이 : %d 바이트\n", hp >h_length); for( i=0; hp >h_addr_list[i]; i++) { memcpy(&in.s_addr, hp >h_addr_list[i],sizeof(in.s_addr)); inet_ntop(af_inet, &in, buf, sizeof(buf)); printf("ip 주소(%d 번째) : %s\n",i+1,buf); for( i=0; hp >h_aliases[i]; i++) printf(" 호스트별명(%d 번째) : %s ",i+1, hp >h_aliases[i]); puts("");

Chapter 5. 소켓프로그래밍기초 115 return 0; 프로그램은먼저목적지호스트의도메인네임을명령인자로받고 gethostbyname() 을이용하여목 적지호스트의 hostent 구조체를얻는다. 다음에는 hostent 내에있는호스트이름, 별명( 있다면), 주 소체계, dotted decimal 인터넷주소를화면에출력한다. 나. 실행화면 4. gethostbyaddr() 입력된 dotted decimal 주소로부터 gethostbyaddr() 을이용하여해당호스트를찾아도메인네임을 얻는프로그램을아래에소개한다. 한편, 현재사용중인자신의컴퓨터의도메인이름을얻으려면 uname() 이나 gethostname() 을이용하면된다. 가. 소스코드예제 10 hostent 구조체내용출력프로그램 #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> int main(int argc, char *argv[]) { struct hostent *myhost; struct in_addr in; if(argc < 2) { printf(" 사용법 exit(0); : %s ip_address \n", argv[0]); inet_pton(af_inet, argv[1], &in.s_addr); // dotted decimal >32bit 주소 한국외국어대학교컴퓨터공학과

116 네트워크프로그래밍및실습 myhost = gethostbyaddr((char *)&(in.s_addr), sizeof(in.s_addr), AF_INET); if (myhost == NULL) { printf("error at gethostbyaddr() \n"); exit(0); printf(" 호스트이름 return 0; : %s\n", myhost >h_name); 나. 실행화면

Chapter 5. 소켓프로그래밍기초 117 S E C T I O N 04 TCP 클라이언트프로그램 여기서는 TCP 클라이언트프로그램의일반적인작성절차를설명하고, 유닉스서버가제공하는 daytime 및에코서비스를이용하는클라이언트프로그램 tcp_daytimecli.c. 와 echocli.c 를각각소개한다. 1. TCP 클라이언트프로그램작성절차 다음그림은 TCP 소켓을사용하는클라이언트프로그램의일반적인작성절차를나타낸것이다. 클 라이언트는먼저 socket() 으로소켓을개설하고 connect() 를호출하여서버에게영ㄴ결을요청한다. 연결이이루어지면 send() 와 recv() 를사용하여데이터를송수신하고작업이종료되면 close() 로소켓 을닫는다. 그림 120 TCP( 연결형) 클라이언트프로그램 작성절차 가. socket(), 소켓개설 socket() 으로소켓을개설할때연결형(TCP) 또는비연결형(UDP) 소켓을선택해야한며연결형소켓 을개설하려면서비스 type 인자는 SOCK_STREAM 으로선택한다. 소켓을이용한통신프로그램에서는사용할트랜스포트프로토콜, 자신의 IP 주소와포트번호, 상대방 의 IP 주소와포트번호등다섯개의정보가지정되어야한다고하였다. 소켓을만들때에는이중에 서트랜스포트프로토콜만을지정하게된다. 밑의그림은 socket() 수행시내부적으로일어나는동 한국외국어대학교컴퓨터공학과

118 네트워크프로그래밍및실습 작을나타내는데, 응용프로그램이프로토콜을지정하여 socket() 을호출하면소켓인터페이스가새 로생성된소켓의소켓번호를리턴하는것을보여주고있다. 그림 121 socket() 호출시소켓번호와소켓인터페이스의관계 나. connect(), 서버에연결요청 클라이언트는 connect() 를호출하여서버에게연결요청을하며이때 3-way 핸드쉐이크가시작된다. connect() 의사용문법은다음과같다. int connect ( int s, // 서버와연결시킬소켓번호 const struct sockaddr* addr, // 상대방서버의소켓주소구조체 int addrlen ); // 구조체 *addr의크기 인자 addr는연결한서버의 IP 주소와 2바이트의포트번호를포함하고있는소켓주소구조체 sockaddr_in 을지정한다. 인자 s는소켓번호이며연결이이루어지고나면앞으로이소켓번호를사용 해서서버와통신을하게된다. 다음그림에 connect() 의수행내용을나타냈다. 3-way 핸드쉐이크가성공하여서버와연결이되면 connect() 는 0 을리턴한다. 실패하면 -1을리턴하 며전역변수 errno 에에러코드가들어있게된다. 서버와연결이되려면서버측에서는 listen() 과 accept() 를호출해두고있어야한다. connect() 호출중에에러가발생하였을때는바로다시 connect() 를호출하지말고해당소켓을 close() 로닫고새로운소켓을 socket() 으로만든후사용하는것이안전하다.

Chapter 5. 소켓프로그래밍기초 119 그림 122 connect() 호출시소켓번호와소켓주소의관계 다. send(), recv(), 데이터송수신 클라이언트가서버와연결되면 send(), recv() 또는 write(), read() 를사용하여서버와데이터를송수신 할수있는데이함수들을표에정리하였다. 문법 int send( int s, char* buf, int length, int flags ); int write( int s, const void* buf, int length ); int recv( int s, char* buf, int length, int flags ); int read( int s, void* buf, int length ); 표 40 TCP( 스트림) 소켓의데이터송수신함수 인자 s 소켓번호 buf 전송할데이터가저장된버퍼 length buf 버퍼의크기 flags 보통 0 s 소켓번호 buf 전송할데이터가저장된버퍼 length buf 버퍼의길이 s 소켓번호 buf 수신데이터를저장한버퍼 length buf 버퍼의길이 flags 보통 0 s 소켓번호 buf 수신데이터를저장할버퍼 length buf 버퍼의길이 send() 와 write() 는스트림형(TCP) 소켓을통하여데이터를송신하는함수이며데이터를전송할소켓 번호(s), 송신할데이터버퍼(buf), 전송할데이터크기(length) 를지정해야한다. send(), write() 두함 수의동작은같다. 단 send() 는 flags 인자를추가로사용할수있다는점만다르다. 두함수모두실 제로전송된데이터크기를바이트단위로리턴한다. recv() 와 read() 는스트림형소켓을통하여데이터를수신하는함수이며데이터를수신할소켓번호(s), 한국외국어대학교컴퓨터공학과

120 네트워크프로그래밍및실습 수신버퍼(buf), 읽을데이터의크기(length) 를지정해야한다. 두함수모두실제로읽은데이터크기 를바이트단위로리턴한다. send() 와 recv() 함수에서는 flags 옵션을지정할수있는데이는일반데이터를다룰때에는 0으로선 택한다. flags 는데이터송수신시에특별한옵션을지정할때사용된다. 예를들어, recv() 호출시에 flags를 MSG_PEEK 로설정하면수신된데이터를읽은후에데이터를수신버퍼에그대로남겨둔다. 즉, 다음번 read() 에서도같은데이터를다시읽을수있도록한다. flags 사용에대해서는추후소켓 옵션에서설명한다. 한 IP 데이터그램에실어서전송할수있는최대데이터크기인최대세그먼트크기(MSS: Maximum Segment Size) 보다큰데이터를 write() 나 send() 로보내면전체데이터가 MSS 크기로분할되어여 러번에나누어전송된다. 주의할것은 TCP에서는세그먼트의순서확인과데이터그램분실을수신측 에서검사하고재전송을함으로써신뢰할만한통신을제공하나 제공되지않는다는것이다. UDP를사용하면이러한에러제어는 TCP 소켓에서 write() 나 send() 를실행하면데이터는먼저 TCP 계층에있는송신버너(send buffer) 로 들어간다. 만일송신버퍼가비어있지않아데이터를이곳에쓸수없으면프로세스는블록상태로 가며프로그램은 write() 문에서기다리게된다( 이것은소켓동작모드가블록형일때이며소켓을넌 블록형모드로바꾸었을때에는블록되지않는다). write() 문이블록된경우에송신버퍼에먼저들어 있던데이터가전송되고 write한데이터가송신버퍼로모두이동하면 write() 문이리턴된다. 즉, write() 문이리턴되었다는것은데이터가이제비로소자신의 을의미하며데이터가목적지에전달된것이아님을주의하여야한다. TCP에있는송신버퍼에들어갔다는것 라. close(), 소켓닫기 소켓의사용을마치려면해당소켓번호를지정하여 close() 를호출하여소켓을닫아야한다. close() 는 클라이언트나서버중누구나먼저호출할수있다. close() 를호출한시점에송신버퍼에들어있으나 아직전송되지못한, 또는네트워크내에서전달중인데이터가있을수있는데, 기본적으로는이러 한데이터가모두전달된후에 TCP 연결이종료된다. 소켓옵션을변경하여미전송된데이터를모두 버리고종료하도록할수도있다( 소켓옵션에대해서는추후다루게된다). 2. TCP 클라이언트예제프로그램 가. daytime 클라이언트 유닉스서버가제공하는 daytime 서비스를이용하는클라이언트프로그램을작성한다. daytime 서비 스를제공하는서버의 리턴한다. dotted decimal IP 주소를명령문인자로입력하면오늘날짜와현재시각을 다음은프로그램을실행한결과이다.

Chapter 5. 소켓프로그래밍기초 121 이프로그램에서는사용할소켓을아래와같이개설하는데, 프로토콜체계로는인터넷을, 서비스는 연결형(TCP) 을지정하고있다. s = socket( PF_INET, SOCK_STREAM, 0 ); 다음에는연결할서버의소켓주소구조체를만들어야하는데주소체계를인터넷(AF_INET) 으로지정 하고서버의 IP 주소와포트번호를소켓주소구조체 servaddr 에기록하였다. struct sockaddr_int servaddr; // 서버의소켓주소구조체 servaddr.sin_family = AF_INET; // 주소체계선택 inet_pton( AF_INET, argv[1], &servaddr.sin_addr ); // 32비트 IP 주소로변환 servaddr.sin_port = htons(13); // daytime 서비스포트번호 위에서 inet_pton() 함수는사용자가명령인자로입력한 dotted deciaml 형태의 IP 주소문자열 argv[1] 을 32비트 IP 주소로변환하는함수이다. daytime 서비스를받기위하여 well-known 포트번 호 13 번을지정하였다. 이상과같이서버의주소정보를모두 위해 servaddr connect() 함수를다음과같이호출한다. 구조체에기록한다음에는서버에연결을요청하기 connect( s, (struct sockaddr*)&servaddr, sizeof(servaddr) ); 서버와연결된후, 서버가보내오는문자열( 날짜와시간) 을수신하기위해클라이언트는 read() 를아 래와같이호출하는데, 소켓을통한입출력도파일입출력과유사하게이루어지는것을알수있다. n = read( s, buf, sizeof(buf) ); // recv(s, buf, sizeof(buf), 0 ); 도같은동작은한다. 1) 소스코드 예제 11 daytime 서비스를요청하는 TCP( 연결형) 클라이언트 #include <stdio.h> 한국외국어대학교컴퓨터공학과

122 네트워크프로그래밍및실습 #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define MAXLINE 127 int main(int argc, char *argv[]) { int s, nbyte; struct sockaddr_in servaddr; char buf[ MAXLINE+1 ]; if(argc!= 2) { printf("usage: %s ip_address\n", argv[0]); exit(0); // 소켓생성 if((s = socket(pf_inet, SOCK_STREAM, 0)) < 0) { perror("socket fail"); exit(1); // 서버의소켓주소구조체 servaddr 을 '\0' 으로초기화 bzero((char *)&servaddr, sizeof(servaddr)); // servaddr 의주소지정 servaddr.sin_family = AF_INET; inet_pton(af_inet, argv[1], &servaddr.sin_addr); servaddr.sin_port = htons(13); // 연결요청 if(connect(s, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("connect fail"); exit(1); // 서버가보내오는 daytime 데이터의수신및화면출력 if((nbyte=read(s, buf, MAXLINE )) < 0) { perror("read fail"); exit(1); buf[nbyte] = 0; printf("%s", buf);

Chapter 5. 소켓프로그래밍기초 123 close(s); return 0; 위에서 bzero() 는어떤포인터가가리키는구조체의내용을모두 0으로설정하기위해사용하였는데 bzero() 대신 memset() 을사용할수도있다. 참고로 C 언어에서는문자열즉, string을다룰때문자 열의끝에 NULL 문자를자동으로추가하므로통신프로그램에서사용하는정보( 예: 2바이트로된포 트번호, 4바이트로된 IP 주소등) 를 C 스트링으로다루면안된다( 즉, strcpy() 등을사용할수없다). connect() 문에서소켓주소구조체를나타내는함수인자로 servddr 을바로사용하지않고 (struct sockaddr*)&servaddr 와같이캐스팅(casting) 하여사용한것을볼수있다. 이렇게하는이유는대부 분의인터넷소켓프로그램에서는인터넷주소를편리하게다루기위하여(IP 주소와포트번호를직접 쓰거나읽을수있도록) sockaddr_in 구조체를사용하고있으나, connect() 함수를비롯한각종소켓 함수의정의에서는일반적인소켓주소구조체인 sockaddr 를사용하도록정의되어있기때문이다. 따 라서 sockaddr_in 구조체를 sockaddr 타읍으로캐스팅하는것이필요하다. sockaddr과 sockaddr_int 두구조체모두 16 바이트로구성되어있어크기가같다. 한편프로그램을실행시켰을때아래와같은에러메시지가출력되는경우가있다. $ tcp_daytimecli 203.252.65.3 connect fail: Connection refused 이것은목적지호스트(203.252.65.3) 에서 ( 주로보안상의이유로) daytime 서비스를제공하지않기때 문이다. 즉, 13 번포트가비활성상태이므로연결이거부된것이다. 만약목적지주소를 127.0.0.1로 하였을때에도 Connection refused" 메시지가출력된다면로컬호스트가 daytime 서비스를하고있 지않은것이다. 로컬호스트에서 daytime 서비스를하고있다면아래와같이출력될것이다. $ tcp_daytimecli 127.0.0.1 Wed Jan 1 09:00:21 2007 daytime 서비스가 13 번포트번호를사용하는것은다음과같이 /etc/services 파일을통해확인할수 있다. $ cat /etc/services grep daytime daytime 13/tcp daytime 13/udp 한국외국어대학교컴퓨터공학과

124 네트워크프로그래밍및실습 나. TCP 에코클라이언트 TCP 클라이언트프로그램의두번째예로에코서비스를이용하는클라이언트프로그램을소개한다. 표준인터넷서비스인에코는 well-known 포트 7번을통해제공되며클라이언트가보낸문자열을 다시클라이언트로전송한다. 다음은프로그램을실행한결과예이다. $ tcp_echo 210.115.36.231 입력 : abcdefghijklmn 수신메시지 : abcdefghijklm 이프로그램도앞에서설명한 daytime 프로그램과거의유사하게작성된다. 한가지차이점은이프 로그램에서는에코서비스를이용해야하므로서버의소켓주소구조체 와같이 7 로바꾸어야한다. servaddr의포트번호를아래 servaddr.sin_port = htons( 7 ); connect() 를호출한후서버와연결이완료되면서버로전송할문자열을키보드를통해아래와같이 입력받는다. fgets( buf, sizeof(buf), stdin ); fgets() 함수는스트링문자열을입력받는함수로첫번째인자(buf) 는문자열을저장할버퍼이고, 두 번째인자는버퍼의크기, 그리고세번째인자는입력스트림포인터이다. 입력스트림포인터로 stdin을지정하였는데 stdin은 FILE 포인터타입의전역변수로표준입력파일인키보드를가리킨다. fgets() 로키보드에서입력받은문자열을 을 read() 로수신하여화면에출력한다. write() 를이용해서버로전송하고서버가에코시켜준문자열 1) 소스코드 예제 12 에코서비스를요청하는 TCP( 연결형) 클라이언트 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h>

Chapter 5. 소켓프로그래밍기초 125 #include <netinet/in.h> #include <unistd.h> #define MAXLINE 127 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; int s, nbyte; char buf[maxline+1]; if(argc!= 2) { printf("usage: %s ip_address\n", argv[0]); exit(0); if((s = socket(pf_inet, SOCK_STREAM, 0)) < 0) { perror("socket fail"); exit(0); // 에코서버의소켓주소구조체작성 bzero((char *)&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(af_inet, argv[1], &servaddr.sin_addr); servaddr.sin_port = htons(7); // 연결요청 if(connect(s, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("connect fail"); exit(0); printf(" 입력: "); if (fgets(buf, sizeof(buf), stdin) == NULL) exit(0); nbyte = strlen(buf); // 에코서버로메시지송신 if (write(s, buf, nbyte) < 0) { printf("write error\n"); exit(0); // 수신된에코데이터화면출력 printf(" 수신 : "); if( (nbyte=read(s, buf, MAXLINE)) <0) { perror("read fail"); exit(0); buf[nbyte]=0; printf("%s", buf); 한국외국어대학교컴퓨터공학과

126 네트워크프로그래밍및실습 close(s); return 0; 2) 실행화면 다. 포트번호배정 클라이언트에서는소켓에특정포트번호를배정(bind) 하지않고시스템이임의로배정하는포트번호 를사용한다고하였다. 포트번호는 TCP 소켓인경우는 connect() 호출이성공한후에, UDP 소켓의 경우는첫번째메시지를보내는 sendto() 함수가성공한후에배정된다. 아래는이것을확인하는예 제프로그램인데 TCP와 UDP 클라이언트에서시스템이배정한포트번호를 getsockname() 을이용하 여얻은후화면에출력하고있다. 참고로 getsockname() 은자신의호스트에있는소켓정보를알아내는함수인데 TCP로연결된상대 방의소켓정보를얻으려면 getpeername() 을사용하면된다. 이프로그램이실행되기위해서는서버에서 TCP 및 UDP 에코서비스가동작하고있어야한다. 예제 13 시스템이자동으로배정한포트번호를출력하는프로그램 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #define MSG "Test Message" int main() { int sd1, sd2 ; int addrlen; struct sockaddr_in servaddr, cliaddr; // unsigned short port1, port2; // 포트번호 // 소켓주소구조체초기화 servaddr.sin_family = AF_INET ; servaddr.sin_addr.s_addr = htonl(inaddr_any) ; servaddr.sin_port = htons(7); 소켓주소구조체

Chapter 5. 소켓프로그래밍기초 127 sd1=socket(pf_inet, SOCK_STREAM, 0); sd2=socket(pf_inet, SOCK_DGRAM, 0); // TCP 소켓의포트번호얻기 if( connect(sd1, (struct sockaddr*)&servaddr, sizeof(servaddr))<0) { perror("connect fail"); exit(1); getsockname(sd1, (struct sockaddr*)&cliaddr, &addrlen) ; port1 = ntohs(cliaddr.sin_port); // UDP 소켓의포트번호얻기 sendto(sd2, MSG, strlen(msg), 0, (struct sockaddr*)&servaddr, sizeof(servaddr)); getsockname(sd2, (struct sockaddr*)&cliaddr, &addrlen) ; port2 = ntohs(cliaddr.sin_port); printf(" 스트림소켓포트번호 = %d\n", port1) ; printf(" 데이터그램소켓포트번호 = %d\n", port2) ; close(sd1) ; close(sd2) ; return 0; 한국외국어대학교컴퓨터공학과

128 네트워크프로그래밍및실습 S E C T I O N 05 TCP 서버프로그램 1. TCP 서버프로그램작성절차 그림 125 Iterative 모델의 TCP 서버 가. socket(), 소켓의생성 1) 서버도클라이언트와통신을하기위해소켓을생성한다. socket( PH_INET, SOCK_STREAM, 0 ); 나. bind 1) socket() 으로생성된소켓의소켓번호는응용프로그램만알고있다. 가 ) 컴퓨터외부와통신하기위해소켓번호와소켓주소를연결해두어야한다.

Chapter 5. 소켓프로그래밍기초 129 그림 126 bind() 호출시소켓번호와소켓주소의관계 2) bind() 가필요한이유 가 ) 임의의클라이언트가서버프로그램의특정소켓으로접속을하려면서버는자신의소켓번호 와클라이언트가알고있는자신의 IP 주소및포트번호를미리연결해두어야한다. 3) bind() 사용문법 int bind( int s, struct sockaddr* addr, int len ); 가 ) 나 ) 다 ) int s : 소켓번호 struct sockaddr* addr : 서버자신의소켓주소구조체포인터 int len : addr 구조체의크기 4) bind() 는성공시 0, 실패시 -1을리턴 다. bind() 사용예 예제 14 bind() 사용예 #define SERV_IP_ADDR "210.115.49.220" #define SERV_PORT 5000 // 소켓생성 s=socket(pf_inet, SOCK_STREAM, 0); struct sockaddr_in server_addr; // 소켓구조체내용 server_addr.sin_family=af_inet; 한국외국어대학교컴퓨터공학과

130 네트워크프로그래밍및실습 server_addr.sin_addr.s_addr=inet_addr(serv_ip_addr); server_addr.sin_port=htons(serv_port); 소켓번호와소켓주소를 // bind bind(s, (struct sockaddr *)&server_addr, sizeof(server_addr)); 라. listen() 1) 사용문법 int listen( int s, int lacklog ); 가 ) 나 ) int s : 소켓번호 int backlog : 연결을기다리는클라이언트의최대수 2) 동작순서 가 ) 클라이언트가 listen() 을호출해둔서버소켓을목적지로 connect() 를호출한다. (1) 3-way 핸드쉐이크연결설정의시작 나 ) 시스템이핸드쉐이크를마친후에는서버애플리케이션이설정된연결을받아들인다. 다 ) (1) accept() 가사용됨 accept() 는한번에하나의연결만가져간다. (1) 여러연결요청이동시에오면시스템은설정된연결들을 accept 큐에넣고대기한다. (2) backlog 인자는대기시킬수있는연결의최대수 3) listen 은소켓을단지수동대기모드로바꾸어주는역할만한다. 가 ) 성공시 0, 실패시 -1을리턴 마. accept() 1) 서버가 listen() 의호출이후클라이언트와설정된연결을실제로받기위해사용한다. 2) 사용문법 int accept( int s, struct sockaddr* addr, int* addrlen ); 가 ) 나 ) 다 ) int s : 소켓번호 struct sockaddr* addr : 연결요청을한클라이언트의소켓주소구조체 int* addrlen : *addr 구조체크기의포인터 3) 특징 가 ) 수행성공시클라이언트와의통신에사용할새로운소켓이생성된다. (1) 실패시에는 -1이리턴 나 ) 서버는클라이언트와통신하기위해새로만들어진소켓번호를사용한다. 다 ) 연결된클라이언트의소켓주소구조체와소켓주소구조체의길이의포인터를리턴한다.

Chapter 5. 소켓프로그래밍기초 131 (1) addr과 addrlen 인자로리턴한다. (2) 서버는 addr 소켓주소내용으로연결된클라이언트의 IP 주소를알수있다. 2. TCP 에코서버프로그램 예제 15 TCP 에코서버프로그램 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #define MAXLINE 127 int main(int argc, char *argv[]) { struct sockaddr_in servaddr, cliaddr; int listen_sock, accp_sock, // 소켓번호 addrlen=sizeof(cliaddr), // 주소구조체길이 nbyte; char buf[maxline+1]; if(argc!= 2) { printf("usage: %s port\n", argv[0]); exit(0); // 소켓생성 if((listen_sock = socket(pf_inet, SOCK_STREAM, 0)) < 0) { perror("socket fail"); exit(0); // servaddr 을 '\0' 으로초기화 bzero((char *)&servaddr, sizeof(servaddr)); // servaddr 세팅 servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(inaddr_any); servaddr.sin_port = htons(atoi(argv[1])); // bind() 호출 if(bind(listen_sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("bind fail"); exit(0); 한국외국어대학교컴퓨터공학과

132 네트워크프로그래밍및실습 // 소켓을수동대기모드로세팅 listen(listen_sock, 5); // iterative 에코서비스수행 while(1) { puts(" 서버가연결요청을기다림.."); // 연결요청을기다림 accp_sock = accept(listen_sock, (struct sockaddr *)&cliaddr, &addrlen); if(accp_sock < 0) { perror("accept fail"); exit(0); puts(" 클라이언트가연결됨.."); nbyte = read(accp_sock, buf, MAXLINE); write(accp_sock, buf, nbyte); close(accp_sock); close(listen_sock); return 0; 실행화면은다음과같다. 3. 연습문제풀이 가. bind() 함수를잘못사용하는경우발생하는에러코드를확인해보시오. 아래의테스트항목별로 bind() 함수를실행하고 bind() 함수가실패한경우 perror() 함수를사용하여에러의내용을출력 하시오. int bind(int s, struct sockaddr* addr, int len); 예제 16 bind() 함수에러코드확인 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #define MAXLINE 127

Chapter 5. 소켓프로그래밍기초 133 int main(int argc, char *argv[]) { struct sockaddr_in servaddr, cliaddr; int listen_sock, accp_sock, addrlen=sizeof(cliaddr), nbyte; char buf[maxline+1]; if(argc!= 2) { printf("usage: %s port\n", argv[0]); exit(0); if((listen_sock = socket(pf_inet, SOCK_STREAM, 0)) < 0) { perror("socket fail"); exit(0); bzero((char *)&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(inaddr_any); servaddr.sin_port = htons(atoi(argv[1])); if(bind(listen_sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror( "bind fail" ); exit(0); if( bind( 4, (struct sockaddr*)&servaddr, sizeof(servaddr) ) < 0 ) perror( "test1 bind fail: "); if( bind( 1, (struct sockaddr*)&servaddr, sizeof(servaddr) ) < 0 ) perror( "test2 bind fail: "); servaddr.sin_port = htons(80); if( bind( listen_sock, (struct sockaddr*)&servaddr, sizeof(servaddr) ) < 0 ) perror( "test3 bind fail: " ); servaddr.sin_port = htons(200); if( bind( listen_sock, (struct sockaddr*)&servaddr, sizeof(servaddr) ) < 0 ) perror( "test4 bind fail: " ); return 0; 한국외국어대학교컴퓨터공학과

134 네트워크프로그래밍및실습 나. TCP 에코서버프로그램과 TCP 에코클라이언트프로그램을응용하여, 클라이언트 2개의접속을 받아메시지를교환하는프로그램을작성하시오. 1) 문제해결 가 ) 서버프로그램에서 accept() 함수를두번호출하여클라이언트 2 개의접속을기다린다. 나 ) 클라이언트가접속하면각각 다 ) client1 에서메시지를서버로전송한다. accp_sock1, accp_sock2 에값을저장한다. 라 ) server는메시지를받아 client2 에게그메시지를보낸다. 마 ) 바 ) client2 는받은메시지를출력한다. client2 에서메시지를서버로전송한다. 사 ) server는메시지를받아 client1 에게그메시지를보낸다. 아 ) client1 은받은메시지를출력한다. 자 ) 프로그램종료. 예제 17 서버프로그램소스코드 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #define MAXLINE 127 int main(int argc, char *argv[]) { struct sockaddr_in servaddr, cliaddr; int listen_sock, accp_sock1, accp_sock2, addrlen=sizeof(cliaddr), nbyte; char buf[maxline+1]; if(argc!= 2) { printf("usage: %s port\n", argv[0]); exit(0); if((listen_sock = socket(pf_inet, SOCK_STREAM, 0)) < 0) { perror("socket fail"); exit(0); bzero((char *)&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(inaddr_any); servaddr.sin_port = htons(atoi(argv[1])); if(bind(listen_sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {

Chapter 5. 소켓프로그래밍기초 135 perror("bind fail"); exit(0); listen(listen_sock, 5); puts( "Server is starting." ); accp_sock1 = accept( listen_sock, (struct sockaddr*)&cliaddr, &addrlen ); if( accp_sock1 < 0 ) { perror( "accept socket1 fail" ); exit( 0 ); puts( "Client1 Connected." ); accp_sock2 = accept( listen_sock, (struct sockaddr*)&cliaddr, &addrlen ); if( accp_sock2 < 0 ) { perror( "accept socket2 fail" ); exit( 0 ); puts( "Client2 Connected." ); nbyte = read(accp_sock1, buf, MAXLINE); write(accp_sock2, buf, nbyte); nbyte = read(accp_sock2, buf, MAXLINE); write(accp_sock1, buf, nbyte); close( accp_sock1 ); close( accp_sock2 ); close(listen_sock); return 0; 예제 18 클라이언트프로그램소스코드 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <unistd.h> #define MAXLINE 127 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; int s, nbyte; char buf[maxline+1]; if(argc!= 2) 한국외국어대학교컴퓨터공학과

136 네트워크프로그래밍및실습 { printf("usage: %s ip_address\n", argv[0]); exit(0); if((s = socket(pf_inet, SOCK_STREAM, 0)) < 0) { perror("socket fail"); exit(0); bzero((char *)&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(af_inet, argv[1], &servaddr.sin_addr); servaddr.sin_port = htons(atoi(argv[1])); if(connect(s, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("connect fail"); exit(0); if (fgets(buf, sizeof(buf), stdin) == NULL) exit(0); nbyte = strlen(buf); printf(" 입력: "); if (write(s, buf, nbyte) < 0) { printf("write error\n"); exit(0); printf(" 수신 : "); if( (nbyte=read(s, buf, MAXLINE)) <0) { perror("read fail"); exit(0); buf[nbyte]=0; printf("%s", buf); close(s); return 0; 2) 실행화면 가 ) 서버프로그램

Chapter 5. 소켓프로그래밍기초 137 나 ) 클라이언트 1 다 ) 클라이언트 2 한국외국어대학교컴퓨터공학과

138 네트워크프로그래밍및실습 S E C T I O N 06 UDP 프로그램 1. UDP 프로그램작성절차 그림 132 USP 소켓프로그래밍절차 가. 특징 1) type 인자로 SOCK_DGRAM 을지정 2) UDP 소켓은특정호스트와의일대일통신이아니라임의의호스트와데이터그램송수신 3) 비연결형소켓 가 ) 연결설정을위한 connect() 시스템콜을사용할필요없음 나 ) 소켓개설후바로임의의상대방과데이터송수신가능 4) 데이터송수신시각데이터그램마다목적지의 IP 주소와포트번호를항상함수인자로줌 나. sendto() recvfrom() 와함수의사용법 1) sendto() 가 ) UDP 소켓을통한데이터의송신함수 2) recvfrom() 가 ) UDP 소켓을통한데이터의수신함수 나 ) 성공적으로수행되면 from 구조체에데이터그램을보낸상대방의소켓주소가들어감

Chapter 5. 소켓프로그래밍기초 139 다 ) 데이터를보낸발신자에게데이터를보낼때 (1) from에있는소켓주소를 sendto() 의 to로복사하여사용 라 ) fromlen에는 from 구조체의길이가정수형포인터로리턴됨 문법 int sendto( int s, char* buf, int length, int flags sockaddr* to, int tolen ); int recvfrom( int s, char* buf, ); int length, int flags, sockaddr* from, int* fromlen 표 41 sendto() 와 recvfrom() 함수의사용법 인자 s 소켓번호 buf 전송할데이터가저장된버퍼 length buf 버퍼의크기 flags 보통 0 to 목적지의소켓주소구조체 tolen to 버퍼의크기 s 소켓번호 buf 수신데이터를저장할버퍼 length buf 버퍼의길이 flags 보통 0 from 발신자의소켓주소구조체 fromlen from 버퍼의크기 2. UDP 에코프로그램 예제 19 echo 서비스를수행하는 UDP 클라이언트 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #define MAXLINE 511 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; int s, nbyte, addrlen = sizeof(servaddr); char buf[maxline+1]; if(argc!= 3) { printf("usage: %s ip_address port_number\n", argv[0]); exit(0); if((s = socket(pf_inet, SOCK_DGRAM, 0)) < 0) { perror("socket fail"); exit(0); 한국외국어대학교컴퓨터공학과

140 네트워크프로그래밍및실습 // 에코서버의소켓주소구조체작성 bzero((char *)&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = inet_addr(argv[1]); servaddr.sin_port = htons(atoi(argv[2])); // 키보드입력을받음 printf(" 입력 : "); if (fgets(buf, MAXLINE, stdin)==null) { printf("fgets 실패"); exit(0); // 에코서버로메시지송신 if (sendto(s, buf, strlen(buf), 0, (struct sockaddr *)&servaddr, addrlen) < 0) { perror("sendto fail"); exit(0); // 수신된에코메시지 if ( (nbyte=recvfrom(s, buf, MAXLINE, 0, (struct sockaddr *)&servaddr, &addrlen)) < 0) { perror("recevfrom fail"); exit(0); buf[ nbyte ]=0; printf("%s\n", buf); close(s); return 0; 실행화면은다음과같다. 3. UDP 에코서버 예제 20 echo 서비스를수행하는 UDP 서버 #include <stdio.h> #include <stdlib.h>

Chapter 5. 소켓프로그래밍기초 141 #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #define MAXLINE 511 int main(int argc, char *argv[]) { struct sockaddr_in servaddr, cliaddr; int s, nbyte, addrlen = sizeof(struct sockaddr); char buf[maxline+1]; if(argc!= 2) { printf("usage: %s port\n", argv[0]); exit(0); // 소켓생성 if((s = socket(pf_inet, SOCK_DGRAM, 0)) < 0) { perror("socket fail"); exit(0); // servaddr 을 '\0' 으로초기화 bzero((char *)&servaddr,addrlen); bzero((char *)&cliaddr, addrlen); // servaddr 세팅 servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(inaddr_any); servaddr.sin_port = htons(atoi(argv[1])); // bind() 호출 if(bind(s, (struct sockaddr *)&servaddr, addrlen) < 0) { perror("bind fail"); exit(0); // iterative 에코서비스수행 while(1) { puts("server : waiting request."); nbyte = recvfrom(s, buf, MAXLINE, 0, (struct sockaddr *)&cliaddr, &addrlen); if(nbyte< 0) { perror("recvfrom fail"); exit(1); 한국외국어대학교컴퓨터공학과

142 네트워크프로그래밍및실습 buf[nbyte] = 0; printf("%d byte recv: %s\n",nbyte, buf); if (sendto(s, buf, nbyte, 0, (struct sockaddr *)&cliaddr, addrlen) < 0) { perror("sendto fail"); exit(1); puts("sendto complete"); 실행화면은다음과같다.

Chapter 6. 고급소켓프로그래밍

144 네트워크프로그래밍및실습 S E C T I O N 01 소켓의동작모드 1. 블록모드 가. 소켓에대해시스템콜호출시시스템이동작완료할때까지시스템콜에서프로세스가멈추어있는모드나. 소켓을처음생성할때디폴트로블록모드가됨다. 블록모드의소켓을사용하는프로그램에서프로세스가영원히블록상태가될수있음라. 소켓관련시스템콜중에블록될수있는예 1) listen(), connect(), accept(), recv(), send(), read(), write(), recvfrom(), sendto(), close() 마. 응용프로그램이일대일통신을하거나한가지작업만할경우에사용 2. 넌블록모드 가. 소켓관련시스템콜에대하여시스템이즉시처리할수있으면바로결과를리턴나. 즉시처리할수없는경우시스템콜이바로리턴되어응용프로그램이블록되지않음다. 폴링을주로사용 1) 다중화를위해시스템콜이성공적으로실행될때까지계속확인하는방법라. 통신상대가여럿이거나여러작업을병행하여실행할경우사용 3. 비동기보드 가. 소켓에서어떤 I/O 변화발생시그사실을응용프로그램이알수있도록함 1) 이때입출력처리등원하는동작을함 나. 소켓을비동기로바꾸는방법 1) select() 함수를이용 가 ) I/O 변화가발생할수있는소켓들전체를대상으로 select() 를호출해둠 나 ) 이중임의의소켓에서 I/O 변화가발생시 select() 문이리턴되어원하는작업을함 2) fcntl() 를사용하여소켓을 signal-driven I/O 모드로바꾸는방법 가 ) 특정소켓에서 I/O 변화가발생시 SIGIO 시그널을발생시킴 나 ) 응용프로그램에서는이시그널을받아필요한작업을함 다. 통신상대가여럿이거나여러작업을병행하여실행할경우사용

Chapter 6. 고급소켓프로그래밍 145 S E C T I O N 02 다중처리기술 다중입출력서치를포함해네트워크프로그램에서네트워크프로그램에서동시에려어작업을처리하 는기술 1. 멀티태스킹 여러작업을병행하여처리하는기법 ( 멀티프로세스, 멀티스레드) 가. 멀티프로세스 1) 유닉스에서멀티태스킹을위해프로세스를여러개실행시키는방법 2) 독립적으로처리해야할작업의수만큼프로세스를만드는방법 3) 각프로세스들이독립적으로작업을처리하므로구현이간편함 4) 다중처리할작업이상당히독립적으로진행되어야할경우에적합 5) 병렬처리해야할적업수만큼프로세스를생성하는단점가 ) 프로세스수가증가하면메모리사용량이증가하고프로세스스케줄링횟수가많아짐나 ) 동시에개설할수있는프로세스의수를제한함 6) 프로세스간데이터를공유하기가불편함가 ) 운영체제의도움을받아프로세스간통신(IPC) 를하므로프로그램구현이복잡해짐 나. 멀티스레드 1) 유닉스에서멀티태스킹을위해스레드를여러개실행시키는방법 2) 프로세스내에서독립적으로실행하는스레드를여러개실행시킴가 ) 외부에서는이스레드들전체가하나의프로세스처럼취급됨 3) 프로세스에서스레드를생성하면새로생성된스레드는원래프로세스의이미지를같이사용 4) 새로생성된스레드용스택영역은스레드별로별도로배정되며스택을공유하지않음 5) 멀리프로세스방식보다필요로하는메모리양이적고스레드생성시간이짦음 6) 스레드간스케줄링도프로세스간스케줄링보다빠르게이루어짐 7) 다중처리할작업들이서로밀접한관계로데이터공유가많이필요한경우에적합 8) 동기화문제발생가 ) 한프로세스내에서생성된스레드들이이미지를공유하므로전역변수를같이사용함나 ) 스레드들은쉽게데이터를공유할수있음다 ) 한스레드가어떤변수의값을변경하는도중에다른스레드가동시에이변수를액세스라 ) 변수의값이명확하게사용되지않음 2. 다중화 한프로세스또는스레드내에서이루어지는다중처리방법 한국외국어대학교컴퓨터공학과

146 네트워크프로그래밍및실습 가. 폴링처리해야할작업들을순차적으로돌아가면서처리하는방법 1) 서버가여러클라이언트와통신할때각클라이언트로부터의데이터수신을순차적으로처리 2) 입출력함수가어느한곳에서블록되지않아야하므로파일이나소켓을넌블록모드로설정 3) 넌블록모드의파일이나소켓가 ) 블록될수있었던입출력함수호출시시스템이즉시처리하면결과를바로리턴나 ) 즉시처리할수없는경우엔함수가즉시리턴되어프로그램이블록되지않는모드 4) 여러클라이언트들이고르게트래픽을발생시키는경우에서버에서사용하기에적합가 ) 서버는폴링을할때마다수신할데이터가거의항상기다리고있기때문에프로그램이효율적으로실행됨 나. 인터럽트 프로세스가어떤작업을처리하는도중에특정한이벤트가발생하면해당이벤트를처리하는방법 1) 하드웨어인터럽트 가 ) 키보드등을사용하여입력하였을때발생하는인터럽트 2) 소프트웨어인터럽트 가 ) 프로세스사이에이벤트발생을알려주기위한시그널 3) 인터럽트처리 가 ) CPU에서하드웨어인터럽트를인터럽트처리루틴에서처리함 나 ) 시그널을사용하여데이터입출력을인터럽트방식으로처리함 다. 셀렉팅 폴링의반대개념 1) 어떤클라이언트로부터데이터가도착하면서버는데이터가도착한클라이언트와의입출력처리 2) 클라이언트로부터의데이터도착이불규칙적인경우에적합 3) 유닉스에서셀렉팅을사용하기위해 select() 함수를이용 가 ) 입출력을처리할파일이나소켓들을비동기모드로바꿈

Chapter 6. 고급소켓프로그래밍 147 S E C T I O N 03 비동기형채팅프로그램 1. 채팅서버프로그램구조 가. 연결용소켓 1) 서버에서 socket() 을호출하여채팅참가를접수할소켓을개설 2) 이소켓을자신의소켓주소와 bind() 함 나. 통신용소켓 1) 연결용소켓을대상으로 select() 를호출하여새로운참가요청을처리 2) accept() 가리턴하는소켓번호를채팅참가자리스트에등록 3) 서버는연결용소켓과통신용소켓을대상으로다시 select() 호출 그림 135 select() 를이용한비동기형채팅서버 다. 채팅서버와클라이언트의연결관계 그림 136 채팅서버와클라이언트의연결관계 한국외국어대학교컴퓨터공학과

148 네트워크프로그래밍및실습 1) select() 가 ) 소켓에서발생하는 I/O변화를기다리다가지정된 I/O변화발생시리턴 나 ) 으용프로그램에서는 select() 리턴시어떤소켓에서어떤 I/O변화인지확인하고작업처리 2) 서버 가 ) 연결용소켓과채팅참가자리스트를대상으로 나 ) select() 를호출하고대기 select() 문이리턴시새로운참가자의연결요청인지기존채팅참가자중누군가채팅메시지 를보낸것인지구분하여필요한작업수행 2. select() 가. 사용문법 int select( int amxfdp1, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* tvptr ); 1) maxfdp1 : 현재개설된소켓번호중가장큰소켓번호 +1의값 2) fd_set 타입의인자 : 각각읽기, 쓰기, 예외발생같은 I/O변화시이를감지할대상이되는소켓들 을지정하는배열형구조체 가 ) 이세구조체를통해어떤소켓에서어떤종류의 3) tvptr : select() 가기다리는시간을지정 가 ) NULL인경우지정한 I/O변화가발생할때까지무한히기다림 나 ) 0인경우기다리지않고바로리턴 다 ) 그외엔지정된시간만큼또는도중에 I/O 변화가발생하는지, 감지할지를선택 I/O변화가발생할때까지기다림 나. fd_set 타입의구조체와소켓번호와의관계 그림 137 1) 동작방식 fd_set 타입의구조체와소켓번호와의관계 가 ) fd_set 타입구조체에 I/O변화를감지할소켓이나파일을 1로 set 나 ) select() 를호출해두면해당조건이만족되는순간 select() 문이리턴 2) readfds 가 ) 0, 3번이세트되어있음 나 ) 키보드( 표준입력 0), 소켓번호 3에서어떤데이터가입력시프로그램이이를읽을수있는상 3) writefds 가 ) 태가되면 selct() 문이리턴 1, 3번이세트되어있음

Chapter 6. 고급소켓프로그래밍 149 나 ) 파일기술자( 표준출력 1) 나소켓번호 3번이 write를할수있는상태로변할시 select() 문이리턴 다. fd_set을사용하기위한매크로 1) fd_set 타입구조체의배열값을편리하게지정하기위해매크로가제공됨 가 ) FD_ZERO( fd_set* fdset ) (1) fdset의모든비트를지움 나 ) FD_SET( int fd, fd_set* fdset ) (1) fdset 중소켓 fd에해당하는비트를 1로함 다 ) FD_CLR( int fd, fd_set* fdset ) (1) fdset 중소켓 fd에해당하는비트를 0으로함 라 ) FD_ISSET( int fd, fd_set* fdset ) (1) fdset 중소켓 fd에해당하는비트가 set되어있으면양수값을리턴 3. 채팅서버프로그램 예제 21 채팅참가자관리, 채팅메시지수신및방송서버 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <fcntl.h> #include <sys/socket.h> #include <sys/file.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #define MAXLINE 511 #define MAX_SOCK 1024 // 솔라리스의경우 64 char *EXIT_STRING = "exit"; // char *START_STRING = "Connected to chat_server \n"; // 클라이언트의종료요청문자열클라이언트환영메시지 int maxfdp1; // 최대소켓번호 +1 int num_chat = 0; // 채팅참가자수 int clisock_list[max_sock]; // 채팅에참가자소켓번호목록 int listen_sock; // 서버의리슨소켓 // 새로운채팅참가자처리 void addclient(int s, struct sockaddr_in *newcliaddr); int getmax(); // 최대소켓번호찾기 void removeclient(int s); // 채팅탈퇴처리함수 int tcp_listen(int host, int port, int backlog); // 소켓생성및 void errquit(char *mesg) { perror(mesg); exit(1); listen int main(int argc, char *argv[]) { 한국외국어대학교컴퓨터공학과

150 네트워크프로그래밍및실습 struct sockaddr_in cliaddr; char buf[maxline+1]; int i, j, nbyte, accp_sock, addrlen = sizeof(struct sockaddr_in); fd_set read_fds; // 읽기를감지할fd_set 구조체 if(argc!= 2) { printf(" 사용법 exit(0); :%s port\n", argv[0]); // tcp_listen(host, port, backlog) 함수호출 listen_sock = tcp_listen(inaddr_any, atoi(argv[1]), 5); while(1) { FD_ZERO(&read_fds); FD_SET(listen_sock, &read_fds); for(i=0; i<num_chat; i++) FD_SET(clisock_list[i], &read_fds); maxfdp1 = getmax() + 1; // maxfdp1 재계산 puts("wait for client"); if(select(maxfdp1, &read_fds,null,null,null)<0) errquit("select fail"); if(fd_isset(listen_sock, &read_fds)) { accp_sock=accept(listen_sock, (struct sockaddr*)&cliaddr, &addrlen); if(accp_sock == 1) errquit("accept fail"); addclient(accp_sock,&cliaddr); send(accp_sock, START_STRING, strlen(start_string), 0); printf("%d 번째사용자추가.\n", num_chat); // 클라이언트가보낸메시지를모든클라이언트에게방송 for(i = 0; i < num_chat; i++) { if(fd_isset(clisock_list[i], &read_fds)) { nbyte = recv(clisock_list[i], buf, MAXLINE, 0); if(nbyte<= 0) { removeclient(i); // 클라이언트의종료 continue; buf[nbyte] = 0; // 종료문자처리 if(strstr(buf, EXIT_STRING)!= NULL) { removeclient(i); // 클라이언트의종료 continue; // 모든채팅참가자에게메시지방송 for (j = 0; j < num_chat; j++) send(clisock_list[j], buf, nbyte, 0); printf("%s\n", buf);

Chapter 6. 고급소켓프로그래밍 151 // end of while return 0; // 새로운채팅참가자처리 void addclient(int s, struct sockaddr_in *newcliaddr) { char buf[20]; inet_ntop(af_inet,&newcliaddr >sin_addr,buf,sizeof(buf)); printf("new client: %s\n",buf); // 채팅클라이언트목록에추가 clisock_list[num_chat] = s; num_chat++; // 채팅탈퇴처리 void removeclient(int s) { close(clisock_list[s]); if(s!= num_chat 1) clisock_list[s] = clisock_list[num_chat 1]; num_chat ; printf(" 채팅참가자 1 명탈퇴. 현재참가자수 = %d\n", num_chat); // 최대소켓번호찾기 int getmax() { // Minimum 소켓번호는가정먼저생성된 listen_sock int max = listen_sock; int i; for (i=0; i < num_chat; i++) if (clisock_list[i] > max ) max = clisock_list[i]; return max; // listen 소켓생성및 listen int tcp_listen(int host, int port, int backlog) { int sd; struct sockaddr_in servaddr; sd = socket(af_inet, SOCK_STREAM, 0); if(sd == 1) { perror("socket fail"); exit(1); // servaddr 구조체의내용세팅 bzero((char *)&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(host); servaddr.sin_port = htons(port); 한국외국어대학교컴퓨터공학과

152 네트워크프로그래밍및실습 if (bind(sd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("bind fail"); exit(1); // 클라이언트로부터연결요청을기다림 listen(sd, backlog); return sd; 실행화면은다음과같다. 4. 통신용소켓구분 그림 139 TCP서버에서의다수의통신용소켓을구분하는방법 가. 채팅서버는클라이언트와통신에사용하기위해다수의통신용소켓을개설 1) 모든소켓은같은포트번호를사용함 2) 클라이언트들은서버의포트번호만알고있음 3) 서버에서다른포트번호를사용하면통신이안됨 나. 한개의포트번호만으로각클라이언트와연결할수있는이유 1) 클라이언트의 IP 주소와포트번호를내부적인키로사용

Chapter 6. 고급소켓프로그래밍 153 2) 채팅서버에도착하는데이터그램의포트번호는모두같음 3) 서버는클라이언트의 IP 주소와포트번호를키값으로사용하여해당통신용소켓으로메시지전달 다. 두개의 telnet 클라이언트가서버에접속시 1) 서버에두개의통신용소켓이생성됨 2) 통신용소켓은모두 23번을포트번호로사용함 3) 각클라이언트를구분하기위해클라이언트의 IP주소와포트번호를키로사용 5. 채팅클라이언트프로그램 예제 22 서버에접속한후키보드의입력을서버로전달하고, 서버로부터오는메시지를화면에출력 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <fcntl.h> #include <sys/socket.h> #include <netinet/in.h> #include <sys/time.h> #include <unistd.h> #include <arpa/inet.h> #define MAXLINE 1000 #define NAME_LEN 20 char *EXIT_STRING= "exit"; // 소켓생성및서버연결, 생성된소켓리턴 int tcp_connect(int af, char *servip, unsigned short port); void errquit(char *mesg) { perror(mesg); exit(1); int main(int argc, char *argv[]) { char bufall[maxline+name_len], // 이름+ 메시지를위한버퍼 *bufmsg; // bufall 에서메시지부분의포인터 int maxfdp1, // 최대소켓디스크립터 s, // 소켓 namelen; // 이름의길이 fd_set read_fds; if(argc!= 4) { printf(" 사용법 exit(0); : %s sever_ip port name \n", argv[0]); sprintf(bufall, "[%s] :", argv[3]); // bufall namelen= strlen(bufall); 의앞부분에이름을저장 한국외국어대학교컴퓨터공학과

154 네트워크프로그래밍및실습 bufmsg = bufall+namelen; // 메시지시작부분지정 s = tcp_connect(af_inet, argv[1], atoi(argv[2])); if(s== 1) errquit("tcp_connect fail"); puts(" 서버에접속되었습니다."); maxfdp1 = s + 1; FD_ZERO(&read_fds); while(1) { FD_SET(0, &read_fds); FD_SET(s, &read_fds); if(select(maxfdp1, &read_fds, NULL,NULL,NULL) < 0) errquit("select fail"); if (FD_ISSET(s, &read_fds)) { int nbyte; if ((nbyte = recv(s, bufmsg, MAXLINE, 0)) > 0) { bufmsg[nbyte] = 0; printf("%s \n", bufmsg); if (FD_ISSET(0, &read_fds)) { if(fgets(bufmsg, MAXLINE, stdin)) { if (send(s, bufall, namelen+strlen(bufmsg), 0) < 0) puts("error : Write error on socket."); if (strstr(bufmsg, EXIT_STRING)!= NULL ) { puts("good bye."); close(s); exit(0); // end of while int tcp_connect(int af, char *servip, unsigned short port) { struct sockaddr_in servaddr; int s; // 소켓생성 if ((s = socket(af, SOCK_STREAM, 0)) < 0) return 1; // 채팅서버의소켓주소구조체 servaddr 초기화 bzero((char *)&servaddr, sizeof(servaddr)); servaddr.sin_family = af; inet_pton(af_inet, servip, &servaddr.sin_addr); servaddr.sin_port = htons(port); // 연결요청

Chapter 6. 고급소켓프로그래밍 155 if(connect(s, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) return 1; return s; 실행화면은다음과같다. 한국외국어대학교컴퓨터공학과

156 네트워크프로그래밍및실습 S E C T I O N 04 폴링형채팅프로그램 소켓을넌블록보드로설정하고채팅메시지수신여부를폴링방식으로점검 1. fcntl() 소켓을넌블록모드나비동기모드로바꿈 가. 나. file control 을의미함 fcntl() 의동작 1) 소켓을넌블록모드로설정 2) 소켓을비동기모드로설정 3) 소켓의소유자를설정하거나현재소유자를얻어옴 다. 넌블록모드설정 1) 문법 가 ) 나 ) fd : 모드변경을원하는소켓디스크립터 cmd( 명령) (1) F_SETFL : 플래그세트 (2) F_GETF : 플래그읽기 (3) F_SETOWN : 소켓의소유자설정 (4) F_GETWON : 소켓의소유자얻기 다 ) flag (1) 구체적인옵션을설정 (2) O_NONBLOCK : 넌블록모드로설정 (3) O_ASYNC : SIGIO 시그널에의해구동되도록비오기모드로설정 라. 설정코드 1) 소켓을넌블록모드로설정하는코드 2) 기존의플래그값을유지 가 ) F_GETFL 명령으로얻은후이를 O_NONBLOCK와 OR연산 마. 비동기모드설정 1) 비동기모드로설정하는이유 가 ) select() 함수자체는블록형함수임 (1) 아무입출력변화가없으면프로그램은 select() 에서블록됨 나 ) 인터럽트형다중화에서는 SIGIO 등의시그널이발생할때입출력을처리할수있음 바. 소켓의소유자를설정하거나현재소유자를얻어옴

Chapter 6. 고급소켓프로그래밍 157 1) 소켓의소유자를설정하는이유 가 ) 소켓을처음생성하면소유자가없음 나 ) 프로세스에서나중에시그널을수신하도록하려면소켓의소유자가필요 다 ) (1) 소켓의소유자에게 SIGIO나 SIGURG 등의시그널이전달됨 accept() 가리턴하는통신용소켓은연결용소켓의소유자를상속받음 2. 폴링형채팅서버 폴링형채팅서버프로그램에서는소켓들을넌블록모드로설정한후무한루프로입출력폴링 가. 넌블록모드 1) 소켓에대해 read(), write() 등의입출력함수를호출하면함수는바로리턴 가 ) 함수의리턴값을보고원하는작업의실행여부를확인 (1) 정상의경우 0, 에러의경우 -1이리턴 나 ) 에러의경우에러코드값을보고함수자체의에러인지소켓이즉시리턴된것인지확인 (1) 소켓이즉시리턴된것은소켓이넌블록모드이므로리턴됨 (2) 이경우에러코드는 EWOULDBLOCK 2) recv() 를호출했을때 EWOULDBLOCK 이외의에러 가 ) 클라이언트와의연결종료또는클라이언트가리셋을보낸경우 (1) 클라이언트를채팅목록에서제거하는등의에러처리를함 3) accept() 가 ) 넌블록모드의소켓에 accept() 를호출한경우 (1) accept() 가리턴한소켓은디폴트로블록모드임 (2) 필요한경우명시적으로넌블록모드로변환해야함 나. 소켓의모드확인 1) val = fcntl( sockfd, F_GETFL, 0 ); 가 ) 기존의플래그값을얻어옴 2) if( val & O_NONBLOCK ) 가 ) 넌블록모드인지확인 나 ) 넌블록모드일경우 0 을리턴, 아니면 -1을리턴 3. 폴링형채팅서버프로그램 예제 23 넌블록모드의채팅서버 #include <stdio.h> #include <fcntl.h> #include <stdlib.h> #include <signal.h> #include <sys/socket.h> #include <arpa/inet.h> 한국외국어대학교컴퓨터공학과

158 네트워크프로그래밍및실습 #include <sys/types.h> #include <sys/file.h> #include <netinet/in.h> #include <string.h> #include <unistd.h> #include <errno.h> #define MAXLINE 511 #define MAX_SOCK 1024 // 솔라리스는 64 char *EXIT_STRING = "exit"; char *START_STRING = "Connected to chat_server\n"; int maxfdp1; // 최대소켓번호 +1 int num_chat = 0; // 채팅참가자수 int clisock_list[ MAX_SOCK ]; // 채팅에참가자소켓번호목록 int listen_sock; // 새로운채팅참가자처리 void addclient(int s, struct sockaddr_in *newcliaddr); void removeclient(int); // 채팅탈퇴처리함수 int set_nonblock(int sockfd); // 소켓을넌블록으로설정 int is_nonblock(int sockfd); // 소켓이넌블록모드인지확인 int tcp_listen(int host, int port, int backlog); // 소켓생성및 listen void errquit(char *mesg) { perror(mesg); exit(1); int main(int argc, char *argv[]) { char buf[maxline]; int i, j, nbyte, count; int accp_sock, clilen; struct sockaddr_in cliaddr; if(argc!= 2) { printf(" 사용법 exit(0); :%s port\n", argv[0]); listen_sock = tcp_listen(inaddr_any,atoi(argv[1]),5); if(listen_sock== 1) errquit("tcp_listen fail"); if(set_nonblock(listen_sock) == 1) errquit("set_nonblock fail"); for(count=0; ;count++) { if(count==100000) { putchar('.' ); fflush(stdout); count=0; addrlen = sizeof(cliaddr); accp_sock= accept(listen_sock, (struct sockaddr *) &cliaddr, &clilen);

Chapter 6. 고급소켓프로그래밍 159 if(accp_sock == 1 && errno!=ewouldblock) errquit("accept fail"); else if(accp_sock >0) { // 채팅클라이언트목록에추가 clisock_list[num_chat] = accp_sock; // 통신용소켓은넌블록모드가아님 if(is_nonblock(accp_sock)!=0 && set_nonblock (accp_sock)<0 ) errquit("set_nonblock fail"); addclient(accp_sock,&cliaddr); send(accp_sock, START_STRING, strlen(start_string), 0); printf("%d 번째사용자추가.\n", num_chat); // 클라이언트가보낸메시지를모든클라이언트에게방송 for(i = 0; i < num_chat; i++) { errno = 0; nbyte = recv(clisock_list[i], buf, MAXLINE, 0); if(nbyte==0) { removeclient(i); // abrupt exit continue; else if( nbyte== 1 && errno == EWOULDBLOCK) continue; // 종료문자처리 if(strstr(buf, EXIT_STRING)!= NULL) { removeclient(i); // abrupt exit continue; // 모든채팅참가자에게메시지방송 buf[nbyte] = 0; for (j = 0; j < num_chat; j++) send(clisock_list[j], buf, nbyte, 0); printf("%s\n", buf); // 새로운채팅참가자처리 void addclient(int s, struct sockaddr_in *newcliaddr) { char buf[20]; inet_ntop(af_inet,&newcliaddr >sin_addr,buf,sizeof(buf)); printf("new client: %s\n",buf); // 채팅클라이언트목록에추가 clisock_list[num_chat] = s; num_chat++; // 채팅탈퇴처리 void removeclient(int i) { close(clisock_list[i]); 한국외국어대학교컴퓨터공학과

160 네트워크프로그래밍및실습 if(i!= num_chat 1) clisock_list[i] = clisock_list[num_chat 1]; num_chat ; printf(" 채팅참가자 1 명탈퇴. 현재참가자수 = %d\n", num_chat); // 소켓이 nonblock 인지확인 int is_nonblock(int sockfd) { int val; // 기존의플래그값을얻어온다 val=fcntl(sockfd, F_GETFL,0); // 넌블록모드인지확인 if(val & O_NONBLOCK) return 0; return 1; // 소켓을넌블록모드로설정 int set_nonblock(int sockfd) { int val; // 기존의플래그값을얻어온다 val=fcntl(sockfd, F_GETFL,0); if(fcntl(sockfd, F_SETFL, val O_NONBLOCK) == 1) return 1; return 0; // listen 소켓생성및 listen int tcp_listen(int host, int port, int backlog) { int sd; struct sockaddr_in servaddr; sd = socket(af_inet, SOCK_STREAM, 0); if(sd == 1) { perror("socket fail"); exit(1); // servaddr 구조체의내용세팅 bzero((char *)&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(host); servaddr.sin_port = htons(port); if (bind(sd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("bind fail"); exit(1); // 클라이언트로부터연결요청을기다림 listen(sd, backlog); return sd;

Chapter 6. 고급소켓프로그래밍 161 4. 연습문제풀이 가. 하나의채팅클라이언트프로그램에서최대 50개의 TCP 소켓을개설하고이들을각각서버에연 결하는 busy_connect.c 프로그램을작성하시오( 채팅메시지는보내지않음). 이때연결의수는 main() 함수의인자로받도록하시오. 예를들어아래와같이실행하면 21.0211.123.123/9999 의호 스트에 40개의 TCP 접속을한다. $ busy_connect 210.211.123.123. 9999 40 예제 24 TCP 소켓을개설하고이들을각각서버에연결하는프로그램 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <fcntl.h> #include <sys/socket.h> #include <netinet/in.h> #include <sys/time.h> #include <unistd.h> #include <arpa/inet.h> #define MAXLINE 1000 #define NAME_LEN 20 char* EXIT_STRING = "exit"; int tcp_connect( int af, char* servip, unsigned short port ); void errquit( char* msg ) { perror(msg); exit(1); int main( int argc, char* argv[] ) { char bufall[ MAXLINE + NAME_LEN ]; // buffer for name + msg char* bufmsg; // pointer of msg part in bufall int maxfdp1; // maximum value of socket desc. int s; // socket int namelen; // length of name fd_set read_fds; int connectno; int* asocket; int i; // number of connection // array of sockets if( argc!= 5 ) { printf( "Usage : %s server_ip port name connectno \n", argv[0] ); exit( 0 ); sprintf( bufall, "[%s] : ", argv[3] ); namelen = strlen( bufall ); bufmsg = bufall + namelen; // assign array of sockets connectno = atoi( argv[4] ); asocket = (int*)malloc( sizeof(int) * connectno ); 한국외국어대학교컴퓨터공학과

162 네트워크프로그래밍및실습 // make connects for( i = 0; i < connectno; i++ ) { asocket[i] = tcp_connect( AF_INET, argv[1], atoi(argv[2]) ); if( asocket[i] == 1 ) errquit( "failed to connect" ); printf( "Connected to server (socket no : %d)\n", i ); maxfdp1 = asocket[i] + 1; FD_ZERO( &read_fds ); while( 1 ) { FD_SET( 0, &read_fds ); if( select( maxfdp1, &read_fds, NULL, NULL, NULL ) < 0 ) errquit( "failed to select()" ); if( FD_ISSET( 0, &read_fds ) ) { if( fgets( bufmsg, MAXLINE, stdin ) ) { if( strstr( bufmsg, EXIT_STRING )!= NULL ) { puts( "Good bye." ); for( i = 0; i < connectno; i++ ) { close( asocket[i] ); printf("disconnected (socket no : %d)\n", i); exit( 0 ); free( asocket ); return 0; int tcp_connect( int af, char* servip, unsigned short port ) { struct sockaddr_in servaddr; int s; if( ( s = socket( af, SOCK_STREAM, 0 ) ) < 0 ) return 1; bzero( (char*)&servaddr, sizeof(servaddr) ); servaddr.sin_family = af; inet_pton( AF_INET, servip, &servaddr.sin_addr ); servaddr.sin_port = htons( port ); if( connect( s, (struct sockaddr*)&servaddr, sizeof(servaddr) ) < 0 ) return 1; return s;

Chapter 6. 고급소켓프로그래밍 163 나. fork & exec를이용해서문제 4-1의 busy_connect.c 프로그램을여러개실행시킬수있는 bomb.c 프로그램을작성하시오. 이제 bomb.c 를사용하여임의의채팅서버( 예: 비동기형채팅서버) 에총몇명까지접속할수있는지를확인하시오.( 주의: 서버가다운될수있음) 예제 25 busy_connect.c 프로그램을여러개실행시킬수있는 bomb.c 프로그램 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> void child_start(); 한국외국어대학교컴퓨터공학과

164 네트워크프로그래밍및실습 int main( int argc, char* argv[] ) { pid_t pid; int child_status, child_return; int ncount; ncount = 1; while( 1 ) { if( ( pid = fork() ) < 0 ) { perror( "fork failed : " ); exit( 0 ); else if( pid == 0 ) child_start(); else if( pid > 0 ) printf( "\n\t** parent pid: %d, child pid = %d, Start %dth child program\n", getpid(), pid, ncount ); ncount++; return 0; void child_start() { char* args[] = { "q1", "127.0.0.1", "6666", "xissy", "20", NULL ; execv( "./q1", args ); perror( "exec error at child: " ); exit( 0 );

Chapter 6. 고급소켓프로그래밍 165 tcp_chatserv 프로그램에서 1020 명까지접속할수있다. 이는프로세스마다생성할수있는파일/ 소 켓디스크립트의갯수가 1024 개로제한되어있기때문이다. fd0~2와 listen 소켓을제외한 1020의소 켓을생성하여클라이언트의접속을받을수있다. 다. 네트워크관련에러코드에는아래와같은것들이있는데 strerrer() 함수를이용해서아래각 errno 에대한에러내용을출력해보시오. ( 힌트: 아래와같은 define 문을사용하면타이핑을줄일수있다. 예를들어, net_errmsg(eagain) 을호출하면 "EAGAIN = xxxxxxxxxx" 와같은형태로출력된다.) 한국외국어대학교컴퓨터공학과

166 네트워크프로그래밍및실습 #define net_errmsg(no) printf("%s=%s\n", #no, strerror(no)) 예제 26 네트워크관련에러코드출력프로그램 #include <stdio.h> #include <string.h> #include <errno.h> #define net_errmsg(no) printf( "%s : \t\t%s\n", #no, strerror(no) ) int main() { printf( "Print ERROR Messages\n" ); net_errmsg( EAGAIN ); net_errmsg( ENOTSOCK ); net_errmsg( EDESTADDRREQ ); net_errmsg( EMSGSIZE ); net_errmsg( EPROTOTYPE ); net_errmsg( ENOPROTOOPT ); net_errmsg( EPROTONOSUPPORT ); net_errmsg( ESOCKTNOSUPPORT ); net_errmsg( EOPNOTSUPP ); net_errmsg( ESHUTDOWN ); net_errmsg( ETOOMANYREFS ); net_errmsg( ETIMEDOUT ); net_errmsg( ECONNREFUSED ); net_errmsg( EHOSTDOWN ); net_errmsg( EHOSTUNREACH ); net_errmsg( EWOULDBLOCK ); net_errmsg( EALREADY ); net_errmsg( EINPROGRESS ); net_errmsg( EPFNOSUPPORT ); net_errmsg( EADDRINUSE ); net_errmsg( EADDRNOTAVAIL ); net_errmsg( ENETDOWN ); net_errmsg( ENETUNREACH ); net_errmsg( ENETRESET ); net_errmsg( ECONNABORTED ); net_errmsg( ECONNRESET ); net_errmsg( ENOBUFS ); net_errmsg( EISCONN ); net_errmsg( ENOTCONN ); return 0;

Chapter 6. 고급소켓프로그래밍 167 라. 비동기형채팅프로그램에서서버는 select() 함수에서블록상태가된다. 이때클라이언트가 CTRL+C 를누르면서버의 select() 가에러를리턴하는지를확인하시오. 에러가발생하지않았다면 select() 이후호출되는 recv() 에서는에러가발생하는지확인하시오. recv() 나 select() 에서에러가 발생하면에러의원인을찾아내고에러가발생하지않도록프로그램을수정하시오. ( 힌트: 클라이언트프로그램에서 CTRL+C를누르면비정상적으로프로그램이종료되므로 TCP는 리셋(RST) 을서버로전송하고서버측 TCP는리셋을수신하면 errno에 ECONNRESET 값을설정한다.) 예제 27 네트워크관련에러코드출력프로그램 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <fcntl.h> #include <sys/socket.h> #include <sys/file.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #define MAXLINE 511 #define MAX_SOCK 1024 한국외국어대학교컴퓨터공학과

168 네트워크프로그래밍및실습 char *EXIT_STRING = "exit"; char *START_STRING = "Connected to chat_server \n"; int maxfdp1; int num_chat = 0; int clisock_list[max_sock]; int listen_sock; void addclient(int s, struct sockaddr_in *newcliaddr); int getmax(); void removeclient(int s); int tcp_listen(int host, int port, int backlog); void errquit(char *mesg) { perror(mesg); exit(1); int main(int argc, char *argv[]) { struct sockaddr_in cliaddr; char buf[maxline+1]; int i, j, nbyte, accp_sock, addrlen = sizeof(struct sockaddr_in); fd_set read_fds; if(argc!= 2) { printf("usage : %s port\n", argv[0]); exit(0); listen_sock = tcp_listen(inaddr_any, atoi(argv[1]), 5); while(1) { FD_ZERO(&read_fds); FD_SET(listen_sock, &read_fds); for(i=0; i<num_chat; i++) FD_SET(clisock_list[i], &read_fds); maxfdp1 = getmax() + 1; puts("wait for client"); if(select(maxfdp1, &read_fds,null,null,null)<0) { //errquit("select fail"); perror("select fail"); exit(0); if(fd_isset(listen_sock, &read_fds)) { accp_sock=accept(listen_sock, (struct sockaddr *)&cliaddr, &addrlen); if(accp_sock == 1) errquit("accept fail"); addclient(accp_sock,&cliaddr); send(accp_sock, START_STRING, strlen(start_string), 0); printf("connected %dth chatter. \n", num_chat); for(i = 0; i < num_chat; i++) { if(fd_isset(clisock_list[i], &read_fds)) { nbyte = recv(clisock_list[i], buf, MAXLINE, 0); if(nbyte<= 0) { perror("recv fail"); removeclient(i); continue; buf[nbyte] = 0;

Chapter 6. 고급소켓프로그래밍 169 return 0; if(strstr(buf, EXIT_STRING)!= NULL) { removeclient(i); continue; for (j = 0; j < num_chat; j++) send(clisock_list[j], buf, nbyte, 0); printf("%s\n", buf); void addclient(int s, struct sockaddr_in *newcliaddr) { char buf[20]; inet_ntop(af_inet,&newcliaddr >sin_addr,buf,sizeof(buf)); printf("new client: %s\n",buf); clisock_list[num_chat] = s; num_chat++; void removeclient(int s) { close(clisock_list[s]); if(s!= num_chat 1) clisock_list[s] = clisock_list[num_chat 1]; num_chat ; printf("a chatter leaved. Current chatters = %d\n", num_chat); int getmax() { int max = listen_sock; int i; for (i=0; i < num_chat; i++) if (clisock_list[i] > max ) max = clisock_list[i]; return max; int tcp_listen(int host, int port, int backlog) { int sd; struct sockaddr_in servaddr; sd = socket(af_inet, SOCK_STREAM, 0); if(sd == 1) { perror("socket fail"); exit(1); bzero((char *)&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(host); servaddr.sin_port = htons(port); if (bind(sd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("bind fail"); exit(1); listen(sd, backlog); return sd; 한국외국어대학교컴퓨터공학과

Chapter 7. 소켓옵션

Chapter 7. 소켓옵션 171 S E C T I O N 01 소켓옵션종류 1. SO_KEEPALIVE TCP 연결이정상적으로지속되고있는지주기적으로확인 가. 사용방법 int set = 1; setsockopt( s, SOL_SOCKET, SO_KEEPALIVE, (char*)&set, sizeof(int) ); 나. 동작방식 1) 옵션이셋팅되어있으면 TCP 는확인시간( 예: 2 시간) 동안데이터송수신을기다림 가 ) TCP 없을시연결이아직살아있는지질문(keep alive prove) 을보냄 2) Keep alive 질문에대해중간라우터가 ICMP 메시지로 host unreadchable error 또는 network unreachable error 를보낸경우 가 ) EHOSTENREACH 또는 ENETUNREACH 에러가리턴 다. keep alive prove 의응답 1) 상대방이 ACK를보냄 가 ) TCP 연결이정상적으로동작중이므로확인시간이후에질문을다시보냄 2) 상대방이 RST 에러를보냄 가 ) 상대방호스트가꺼진후재부팅된상태 나 ) TCP는소켓을닫으며 ECONNRESET의에러코드를가짐 3) 아무응답이없음 가 ) 질문을몇번더보낸후연결을종료 나 ) 에러코드는 ETIMEDOUT 라. 사용용도 1) 비정상적인 TCP연결종료를찾아소켓을종료하기위해사용 2. SO_LINGER close() 를호출후에상대방에서도정상적으로종료절차가이루어지는지확인 가. 사용방법 한국외국어대학교컴퓨터공학과

172 네트워크프로그래밍및실습 struct linger{ int l_onoff; int l_linger; ; struct linger ling; ling.l_onoff = 1; ling.l_linger = 0; if( setsockopt( sockd, SOL_SOCKET, SO, LINGER, (void*) &ling, sizeof(struct linger) )!= 0 ) { perror(" 소켓옵션지정실패 ); exit(1); 나. 동작방식 1) close() 를호출한이후에상대방에서도정상적으로종료절차가이루어졌는지사용 2) TCP 연결을종료할때 close() 를호출하여종료함 가 ) close() 는즉시리턴됨 나 ) 소켓송신버퍼에전송할데이터가있어도 다 ) close() close() 가리턴되어도모든데이터의전송여부는보장되지않음 3) close() 는옵션에서지정한 linger 시간또는정상종료때까지블록 는리턴되며모두전송후연결종료 가 ) 정상종료전에 linger 시간이먼저타임아웃되면 ETIMEDOUT 에러발생 4) 송신버퍼에남아있던데이터를전송하기까지기다리는최대시간을제한하는데사용 가 ) close() 는 linger 시간이내에데이터를전송하지못하면에러를리턴하고통신이종료되기때문 다. 사용방법 1) 송신버퍼에데이터가남아있어도타임아웃이되면데이터를폐기하도록옵션을지정 2) l_onoff는 LINGER 옵션의사용여부를지정 가 ) l_onoff = 0 일때 LINGER 옵션을사용하지않음 나 ) close() 는원래대로동작하여송신버퍼에전송할데이터가남아있어도즉시리턴 다 ) 송신버퍼에데이터가남아있었다면커널에의해전송이마무리됨 라 ) 전송이실패시프로그램에서확인할수없음 마 ) 연결종료후가시간동안 3) l_linger 는기다리는시간을지정 가 ) l_linger = 0 TCP 2MSL TIME-WAIT 상태가됨 일때송신버퍼에남아있는데이터는파기 나 ) 정상적인종료과정을거치지않고 RST을전송하여 TCP 연결을즉시리셋 다 ) 이때는 TCP가 TIME-WAIT 상태에도머무르지않음 라 ) l_linger 이양수값일때송신버퍼에데이터가있는경우, close() 는데이터를모두전송하고 ACK를수신할때까지또는 l_linger 마 ) 소켓이넌블록모드일경우 close() 시간이지날때까지블록 함수는즉시리턴 바 ) l_linger 시간이먼저지나서 close() 가리턴된경우에는송신버퍼에남은데이터는폐기

Chapter 7. 소켓옵션 173 3. shutdown() close() 대신상대방이 FIN을보낼때까지기다리는방법 가. 사용방법 int shutdown( int s, int how ); 나. 동작방식 1) 호스트 A가호스트 B로데이터를보낸후 close() 를호출한시점에아직호스트 B의수신버퍼에 호스트 A가보낸데이터가읽히지않고대기하고있는경우 가 ) 호스트 B는수신버퍼에아직읽지않은데이터가있어도호스트 A가보내온 FIN에대한 ACK 를보냄 나 ) 호스트 A의 close() 함수가정상적으로리턴되어도호스트 B의응용프로그램이호스트 A가 보낸모든데이터를읽었는지는모름 다 ) 이경우 shutdown() 을사용하여상대방이 FIN을보낼때까지대기 다. 사용방법 1) int s 가 ) 소켓번호 2) int how 가 ) 양방향스트림에대한동작을지정하는인자 나 ) SHUT_WR : 송신스트림만닫음 다 ) SHUT_RD : 수신스트림만닫음 라 ) SHUT_RDWR : 송신및수신의양방향스트림을닫음 4. SO_RCVBUF 와 SO_SNDBUF 소켓의송신버퍼와수신버퍼의크기를변경할때사용 가. 동작방식 1) 송신버퍼 가 ) TCP 의경우응용프로그램에서 write() 함수를호출시커널은데이터를소켓송신버퍼로복사 나 ) 데이터가송신버퍼에모두복사되면데이터를전송하기시작 다 ) 전송한후에바로송신버퍼를지우지않고재전송의가능성을위해저장하고있다가 받은후에송신버퍼를지움 라 ) 송신버퍼가가득차게되면 2) 수신버퍼 가 ) TCP write() 함수는블록됨 는흐름제어를위해수신버퍼의여유공간을상대방에게알려줌 나 ) 여유공간의크기를윈도우라고함 ACK를 한국외국어대학교컴퓨터공학과

174 네트워크프로그래밍및실습 다 ) 라 ) TCP에서는수신버퍼의용량보다큰데이터를받게되면나머지는버려짐 UDP에서는윈도우를이용한흐름제어가없어큰데이터전송시주의해야함 마 ) UDP의수신버퍼의크기는보통 40000바이트정도이며최소한 576바이트이상은보장 나. 버퍼의크기변경시기 1) 클라이언트 : connect() 함수호출이전 2) 서버 : listen() 함수호출이전 5. SO_REUSEADDR 동일한소켓주소의중복사용을허용 가. 사용방법 int set = 1; setsockopt( udp_sock1, SOL_SOCKET, SO_REUSEADDR, (char*)&set, sizeof(int) ); 나. 동작방식 1) 동일한소켓주소를여러프로세스나한프로세스내의여러소켓에서중복사용을허용 2) 기본적으로는한호스트내에서같은포트번호를중복하여사용할수없음 3) 주로서버에서사용됨 다. TIME-WAIT 상태에서의주소재사용 1) active close를하면 TCP가 TIME-WAIT상태에서 2MSL 시간동안기다림 가 ) 이동안사용중이던포트번호를중복하여사용할수없음 2) bind() 를호출하기전에 SO_REUSEADDR 옵션을지정하여 TIME-WAIT 상태에서포트번호를중 복하여사용함 3) 단, TIME-WAIT 상태에있을때만주소재사용이가능함 가 ) close 소켓이되지않은상태에서는소켓주소를중복하여사용할수없음 4) TCP가 TIME-WAIT 상태동안에도서비스를즉시재시작하기위해사용 라. 자식프로세스가서버인경우 1) TCP 서버프로그램에서자식프로세스가서비스처리를담당하던중부모프로세스가재시작시 가 ) 포트번호사용중에러가발생 나 ) 과거의자식프로세스가해당포트번호를사용하기때문에발생 2) 소켓개설후 bind() 호출전옵션지정 마. 멀티홈서버의경우 1) 멀티홈호스트 가 ) 호스트가두개이상의랜에접속된경우에호스트가두개이상의 IP 주소를가짐 2) 멀티홈호스트에서두개이상의 IP 주소가같은포트번호를사용하는것이필요한경우

Chapter 7. 소켓옵션 175 가 ) 소켓주소재사용옵션설정 나 ) 같은포트번호를사용하면서둘이상의다른 트내에서실행시킬때소켓주소재사용옵션설정이필요 IP 주소를사용하는서버프로그램들을한호스 3) 하나의프로세스내에서여러소켓을개설하고각소켓들이같은포트번호다른IP 주소를사용할 때도옵션설정이필요 가 ) 각소켓별로각각다른 IP 주소를지정하여 bind() 함 바. 완전중복바인딩 1) 여러프로세스에서동일한 IP 주소와동일한포트번호를중복하여 bind() 하는것 가 ) UDP 2) 사용예 소켓에서만사용가능함 가 ) 멀티캐스트데이터그램을수신하는경우 3) 하나의호스트내에서여러멀티캐스트가입자들이데이터를수신하려고할때 가 ) 각가입자들이동일한데이터그램을수신하기위해완전중복바인딩된소켓주소를사용 6. 기타옵션 가. SO_OOBINLINE 1) 대역외데이터를일반데이터의수신버퍼에같이저장되도록함가 ) 동등한순서로처리됨나 ) 일반적으로대역외데이터는일반데이터보다우선순위가높은버퍼에저장됨 나. SO_RCVLOWAT 와 SO_SNDLOWAT 1) 수신버퍼최소량은기본적으로 TCP와 UDP에서한바이트임 가 ) select() 함수가데이터읽기조건이만족되어리턴하기위해한바이트이상의데이터가수신 버퍼에도착해야함 나 ) 한바이트이상의데이터가수신되면 select() 2) 송신버퍼의최소량은기본적으로 2048바이트임 함수가리턴됨 다. SO_DONTROUTE 1) send(), sendto(), sendmsg() 호출시에사용 2) 라우팅테이블이잘못되었을때라우팅테이블을무시할때사용 가 ) 데이터그램을로컬인터페이스에있는장비로전송 라. SO_BROADCAST 1) 브로드캐스트는 UDP 소켓에서만사용가능함 2) 이더넷같은방송형서브네트워크에서만가능함 마. TCP_NODELAY 1) Nagle's 알고리즘이디폴트로실행되므로이를취소해야할때사용 2) Nagle's 알고리즘 한국외국어대학교컴퓨터공학과

176 네트워크프로그래밍및실습 가 ) 앞에전송된데이터 나 ) ACK를받기전까지는작은크기의데이터는전송하지않고기다림 ACK를기다리는동안작은데이터들을모아서전송 (1) 너무작은데이터세그먼트들이자주발생하지않게하여전송효율높임 (2) ACK가빨리오면작은데이터송신도이에비례하므로통신처리가늦어지지않음 3) delayed ACK 알고리즘 가 ) 데이터를수신한경우그데이터에대해 나 ) piggyback ACK를즉시보내지않고약간의지연을둠 (1) 지연시간동안상대에게보낼데이터에 ACK 비트를세트함으로별도의 ACK를생략 (2) 지연타이머는 500ms 이하이며보통 50 ~ 200ms를사용 바. TCP_MAXSEG 1) 최대세그먼트크기의지원은모든시스템이지원하지않음가 ) 리눅스에서는지원됨 2) 최대세그먼트크기변경은연결설정이전에함 3) 너무큰값으로변경되었을시연결설정과정에서지정값보다작게재지정됨

Chapter 7. 소켓옵션 177 S E C T I O N 02 소켓옵션변경 1. 소켓옵션변경함수 accept() 를호출하기전에소캣옵션을지정 가. 사용방법 int getsockopt(int s, int level, int opt, const char *optval, int *optlen) int setsockopt(int s, int level, int opt, const char *optval, int optlen) 1) int s 가 ) 옵션내용을읽을또는변경할소켓번호 2) int level 가 ) 프로토콜레벨을지정 나 ) 소켓레벨의옵션 : SOL_SOCKET 다 ) 라 ) IP 3) int opt 프로토콜에관한옵션 TCP 에관한옵션 : IPPROTO_IP : IPPROTO_TCP 가 ) 변경할또는읽을옵션을지정 4) const char *optval 가 ) 지정하려는또는읽을옵션값을가리키는포인터 5) int *optlen, int optlen 가 ) *optval의크기를나타냄 나 ) getsockopt() 에서는 optlen을 setsockopt() 에서는 *optlen을사용 나. 소켓옵션의종류레벨 옵션 의미 SO_BROADCAST 방송형메시지전송허용 SO_DEBUG DEBUG 모드를선택 SO_REUSEADDR 주소의재사용선택 SO_LINGER 소켓을닫을때미전송된데이터가있어도지정된 SOL_SOCKET 시간만큼기다렸다가소켓을닫음 SO_KEEPALIVE TCP의keep-alive동작선택 SO_OOBINLINE OOB 데이터를일반데이터처럼읽음 SO_RCVBUF 수신버퍼의크기변경 SO_SNDBUF 송신버퍼의크기변경 IP_TTL Time To Live 변경 IPPROTO_IP IP_MULTICAST_TTL 멀티캐스트데이터그램의 TTL 변경 IP_ADD_MEMBERSHIP 멀티캐스트그룹에가입 한국외국어대학교컴퓨터공학과

178 네트워크프로그래밍및실습 IP_DROP_MEMBERSHIP 멀티캐스트그룹에서탈퇴 IP_MULTICAST_LOOP 멀티캐스트데이터그램의 loopback 허용여부 IP_MULTICAST_IF 멀티캐스트데이터그램전송용인터페이스지정 TCP_KEEPALIVE keep-alive 확인메시지전송시간지정 IPPROTO_TCP TCP_MAXSEG TCP의 MSS( 최대메시지크지) 지정 TCP_NODELAY Nagle 알고리즘의선택 표 42 소켓옵션의종류 2. 소켓옵션변경예제프로그램 예제 28 소켓옵션을사용하여수신버퍼의크기변경 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> int main() { int s; int val, len; // 소켓생성 if ((s = socket(af_inet, SOCK_STREAM, 0)) < 0) { perror("socket fail"); exit(1); len = sizeof(val); // 수신버퍼의크기값을얻어옴 if (getsockopt(s, SOL_SOCKET, SO_RCVBUF, &val, &len) < 0) { perror("socket fail"); exit(1); // 현재디폴트로설정된수신버퍼의크기를출력 printf(" 디폴트수신버퍼크기: %d\n", val); val = 1024; // 수신버퍼를 val 사이즈만큼변경 setsockopt(s, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val)); // 변경된수신버퍼의값을얻어옴 getsockopt(s, SOL_SOCKET, SO_RCVBUF, &val, &len); printf("1024 로변경한수신버퍼크기: %d\n", val); return 0;

Chapter 7. 소켓옵션 179 실행화면은다음과같다. 수신버퍼의크기는커널이두배의값으로변경한다. 1024호설정하여도출력에서는 2048이나온 다. 3. 연습문제풀이 가. TCP 에코클라이언트에서메시지를전송한후 MSG_PEEK 옵션을주고 recv() 함수를호출하여수 신버퍼에메시지가얼마나도착했는지를확인하는프로그램을작성하시오( 아래의동작순서참고.) 1) 에코클라이언트에서메시지를전송한후 MSG_PEEK 옵션을주고 recv() 를호출한다. 그러면서 버가메시지를에코할때까지대시상태가된다. 2) 서버는즉시에코를하고 2초간 sleep 한다. 3) 클라이언트는 recv() 함수가리턴한후 4초간 sleep 한다. 4) 서버는클라이언트보다먼저깨어나며클라이언트가 sleep 하는동안 "end of message" 를전송한다. 5) 클라이언트는깨어난후에 MSG_PEEK 옵션을주고 recv() 를호출하여도착한메시지가더있는지 를확인한다. 6) 클라이언트는 MSG_PEEK 옵션없이 recv() 를호출하여도착한메시지를모두읽는다. 예제 29 서버프로그램소스 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #define MAXLINE 127 int main(int argc, char *argv[]) { struct sockaddr_in servaddr, cliaddr; int listen_sock, accp_sock, addrlen=sizeof(cliaddr), nbyte; char buf[maxline+1]; if(argc!= 2) { printf("usage: %s port\n", argv[0]); exit(0); if((listen_sock = socket(pf_inet, SOCK_STREAM, 0)) < 0) { 한국외국어대학교컴퓨터공학과

180 네트워크프로그래밍및실습 perror("socket fail"); exit(0); bzero((char *)&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(inaddr_any); servaddr.sin_port = htons(atoi(argv[1])); if(bind(listen_sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("bind fail"); exit(0); listen(listen_sock, 5); while(1) { puts("server is waiting client..."); accp_sock = accept(listen_sock, (struct sockaddr *)&cliaddr, &addrlen); if(accp_sock < 0) { perror("accept fail"); exit(0); puts("client connected"); nbyte = read(accp_sock, buf, MAXLINE); write(accp_sock, buf, nbyte); sleep(2); sprintf( buf, "end of server message\n" ); write(accp_sock, buf, strlen(buf)); close(accp_sock); close(listen_sock); return 0; 예제 30 클라이언트프로그램소스 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #define MAXLINE 127

Chapter 7. 소켓옵션 181 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; int s, nbyte; char buf[maxline+1]; if(argc!= 2) { printf("usage: %s ip_address\n", argv[0]); exit(0); if((s = socket(pf_inet, SOCK_STREAM, 0)) < 0) { perror("socket fail"); exit(0); bzero((char *)&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(af_inet, argv[1], &servaddr.sin_addr); servaddr.sin_port = htons(6666); if(connect(s, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("connect fail"); exit(0); printf("input: "); if (fgets(buf, sizeof(buf), stdin) == NULL) exit(0); nbyte = strlen(buf); if (write(s, buf, nbyte) < 0) { printf("write error\n"); exit(0); printf("first Peek Recv : "); sleep(1); nbyte = recv( s, buf, MAXLINE, MSG_PEEK MSG_DONTWAIT ); if( nbyte < 0 ) { perror("first recv fail"); exit(0); buf[nbyte]=0; printf("%s", buf); sleep(4); printf("second Peek Recv : "); nbyte = recv( s, buf, MAXLINE, MSG_PEEK MSG_DONTWAIT ); if( nbyte < 0 ) { perror("second recv fail"); 한국외국어대학교컴퓨터공학과

182 네트워크프로그래밍및실습 exit(0); buf[nbyte]=0; printf("%s", buf); printf("first Non peek Recv : "); if( (nbyte=read(s, buf, MAXLINE)) <0) { perror("read fail"); exit(0); buf[nbyte]=0; printf("%s", buf); printf("second Non peek Recv : "); if( (nbyte=read(s, buf, MAXLINE)) <0) { perror("read fail"); exit(0); buf[nbyte]=0; printf("%s\n", buf); close(s); return 0; MSG_PEEK 옵션을이용해 recv() 하면버퍼의내용이지워지지않고그대로남아있지만, MSG_PEEK 옵션을사용하지않으면읽어온내용이버퍼에서사라짐을확인할수있다.

Chapter 7. 소켓옵션 183 나. 위의내용을 UDP 소켓에서작성하고 TCP 소켓의경우와어떤차이가있는지를설명하시오. 예제 31 서버프로그램소스 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #define MAXLINE 511 int main(int argc, char *argv[]) { struct sockaddr_in servaddr, cliaddr; int s, nbyte, addrlen = sizeof(struct sockaddr); char buf[maxline+1]; if(argc!= 2) { printf("usage: %s port\n", argv[0]); exit(0); if((s = socket(pf_inet, SOCK_DGRAM, 0)) < 0) { perror("socket fail"); exit(0); bzero((char *)&servaddr,addrlen); bzero((char *)&cliaddr, addrlen); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(inaddr_any); servaddr.sin_port = htons(atoi(argv[1])); if(bind(s, (struct sockaddr *)&servaddr, addrlen) < 0) { perror("bind fail"); exit(0); while(1) { puts("server : waiting request."); nbyte = recvfrom(s, buf, MAXLINE, 0, (struct sockaddr *)&cliaddr, &addrlen); if(nbyte< 0) { perror("recvfrom fail"); exit(1); buf[nbyte] = 0; printf("%d byte recv: %s\n",nbyte, buf); if (sendto(s, buf, nbyte, 0, (struct sockaddr *)&cliaddr, addrlen) < 0 ) { 한국외국어대학교컴퓨터공학과

184 네트워크프로그래밍및실습 perror("sendto fail"); exit(1); ) sleep(2); sprintf( buf, "end of server message\n" ); if (sendto(s, buf, strlen(buf), 0, (struct sockaddr*)&cliaddr, addrlen) < 0 { perror("sendto fail"); exit(1); puts("sendto complete"); return 0; 예제 32 클라이언트프로그램소스 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #define MAXLINE 511 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; int s, nbyte, addrlen = sizeof(servaddr); char buf[maxline+1]; if(argc!= 3) { printf("usage: %s ip_address port_number\n", argv[0]); exit(0); if((s = socket(pf_inet, SOCK_DGRAM, 0)) < 0) { perror("socket fail"); exit(0); bzero((char *)&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = inet_addr(argv[1]); servaddr.sin_port = htons(atoi(argv[2])); printf("input : "); if (fgets(buf, MAXLINE, stdin)==null) {

Chapter 7. 소켓옵션 185 printf("fgets fail"); exit(0); if (sendto(s, buf, strlen(buf), 0, (struct sockaddr *)&servaddr, addrlen) < 0) { perror("sendto fail"); exit(0); sleep(1); if ( (nbyte=recvfrom(s, buf, MAXLINE, MSG_PEEK MSG_DONTWAIT, (struct sockaddr *)&servaddr, &addrlen)) < 0) { perror("first recvfrom fail"); exit(0); buf[ nbyte ]=0; printf("first Peek recvfrom : %s\n", buf); sleep(4); if ( (nbyte=recvfrom(s, buf, MAXLINE, MSG_PEEK MSG_DONTWAIT, (struct sockaddr *)&servaddr, &addrlen)) < 0) { perror("second recvfrom fail"); exit(0); buf[ nbyte ]=0; printf("second Peek recvfrom : %s\n", buf); if ( (nbyte=recvfrom(s, buf, MAXLINE, 0, (struct sockaddr *)&servaddr, &addrlen)) < 0) { perror("first Non peek recvfrom fail"); exit(0); buf[ nbyte ]=0; printf("first Non peek recvfrom : %s\n", buf); if ( (nbyte=recvfrom(s, buf, MAXLINE, 0, (struct sockaddr *)&servaddr, &addrlen)) < 0) { perror("second Non peek recvfrom fail"); exit(0); buf[ nbyte ] =0; printf("second Non peek recvfrom : %s\n", buf); close(s); return 0; 한국외국어대학교컴퓨터공학과

186 네트워크프로그래밍및실습 TCP 는스트림서비스를제공하여연속적으로도착한데이터의경계를구분하지못하기때문에 MSG_PEEK 으로버퍼에남아있는내용과그이후에버퍼로들어온내용이함께출력된다. UDP는데이터그램단위로데이터를읽을수있기때문에 음번 recvfrom() 에서처리하는것을볼수있다. 하지만 MSG_PEEK으로읽은데이터크기만큼다 다. TCP 에코클라이언트프로그램의마지막부분에서 close() 함수를호출하면서버와 FIN 패킷을주 고받게된다. SO_LINGER 옵션을사용해서 RST(reset) 이전송되는 tcp_reset_cli.c 프로그램을작성 하시오. 이때서버에서 RST을수신한것을확인할수있도록 recv() 함수의리턴값을검사하고 perror() 함수로에러의원인을출력하시오. ( 힌트: l_onoff 값은 1 로, l_linger 값을 0으로설정하여 SO_LINGER 옵션을지정한후에 close() 함수 를호출하면된다.) 예제 33 서버프로그램소스 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #define MAXLINE 127 int main(int argc, char *argv[]) { struct sockaddr_in servaddr, cliaddr;

Chapter 7. 소켓옵션 187 int listen_sock, accp_sock, addrlen=sizeof(cliaddr), nbyte; char buf[maxline+1]; if(argc!= 2) { printf("usage: %s port\n", argv[0]); exit(0); if((listen_sock = socket(pf_inet, SOCK_STREAM, 0)) < 0) { perror("socket fail"); exit(0); bzero((char *)&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(inaddr_any); servaddr.sin_port = htons(atoi(argv[1])); if(bind(listen_sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("bind fail"); exit(0); listen(listen_sock, 5); while(1) { puts("server is waiting client..."); accp_sock = accept(listen_sock, (struct sockaddr *)&cliaddr, &addrlen); if(accp_sock < 0) { perror("accept fail"); exit(0); puts("client connected"); nbyte = read(accp_sock, buf, MAXLINE); write(accp_sock, buf, nbyte); sleep(1); nbyte = recv(accp_sock, buf, MAXLINE, 0 ); if( nbyte < 0 ) { perror("recv fail"); close(accp_sock); close(listen_sock); return 0; 한국외국어대학교컴퓨터공학과

188 네트워크프로그래밍및실습 예제 34 클라이언트프로그램소스 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #define MAXLINE 127 int main( int argc, char* argv[] ) { struct sockaddr_in servaddr; int s, nbyte; char buf[maxline+1]; struct linger ling; if( argc!= 2 ) { printf("usage: %s ip_address\n", argv[0]); exit(0); if((s = socket(pf_inet, SOCK_STREAM, 0)) < 0) { perror("socket fail"); exit(0); ling.l_onoff = 1; ling.l_linger = 0; if(setsockopt(s, SOL_SOCKET, SO_LINGER, (void*)&ling, sizeof(struct linger))!= 0) { perror( "setsockopt fail " ); bzero((char *)&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(af_inet, argv[1], &servaddr.sin_addr); servaddr.sin_port = htons(6666); if(connect(s, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("connect fail"); exit(0); printf("input: "); if (fgets(buf, sizeof(buf), stdin) == NULL) exit(0); nbyte = strlen(buf); if (write(s, buf, nbyte) < 0) {

Chapter 7. 소켓옵션 189 printf("write error\n"); exit(0); printf("recv : "); if( (nbyte=read(s, buf, MAXLINE)) <0) { perror("recv fail"); exit(0); buf[nbyte]=0; printf("%s", buf); close(s); return 0; 서버프로그램실행화면에서 RST 을수신한것을 "recv fail: Connection reset by peer" 을통해알수 있다. 한국외국어대학교컴퓨터공학과

Chapter 8. 프로세스

Chapter 8. 프로세스 191 S E C T I O N 01 프로세스의이해 1. 프로세스 가. OS상에서실행되는개개의프로그램 1) task라고도함 2) 기계어로이루어진수동적인프로그램이아님 3) 시스템자원을할당받아동작되고있는능동적인프로그램 가 ) CPU 의레지스터, 프로그램포인터, 스택메모리등프로그램실행에필요한자원을할당받음 2. 프로세스식별자 가. 커널에서관리하기위해내부프로세스테이블에등록 1) 이등록번호를프로세스식별자(PID) 라함 나. 프로세스식별자를통해 Linux 내부의모든프로세스를통제함 1) 프로세스식별자를통해프로세스에게신호를전달 2) 생성과동시에발생하며종료시커널에반환 3. 프로세스식별자확인 가. getpid() 1) 호출프로세스의프로세스 ID 를반환, 입력값은없음 나. getpgrp() 1) 호출프로세스의그룹ID 반환, 입력값은없음 다. getppid() 1) 부모프로세스ID 반환 라. getpgid() 1) 프로세스ID를입력값으로가짐 2) 성공시부모프로세스의그룹ID 반환 3) 에러시 pid - 1를반환 4. 프로세스그룹 가. 어떤하나의작업을수행하는데관계된프로세스들의집합 나. 프로세스그룹 ID 1) 동일프로세스그룹내에있는프로세스들은동일한프로세스그룹 ID를소유 다. 프로세스그룹생존시간 1) 프로세스그룹의생성에서모든프로세스의종료까지의시간간격 2) 프로세스그룹리더에의해프로세스에서의프로세스를생성, 종료 3) 같은그룹내의모든프로세스가없어질때까지그그룹은존재함 한국외국어대학교컴퓨터공학과

192 네트워크프로그래밍및실습 S E C T I O N 02 프로세스의생성과종료 1. 프로세스의생성 - fork() 가. 사용방법 int PID = fork(); if (PID == 0) { child_work(); else if (PID > 0) { else { parent_work(); error(); 나. 리눅스에서유일하게프로세스를생성하는함수 다. 성공시프로세스 라. 동작방법 ID를반환함 1) fork() 를실행한프로세스로부터새프로세스가복제됨 2) fork() 를실행한프로세스는부모프로세스라함 3) 새프로세스는자식프로세스라함 2. 프로세스의종료 가. exit() 1) 정상적인종료 가 ) main() 함수의묵시적인종료 나 ) main() 함수의 return 사용 다 ) exit() 2) 사용방법 호출 가 ) 종료의상태로사용할정수값을인자로받아해당상태로종료됨 나. abort() 1) 비정상적인종료 가 ) 신호에의한종료 나 ) abort() 호출

Chapter 8. 프로세스 193 S E C T I O N 03 데몬서버구축방법 1. 데몬프로세스 백그라운드로실행되는프로세스로특정터미널제어와관계없이실행되는프로세스 가. 데몬프로세스생성 1) 백그라운드로실행되는프로세스 가 ) 터미널에서데몬실행후사용자가로그아웃해도데몬은종료되지않음 나 ) 데몬은 kill 명령이나 SIGKILL 시그널을보냄으로종료 나. 사용방법 struct sigaction sact; sigset_t mask; // 부모프로세스를종료시키고자식프로세스에서실행됨 if((pid = fork())!= 0) // exit(0); 스스로세션리더가됨 setsid(); // SIGHUP 시그널을무시, 손자프로세스와터미널의연관을끊음 sact.sa_handler = SIG_IGN; sact.sa_flags = 0; sigemptyset(&sact.sa_mask); sigaddset(&sact.sa_mask, SIGHUP); sigaction(sighup, &sact); // 손자프로세스를생성허고부모프로세스를종료 if((pid = fork())!= 0) // exit(0); 작업디렉토리를루트디렉토리로변경 chdir("/"); // 새로생성되는파일이임의의소유권한을가지게함 umask(0); // 혹시개설되어있을소켓을닫음 for(i=0 ; i<maxfd ; i++) close(1); 1) 위의코드를프로그램앞부분에추가 한국외국어대학교컴퓨터공학과

194 네트워크프로그래밍및실습 2. 데몬서버종류 httpd, sendmail, named 등과같이항상실행되고있으면서서비스를제공하는데몬 예제 35 독립형데몬서버구축 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <signal.h> #include <syslog.h> #include <stdarg.h> #include <unistd.h> #include <sys/stat.h> #define MAXLINE 511 #define MAXFD 64 int tcp_listen(int host, int port, int backlog); int main(int argc, char *argv[]) { struct sockaddr_in cliaddr; int listen_sock, accp_sock; // int i, addrlen, nbyte; pid_t pid; char buf[maxline+1]; struct sigaction sact; 소켓번호 if (argc!= 2) { printf(" 사용법: %s port\n", argv[0]); exit(0); // 프로그램을데몬프로세스로실행시키는코드 if((pid = fork())!= 0) exit(0);// 부모프로세스는종료시킨다 // 즉, 자식프로세스만아래부분을실행한다 setsid(); // 스스로세션리더가된다 // SIGHUP 시그널(hang up) 을무시한다 sact.sa_handler = SIG_IGN; sact.sa_flags = 0; sigemptyset(&sact.sa_mask); sigaddset(&sact.sa_mask, SIGHUP); sigaction(sighup, &sact,null); if ((pid = fork())!= 0)// 다시자식프로세스( 손자) 를만든다 exit(0); // 부모프로세스는종료시킨다

Chapter 8. 프로세스 195 chdir ("/"); // 디렉토리변경 umask(0); // umask 설정 for (i = 0; i < MAXFD ; i++) close(i); // 혹시개설되어있을지모르는소켓을닫는다 listen_sock = tcp_listen(inaddr_any, atoi(argv[1]),5); // interactive 에코서비스수행 while(1) { addrlen = sizeof(cliaddr); // 연결요청을기다림 if ((accp_sock = accept(listen_sock, (struct sockaddr *)&cliaddr, &addrlen)) < 0) exit(0); if ((nbyte = read(accp_sock, buf, MAXLINE)) < 0) exit(0); write(accp_sock, buf, nbyte); close(accp_sock); close(listen_sock); // listen 소켓생성및 listen int tcp_listen(int host, int port, int backlog) { int sd; struct sockaddr_in servaddr; sd = socket(af_inet, SOCK_STREAM, 0); if(sd == 1) { perror("socket fail"); exit(1); // servaddr 구조체의내용세팅 bzero((char *)&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(host); servaddr.sin_port = htons(port); if (bind(sd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("bind fail"); exit(1); // 클라이언트로부터연결요청을기다림 listen(sd, backlog); return sd; 프로그램을실행하면데몬서버가구동됨을확인할수있다. 한국외국어대학교컴퓨터공학과

196 네트워크프로그래밍및실습 3. 연습문제풀이 가. 앞부분에서소개한임의의채팅서버를데몬형태로동작시키고동작을확인하시오. ( 힌트: 채팅서버를시작하기전에데몬프로세스가되기위한초기화작업을하면된다. 데몬은 화면에출력할수없으므로모든화면출력메시지를특정파일에기록해야한다.) 예제 36 서버프로그램소스 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <fcntl.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/file.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <signal.h> #include <syslog.h> #include <stdarg.h> #include <sys/stat.h> #define MAXLINE 511 #define MAX_SOCK 1024 char *EXIT_STRING = "exit"; char *START_STRING = "Connected to chat_server \n"; int maxfdp1; int num_chat = 0; int clisock_list[max_sock]; int listen_sock; void addclient(int s, struct sockaddr_in *newcliaddr); int getmax(); void removeclient(int s); int tcp_listen(int host, int port, int backlog); void errquit(char *mesg) { perror(mesg); exit(1); int main(int argc, char *argv[]) { struct sockaddr_in cliaddr; char buf[maxline+1]; int i, j, nbyte, accp_sock, addrlen = sizeof(struct sockaddr_in);

Chapter 8. 프로세스 197 fd_set read_fds; pid_t pid; struct sigaction sact; FILE* flog; char szmsg[1024]; if(argc!= 2) { printf("usage :%s port\n", argv[0]); exit(0); if( ( pid = fork() )!= 0 ) exit( 0 ); setsid(); sact.sa_handler = SIG_IGN; sact.sa_flags = 0; sigemptyset( &sact.sa_mask ); sigaddset( &sact.sa_mask, SIGHUP ); sigaction( SIGHUP, &sact, NULL ); if( ( pid = fork() )!= 0 ) exit( 0 ); umask( 0 ); for( i = 0; i < MAX_SOCK; i++ ) close( i ); listen_sock = tcp_listen(inaddr_any, atoi(argv[1]), 5); while(1) { FD_ZERO(&read_fds); FD_SET(listen_sock, &read_fds); for(i=0; i<num_chat; i++) FD_SET(clisock_list[i], &read_fds); maxfdp1 = getmax() + 1; flog = fopen( "./daemon.log", "a" ); sprintf( szmsg, "wait for client\n" ); fwrite( szmsg, strlen(szmsg), 1, flog ); fclose( flog ); if(select(maxfdp1, &read_fds,null,null,null)<0) errquit("select fail"); if(fd_isset(listen_sock, &read_fds)) { accp_sock=accept(listen_sock, (struct sockaddr *)&cliaddr, &addrlen); if(accp_sock == 1) errquit("accept fail"); addclient(accp_sock,&cliaddr); send(accp_sock, START_STRING, strlen(start_string), 0); flog = fopen( "./daemon.log", "a" ); 한국외국어대학교컴퓨터공학과

198 네트워크프로그래밍및실습 sprintf( szmsg, "Added %dth User.\n", num_chat ); fwrite( szmsg, strlen(szmsg), 1, flog ); fclose( flog ); for(i = 0; i < num_chat; i++) { if(fd_isset(clisock_list[i], &read_fds)) { nbyte = recv(clisock_list[i], buf, MAXLINE, 0); if(nbyte<= 0) { removeclient(i); continue; buf[nbyte] = 0; if(strstr(buf, EXIT_STRING)!= NULL) { removeclient(i); continue; for (j = 0; j < num_chat; j++) send(clisock_list[j], buf, nbyte, 0); flog = fopen( "./daemon.log", "a" ); sprintf( szmsg, "%s\n", buf ); fwrite( szmsg, strlen(szmsg), 1, flog ); fclose( flog ); // end of while return 0; void addclient(int s, struct sockaddr_in *newcliaddr) { FILE* flog; char szmsg[1024]; char buf[20]; inet_ntop(af_inet,&newcliaddr >sin_addr,buf,sizeof(buf)); flog = fopen( "./daemon.log", "a" ); sprintf( szmsg, "new client: %s\n", buf ); fwrite( szmsg, strlen(szmsg), 1, flog ); fclose( flog ); clisock_list[num_chat] = s; num_chat++; void removeclient(int s) { FILE* flog; char szmsg[1024]; close(clisock_list[s]); if(s!= num_chat 1)

Chapter 8. 프로세스 199 clisock_list[s] = clisock_list[num_chat 1]; num_chat ; flog = fopen( "./daemon.log", "a" ); sprintf( szmsg, "Leave a User. Current Users = %d\n", num_chat ); fwrite( szmsg, strlen(szmsg), 1, flog ); fclose( flog ); int getmax() { int max = listen_sock; int i; for (i=0; i < num_chat; i++) if (clisock_list[i] > max ) max = clisock_list[i]; return max; int tcp_listen(int host, int port, int backlog) { int sd; struct sockaddr_in servaddr; sd = socket(af_inet, SOCK_STREAM, 0); if(sd == 1) { perror("socket fail"); exit(1); bzero((char *)&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(host); servaddr.sin_port = htons(port); if (bind(sd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("bind fail"); exit(1); listen(sd, backlog); return sd; 한국외국어대학교컴퓨터공학과

200 네트워크프로그래밍및실습

Chapter 9. 시그널

202 네트워크프로그래밍및실습 S E C T I O N 01 시그널종류 1. 시그널의종류 가. 시그널의정의 1) 소프트웨어인터럽트 2) 운영체제또는커널에서일반프로세스로보냄 3) 일반프로세스에서커널의도움을받아다른프로세스로시그널을넘김 나. 시그널의발생 1) kill() 시스템콜 int kill( pid_t pid, int sig ); 가 ) 다른프로세스에게시그널이발생되도록함 나 ) 프로세스 pid인프로세스에게 sig 시그널이발생 2) raise() int raise( int sig ); 가 ) 프로세스가자신에게시그널을보냄 다. 시그널의종류 1) 리눅스시그널의이름은 SIG로시작 2) signal.h 파일에소개되어있음 시그널 발생조건 SIGINT 인터럽트키(CTRL-C) 를입력했을때발생 SIGKILL SIGIO SIGPIPE SIGCHLD 강제종료시그널로서, 프로세스에서이시그널을무시하거나블록할수없음 비동기입출력이발생했을때전달 닫힌파이프나소켓에데이터르쓰거나읽기를시도할때발생 프로세스가종료되거나취소될때부모프로세스에게전달 SIGPWR 전원의중단및재시작시에 init 프로세스로전달 SIGTSTP 사용자가키보드에서중지키(CTRL-Z) 를눌렀을때발생 SIGSYS SIGURG SIGUSR1 SIGUSR2 잘못된시스템호출시에발생 대역외데이터를수신시에발생 사용자가임의의목적으로사용할수있는시그널

Chapter 9. 시그널 203 SIGHUP 터미널과연결이끊어졌을때세션리더에게보내짐 SIGQUIT 종료키(CTRL- ) 를눌렀을때전달 SIGILL SIGTRAP SIGABRT 프로세스가규칙에어긋난명령을수행하려고할때전달 프로그램이디버깅지점에도달하면전달 abort() 함수를호출하면발생 SIGFPE 숫자를 0으로나누거나연산에러시발생 SIGVTALRM 표 43 시그널의종류 setitimer() 함수에의한가상타이머시간만료를알림 한국외국어대학교컴퓨터공학과

204 네트워크프로그래밍및실습 S E C T I O N 02 시그널처리 1. 시그널처리기본동작 가. 정해진기본동작수행 1) 프로세스가시그널을수신시각시그널에따라처리할기본동작이미리정해져있음가 ) 대부분프로세스종료임나 ) SIGKILL, SIGSTOP 시그널 (1) 기본동작으로프로세스종료임 (2) 무시나시그널핸들러등록이허용되지않음 나. 사용자가지정한작업수행 1) 시그널핸들러수행가 ) 미리정해진함수를수행 2) 시그널무시가 ) 프로세스는시그널을무시하므로아무영향이없음 3) 시그널블록가 ) 해당시그널의수신시시그널대기큐에들어감 (1) 현재의동작을계속하고나중에시그널을처리나 ) bitmask 형태의시그널블록마스크에추가해서블록함다 ) 블록된시그널은중복되지않음 2. 시그널핸들러 가. 시그널집합 1) 여러개의시그널을한번에표시하기위해 sigset_t를사용 2) sigset_t 변수에는여러개의시그널을 bitmask 형태로누적하여표현 #include <signal.h> int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset(sigset_t *set, int signum); int sigdelset(sigset_t *set, int signum); int sigismember(const sigset_t *set, int signum); 3) sigemptyset() 가 ) 시그널집합을비어있는상태로초기화 4) sigfillset() 가 ) 시스템에정의되어있는모든시그널들을시그널집합에넣음

Chapter 9. 시그널 205 5) sigaddset() 가 ) 시그널집합에특정시그널을추가 6) sigdelset() 가 ) 시그널집합에서특정시그널을제거 7) sigismember() 가 ) 시그널집합안에특정시그널이존재하는지여부를확인 나. 시그널핸들러설정 1) sigaction() 함수 가 ) 시그널핸들러를등록하기위해 sigaction() 함수를사용 int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); 나 ) 인자 signum (1) 처리할대상이되는시그널 (2) SIGKILL과 SIGSTOP를제외한모든시그널이가능함 다 ) 인자 act (1) signum 시그널발생시동작을결정에참조되는구조체 (2) 시그널핸들러이름, 실행도중블록시킬시그널집합, 환경설정용 flag인자포함 2) sigaction 구조체 struct sigaction { void(*sa_handler)(int); void(*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void(*sa_restore)(void); 가 ) sa_handler, sa_sigaction (1) 시그널핸들러를가리킴 (2) 일반적으로 sa_handler를사용하고 sa_sigaction은부가적인정보를이용할때사용 나 ) sa_mask (1) 시그널핸들러가동작되는동안블록시킬시그널집합 (2) 시그널핸들러가실행되는도중에 sa_mask 없는시그널도착시 ( 가) 시그널핸들러는인터럽트되고새로도착한시그널에대한처리가진행됨 ( 나) 인터럽트를받지않는게안전하므로 sigfillset(&act.sa_mask) 로모두포함시킴 다 ) sa_restore (1) 사용되지않음 라 ) sa_flags (1) SA_RESTART ( 가) 인터럽트되었던시스템콜이시그널핸들러처리한후에계속수행되도록함 한국외국어대학교컴퓨터공학과

206 네트워크프로그래밍및실습 (2) SA_NOCLDWAIT ( 가) 좀비프로세스를만들지않도록하기위한플래그 ( 나) SIGCHLD 시그널처리시주로사용됨 (3) SA_NODEFER ( 가) 도착한시그널의수만큼핸들러가수행됨 (4) SA_SIGINFO ( 가) 미설정시시그널이발생하면 sa_handler에명시된핸들러함수가호출됨 ( 나) 설정시시그널이발생하면 sa_sigaction에명시된핸들러함수가호출됨 (5) SA_ONESHOT or SA_RESETHAND ( 가) 시그널핸들러가한번실행된후에시그널처리기본동작으로되돌아감 ( 나) 설치한시그널핸들러는한번만실행되고이후의시그널은 SIG_DFL에의해수행 3) signal_unsafe 함수 가 ) 결과를예측할수없는함수 (1) malloc(), free(), alarm(), sleep(), fcntl(), fork() 등 (2) 시그널핸들러외부의작업과충돌을일으킬가능성이있음 (3) 정적인변수나전역변수를사용 나 ) 호출해야할경우 (1) 호출보다핸들러내에서는특정플래그만설정 (2) 핸들러가종료된후에플래그를확인하고 signal_unsafe한함수를핸들러밖에서호출 4) signal() 함수 가 ) sigaction() 이소개되기이전에사용되던함수 나 ) 시그널핸들러를등록하거나시그널무시를설정함 다 ) 현재사용하지않기를권장함 3. 시그널처리예 예제 37 SIGINT 시그널에대한핸들러등록하는예제프로그렘 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <signal.h> int count; // 시그널핸들러 void catch_sigint(int signum) { printf("\n(count=%d) CTRL C pressed!\n",count); return;

Chapter 9. 시그널 207 int main(int argc, char *argv[]) { struct sigaction act; sigset_t masksets; int i; char buf[10]; sigfillset(&masksets); // 시그널핸들러설치 act.sa_handler = catch_sigint; // 시그널핸들러가실행되는동안모든시그널을블록함 act.sa_mask = masksets; act.sa_flags = 0; sigaction(sigint, &act, NULL); for(count=0; count<3 ; count++ ) read(0, buf, sizeof(buf)); return 0; 실행화면은다음과같다. CTRL-C를눌러도 catch_sigint() 가 SIGINT 를잡아서처리하므로종료가되지않는다. 시그널핸들러를 수행하는동안도착한모든시그널은블록된다. 한국외국어대학교컴퓨터공학과

208 네트워크프로그래밍및실습 S E C T I O N 03 SIGCHLD 와프로세스의종료 1. 프로세스의종료 가. SIGCHLD 시그널 1) 프로세스종료시자신의부모프로세스에게보내는시그널 2) 부모프로세스는원하면이시그널을받아처리함 나. wait() 1) 부모프로세스가자식프로세스의종료시점이나종료상태값을알기위해사용 pid_t pid; int stat; pid = wait(&stat); 2) 부모프로세스가자식프로세스가종료된것을확인하고다른작업을할경우 가 ) pid = wait(&stat); 의작업수행 (1) 자식프로세스가종료될때까지블록됨 (2) 종료상태값은 stat 인자로리턴됨 나 ) 위의작업이후에다른작업수행됨 3) 좀비상태발생 가 ) 자식프로세스종료후에부모프로세스가 wait() 호출로종료상태를읽지않을경우발생 나 ) 부모프로세스가나중에라도자식프로세스의종료상태를읽기위해좀비상태를만듬 다 ) 좀비상태의프로세스는사용자영역의메모리는모두 free임 라 ) 커널이관리하는메모리는남아있으므로메모리낭비가됨 4) SIGCHLD를잡아처리하는것으로 wait() 를대신할수있음 가 ) 인터럽트방식으로자식프로세스의종료시점파악 다. 기본동작으로서 SIGCHLD 를무시하는경우 1) 부모프로세스가자식프로세스의종료상태를읽을필요가없을때단순히무시 2) 부모프로세스가자식프로세스의종료시점을 SIGCHLD를통해파악할경우 가 ) 부모프로세스에서 (1) 부모프로세스는 wait() 함수에서대기 wait() 를호출한이후에자식프로세스가종료 (2) 자식프로세스가종료할때 wait() 함수가리턴 나 ) 부모프로세스가 (1) 자식프로세스가좀비가됨 wait() 를호출하기이전에자식프로세스가종료 (2) 부모프로세스는필요시나중에 wait() 를호출하여자식의종료상태를읽음 다 ) 부모프로세스가 wait() 를호출하지않고종료 (1) 시스템의 init 프로세스가좀비프로세스에대해부모프로세스역할을함 (2) 대신 wait() 를호출

Chapter 9. 시그널 209 라. SIG_IGN 으로 SIGCHLD 를무시하는경우 1) 시그널핸들러에 SIG_IGN을등록하여 SIGCHLD를무시 2) 기본동작으로 SIGCHLD를무시하는것과거의같은동작을함 가 ) 부모프로세스가 wait() 호출전에자식프로세스가종료되어도좀비가발생하지않음 나 ) 이미종료된자식프로세스의상태를얻지못함 다 ) wait() 호출시자식프로세스가없으면 -1을리턴 마. 시그널핸들러를등록한경우 1) 부모프로세스가자신의작업을수행하다가자식프로세스의종료시점에종료상태를알고자할 때 가 ) 미리 wait() 를호출하여블록될필요가없음 나 ) 시그널핸들러가실행되어도자식프로세스의좀비상태는끝나지않음 (1) wait() 를실행해야함 2. 프로세스의종료처리 예제 38 wait() 함수동작의이해 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> #include <errno.h> // 사용자시그널핸들러함수 void catch_sigchld(int signo) { puts("###( Parent ) catch SIGCHLD "); int chstat; // 종료상태값 int main(int argc, char *argv[]) { int i,n; struct sigaction sact; sact.sa_flags = 0; sigemptyset(&sact.sa_mask); sigaddset(&sact.sa_mask, SIGCHLD); // 시그널핸들러등록 sact.sa_handler = SIG_DFL; // 기본동작( 무시) //sact.sa_handler = SIG_IGN; // 시그널무시등록 //sact.sa_handler = catch_sigchld; // 사용자핸들러등록 sigaction(sigchld, &sact, NULL); // 자식프로세스생성 한국외국어대학교컴퓨터공학과

210 네트워크프로그래밍및실습 for(i=0; i<5; i++) { if(fork()==0) { if(i>2) sleep(6); // 부모의 wait() 호출보다늦게종료 printf("(%d번 Child),PID=%d,PPID=%d Exited\n",i, getpid(),getppid()); exit(13); // 종료값 13번 sleep(3); // 0,1,2 번자식이종료하기를기다림 puts(" "); system("ps a"); // 현재시스템의프로세스상태확인 puts(" "); puts("#( Parent ) wait 호출함"); for( ; ; ) { chstat = 1; // 초기화 n = wait(&chstat); printf("# wait = %d(child stat=%d)\n",n, chstat); if( n == 1) { if(errno ==ECHILD) { perror(" 기다릴자식프로세스가존재하지않음"); break; else if(errno == EINTR) { perror("wait 시스템콜이인터럽트됨"); continue; puts("#( Parent ) 종료함"); return 0;

Chapter 9. 시그널 211 다음실행화면 5개의프로세스중 wait() 호출이전에종료한 0, 1, 2 는좀비가됨을보여준다. 하지만 sact.sa_handler = SIG_IGN; 으로시그널무시를등록한경우, 좀비프로세스가발생하지않아 다음실행화면과같이 0, 1, 2 번프로세스의종료상태는영원히알수없다. 또한 sact.sa_handler = catch_sigchld; 로 SIGCHLD 시그널핸들러를등록한경우, 0, 1, 2번프로세스 가종료하면서발생한 SIGCHLD에의해시그널핸들러 catch_sigchld() 함수가호출된다. 한국외국어대학교컴퓨터공학과

212 네트워크프로그래밍및실습 부모프로세스에게아직종료하지않은자식프로세스가있으므로이후 로세스가종료하면부모프로세스에게 SIGCHLD 시그널이전달된다. wait() 로대기하다가자식프 가. 시스템콜인터럽트경우 1) 부모프로세스가다른시스템콜을호출한상태에서 SIGCHLD 시그널도착한경우 2) SIGCHLD의시그널핸들러함수를등록했다면 SIGCHLD 시스템콜을인터럽트시킴 3) 시그널처리가기본동작이나시그널무시상태이면시스템콜은아무영향받지않음 나. waitpid() 1) 특정 PID를갖는자식프로세스의종료만을기다림 2) 만약앞으로종료할자식프로세스가없을경우 가 ) 부모프로세스는영원히기다리게됨 나 ) flags에서 WNOHANG 옵션을사용해서문제해결 (1) waitpid() 호출시에실행중인자식프로세스가있을때에만블록 (2) 실행중이거나종료된자식프로세스가없으면블록되지않음

Chapter 9. 시그널 213 3. 연습문제풀이 가. 비동기형채팅서버프로그램에서 SIGINT 시그널을 3초이내에연속적으로두번받으면프로그램 을종료하도록수정하시오. ( 힌트: SIGINT 시그널이발생했을때시각을저장하고또한번의 SIGINT 시그널이발생하면이 전에저장한시각과의차이를구해서 3 초보다적으면종료하도록한다. 3초보다크면현재시각 을저장하고다음 SIGINT 시그널이발생하기를기다렸다가같은작업을반복한다. SIGINT 시그널 핸들러동작중에는모든시그널이블록되도록하고 sigaction 구조채의 sa_flags에는 SA_RESTART 를설정한다.) 예제 39 서버프로그램소스 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <fcntl.h> #include <sys/socket.h> #include <sys/file.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <errno.h> #include <signal.h> #define MAXLINE 511 #define MAX_SOCK 1024 char *EXIT_STRING = "exit"; char *START_STRING = "Connected to chat_server \n"; int maxfdp1; int num_chat = 0; int clisock_list[max_sock]; int listen_sock; time_t oldtime = 0; time_t curtime = 0; void addclient(int s, struct sockaddr_in *newcliaddr); int getmax(); void removeclient(int s); int tcp_listen(int host, int port, int backlog); void errquit(char *mesg) { perror(mesg); exit(1); // signal handler void catch_sigint( int signum ) { time( &curtime ); if( oldtime == 0 ) { oldtime = curtime; printf( "\nfirst CTRL C Pressed!\n" ); 한국외국어대학교컴퓨터공학과

214 네트워크프로그래밍및실습 else if( curtime oldtime > 3 ) { oldtime = curtime; printf( "\nfirst CTRL C Pressed!\n" ); else { printf( "\nsecond CTRL C Pressed! Program will be exit!\n" ); exit( 0 ); return; int main(int argc, char *argv[]) { struct sockaddr_in cliaddr; char buf[maxline+1]; int i, j, nbyte, accp_sock, addrlen = sizeof(struct sockaddr_in); fd_set read_fds; int n; struct sigaction act; sigset_t masksets; sigfillset( &masksets ); act.sa_handler = catch_sigint; act.sa_mask = masksets; act.sa_flags = SA_RESTART; sigaction( SIGINT, &act, NULL ); if(argc!= 2) { printf("usage :%s port\n", argv[0]); exit(0); listen_sock = tcp_listen(inaddr_any, atoi(argv[1]), 5); while(1) { FD_ZERO(&read_fds); FD_SET(listen_sock, &read_fds); for(i=0; i<num_chat; i++) FD_SET(clisock_list[i], &read_fds); maxfdp1 = getmax() + 1; puts("wait for client"); if( select( maxfdp1, &read_fds, NULL, NULL, NULL ) < 0 ) errquit("select fail"); if(fd_isset(listen_sock, &read_fds)) { accp_sock=accept(listen_sock, (struct sockaddr *)&cliaddr, &addrlen); if(accp_sock == 1) errquit("accept fail"); addclient(accp_sock,&cliaddr);

Chapter 9. 시그널 215 send(accp_sock, START_STRING, strlen(start_string), 0); printf("added %dth user.\n", num_chat); for(i = 0; i < num_chat; i++) { if(fd_isset(clisock_list[i], &read_fds)) { nbyte = recv(clisock_list[i], buf, MAXLINE, 0); if(nbyte<= 0) { removeclient(i); continue; buf[nbyte] = 0; return 0; if(strstr(buf, EXIT_STRING)!= NULL) { removeclient(i); continue; for (j = 0; j < num_chat; j++) send(clisock_list[j], buf, nbyte, 0); printf("%s\n", buf); void addclient(int s, struct sockaddr_in *newcliaddr) { char buf[20]; inet_ntop(af_inet,&newcliaddr >sin_addr,buf,sizeof(buf)); printf("new client: %s\n",buf); clisock_list[num_chat] = s; num_chat++; void removeclient(int s) { close(clisock_list[s]); if(s!= num_chat 1) clisock_list[s] = clisock_list[num_chat 1]; num_chat ; printf("leave a user. Current users = %d\n", num_chat); int getmax() { int max = listen_sock; int i; for (i=0; i < num_chat; i++) if (clisock_list[i] > max ) max = clisock_list[i]; return max; int tcp_listen(int host, int port, int backlog) { int sd; 한국외국어대학교컴퓨터공학과

216 네트워크프로그래밍및실습 struct sockaddr_in servaddr; sd = socket(af_inet, SOCK_STREAM, 0); if(sd == 1) { perror("socket fail"); exit(1); bzero((char *)&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(host); servaddr.sin_port = htons(port); if (bind(sd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("bind fail"); exit(1); listen(sd, backlog); return sd; sa_flags를 SA_RESTART로설정하였음에도 select() 함수에서인터럽트를발생하며멈췄다. 교재 190페 이지의내용처럼 DB 서버(Redhat Linux) 에서는 select() 함수가시그널이후다시시작되지않는예외적 인함수임을볼수있다. 나. 위의문제에서 SA_RESTART 플래스를해제하면결과가어떻게달라지는지확인하시오. 에러가발 생한다면에러의이유를알아보고 있도록프로그램을수정하시오. SA_RESTART 플래그를설정하지않고도정상적으로동작할수 ( 힌트: select() 가음수를리턴하면에러가발생한것이며이때 errno 변수에에러원인이저장된 다. 시그널에의해 select() 가실패한경우에는 errno는 EINTR로설정되므로 errno가 EINTR이면다 시한번 select() 함수를호출하도록수정하면된다. recv() 함수에대해서도마찬가지다.) 예제 40 서버프로그램소스 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <fcntl.h> #include <sys/socket.h> #include <sys/file.h> #include <netinet/in.h>

Chapter 9. 시그널 217 #include <arpa/inet.h> #include <unistd.h> #include <errno.h> #include <signal.h> #define MAXLINE 511 #define MAX_SOCK 1024 char *EXIT_STRING = "exit"; char *START_STRING = "Connected to chat_server \n"; int maxfdp1; int num_chat = 0; int clisock_list[max_sock]; int listen_sock; time_t oldtime = 0; time_t curtime = 0; void addclient(int s, struct sockaddr_in *newcliaddr); int getmax(); void removeclient(int s); int tcp_listen(int host, int port, int backlog); void errquit(char *mesg) { perror(mesg); exit(1); // signal handler void catch_sigint( int signum ) { time( &curtime ); if( oldtime == 0 ) { oldtime = curtime; printf( "\nfirst CTRL C Pressed!\n" ); else if( curtime oldtime > 3 ) { oldtime = curtime; printf( "\nfirst CTRL C Pressed!\n" ); else { printf( "\nsecond CTRL C Pressed! Program will be exit!\n" ); exit( 0 ); return; int main(int argc, char *argv[]) { struct sockaddr_in cliaddr; char buf[maxline+1]; int i, j, nbyte, accp_sock, addrlen = sizeof(struct sockaddr_in); 한국외국어대학교컴퓨터공학과

218 네트워크프로그래밍및실습 fd_set read_fds; int n; struct sigaction act; sigset_t masksets; sigfillset( &masksets ); act.sa_handler = catch_sigint; act.sa_mask = masksets; act.sa_flags = 0; sigaction( SIGINT, &act, NULL ); if(argc!= 2) { printf("usage :%s port\n", argv[0]); exit(0); listen_sock = tcp_listen(inaddr_any, atoi(argv[1]), 5); while(1) { FD_ZERO(&read_fds); FD_SET(listen_sock, &read_fds); for(i=0; i<num_chat; i++) FD_SET(clisock_list[i], &read_fds); maxfdp1 = getmax() + 1; puts("wait for client"); n = select( maxfdp1, &read_fds, NULL, NULL, NULL ); if( n == 1 && errno == EINTR ) { printf( "select function Interrupted\n" ); continue; else if( n < 0 ) errquit("select fail"); if(fd_isset(listen_sock, &read_fds)) { accp_sock=accept(listen_sock, (struct sockaddr *)&cliaddr, &addrlen); if(accp_sock == 1) errquit("accept fail"); addclient(accp_sock,&cliaddr); send(accp_sock, START_STRING, strlen(start_string), 0); printf("added %dth user.\n", num_chat); for(i = 0; i < num_chat; i++) { if(fd_isset(clisock_list[i], &read_fds)) { nbyte = recv(clisock_list[i], buf, MAXLINE, 0); if(nbyte<= 0) { removeclient(i); continue; buf[nbyte] = 0; if(strstr(buf, EXIT_STRING)!= NULL) {

Chapter 9. 시그널 219 return 0; removeclient(i); continue; for (j = 0; j < num_chat; j++) send(clisock_list[j], buf, nbyte, 0); printf("%s\n", buf); void addclient(int s, struct sockaddr_in *newcliaddr) { char buf[20]; inet_ntop(af_inet,&newcliaddr >sin_addr,buf,sizeof(buf)); printf("new client: %s\n",buf); clisock_list[num_chat] = s; num_chat++; void removeclient(int s) { close(clisock_list[s]); if(s!= num_chat 1) clisock_list[s] = clisock_list[num_chat 1]; num_chat ; printf("leave a user. Current users = %d\n", num_chat); int getmax() { int max = listen_sock; int i; for (i=0; i < num_chat; i++) if (clisock_list[i] > max ) max = clisock_list[i]; return max; int tcp_listen(int host, int port, int backlog) { int sd; struct sockaddr_in servaddr; sd = socket(af_inet, SOCK_STREAM, 0); if(sd == 1) { perror("socket fail"); exit(1); bzero((char *)&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(host); servaddr.sin_port = htons(port); if (bind(sd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("bind fail"); exit(1); 한국외국어대학교컴퓨터공학과

220 네트워크프로그래밍및실습 listen(sd, backlog); return sd; 다. 채팅서버프로그램을수정하여 SIGUSR1 시그널을받으면모든클라이언트에게서버레처음접속 한시간을보내고, 서버화면에는총접속자수와각클라이언트의접속한시간, 클라이언트의 IP 주소를출력하는프로그램을작성하시오. 예제 41 서버프로그램소스 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <fcntl.h> #include <sys/socket.h> #include <sys/file.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <errno.h> #include <signal.h> #include <sys/types.h> #define MAXLINE 511 #define MAX_SOCK 1024 char *EXIT_STRING = "exit"; char *START_STRING = "Connected to chat_server \n"; int maxfdp1; int num_chat = 0; int clisock_list[max_sock]; time_t clitime_list[max_sock]; char cliip_list[max_sock][20]; int listen_sock; struct sockaddr_in cliaddr;

Chapter 9. 시그널 221 char buf[maxline+1]; char buf2[maxline+1]; int i, j, nbyte, accp_sock, addrlen = sizeof(struct sockaddr_in); fd_set read_fds; int n; void addclient(int s, struct sockaddr_in *newcliaddr); int getmax(); void removeclient(int s); int tcp_listen(int host, int port, int backlog); void errquit(char *mesg) { perror(mesg); exit(1); // signal handler void catch_sigusr1( int signum ) { printf( "\ncurrent users : %d\n", num_chat ); for( i = 0; i < num_chat; i++ ) { printf( "%dth user's connected time : %d, IP addr : %s\n", i+1, clitime_list[i], cliip_list[i] ); for(i = 0; i < num_chat; i++) { for (j = 0; j < num_chat; j++) { sprintf( buf2, "Server Conntected Time is %d", clitime_list[j] ); send( clisock_list[j], buf2, strlen(buf2), 0 ); return; int main(int argc, char *argv[]) { struct sigaction act; sigset_t masksets; sigfillset( &masksets ); act.sa_handler = catch_sigusr1; act.sa_mask = masksets; act.sa_flags = 0; sigaction( SIGUSR1, &act, NULL ); if(argc!= 2) { printf("usage :%s port\n", argv[0]); exit(0); listen_sock = tcp_listen(inaddr_any, atoi(argv[1]), 5); while(1) { FD_ZERO(&read_fds); FD_SET(listen_sock, &read_fds); for(i=0; i<num_chat; i++) FD_SET(clisock_list[i], &read_fds); 한국외국어대학교컴퓨터공학과

222 네트워크프로그래밍및실습 maxfdp1 = getmax() + 1; puts("wait for client"); n = select( maxfdp1, &read_fds, NULL, NULL, NULL ); if( n == 1 && errno == EINTR ) { printf( "select function Interrupted\n" ); continue; else if( n < 0 ) errquit("select fail"); if(fd_isset(listen_sock, &read_fds)) { accp_sock=accept(listen_sock, (struct sockaddr *)&cliaddr, &addrlen); if(accp_sock == 1) errquit("accept fail"); addclient(accp_sock,&cliaddr); send(accp_sock, START_STRING, strlen(start_string), 0); printf("added %dth user.\n", num_chat); for(i = 0; i < num_chat; i++) { if(fd_isset(clisock_list[i], &read_fds)) { nbyte = recv(clisock_list[i], buf, MAXLINE, 0); if(nbyte<= 0) { removeclient(i); continue; buf[nbyte] = 0; return 0; if(strstr(buf, EXIT_STRING)!= NULL) { removeclient(i); continue; for (j = 0; j < num_chat; j++) { send(clisock_list[j], buf, nbyte, 0); printf("%s\n", buf); void addclient(int s, struct sockaddr_in *newcliaddr) { char buf[20]; inet_ntop(af_inet,&newcliaddr >sin_addr,buf,sizeof(buf)); strcpy( cliip_list[i], buf ); printf("new client: %s\n",buf); clisock_list[num_chat] = s; time( &clitime_list[num_chat] );

Chapter 9. 시그널 223 num_chat++; void removeclient(int s) { close(clisock_list[s]); if(s!= num_chat 1) clisock_list[s] = clisock_list[num_chat 1]; num_chat ; printf("leave a user. Current users = %d\n", num_chat); int getmax() { int max = listen_sock; int i; for (i=0; i < num_chat; i++) if (clisock_list[i] > max ) max = clisock_list[i]; return max; int tcp_listen(int host, int port, int backlog) { int sd; struct sockaddr_in servaddr; sd = socket(af_inet, SOCK_STREAM, 0); if(sd == 1) { perror("socket fail"); exit(1); bzero((char *)&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(host); servaddr.sin_port = htons(port); if (bind(sd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("bind fail"); exit(1); listen(sd, backlog); return sd; 한국외국어대학교컴퓨터공학과

224 네트워크프로그래밍및실습

Chapter 10. 프로세스간통신

226 네트워크프로그래밍및실습 S E C T I O N 01 파이프 운영체제가제공하는프로세스간통신채널로서특별한타입의파일 1. 파이프의정의 가. 운영체제가관리하는임시파일 1) 일반파일과달리메모리에저장되지않음 2) 데이터저장용이아님 3) 프로세스간데이터전달용으로사용나. 통신기능 1) 송신프로세스에서파이프에데이트를쓰고수신프로세스에서파이프에서데이터를읽음 2) 스트림채널을제공하여송신된데이터는바이트순서가유지됨 3) 같은컴퓨터내의프로세스간에스트림채널을제공 2. pipe() 가. 파이프를열기위한명령 #include <unistd.h> int pipe(int fd[2]); 나. 두개의파일디스크립터가생성됨 1) 0 번 : 파이프로부터데이터를읽음 2) 1 번 : 파이프에데이터를씀 3. fork() 호출 그림 165 파이프생성후 fork() 를호출 가. 파일디스크립터 fd[0], fd[1]

Chapter 10. 프로세스간통신 227 1) 부모와자식프로세스는모두파일디스크립터 fd[0] 로부터데이터를읽음 2) 부모와자식프로세스는모두파일디스크립터 fd[1] 로데이터를씀 4. 불필요한파일디스크립터해제 그림 166 사용하지않는파일디스크립터를닫음 가. 자식프로세스가부모프로세스로데이터를보내는경우 1) 자식프로세스는 fd[1] 로데이터를쓰고부모프로세스는 fd[0] 으로읽음 2) 자식프로세스의 fd[0] 과부모프로세스의 fd[1] 은사용하지않으므로닫음 5. 파이프를이용한에코서버프로그램 예제 42 파이프를통해에코메시지를전달하는 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include <sys/time.h> #include <sys/types.h> #include <sys/socket.h> #include <errno.h> #define MAX_BUFSZ 512 // 파이프에쓰는데이터구조 typedef struct mesg { struct sockaddr_in addr; // 클라이언트주소 char data[max_bufsz]; // 에코할데이터 mesg_t; void child_start(int sock, int pipefd[]); // 자식프로세스 void parent_start(int sock, int pipefd[]); // 부모프로세스 void errquit(char *mesg) { perror(mesg); exit(1); int main(int argc, char **argv) { UDP 에코서버 한국외국어대학교컴퓨터공학과

228 네트워크프로그래밍및실습 struct sockaddr_in servaddr; pid_t pid; int sock, pipefd[2], port, len = sizeof(struct sockaddr); if(argc!=2) { printf("\n Usage : %s port\n",argv[0]); exit(exit_failure); // 포트번호 port = atoi(argv[1]); sock = socket(af_inet,sock_dgram,0); if(sock<0) { perror("socket failed"); exit(exit_failure); bzero(&servaddr,len); servaddr.sin_addr.s_addr = htonl(inaddr_any); servaddr.sin_family=af_inet; servaddr.sin_port = ntohs(port); bind(sock, (struct sockaddr*)&servaddr, len); // 파이프생성 if(pipe(pipefd) == 1) errquit("pipe fail "); pid = fork(); if (pid < 0) errquit("fork fail"); else if(pid > 0) parent_start(sock, pipefd); else if(pid ==0) child_start(sock, pipefd); return 0; // 자식프로세스 void child_start(int sock,int pipefd[]) { mesg_t pmsg; int nbytes=0, len = sizeof(struct sockaddr); close(pipefd[1]); while(1) { // 부터읽기대기 nbytes = read(pipefd[0], (char *)&pmsg, sizeof(mesg_t)); if(nbytes < 0) errquit("read failed "); printf("child : read from pipe\n",nbytes);

Chapter 10. 프로세스간통신 229 // 파이프로부터읽은데이터를에코 nbytes = sendto(sock, &pmsg.data, strlen(pmsg.data), 0, (struct sockaddr*)&pmsg.addr, len); printf("child : %d bytes echo response\n",nbytes); printf(" \n"); // 부모프로세스 void parent_start(int sock, int pipefd[]) { mesg_t pmsg; int nbytes, len = sizeof(struct sockaddr); // 읽기파이프닫음 close( pipefd[0] ); printf("my echo server wait...\n"); while(1) { // 소켓으로부터읽기 nbytes = recvfrom(sock, (void*)&pmsg.data, MAX_BUFSZ, 0,(struct sockaddr*)&pmsg.addr,&len); if(nbytes < 0) errquit("recvfrom failed "); printf("parent : %d bytes recv from socket\n",nbytes); pmsg.data[nbytes]=0; // 소켓으로부터읽은데이터를파이프에쓰기 if( write(pipefd[1], (char*)&pmsg, sizeof(pmsg))<0 ) perror("write fail "); printf("parent : write to pipe\n",nbytes); 예제 43 udp 에코클라이언트 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> int main(int argc, char *argv[]) { struct sockaddr_in peer; int sock; int nbytes; char buf[512]; 한국외국어대학교컴퓨터공학과

230 네트워크프로그래밍및실습 if(argc!= 3) { printf("usage : %s ip port\n",argv[0]); exit(1); // UDP 소켓생성및바인딩 sock = socket(af_inet,sock_dgram,0); bzero(&peer,sizeof(struct sockaddr)); peer.sin_family=af_inet; peer.sin_port = htons(atoi(argv[2])); peer.sin_addr.s_addr = inet_addr(argv[1]); // 키보드입력을받고서버로보내는부분 while( fgets(buf, sizeof(buf), stdin)!= NULL) { nbytes = sendto(sock, buf, strlen(buf), 0, (struct sockaddr*)&peer, sizeof(peer)); if(nbytes<0) { perror("sendto fail "); exit(0); nbytes = recvfrom(sock, buf, sizeof(buf) 1, 0,0,0); if(nbytes<0) { perror("recvfrom fail "); exit(0); buf[nbytes] = 0; fputs(buf, stdout); close(sock); exit(0); 실행화면은다음과같다.

Chapter 10. 프로세스간통신 231 가. 동작설명 1) 'test message' 라는빈칸포함 12 바이트에문장끝 NULL' 문자까지 13바이트가쓰임 2) 부모프로세스는클라이언트로부터받은메시지를파이프에씀 3) 자식프로세스는파이프로부터데이터를읽어서클라이언트로에코해줌 한국외국어대학교컴퓨터공학과

232 네트워크프로그래밍및실습 S E C T I O N 02 FIFO(First In First Out) 임의의프로세스에서파이프에접근하는 named pipe 1. FIFO의정의 가. 파이프의한계극복 1) 파이프는자식 부모프로세스간의통신에만사용됨 2) 파이프에이름을지정해가족관계가아닌임의의다른프로세스에서접근 2. mkfifo() int mkfifo(const char *pathname, mode_t mode); 가. pathname 인자 1) 파이프의이름지정 2) 경로명없이파이프의이름만인자로주면현재디렉토리에 FIFO 생성 나. mode 1) 생성되는 FIFO의파일접근권한설정 2) 일반파일생성과마찬가지로최종적으로 umask 값에영향을받음 3. FIFO를이용한프로세스간통신 그림 169 FIFO를이용한프로세스간통신 가. 나. FIFO도파이프의일종으로프로세스간에스트림채널을형성 FIFO는파이프와달리파일이름으로액세스함 4. FIFO를이용한에코서버프로그램 예제 44 FIFO를이용하여에코메시지를전달 #include <stdio.h> #include <stdlib.h>

Chapter 10. 프로세스간통신 233 #include <string.h> #include <unistd.h> #include <netinet/in.h> #include <sys/time.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #define MAX_BUFSZ 512 #define FIFONAME "myfifo" typedef struct mesg { struct sockaddr_in addr; // 클라이언트주소 char data[ MAX_BUFSZ ]; // 읽은데이터 mesg_t; void child_start(int sock); // 자식프로세스 void parent_start(int sock); // 부모프로세스 void errquit(char *mesg) { perror(mesg); exit(1); int main(int argc, char **argv) { struct sockaddr_in servaddr; pid_t pid; int sock, port, len = sizeof(struct sockaddr); if(argc!=2) { printf("\n Usage : %s port\n",argv[0]); exit(exit_failure); port = atoi(argv[1]); sock = socket(af_inet,sock_dgram,0); if(sock<0) { perror("socket failed"); exit(exit_failure); bzero(&servaddr,len); servaddr.sin_addr.s_addr = htonl(inaddr_any); servaddr.sin_family = AF_INET; servaddr.sin_port = ntohs(port); bind(sock, (struct sockaddr*)&servaddr, len); // FIFO 생성 if(mkfifo(fifoname,0660) == 1 && errno!=eexist) errquit("mkfifo fail "); 한국외국어대학교컴퓨터공학과

234 네트워크프로그래밍및실습 pid = fork(); if (pid < 0 ) errquit("fork fail"); else if(pid > 0) parent_start(sock); else if(pid ==0) child_start(sock); return 0; // 자식프로세스 void child_start(int sock) { mesg_t pmsg; int nbytes, fiford, // 읽기모드의 FIFO len = sizeof(struct sockaddr); // 읽기모드로 FIFO open fiford = open(fifoname,o_rdonly); if(fiford== 1) errquit("fifo open fail "); while(1) { // 파이프로부터읽기대기 nbytes = read(fiford, (char *)&pmsg,sizeof(pmsg)); if(nbytes<0) errquit("read failed "); printf("child : read from fifo\n",nbytes); // 파이프로부터읽은데이터를클라이언트로전송 nbytes = sendto(sock, &pmsg.data, strlen(pmsg.data),0,(struct sockaddr*)&pmsg.addr, len); printf("child : %d bytes echo response\n",nbytes); printf(" \n"); // 부모프로세스 void parent_start(int sock) { mesg_t pmsg; int nbytes, fifowd, // 쓰기모드의 FIFO len = sizeof(struct sockaddr); // 쓰기모드로 FIFO open fifowd = open(fifoname,o_wronly); if(fifowd== 1) errquit("fifo open fail "); printf("my echo server wait...\n"); while(1) { // 소켓으로부터읽기대기 nbytes = recvfrom(sock, (void*)&pmsg.data,

Chapter 10. 프로세스간통신 235 MAX_BUFSZ, 0,(struct sockaddr*)&pmsg.addr,&len); if(nbytes < 0) errquit("recvfrom failed "); pmsg.data[ nbytes ]=0; printf("parent : %d bytes recv from socket\n",nbytes); // 소켓으로부터읽은데이터를 FIFO에쓰기 if( write(fifowd, &pmsg, sizeof(pmsg))<0) perror("write fail "); printf("parent : write to fifo\n",nbytes); 서버프로그램의실행화면 클라이언트프로그램의실행화면 fifo 파일이생성되었음을볼수있다. 가. 동작설명 1) 부모프로세스는클라이언트로부터받은메시지를 FIFO에씀 2) 자식프로세스는 FIFO로부터데이터를읽어서클라이언트로에코해줌 3) 디렉토리에서버프로그램에서정한대로 FIFO 파일이생성됨 가 ) 여기서는 myfifo' 로생성됨 한국외국어대학교컴퓨터공학과

236 네트워크프로그래밍및실습 S E C T I O N 03 메시지큐 메시지단위의송수신용메시지큐 그림 173 메시지큐를이용한프로세스간통신 1. 메시지큐의정의 가. 특정프로세스를대상으로메시지를전송할수있음 나. 메시지전송에우선순위를줄수있음 다. 메시지큐의기능 1) 임의의프로세스는메시지큐에메시지를쓰거나(put), 읽을수(get) 있음 라. 메시지큐는일차원적인큐를제공하지않음 1) 여러개의큐를동시에제공하는 2차원큐로제공됨 2. 타입이있는메시지큐 가. 동작방식 1) 각타입의메시지큐에메시지가있을때 2) 메시지송수신시프로세스들은타입을지정하여야함 가 ) 메시지큐에메시지를쓰거나읽을때타입을저정 3) 메시지를쓸때에수신할프로세스의 PID 값을타입값으로지정 4) 해당프로세스에서는자신의 PID를타입값으로하여메시지를읽음 5) 지정된타입에서꺼내올메시지가없을경우 가 ) 그보다작은타입의큐에서메시지를꺼내옴 나. 메시지큐의우선순위 1) 타입값을우선순위로사용하면우선순위가높은타입값이큰메시지를먼저수신 2) 우선순위가높은메시지가없는때에만우선순위가낮은메시지를처리 3) 메시지를쓰인순서나우선순위순으로읽을수있음

Chapter 10. 프로세스간통신 237 그림 174 타입이있는메시지큐 3. 메시지큐생성 가. msgget() int msgget(key_t key, int msgflg); 1) key 가 ) 메시지큐를구분하기위한고유키 나 ) 다른프로세스에서이메시지큐에접근하기위해서는 2) msgflg 인자 가 ) 메시지큐의생성시옵션을지정 나 ) bitmask 형태의인자를취함 key 값을알아야함 다 ) IPC_CREAT, IPC_EXCL등의상수와유닉스파일접근권한도 bitmask 형태로추가지정가능 나. msgflg 설정 int mode = 0660; int msgflag = IPC_CREAT mode; int msgid = msgget(key, size, msgflag); 1) msgget() 을호출시똑같은 key 값을사용하는메시지큐존재시그객체에대한 ID를리턴 2) key 값에대한메시지큐객체가존재하지않으면새로운메시지큐객체를생성하고 ID를리턴 다. msgflg 추가설정 한국외국어대학교컴퓨터공학과

238 네트워크프로그래밍및실습 int mode = 0660; int msgflag = IPC_CREAT IPC_EXCL mode; int msgid = msgget(key, size, msgflag); 1) IPC_EXCL을추가설정하여 key 값을사용하는메시지큐존재시 msgget() 실패후 -1 리턴 2) IPC_EXCL은 IPC_CREAT와같이사용하여야함 3) key 값에 IPC_PRIVATE를넣을경우 key가없는메시지큐생성 가 ) 메시지큐 ID 를공유하는부모 자식프로세스간은통신하나외부프로세스는접근불가 4. 메시지송수신 가. msgsnd() int msgsnd(int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg); 1) 메시지큐에메시지를넣는함수 2) msqid 인자 가 ) 메시지큐객체의 ID 3) smsgp 인자 가 ) msgbuf 인자의첫 4바이트는반드시 long 타입이어야함 나 ) mtype는메시지타입 (1) 반드시 1 이상이어야함 다 ) mtext (1) 메시지데이터 (2) 문자열일필요는없으며 binary등임의의데이터를가리킬수있음 4) smsgsz 인자 가 ) 메시지데이터의길이를나타내며 5) smsgflg 인자 가 ) 나 ) 0으로한경우 mtext의크기만을나타냄 (1) msgsnd() 호출시메시지큐공간이부족하면블록됨 IPC_NOWAIT로한경우 (1) 메시지큐공간이부족한경우블록되지않고 EAGAIN 에러코드와 -1을리턴 나. msgrcv() ssize_t msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long msgtype, int msgflg); 1) 메시지큐로부터메시지를읽는함수 2) msqid 인자 가 ) 메시지큐객체의 ID 3) msgp 인자

Chapter 10. 프로세스간통신 239 가 ) 메시지큐로부터읽은메시지를저장하는수신공간 나 ) msgbuf 4) msgsz 인자 가 ) 수신공간의크기 5) msgtype 인자 타입의구조체를가리킴 가 ) 읽을메시지의타입을지정 나 ) 0이면타입의구분없이메시지큐에입력된순서대로메시지를읽음 다 ) 음수값을넣으면특별한동작을함 (1) 예로 -10이면타입이 10보다같거나작은메시지를읽는데 10인메시지부터우선순위 (2) 우선순위큐의구현에사용할수있음 6) msgflag 인자 가 ) 메시지큐에메시지가없는경우취할동작을지정 나 ) 0이면데이터가없을때 msgrcv() 함수는대기 다 ) IPC_NOWAIT이면 EAGAIN 에러코드와 -1 리턴 라 ) 읽을메시지가수신공간의크기보다클경우 (1) E2BIG 에러발생 마 ) MSG_NOERROR로설정시 msgsz 크기만큼만읽고뒷부분은잘려짐 5. 메시지큐제어 가. msgctl() 1) 메시지큐를제어 가 ) 정보읽기, 동작허가권한변경, 메시지큐삭제등 2) 사용문법 int msgctl(int msqid, int cmd, struct msqid_ds *buf); 가 ) msqid (1) 메시지큐객체 ID 나 ) cmd (1) 제어명령구분 (2) IPC_STAT로메시지큐객체를얻은후메시지큐객체를변경후 IPC_SET호출 (3) IPC_STAT : 메시지큐객체에대한정보를얻어오는명령 (4) IPC_SET : r/w 권한, euid, egid, msg_qbytes를변경하는명령 (5) IPC_RMID : 메시지큐를삭제하는명령 6. 메시지큐이용예 예제 45 에코메시지를수신하여메시지큐에넣어줌 #include <stdio.h> 한국외국어대학교컴퓨터공학과

240 네트워크프로그래밍및실습 #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <fcntl.h> #include <netinet/in.h> #include <errno.h> #include <sys/ipc.h> # include <sys/msg.h> #define MAX_BUFSZ 512 #define RES_SEND_PROC "./res_send" // 메시지구조정의 typedef struct _msg { long msg_type; struct sockaddr_in addr; char msg_text[ MAX_BUFSZ ]; msg_t ; void fork_and_exec(char *key, char *port); void errquit(char *mesg) { perror(mesg); exit(1); int main(int argc, char *argv[]) { struct sockaddr_in servaddr; msg_t pmsg; key_t key; int msqid, size, nbytes, sock, port, len = sizeof(struct sockaddr); if(argc!= 3) { printf("usage: %s msgq_key port\n",argv[0]); exit(1); key = atoi(argv[1]); port = atoi(argv[2]); // 메시지큐생성 msqid = msgget(key, IPC_CREAT 0600); if(msqid == 1) errquit("msgget fail "); // 소켓생성및바인딩 sock = socket(af_inet, SOCK_DGRAM, 0); if(sock<0) errquit("socket fail "); bzero(&servaddr,len); servaddr.sin_port = htons(port); servaddr.sin_addr.s_addr = htonl(inaddr_any); servaddr.sin_family=af_inet;

Chapter 10. 프로세스간통신 241 if(bind(sock, (struct sockaddr*)&servaddr, len) < 0) errquit("bind fail "); fork_and_exec(argv[1], argv[2]); fork_and_exec(argv[1], argv[2]); fork_and_exec(argv[1], argv[2]); pmsg.msg_type = 1; size = sizeof(msg_t) sizeof(long); puts("server starting..."); while(1) { nbytes = recvfrom(sock,pmsg.msg_text, MAX_BUFSZ, 0, (struct sockaddr *)&pmsg.addr, &len); if(nbytes < 0) { perror("recvfrom fail "); continue; pmsg.msg_text[ nbytes ] = 0; // 메시지큐에쓰기 if( msgsnd(msqid, &pmsg, size, 0)== 1 ) errquit("msgsnd fail "); return 0; void fork_and_exec(char *key, char *port) { pid_t pid = fork(); if (pid < 0) errquit(" fork fail"); else if(pid >0) return; execlp(res_send_proc, RES_SEND_PROC, key, port, 0); perror("execlp fail "); 예제 46 메시지큐에서에코메시지를읽어서응답해줌 (res_send) #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <signal.h> #include <sys/ipc.h> #include <sys/msg.h> #define MAX_BUFSZ 512 typedef struct _msg { 한국외국어대학교컴퓨터공학과

242 네트워크프로그래밍및실습 long msg_type; struct sockaddr_in addr; char msg_text[max_bufsz]; msg_t ; // 메시지큐에서읽어서응답하기 int qread_and_echoreply(int msqid, int sock); void errquit(char *mesg) { perror(mesg); exit(1); int main(int argc, char **argv) { struct sockaddr_in servaddr; key_t key; int sock, port, msqid, len = sizeof(struct sockaddr); key = atoi(argv[1]); port = atoi(argv[2]); if((msqid=msgget(key,0))== 1) errquit("rep msgget fail "); // 소켓생성및바인딩 sock = socket(af_inet,sock_dgram, 0); bzero(&servaddr,len); servaddr.sin_family=af_inet; servaddr.sin_port = htons(port); servaddr.sin_addr.s_addr = htonl(inaddr_any); bind(sock, (struct sockaddr*)&servaddr, len); // msg queue에서읽어서 reply qread_and_echoreply( msqid, sock); return 0; // 메시지큐에서읽어서응답하기 int qread_and_echoreply(int msqid, int sock) { int size, len = sizeof(struct sockaddr); msg_t pmsg; size = sizeof(msg_t) sizeof(long); pmsg.msg_type = 0; for( ; ; ) { if(msgrcv( msqid,(void *)&pmsg, size, 0, 0) < 0 ) errquit("msgrcv fail "); printf("reply process PID = %d\n", getpid()); printf(pmsg.msg_text); if(sendto(sock, pmsg.msg_text, strlen(pmsg.msg_text), 0, (struct sockaddr*)&pmsg.addr,len) < 0) errquit("serv2 sendto fail "); pmsg.msg_text[0] = 0; return 0;

Chapter 10. 프로세스간통신 243 실행화면은다음과같다. 가. 동작설명 1) res_send.c는 exec가호출될때인자로넘어온 msgq key와 port 값으로부터해당메시지큐의키 와포트번호를얻음 2) 메시지큐에서읽기를대기하다가메시지가도착하면클라이언트에게응답메시지전송 한국외국어대학교컴퓨터공학과

244 네트워크프로그래밍및실습 S E C T I O N 04 공유메모리 프로세스들이공통으로사용할수있는메모리영역 1. 공유메모리의정의 가. 프로세스들이공통으로사용하는메모리영역 1) 어떤프로세스에서사용중인메모리는그프로세스만접근가능 2) 공유메모리는특정메모리영역을다른프로세스와공유가 ) 프로세스간통신가능 3) 통신에서데이터를한번읽어도데이터가계속남아있음 4) 같은데이터를여러프로세스가중복하여읽을경우효과적 2. 공유메모리생성 가. shmget() int shmget(key_t key, int, size, int msgflg); 나. 1) key 가 ) 공유메모리를구분하기위한고유키 나 ) 다른프로세스에서이공유메모리에접근하기위해서는 2) shmflg 인자 가 ) 공유메모리의생성시옵션을지정 나 ) bitmask 형태의인자를취함 key 값을알아야함 다 ) IPC_CREAT, IPC_EXCL등의상수와유닉스파일접근권한도 bitmask 형태로추가지정가능 shmflg 설정 int mode = 0660; int shmflag = IPC_CREAT mode; int shmid = shmget(key, size, shmflag); 1) shmget() 을호출시똑같은 key 값을사용하는공유메모리존재시그객체에대한 ID를리턴 2) key 값에대한공유메모리객체가존재하지않으면새로운공유메모리객체를생성하고 ID를리 턴 다. shmflg 추가설정

Chapter 10. 프로세스간통신 245 int mode = 0660; int shmflag = IPC_CREAT IPC_EXCL mode; int shmid = shmget(key, size, shmflag); 1) IPC_EXCL을추가설정하여 key 값을사용하는공유메모리존재시 shmget() 실패후 -1 리턴 2) IPC_EXCL은 IPC_CREAT와같이사용하여야함 3) key 값에 IPC_PRIVATE를넣을경우 key가없는공유메모리생성 4) 공유메모리 ID 를공유하는부모 자식프로세스간은통신하나외부프로세스는접근불가 3. 공유메모리첨부 가. 공유메모리의물리적주소를자신의프로세스의가상메모리주소로매핑나. shmat() void *shmat(int shmid, const void *shmaddr, int shmflg); 1) shmid 인자 : 공유메모리객체 ID 가 ) shmaddr : 프로세스의메모리주소중첨부시킬주소 (1) 0을넣으면커널이자동으로빈공간을찾아서처리함 나 ) shmflg (1) SHM_RDONLY : 공유메모리를읽기전용으로첨부 (2) 0 : 공유메모리에읽기, 쓰기가능 다 ) 정상작동시프로세스내의첨부된주소리턴 라 ) 에러발생시 NULL 포인터리턴 4. 공유메모리의분리 가. 공유메모리사용종료후자신이사용하던메모리영역에서분리나. shmdt() int shmdt(const void *shmaddr); 1) shmaddr 인자 가 ) shmdt() 가리턴했던주소 나 ) 현재프로세스에첨부된공유메모리의시작주소 다. 공유메모리가메모리에서삭제된것이아님 1) 현재프로세스에서만사용하지못함 2) 다른프로세스에서는계속사용가능 라. 공유메모리객체 shmid_ds 구조체의 shm_nattch 멤버변수 1) 현재공유메모리를첨부하고있는프로세스의수를나타냄 2) shmat() 호출시 1증가 3) shmdt() 호출시 1감소 한국외국어대학교컴퓨터공학과

246 네트워크프로그래밍및실습 5. 공유메모리제어 가. chmctl() int shmctl(int shmid, int cmd, struct shmid_ds *buf); 1) 공유메모리를제어 가 ) 정보읽기, 동작허가권한변경, 공유메모리삭제등 2) 사용문법 가 ) shmid (1) 공유메모리객체 ID 나 ) cmd (1) 제어명령구분 (2) IPC_STAT : 공유메모리객체에대한정보를얻어오는명령 (3) struct shmid_ds *buf 인자에공유메모리객체복사 (4) IPC_SET : r/w 권한, euid, egid, msg_qbytes를변경하는명령 (5) IPC_RMID : 공유메모리를삭제하는명령 다 ) buf (1) cmd에따라의미가바뀌는인자 (2) 공유메모리객체정보를얻어오는명령어에서는얻어온객체를 buf에저장 (3) 동작허가권한을변경하는명령에서는변경할내용을 buf에저장 6. 공유메모리정보얻기 가. 공유메모리객체정보얻기 struct shmid_ds shmds; shmctl(shmid, IPC_STAT, buf); 1) IPC_STAT명령으로공유메모리객체정보얻음 2) buf 인자에저장 나. 공유메모리객체의동작허가권한변경 shmctl(shmid, IPC_SET, &shmds); 1) 기존의공유메모리객체의 shmds.shm_perm.uid, shmds.shm_perm.gid, shmds.shm_perm.mode 값만변경됨 2) 위의값들이변경되면 shmid_ds의 chm_ctime값이변경된시각으로바뀜 다. 공유메모리객체의접근권한변경

Chapter 10. 프로세스간통신 247 struct shmid_ds shmds; shmctl(shmid, IPC_STAT, &shmds); shmds.shm_perm.uid = changeuid; shmds.shm_perm.gid = changegid; shmds.shm_perm.mode = changemode; shmctl(shmid, IPC_SET, &shmds); 1) struct shmid_ds shmds; : 공유메모리객체선언 2) shmctl(shmid, IPC_STAT, &shmds); : 공유메모리객체얻어옴 3) shmds.shm_perm.uid = changeuid; : 실행권한 uid 값을변경 4) shmds.shm_perm.gid = changegid; : 실행권한 gid 값을변경 5) shmds.shm_perm.mode = changemode; : 접근권한변경 6) shmctl(shmid, IPC_SET, &shmds); : 공유메모리객체내용변경 7. 공유메모리삭제 가. shmctl() 의 IPC_RMID 명령을이용 shmctl(shmid, IPC_RMID, 0); 나. 공유메모리삭제요청시하나이상의다른프로세스가이공유메모리를사용시공유메모리는삭제 되지않음 1) shm_nattach 값이 0이될때까지기다린후삭제됨 8. 공유메모리의동기화문제 가. 동기화문제 1) 하나의공유데이터를둘이상의프로세스가동시에접근하는문제 예제 47 공유메모리에서동기화문제를보임 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/types.h> void errquit(char *msg){perror(msg); exit(1); // 자식프로세스를생성하여 busy() 함수를호출하도록함 void fork_and_run(); // 각프로세스가공유메모리에경쟁적으로접근하는함수 void busy(); // 공유메모리에접근하는함수 한국외국어대학교컴퓨터공학과

248 네트워크프로그래밍및실습 void access_shm(int count); char *shm_data; // 공유메모리포인터 int shmid; // 공유메모리 ID int main(int argc, char *argv[]) { key_t shmkey; // 공유메모리키 if(argc<2) { printf("usage : %s shmkey\n",argv[0]); exit(1); shmkey = atoi(argv[1]); shmid = shmget(shmkey, 128, IPC_CREAT 0660); if(shmid<0) errquit("shmget fail "); shm_data = (char *)shmat(shmid, (void*)0, 0); if(shm_data == (char*) 1) errquit("shmat fail"); fork_and_run(); fork_and_run(); busy(); wait(null); shmctl(shmid,ipc_rmid,0); // 공유메모리제거 return 0; // 자식프로세스생성및 busy() void fork_and_run() { pid_t pid = fork(); if (pid < 0) errquit("fork fail"); else if(pid == 0) { busy(); exit(0); return ; 수행 // 자식프로세스가수행하는함수 void busy() { int i=0; for( i=0;i<500000 ;i++ ) access_shm(i); shmdt(shm_data); // 공유메모리분리 // 공유메모리에접근을하는부분 void access_shm(int count) { int i; pid_t pid;

Chapter 10. 프로세스간통신 249 // 공유메모리에자신의 PID 기록 sprintf(shm_data, "%d", getpid()); // 공유메모리접근시간에포함 for(i=0; i<1000; i++); pid = atoi(shm_data); // 공유메모리에기록한 PID가자신의 PID가아니면 Error if(pid!= getpid()) printf("error(count=%d) : 다른프로세스도동시에공유메모리접근함\n",count); else { // 정상이며아무출력을하지않음 return; 실행화면은다음과같다. 나. 동작설명 1) 공유메모리를생성하고 fork() 를두번호출하여자식프로세스를두개생성함 2) 부모프로세스를포함한 3개의프로세스가 busy() 를통해공유메모리에경쟁적으로접근함 한국외국어대학교컴퓨터공학과

250 네트워크프로그래밍및실습 S E C T I O N 05 세마포어 한순간에공유데이터를액세스하는프로세스수를하나로제한 1. 세마포어의정의 가. 어떤공유데이터에대해현재사용가능한데이터의수나. 크리티컬영역 1) 동기화문제가발생될수있는코드블록 2. 세마포어생성 가. semget() int semget(key_t key, int nsems, int semflg); 1) key : 세마포어를구분하기위해지정해주는키 2) nsems : 세마포어집합을구성하는멤버의수 3) semflg : 세마포어생성에관한옵션을설정하거나세마포어객체에대한접근권한설정 4) 성공적으로수행시세마포어집합에관한정보를담고있는세마포어객체생성 5) 세마포어객체 ID를리턴 3. 플래그사용예 가. IPC_CREAT semget(key, nsems, IPC_CREAT mode); 1) key에해당하는세마포어객체가존재하면기존의세마포어 ID를리턴 2) key에해당하는세마포어객체가없으면새로생성하고세마포어객체 ID 리턴 3) semget(key, 0, 0) 가 ) key값을갖는세마포어의 ID를단순히알기위해사용 나. IPC_EXCL mode = 0660; semget(key, nsems, IPC_CREAT IPC_EXCL mode); 1) key값에 IPC_PRIVATE를사용하면 key가없는세마포어객체생성 가 ) 키가없는세마포어이므로다른프로세스에서접근못함 2) 세마포어생성후 fork() 를호출하면자식프로세스는세마포어객체의 ID를상속받음 가 ) semget() 함수를사용하지않고세마포어에접근

Chapter 10. 프로세스간통신 251 4. 세마포어객체 struct semid_ds { struct ipc_perm sem_perm; time_t sem_otime; time_t sem_ctime; struct sem *sem_base; struct wait_queue *eventn; struct wait_queue *eventz; struct sem_undo *undo; ushort sem_nsems; ; 가. struct ipc_perm sem_perm; 1) 접근허가내용나. time_t sem_otime; 1) 최근세마포어조작시간다. time_t sem_ctime; 1) 최근변경시각라. struct sem *sem_base; 1) 첫세마포어포인터마. ushort sem_nsems; 1) 세마포어멤버수 5. 세마포어연산 가. 세마포어의값을증가또는감소하는것나. semop() int semop(int semid, struct sembuf *operations, unsigned nsops); struct sembuf { ; short sem_num; short sem_op; short sem_flg; 1) semid : 세마포어 ID 2) operations : sembuf 타입의구조체를가짐 가 ) 나 ) short sem_num; - 멤버세마포어번호( 첫번째멤버세마포어는 0) short sem_op; - 다 ) short sem_flg; 세마포어연산내용 (1) 조작플래그, 0, IPC_NOWAIT, SEM_UNDO등의값을 bitmask 형태로가질수있음 한국외국어대학교컴퓨터공학과

252 네트워크프로그래밍및실습 (2) IPC_NOWAIT시세마포어값이부족해도블록되지않음 (3) SEM_UNDO시프로세스의종료시커널은해당세마포어연산취소 3) nsops : 두번째인자 operations 구조체가몇개의리스트를가지고있는지나타냄 6. 세미포어제어 가. semctl() int semctl(int semid, int member_index, int cmd, union semun semarg); 1) 세마포어의사용종료, 세마포어값읽기및설정, 특정멤버세마포어를기다리는프로세스의수 알기에사용 2) semid 가 ) 제어할대상의세마포어 ID 3) member_index 가 ) 세마포어멤버번호 4) cmd 가 ) 수행할동작 5) semum 타입의 semarg 인자 가 ) cmd의종류에따라각각다르게사용될수있는인자 나. semum 공용체 union semum { ; 1) int val; 가 ) int val; struct semid_ds *buf; unsigned short int *array; struct seminfo * buf; void * pad; SETVAL을위한값 2) struct semid_ds *buf; 가 ) IPC_STAT, IPC_SET을위한버퍼 3) unsigned short int *array; 가 ) GETALL, SETALL을위한배열 4) struct seminfo * buf; 가 ) IPC_INFO를위한버퍼 5) void * pad; 가 ) dummy 다. IPC_STAT

Chapter 10. 프로세스간통신 253 struct semid_ds semobj; union semun semarg; semarg.buf = &semobj; semctl(semid, 0, IPC_STAT, semarg); 1) semid_ds타입의세마포어객체를얻어오는명령 2) union semun semarg인자에세마포어객체가리턴 3) semarg.buf 포인터를통해세마포어객체를접근 4) IPC_STAT 명령을사용할때엔 semctl() 하수의 member_index 인자는사용되지않음(0 으로설정) 라. SETVAL union semun semarg; unsigned short semvalue = 5; semarg.val = semvalue; semctl(semid, 1, SETVAL, semarg); 1) 세마포어를생성한후에공유데이터의수를설정해주는작업 2) 세마포어객체의초기화를하는함수 3) union semun semarg 인자에는설정하려는세마포어의값을지정 4) semarg인자의 semarg.val 변수에원하는초기값을입력 5) 위의예에서는1번멤버세마포어의값을5으로설정 마. SETALL unsigned short values = {1, 2, 3, 4; union semun semarg; semarg.array = values; semctl(semid, 0, SETALL, semarg); 1) 세마포어집합내의모든세마포어의값을초기화하는명령 2) union semun semarg 인자의 semarg.array인자에초기값을배열형태로넣어줌 3) member_index는사용되지않음 4) 위의예제에서는세마포어집합의멤버세마포어수는 4이며 1, 2, 3, 4로초기화 바. GETVAL int n semctl(semid, 1, GETVAL, 0); 1) 특정멤버세마포어의현재값을얻어오는명령 2) 위의예제에서는 1번멤버세마포어값을얻어옴 사. GETALL 한국외국어대학교컴퓨터공학과

254 네트워크프로그래밍및실습 union semun semarg; unsigned short semvalues[4]; semarg.array = semvalues; semctl(semid, 0, GETALL, semarg); 1) 모든멤버세마포어의현재값을읽음 2) 위의예제에서는세마포어집합의멤버수가 4인경우각멤버세마포어값을읽음 아. GETNCNT semctl(semid, member_index, GETNCNT, 0); 1) 세마포어값이원하는값이상으로증가되기를기다리는프로세스의수를얻음 가 ) 특정멤버세마포어를사용하기위해블록되어있는프로세스의수 2) semctl() 함수의리턴값으로세마포어를기다리는프로세스의수를얻음 3) union semun semarg 인자는사용되지않음 자. GETPID int pid = semctl(semid, member_index, GETPID, 0); 1) 특정멤버세마포어에대해마지막으로 semop() 함수를수행한프로세스 PID를얻음 차. IPC_RMID semctl(semid, 0, IPC_RMID, 0); 1) 세마포어를삭제 7. 세마포어이용예 예제 48 공유데이터의경쟁적접근을세마포어로제어 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <sys/types.h> #include <fcntl.h> #include <sys/ipc.h> #include <sys/sem.h> void errquit(char *mesg) { perror(mesg); exit(0);

Chapter 10. 프로세스간통신 255 #define PEN 0 #define NOTE 1 // 세마포어조작 struct sembuf increase[] = { {0, +1, SEM_UNDO, {1, +1, SEM_UNDO ; struct sembuf decrease[] = { {0, 1, SEM_UNDO, {1, 1, SEM_UNDO ; // 세마포어초기값, 연필한자루와노트두권 unsigned short seminitval[] = { 1, 2 ; union semun { int val; struct semid_ds *buf; unsigned short int *array; struct seminfo * buf; semarg; int semid; // 세마포어 ID void do_work(); // 각프로세스가수행할작업 int main(int argc, char *argv[]) { semid = semget(0x1234, 2, IPC_CREAT 0600); if(semid == 1) semid=semget(0x1234, 0, 0); // 세마포어값초기화 semarg.array = seminitval; if(semctl(semid, 0, SETALL, semarg) == 1) errquit("semctl"); // 표준출력 non buffering setvbuf(stdout, NULL, _IONBF, 0); // 총 4개의프로세스를만듬 fork(); fork(); do_work(); semctl(semid,0,ipc_rmid,0); // return 0; 세마포어의삭제 void do_work() { int count=0; #define Semop(val) if((semop val)== 1) errquit("semop") while(count<3) { Semop((semid, &decrease[pen], 1)); printf("[pid:%5d] 연필을들고\n", getpid()); 한국외국어대학교컴퓨터공학과

256 네트워크프로그래밍및실습 Semop((semid, &decrease[note], 1)); printf("\t[pid:%5d] 노트를들고\n", getpid()); printf("\t[pid:%5d] 공부를함\n", getpid()); Semop((semid, &increase[pen], 1)); printf("\t[pid:%5d] 연필을내려놓음\n", getpid()); Semop((semid, &increase[note], 1)); printf("\t[pid:%5d] 노트를내려놓음\n", getpid()); sleep(1); count++; 실행화면은다음과같다. 가. 동작설명 1) 이프로그램은세마포어의이용예를보이는최소한의코드임 2) IPC_RMID에의해세마포어를삭제하는코드는 4개의프로세스에의해 4번실행 가 ) 실제로 3번은실패 3) do_work() 실행동안다른프로세스에의해세마포어가삭제될수있음 8. 공유메모리의동기화문제처리 예제 49 세마포어를이용한공유메모리동기화문제해결 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h>

Chapter 10. 프로세스간통신 257 #include <errno.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/sem.h> #include <sys/types.h> // 세마포어값을조절하는 sembuf struct sembuf waitsem[] = { {0, 1, 0 ; struct sembuf notifysem[] = { {0, +1, 0 ; // semop 함수 wrapper 매크로 //#define Semop(val) \ //{ if(semop val== 1) \ // errquit("semop fail"); \ // #define Semop(val) { if(semop val== 1) errquit("semop fail"); union semun { int val; struct semid_ds *buf; unsigned short int *array; struct seminfo * buf; semarg ; void errquit(char *msg){perror(msg); exit(1); // 자식프로세스를생성하여 busy() void fork_and_run(); 함수를호출 void busy(); // 각프로세스가공유메모리에접근하는함수 void access_shm(); // 공유메모리에접근하는함수 char *shm_data; // 공유메모리에대한포인터 int shmid, semid; // 공유메모리 ID, 세마포어 ID int main(int argc, char *argv[]) { key_t shmkey, semkey; // 공유메모리, 세마포어키 unsigned short initsemval[1]; // 세마포초기값 if(argc<2) { printf("usage : %s shmkey semkey\n",argv[0]); exit(1); shmkey = atoi(argv[1]); semkey = atoi(argv[2]); // 공유메모리생성 shmid = shmget(shmkey, 128, IPC_CREAT 0660); if(shmid<0) errquit("shmget fail "); // 공유메모리첨부 shm_data = (char *)shmat(shmid, (void*)0, 0); if(shm_data == (char*) 1) 한국외국어대학교컴퓨터공학과

258 네트워크프로그래밍및실습 errquit("shmat fail"); // 세마포어생성 semid = semget(semkey, 1, IPC_CREAT 0660); if(semid == 1) errquit("semget fail "); // 세마포어의초기값을 1로설정 // 공유메모리에한프로세스만이접근가능함 initsemval[0] = 1; semarg.array = initsemval; if( semctl(semid,0,setall, semarg)== 1) errquit("semctl "); fork_and_run(); // 자식프로세스생성 fork_and_run(); // 자식프로세스생성 busy(); // 부모프로세스도공유메모리접근 wait(null); // 자식프로세스가끝나기를기다림 wait(null); // 자식프로세스가끝나기를기다림 shmctl(shmid, IPC_RMID, 0); // 공유메모리삭제 semctl(semid, 0, IPC_RMID, 0); // 세마포어삭제 return 0; // 자식프로세스생성및 busy() void fork_and_run() { pid_t pid = fork(); if (pid < 0) errquit("fork fail"); else if(pid == 0) { busy(); // child race exit(0); return; 수행 // 자식프로세스가수행하는함수 void busy() { int i=0; for( i=0; i<100; i++ ) { // 100번접근 Semop((semid, &waitsem[0], 1)); access_shm(); Semop((semid, &notifysem[0], 1)); shmdt(shm_data); // 공유메모리분리 // 공유메모리에접근을하는부분 void access_shm() { int i; struct shmid_ds *buf;

Chapter 10. 프로세스간통신 259 pid_t pid; struct timespec ts; ts.tv_sec = 0; ts.tv_nsec = 100000000; // 0.1 초 // 공유메모리에자신의 pid를기록 sprintf(shm_data, "%d", getpid()); // 공유메모리접근시간 for(i=0; i<1000; i++) ; pid = atoi(shm_data); // 공유메모리에기록한 pid가자신의 pid가아니면 Error if(pid!= getpid()) puts("error : 다른프로세스도동시에공유메모리접근함\n"); else { putchar('.'); // ok fflush(stdout); //nanosleep(&ts, NULL); // sleep sleep(0.5); // sleep return ; 실행화면은다음과같다. 9. 연습문제풀이 가. FIFO 를이용한파일서버와클라이언트를구현하시오. 클라이언트는서버에자신의 PID와파일경 로명을 fifo.serv(fifo) 에전달하면서버는 fifo.serv 로부터경로명을읽어서 fifo.1231( 클라이언트의 PID가 1231일때클라이언트가생성한 FIFO) 에파일의내용을전달해주는프로그램을작성하시 오. ( 힌트: 서버는먼저 fifo.serv라는 FIFO를생성하고클라이언트는 fifo.1231이라는 FIFO 를생성한다.) 예제 50 서버프로그램소스 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include <sys/time.h> #include <sys/types.h> #include <sys/socket.h> 한국외국어대학교컴퓨터공학과

260 네트워크프로그래밍및실습 #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #define MAX_BUFSZ 512 #define FIFONAME "fifo.serv" // 클라이언트가서버에 FIFO로전달할자료의구조 typedef struct mesg { pid_t pid; char path[max_bufsz]; mesg_t; void errquit( char* mesg ) { perror(mesg); exit(1); // 파일의내용을읽어반환하는함수 int readsrcfile( char* filename, char* filedata ) { FILE* fd; fd = fopen( filename, "r" ); return ( fread( filedata, sizeof(char), MAX_BUFSZ 1, fd ) ); int main( int argc, char** argv ) { mesg_t pmsg; int fiford, fifowd; int nbytes; char clifile[max_bufsz]; char filedata[max_bufsz]; // "fifo.serv" 파일생성 if( mkfifo( FIFONAME, 0660 ) == 1 && errno!= EEXIST ) errquit( "mkfifo fail " ); // 읽기전용으로연다 fiford = open( FIFONAME, O_RDONLY ); if( fiford == 1 ) errquit( "fifo open fail " ); printf( "Server is waiting client...\n" ); // 내용이들어오면읽는다 nbytes = read( fiford, (char*)&pmsg, sizeof(pmsg) ); if( nbytes < 0 ) errquit( "read failed " ); printf( "Server reads from fifo\n", nbytes ); // FIFO 를통해클라이언트에서넘어온내용확인 printf( "pid : %d, path : %s\n", pmsg.pid, pmsg.path );

Chapter 10. 프로세스간통신 261 // 지정된파일내용을읽어온다 nbytes = readsrcfile( pmsg.path, filedata ); sprintf( clifile, "fifo.%d", pmsg.pid ); // 클라이언트측 FIFO를쓰기전용으로연다 fifowd = open( clifile, O_WRONLY ); if( fifowd == 1 ) errquit( "fifo open fail " ); filedata[nbytes] = 0; printf( "File Data \n" ); printf( "%s\n", filedata ); // 파일의내용을보낸다 if( write( fifowd, filedata, nbytes ) < 0 ) perror( "write fail " ); printf( "Server writes to fifo\n" ); return 0; 예제 51 클라이언트프로그램소스 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include <sys/time.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #define MAX_BUFSZ 512 // 클라이언트가서버에 FIFO로전달할자료의구조 typedef struct mesg { pid_t pid; char path[max_bufsz]; mesg_t; void errquit( char* mesg ) { perror(mesg); exit(1); int main( int argc, char** argv ) { char fifoname[max_bufsz]; int fiford, fifowd; int nbytes; char filedata[max_bufsz]; pid_t pid; mesg_t pmsg; 한국외국어대학교컴퓨터공학과

262 네트워크프로그래밍및실습 // pid 를얻어파일명을만든다 pid = getpid(); sprintf( fifoname, "fifo.%d", pid ); // FIFO 를생성 if( mkfifo( fifoname, 0660 ) == 1 && errno!= EEXIST ) errquit( "mkfifo fail " ); // 서버측 FIFO를쓰기전용으로연다 fifowd = open( "fifo.serv", O_WRONLY ); if( fifowd == 1 ) errquit( "fifo open fail " ); // 서버에전달한내용을채운다 pmsg.pid = pid; strcpy( pmsg.path, argv[1] ); // 서버측 FIFO에쓴다 if( write( fifowd, &pmsg, sizeof(pmsg) ) < 0 ) perror( "write fail " ); // 클라이언트측 FIFO를읽기전용으로연다 fiford = open( fifoname, O_RDONLY ); // 내용이생기면읽어온다 nbytes = read( fiford, (char*)&filedata, MAX_BUFSZ 1 ); if( nbytes < 0 ) errquit( "read filed " ); filedata[nbytes] = 0; // 서버에서넘어온파일의내용을출력 printf( "File Data \n" ); printf( "%s\n", filedata ); return 0;

Chapter 10. 프로세스간통신 263 나. TCP 채팅서버에서클라이언트로그에대한정보를메시지큐에남겨두면이를읽어파일에기록 하는로그관리프로세스를 아래와같은로그를남길도록하시오. fork() 를이용하여작성하시오, 예를들어클라이언트가접속할때마다 new client from (210.115.33.6) - Mon Nov 24 15:29:50 2003 예제 52 서버프로그램소스 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <fcntl.h> #include <sys/socket.h> 한국외국어대학교컴퓨터공학과

264 네트워크프로그래밍및실습 #include <sys/file.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <errno.h> #include <sys/ipc.h> #include <sys/msg.h> #include <time.h> #define MAXLINE 511 #define MAX_BUFSZ 512 #define MAX_SOCK 1024 // 메시지큐에서사용할자료의구조 typedef struct _msg { long msg_type; struct sockaddr_in addr; char msg_text[ MAX_BUFSZ ]; msg_t; char *EXIT_STRING = "exit"; char *START_STRING = "Connected to chat_server \n"; int maxfdp1; int num_chat = 0; int clisock_list[max_sock]; int listen_sock; void addclient(int s, struct sockaddr_in *newcliaddr); int getmax(); void removeclient(int s); int tcp_listen(int host, int port, int backlog); void errquit(char *mesg) { perror(mesg); exit(1); // 로그관리프로세스를생성하고지정된파일에로그내용을쓰는함수 void fork_and_log( int msqid, char* filename ) { pid_t pid; FILE* fd; int size; msg_t pmsg; pid = fork(); if( pid < 0 ) errquit( "fork fail " ); else if( pid > 0 ) return; size = sizeof(msg_t) sizeof(long); pmsg.msg_type = 1;

Chapter 10. 프로세스간통신 265 while( 1 ) { // 메시지큐에서로그내용이있는메시지를가져온다 if( msgrcv( msqid, (void*)&pmsg, size, 0, 0 ) < 0 ) errquit( "msgrcv fail " ); // 파일을열고로그내용을추가한다 fd = fopen( filename, "a" ); fwrite( pmsg.msg_text, sizeof(char), strlen(pmsg.msg_text), fd ); fclose( fd ); int main(int argc, char *argv[]) { struct sockaddr_in cliaddr; msg_t pmsg; key_t key; int msqid, size; time_t contime; char buf[maxline+1]; int i, j, nbyte, accp_sock, addrlen = sizeof(struct sockaddr_in); fd_set read_fds; int n; if(argc!= 3) { printf("usage :%s port msgq_key\n", argv[0]); exit(0); key = atoi( argv[2] ); // key 값에따라메시지큐생성 msqid = msgget( key, IPC_CREAT 0600 ); if( msqid == 1 ) errquit( "socket fail " ); // 로그관리프로세스를생성하고지정된파일에로그내용을쓰는함수호출 fork_and_log( msqid, "q2.log" ); listen_sock = tcp_listen(inaddr_any, atoi(argv[1]), 5); while(1) { FD_ZERO(&read_fds); FD_SET(listen_sock, &read_fds); for(i=0; i<num_chat; i++) FD_SET(clisock_list[i], &read_fds); maxfdp1 = getmax() + 1; puts("wait for client"); if( select( maxfdp1, &read_fds, NULL, NULL, NULL ) < 0 ) errquit("select fail"); if(fd_isset(listen_sock, &read_fds)) { accp_sock=accept(listen_sock, (struct sockaddr 한국외국어대학교컴퓨터공학과

266 네트워크프로그래밍및실습 *)&cliaddr, &addrlen); if(accp_sock == 1) errquit("accept fail"); addclient(accp_sock,&cliaddr); send(accp_sock, START_STRING, strlen(start_string), 0); printf("added %dth user.\n", num_chat); pmsg.msg_type = 1; size = sizeof(msg_t) sizeof(long); // IP 주소와접속시간을얻는다 inet_ntop( AF_INET, &cliaddr.sin_addr, buf, sizeof(buf) ); time( &contime ); sprintf( pmsg.msg_text, "new client from (%s) %s", buf, ctime(&contime) ); // IP 주소와접속시간을메시지큐로보낸다 if( msgsnd( msqid, &pmsg, size, 0 ) == 1 ) errquit( "msgsnd fail " ); for(i = 0; i < num_chat; i++) { if(fd_isset(clisock_list[i], &read_fds)) { nbyte = recv(clisock_list[i], buf, MAXLINE, 0); if(nbyte<= 0) { removeclient(i); continue; buf[nbyte] = 0; return 0; if(strstr(buf, EXIT_STRING)!= NULL) { removeclient(i); continue; for (j = 0; j < num_chat; j++) send(clisock_list[j], buf, nbyte, 0); printf("%s\n", buf); void addclient(int s, struct sockaddr_in *newcliaddr) { char buf[20]; inet_ntop(af_inet,&newcliaddr >sin_addr,buf,sizeof(buf)); printf("new client: %s\n",buf); clisock_list[num_chat] = s; num_chat++; void removeclient(int s) { close(clisock_list[s]); if(s!= num_chat 1) clisock_list[s] = clisock_list[num_chat 1];

Chapter 10. 프로세스간통신 267 num_chat ; printf("leave a user. Current users = %d\n", num_chat); int getmax() { int max = listen_sock; int i; for (i=0; i < num_chat; i++) if (clisock_list[i] > max ) max = clisock_list[i]; return max; int tcp_listen(int host, int port, int backlog) { int sd; struct sockaddr_in servaddr; sd = socket(af_inet, SOCK_STREAM, 0); if(sd == 1) { perror("socket fail"); exit(1); bzero((char *)&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(host); servaddr.sin_port = htons(port); if (bind(sd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("bind fail"); exit(1); listen(sd, backlog); return sd; 한국외국어대학교컴퓨터공학과

268 네트워크프로그래밍및실습 다. TCP 채팅서버에서클라이언트수및클라이언트의 IP주소를공유메모리에기록하도록프로그램 을변경하시오. 또한공유메모리에있는이정보를 3초주기로읽어서화면에출력하는보고용프 로세스를 fork() 를이용하여작성하시오. ( 힌트: TCP 채팅서버와보고용프로세스가동시에공유메모리에접근할수있으므로세마포어를 사용해야한다.) 예제 53 서버프로그램소스 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <fcntl.h> #include <sys/socket.h> #include <sys/file.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <errno.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/sem.h> #define MAXLINE 511 #define MAX_BUFSZ 512 #define MAX_SOCK 1024 char *EXIT_STRING = "exit"; char *START_STRING = "Connected to chat_server \n";

Chapter 10. 프로세스간통신 269 int maxfdp1, num_chat = 0; int clisock_list[max_sock]; char cliip_list[max_sock][32]; int listen_sock; // 공유메모리를위한전역변수선언 char* shm_data; int shmid; // 세마포어를위한전역변수선언 struct sembuf increase = { 0, +1, SEM_UNDO ; struct sembuf decrease = { 0, 1, SEM_UNDO ; unsigned short seminitval[1]; int semid; union semun { int val; struct semid_ds* buf; unsigned short int* array; struct seminfo* buf; semarg; void addclient(int s, struct sockaddr_in *newcliaddr); int getmax(); void removeclient(int s); int tcp_listen(int host, int port, int backlog); void errquit(char *mesg) { perror(mesg); exit(1); // 공유메모리내용을 3초주기로읽어서출력하는프로세스생성함수 void fork_and_print() { pid_t pid; pid = fork(); if( pid < 0 ) errquit( "fork fail " ); else if( pid > 0 ) return; while( 1 ) { // 세마포어 decrease semop( semid, &decrease, 1 ); // 공유메모리내용출력 printf( "Shared Memory \n%s\n", shm_data ); // 세마포어 increase semop( semid, &increase, 1 ); // 3 초대기 sleep( 3 ); 한국외국어대학교컴퓨터공학과

270 네트워크프로그래밍및실습 int main(int argc, char *argv[]) { struct sockaddr_in cliaddr; key_t shmkey; int msqid, size; time_t contime; char buf[maxline+1]; int i, j, nbyte, accp_sock, addrlen = sizeof(struct sockaddr_in); fd_set read_fds; int n; if(argc!= 3) { printf("usage :%s port shmkey\n", argv[0]); exit(0); // 세마포어생성 semid = semget( 0x1234, 1, IPC_CREAT 0600 ); if( semid == 1 ) semid = semget( 0x1234, 0, 0 ); seminitval[0] = 1; semarg.array = seminitval; if( semctl( semid, 0, SETALL, semarg ) == 1 ) errquit( "semctl " ); shmkey = atoi( argv[2] ); // key 에따라공유메모리생성 shmid = shmget( shmkey, 128, IPC_CREAT 0660 ); if( shmid < 0 ) errquit( "shmget fail " ); shm_data = (char*)shmat( shmid, (void*)0, 0 ); if( shm_data == (char*) 1 ) errquit( "shmat fail " ); // 보고용프로세스생성 fork_and_print(); listen_sock = tcp_listen(inaddr_any, atoi(argv[1]), 5); while(1) { FD_ZERO(&read_fds); FD_SET(listen_sock, &read_fds); for(i=0; i<num_chat; i++) FD_SET(clisock_list[i], &read_fds); maxfdp1 = getmax() + 1; puts("wait for client"); if( select( maxfdp1, &read_fds, NULL, NULL, NULL ) < 0 ) errquit("select fail"); if(fd_isset(listen_sock, &read_fds)) {

Chapter 10. 프로세스간통신 271 accp_sock=accept(listen_sock, (struct sockaddr *)&cliaddr, &addrlen); if(accp_sock == 1) errquit("accept fail"); addclient(accp_sock,&cliaddr); send(accp_sock, START_STRING, strlen(start_string), 0); printf("added %dth user.\n", num_chat); for(i = 0; i < num_chat; i++) { if(fd_isset(clisock_list[i], &read_fds)) { nbyte = recv(clisock_list[i], buf, MAXLINE, 0); if(nbyte<= 0) { removeclient(i); continue; buf[nbyte] = 0; if(strstr(buf, EXIT_STRING)!= NULL) { removeclient(i); continue; for (j = 0; j < num_chat; j++) send(clisock_list[j], buf, nbyte, 0); printf("%s\n", buf); // 세미포어 decrease semop( semid, &decrease, 1 ); // 접속자수와 IP주소를공유메모리에써넣음 sprintf( shm_data, "Current clients : %d\n", num_chat ); for( j = 0; j < num_chat; j++ ) { sprintf( buf, "%dth client's IP : %s\n", j+1, cliip_list[j] ); strcat( shm_data, buf ); // 세마포어 increase semop( semid, &increase, 1 ); // 세마포어삭제 semctl( semid, 0, IPC_RMID, 0 ); return 0; void addclient(int s, struct sockaddr_in *newcliaddr) { char buf[20]; inet_ntop(af_inet,&newcliaddr >sin_addr,buf,sizeof(buf)); printf("new client: %s\n",buf); clisock_list[num_chat] = s; strcpy( cliip_list[num_chat], buf ); 한국외국어대학교컴퓨터공학과

272 네트워크프로그래밍및실습 num_chat++; void removeclient(int s) { close(clisock_list[s]); if(s!= num_chat 1) clisock_list[s] = clisock_list[num_chat 1]; num_chat ; printf("leave a user. Current users = %d\n", num_chat); int getmax() { int max = listen_sock; int i; for (i=0; i < num_chat; i++) if (clisock_list[i] > max ) max = clisock_list[i]; return max; int tcp_listen(int host, int port, int backlog) { int sd; struct sockaddr_in servaddr; sd = socket(af_inet, SOCK_STREAM, 0); if(sd == 1) { perror("socket fail"); exit(1); bzero((char *)&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(host); servaddr.sin_port = htons(port); if (bind(sd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("bind fail"); exit(1); listen(sd, backlog); return sd;

Chapter 10. 프로세스간통신 273 한국외국어대학교컴퓨터공학과

Chapter 11. 스레드프로그래밍

Chapter 11. 스레드프로그래밍 275 S E C T I O N 01 스레드의생성과종료 1. 스레드의정의 가. 프로세스처럼독립적으로수행되는프로그램코드 나. 경량프로세스 1) 프로세스내에서독립적으로수행되는제어의흐름 다. 한프로세스내에서생성된스레드들은서로전역변수를공유 1) 스레드간에데이터를편리하게공유 2) 멀티프로세스프로그램과달리 IPC 기능을사용하지않아도스레드간데이터공유 2. 스레드생성과종료 가. pthread_create() int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg); 1) pthread_t *thread 가 ) 생성된스레드 ID 리턴하며이 ID를통해스레드에접근 2) pthread_attr_t *attr 가 ) 스레드속성을지정하는인자 나 ) 디폴트로사용하려면 NULL 3) void *(*start_routine)(void *) 지정 가 ) 스레드시작함수 (start routine) 4) void *arg 가 ) 스레드 start routine 5) pthread_create() 호출시 가 ) start_routine 나 ) 스레드시작함수는 다 ) 성공시 0, 함수의인자 인자로지정한함수실행 void* 타입의인자를받거나임의의타입을가리키는포인터를리턴 실패시에러코드를정수값으로리턴 (1) 시스템자원부족, 최대생성스레드수(PTHREAD_THREADS_MAX) 를넘을때 EAGAIN 리턴 나. pthread_self() pthread_t pthread_self(void); 1) 스레드자신의 ID를얻음 다. pthread_exit() 한국외국어대학교컴퓨터공학과

276 네트워크프로그래밍및실습 void pthread_exit(void *retval); 1) 스레드가스스로종료 2) 스레드종료방법 가 ) 스레드시작함수내에서 나 ) 임의의위치에서 3) main() 함수 return을만남 pthrea_exit() 호출 가 ) 일종의스레드로 main 스레드또는 initial 스레드라함 나 ) main() 에서의 return은다른스레드에서의 return과달리프로세스를종료시킴 다 ) main() 에서여러스레드를생성한후 return하면모두종료됨 라 ) main() 에서 exit() 를호출하거나 return하는대신 pthread_exit() 를호출하는경우 (1) main 스레드만종료하며생성된스레드들은종료되지않음 (2) 생성된모든스레드가종료될때까지프로세스는종료되지않고기다림 라. pthread_join() int pthread_join(pthread_t thrd, void **thread_return); 1) 자신이생성한자식스레드가종료할때까지기다려야하는경우에사용 2) thrd 인자 가 ) 종료를기다리는스레드의 ID 나 ) 지정한스레드가종료할때까지또는취소될때까지대기 3) thread_return 인자 가 ) 자식스레드의종료상태가저장 나 ) NULL로지정할경우스레드종료상태값을받지않음 다 ) 자식스레드의종료값은자식스레드에서스레드종료시에 return 값으로남길수있음 라 ) 자식스레드가다른스레드에의해취소된경우 (1) 스레드종료값은 PTHREAD_CANCELED pthread_exit() 의인자또는 4) 자식스레드가종료되기전에부모스레드가먼저종료되지않도록할경우에사용 가 ) 자식스레드의종료시점을정확히파악하여다른작업을할경우에사용 나 ) 자식스레드의종료상태값을얻기위해사용 5) main 스레드에서주로사용하게됨 6) main에서생성된스레드들이종료하기전에 main이종료되면생성된스레드가모두종료됨 3. 스레드의사용예 예제 54 스레드의생성과스레드 ID와 pid를확인 #include <stdio.h> #include <stdlib.h> #include <string.h>

Chapter 11. 스레드프로그래밍 277 #include <unistd.h> #include <pthread.h> void *thrfunc(void *arg) ; // 스레드시작함수 char who[10]; int main(int argc, char **argv) { int status; pthread_t tid; pid_t pid; // 자식프로세스생성 pid = fork(); if(pid==0) sprintf(who,"child"); else sprintf(who,"parent"); // 프로세스 ID와초기스레드의 ID 확인 printf("(%s's main) Process ID = %d\n", who, getpid()); printf("(%s's main) Init thread ID = %d\n", who, pthread_self()); // 에러발생시에러코드를리턴 if( (status=pthread_create(&tid, NULL, &thrfunc, NULL))!=0) { printf("thread create error: %s\n", strerror(status)); exit(0); // 인자로지정한스레드 id가종료하기를기다림 pthread_join(tid, NULL); printf("\n(%s)[%d] 스레드가종료했습니다\n", who,tid); return 0; void *thrfunc(void *arg) { printf("(%s' thread routine) Process ID = %d\n",who,getpid()); printf("(%s' thread routine) Thread ID = %d\n",who,pthread_self()); 실행화면은다음과같다. 한국외국어대학교컴퓨터공학과

278 네트워크프로그래밍및실습 가. 동작설명 1) pthread_create() 와 pthread_self() 함수를이용해스레드를생성하고스레드 ID를얻음 2) 컴파일시 -lpthread 로 pthread 라이브러리를링크 4. 스레드의상태 가. 준비, 실행, 블록, 종료중하나를가지게됨 나. 준비 1) 처음생성시가지게되는상태 2) 스레드가실행될수있는상태 다. 실행 1) 운영체제의스케줄링에의해이동 2) CPU의서비스를받고있는상태 라. 블록 1) 즉시처리할수없는작업을만날경우 2) sleep(), read(), 세마포어연산등으로기다리는상태 마. 종료 1) 스레드시작함수에서 return하거나 pthread_exit() 호출, 다른스레드에의해취소될경우 2) 스레드가종료또는취소된상태 5. 스레드의분류 가. joinable 나. 스레드에서 스레드와분리된스레드로나뉨 pthread_detach() 를호출시 1) 데몬프로세스처럼부모스레드와분리된스레드로서실행됨 2) 분리된스레드는 pthread_join() 을호출할수없고종료시종료상태에남아있지않음 가 ) 메모리가모두반환됨 다. 모든스레드는디폴트로 joinable 스레드 1) 부모스레드는 pthread_join() 을호출하여이스레드의종료를기다림

Chapter 11. 스레드프로그래밍 279 S E C T I O N 02 스레드동기화 1. 동기화문제 가. 한스레드가공유데이터를액세스하는도중에다른스레드가이공유데이터억세스시 1) 데이터값을두스레드가정확히예측할수없음 나. 해결방법 1) 스레드들이공유데이터에접근할때서로배타적으로접근 2) 한번에한스레드만공유데이터에접근 3) 뮤텍스(mutex = mutual + exclusion) 사용 2. 동기화문제예 예제 55 스레드동기화문제( 경쟁조건) 의발생 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> #define MAX_THR 2 void *thrfunc(void *arg); // 스레드시작함수 void prn_data(long who); // 스레드 ID 출력 int who_run = 1; // prn_data() 수행중인스레드 ID // 초기값은 1 int main(int argc, char **argv) { pthread_t tid[max_thr]; int i, status; for(i=0; i<max_thr; i++) { if( (status=pthread_create(&tid[i], NULL, &thrfunc, NULL))!=0) { printf("thread create error: %s\n",strerror(status)); exit(0); pthread_join(tid[0],null); return 0; void *thrfunc(void *arg) { while(1) { prn_data(pthread_self()); 한국외국어대학교컴퓨터공학과

280 네트워크프로그래밍및실습 return NULL; void prn_data(long me) { who_run = me; if(who_run!= pthread_self()) { printf("error : %d스레드실행중 who_run=%d\n",me,who_run); who_run = 1; // 초기값으로환원 실행화면은다음과같다. 가. 동작설명 1) 스레드들이스레드 ID를저장하는 run 변수를공유 2) 한스레드가 prn_data() 를호출하는동안다른스레드가 prn_data() 호출시 run 값변경 3) 중간에 run값이변경될경우에러메시지를출력함 3. 플래그사용예 가. void *thrfunc(void *arg) { while(1) { if(run == -1) { return NULL; prn_data(pthread_self()); prn_data() 함수에서동기화문제발생 1) 위의 thread_syn.c 프로그램에서 prn_data() 함수가동시에호출되므로동기화문제발생 나. 스레드시작함수 thrfunc() 를수정

Chapter 11. 스레드프로그래밍 281 1) 플래그를하나정의하여사용함으로써한스레드만 prn_data() 를실행하도록수정 2) run == -1 인경우, 즉아무스레드도 prn_data() 를호출하지않을때만 prn_data() 호출 3) 플래그를조사한직후에스레드스케줄링이일어날경우 가 ) 동기화문제발생 4. 뮤텍스 스레드의동기화문제를해결하는커널이제공하는일종의플래그 가. 뮤텍스사용방법 pthread_mutex_t mutex; pthread_mutex_lock(mutex); pthread_mutex_unlock(mutex); 1) 뮤텍스잠금 가 ) 스레드가공유데이터를사용하기전, 즉크리티컬영역에들어가기전에잠금 나 ) 뮤텍스잠금을한스레드만공유데이터에접근 2) 뮤텍스해제 가 ) 스레드가크리티컬영역을나올때뮤텍스잠금을해제 나 ) 다른스레드는뮤텍스잠금이해제될때까지대기 3) pthread_mutex_t mutex; 가 ) 뮤텍스선언 4) pthread_mutex_lock(mutex); 가 ) 뮤텍스잠금 5) pthread_mutex_unlock(mutex); 가 ) 뮤텍스해제 나. 뮤텍스사용 pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; void *thrfunc(void *arg) { while(1) { pthread_mutex_lock(&lock); prn_data(pthread_self()); pthread_mutex_unlock(&lock); return NULL; 1) pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; 가 ) 뮤텍스선언및초기화 한국외국어대학교컴퓨터공학과

282 네트워크프로그래밍및실습 2) pthread_mutex_lock(&lock); 가 ) 뮤텍스잠금 3) prn_data(pthread_self()); 가 ) 공유데이터액세스 4) pthread_mutex_unlock(&lock); 가 ) 뮤텍스해제 다. 뮤텍스선언및초기화 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr); 1) 뮤텍스선언 가 ) 뮤텍스의변수타입이름은 pthread_mutext_t 나 ) 여러스레드들이사용하므로전역변수로선언 2) 뮤텍스초기화 가 ) pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; (1) 매크로를이용한초기화방법으로기본속성만가짐 나 ) pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr); (1) 함수를이용한초기화방법으로 attr 인자를이용해속성을지정함 (2) attr 인자가 NULL 일경우기본속성이적용됨 3) pthread_mutexattr_settype() 가 ) attr 인자의속성을지정 나 ) 인자로 &attr 과 type가있음 다 ) type은뮤텍스의타입을지정하기위한상수 (1) PTHREAD_MUTEX_TIMED_NP : timed 타입 (2) PTHREAD_MUTEX_RECURSIVE_NP : recursive 타입 (3) PTHREAD_MUTEX_ERRORCHECK_NP : error checking 타입 라. 기본뮤텍스 1) 특별한타입을지정하지않은뮤텍스 2) 단한번의잠금만허용 가 ) 나 ) 1번스레드가뮤텍스잠금을한상황에서2번스레드가잠금시도시 (1) 2번스레드는 1번스레드의잠금이해제될때까지대기 1번스레드에의해잠긴뮤텍스에대해1번스레드가또잠금시도시 (1) 데드락이발생하여 1번스레드는영원히블록상태가됨 (2) pthread_mutex_lock() 대신 pthread_mutex_trylock() 를사용하여블록현상피함 (3) 뮤텍스를얻지못하는경우스레드는블록되지않고리턴되며 EBUSY 에러발생 3) 다른스레드에서해제가능 가 ) 1번스레드에의해잠금된뮤텍스를 2번스레드에서해제가능 마. Timed 타입뮤텍스

Chapter 11. 스레드프로그래밍 283 1) 지정시간동안만블록 2) pthread_mutex_timedlock() 가 ) 나 ) pthread_mutex_lock() 는뮤텍스를얻을때까지무한대기 timed 타입뮤텍스로지정된시간동안만블록 다 ) 리눅스에서기본뮤텍스가 timed 타입뮤텍스로동작함 바. Recursive 타입뮤텍스 1) 한스레드가한번이상잠금 2) pthread_mutex_lock() 을호출한횟수만큼 pthread_mutex_unlock() 를호출 가 ) 한스레드가한번이상의잠금을하므로호출횟수만큼해제해야잠금이해제됨 나 ) 한스레드가잠금을한상태에서다른스레드가잠금해제를시도시에러발생 사. Error Check 타입뮤텍스 1) 두번잠금시도시에러발생후프로세스종료 2) 기본뮤텍스에서한스레드가뮤텍스잠금을두번시도시 가 ) 무한블록상태 3) error check 타입뮤텍스에서두번잠금시도시 가 ) 에러발생후프로세스종료 4) error check 타입뮤텍스에서잠겨있지않은뮤텍스에잠금해제시도시 가 ) 에러발생후프로세스종료 나 ) 기본뮤텍스에서는잠겨있지않은뮤텍스에잠금해제시도시에러없음 5) 자체적으로에러검사를하므로기본뮤텍스보다처리속도가느림 아. 뮤텍스삭제 1) 뮤텍스를더이상사용하지않을때사용 pthread_mutex_unlock(&mutex); pthread_mutex_destroy(&mutex); 2) 뮤텍스가어떤스레드에의해잠금된상태서제거시도시가 ) EBUSY 에러발생나 ) 뮤텍스의현재상태를알아보는방법이없음 3) 안전한뮤텍스제거를위해무조건뮤텍스해제후제거 자. 데드락 1) 두개의스레드가뮤텍스해제를서로기다리는현상 2) 두개이상의스레드가두개이상의뮤텍스를사용하는경우에발생가 ) 각스레드가뮤텍스를하나씩잠그고있는상태에서상대방의뮤텍스해제를서로기다림 3) 데드락발생시프로그램은영원히블록됨 4) 서로연관이있는작업에서는다수의뮤텍스사용을피함가 ) 작업처리의순서를정하여해결 5) 뮤텍스를많이사용하면프로그램성능이저하됨 한국외국어대학교컴퓨터공학과

284 네트워크프로그래밍및실습 가 ) 뮤텍스를얻은상태해서처리하는작업량을최소화하여해결 나 ) 뮤텍스가잠긴동안다른스레드가블록될확률이높아서크리티컬영역이길수록전체성능 이저하됨 5. 뮤텍스사용예 예제 56 1초간격으로두개의스레드가돌아가면서 count 출력 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> // 스레드시작함수 void *thrfunc(void *arg) ; int counting = 0; // 공유데이터 pthread_mutex_t count_lock; // 뮤텍스초기화 pthread_mutexattr_t mutex_attr; // 뮤텍스속성초기화 int main(int argc, char **argv) { pthread_t tid[2]; int i, status; pthread_mutexattr_init(&mutex_attr); // 뮤텍스 pthread_mutex_init(&count_lock, &mutex_attr); // 뮤텍스속성변수 for(i=0; i <2; i++) { // 에러발생시 non zero 값리턴 if((status=pthread_create(&tid[i], NULL, &thrfunc, NULL))!=0) { printf("pthread_create fail: %s",strerror(status)); exit(0); for(i=0; i<2; i++) pthread_join(tid[0], NULL); return 0; // 스레드시작함수 void *thrfunc(void *arg) { while(1) { pthread_mutex_lock( &count_lock ); printf("\n[%ld 스레드] 뮤텍스잠금\n", pthread_self()); printf("[%ld 스레드] counting = %d\n", pthread_self(), counting); counting++; sleep(1); printf("[%ld 스레드] 뮤텍스해제\n", pthread_self()); pthread_mutex_unlock(&count_lock); return NULL;

Chapter 11. 스레드프로그래밍 285 실행화면은다음과같다. 가. 동작설명 1) 한스레드가 count 변수에접근하는동안다른스레드의접근을못하게함 2) main() 에서두개의스레드를만들고스레드시작함수에서뮤텍스를이용해 1초간격으로 count 값을출력 한국외국어대학교컴퓨터공학과

286 네트워크프로그래밍및실습 S E C T I O N 03 스레드간통신 1. 조건변수사용방법 가. 조건변수의정의 1) 스레드간에공유데이터의상태에대한정보를주고받는변수 2) 조건변수의사용예가 ) 생산자스레드는큐에메시지를쓰고소비자스레드는큐의메시지를꺼내서출력 (1) 큐는스레드간공유데이터나 ) 동기화문제를피하기위해뮤텍스를사용다 ) 운영체제의스케줄러가생산자와소비자의두스레드가교대로실행함을보장하지않음 (1) 어느한스레드가연속으로두번실행될수있음라 ) 조건변수를이용해큐에메시지가쓰였음을소비자스레드에알려줌 나. 조건알림 1) 조건변수를통해어떤조건이만족됨을다른스레드에알려주는기능 2. 조건변수의동작 가. 뮤텍스를정의한후뮤텍스와연계하여조건변수를정의나. 반드시뮤텍스와함께사용 int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); 다. pthread_cond_wait() 라. 1) 소비자스레드에서조건변수를기다리는함수 2) 조건변수 cond 인자 가 ) 조건알림이발생할때까지대기 3) 뮤텍스타입의 mutex 인자 가 ) 조건변수를제어하는인자 pthread_cond_wait() 호출시내부동작 1) 뮤텍스해제 블록상태 조건알림을받음 깨어나면서뮤텍스를얻음 마. pthread_cond_timedwait() 1) 지정한시간동안만블록상태에서조건알림을기다림 가 ) 조건알림을무한히기다리는것을방지 바. pthread_cond_signal()

Chapter 11. 스레드프로그래밍 287 int pthread_cond_signal(pthread_cond_t *cond); 1) 조건알림을기다리는스레드에게조건알림을보냄 2) 조건알림을기다리는스레드를블록상태에서깨어나게함 사. pthread_cond_broadcast() 1) 여러스레드를동시에깨움 2) 생산자스레드보다소비자스레드가많을경우 가 ) pthread_cond_signal() 은한번에하나의스레드만깨움 나 ) 조건알림을기다리는스레드가둘이상일때모든 함수를깨우기위해사용 3) 여러스레드를동시에깨우지만공유데이터접근을위해서는뮤텍스를얻어야함 wait 3. 조건변수의생성과삭제 가. 조건변수초기화 pthread_cond_t cond = PTHREAD_COND_INITIALIZER; pthread_cond_t cond; pthread_cond_init(&cond, NULL); 1) pthread_cond_init() 를이용해사용전초기화 2) pthread_cond_t cond = PTHREAD_COND_INITIALIZER 가 ) 매크로를이용한초기화방법 3) 함수를이용한초기화방법으로 3번줄에서조건변수선언 4) &cond는초기화시킬조건변수 5) 두번째인자는조건변수의속성으로 NULL 일경우기본속성으로초기화 나. 조건변수삭제 pthread_cond_destroy(&cond); 1) pthread_cond_dsestroy() 를이용해삭제 4. 조건변수사용예 예제 57 조건변수를통해서두스레드간에통신하는예 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <pthread.h> 한국외국어대학교컴퓨터공학과

288 네트워크프로그래밍및실습 // 뮤텍스, 조건변수및공유데이터 typedef struct _complex { pthread_mutex_t mutex; // 뮤텍스 pthread_cond_t cond ; // 조건변수 int value; // 공유데이터 thread_control_t; // 뮤텍스, 조건변수, 공유데이터의초기화 thread_control_t data = { PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, 0; // 에러출력및스레드종료 void thr_errquit(char *msg,int errcode) { printf("%s: %s\n",msg,strerror(errcode)); pthread_exit(null); void *wait_thread(void *arg); // 스레드시작함수 int sleep_time ; // 자식스레드가처음잠자는시간 int main(int argc, char **argv) { int status; pthread_t wait_thr; struct timespec timeout; if (argc!= 2 ) { printf(" 사용법 : cond 5 ( 자식스레드가 sleep 할시간)\n"); exit(0); sleep_time = atoi(argv[1]); if( (status=pthread_create(&wait_thr, NULL, wait_thread, NULL))!=0) { printf("pthread_create fail : %s\n",strerror(status)); exit(1); timeout.tv_sec = time(null)+3; // 현재의시간 + 3초 timeout.tv_nsec = 0; // 뮤텍스잠금 if( (status=pthread_mutex_lock(&data.mutex))!= 0) { printf("pthread_mutex_lock fail : %s\n",strerror(status)); exit(1); if (data.value == 0) { status = pthread_cond_timedwait(&data.cond, &data.mutex, &timeout); if(status == ETIMEDOUT) { printf("condtion wait time out\n");

Chapter 11. 스레드프로그래밍 289 else { printf("wait on Condition...\n"); // data.value 값이자식스레드에서 1로바뀜 if(data.value == 1) printf("condition was signaled.\n"); if( (status=pthread_mutex_unlock(&data.mutex))!= 0) { printf("pthread mutex_unlock fail : %s\n",strerror(status)); exit(0); // 자식스레드가종료하기를기다림 if( (status=pthread_join(wait_thr, NULL))!= 0) { printf("pthread_join: %s\n",status); exit(0); return 0; // 스레드시작함수 void *wait_thread(void *arg) { int status; sleep(sleep_time); if((status=pthread_mutex_lock(&data.mutex))!= 0) thr_errquit("pthread_mutex_lock failure",status); data.value = 1; if((status=pthread_cond_signal(&data.cond))!= 0) thr_errquit("pthread_cond_signalure",status); if((status=pthread_mutex_unlock(&data.mutex))!= 0) thr_errquit("pthread_mutex_unlock failure",status); return NULL; 실행화면은다음과같다. 가. 동작설명 1) 공유데이터인 data.value의초기값은 0이나부모스레드는지정된간동안이값을자식스레드 가 1로설정해주기를기다림 한국외국어대학교컴퓨터공학과