연구총서 1-19
목차 1 장자바스레드 1.1 기본개념 7 1.2 사용자 Thread 의생성 13 1.3 스레드클래스지원메소드 20 1.4 스레드동기화 41 1.5 스레드간의통신 50 1.6 자바 1.5에추가된기능 73 1.7 애플릿과스레드 89 2 장중요사항 Review 2.1 이벤트의멀티캐스트 (multicast) 99 2.2 메뉴를사용한간단한문서편집기 102 2.3 파일복사프로그램 107 2.4 테이블사용 109 2.5 테이블모델을사용한예제프로그램 110 2.6 Properties 클래스 113 2.7 Timer 클래스 117 3 장네트워크프로그래밍 3.1 IP 주소와 Port 123 3.2 InetAddress 클래스 125 3.3 URL 클래스 130 3.4 URLConnection 137
3.5 TCP Socket 142 3.6 채팅예제프로그램 188 3.7 UDP(User Datagram Protocol) 216 3.8 Multicast 소켓 236 4 장 Remote Method Invocation(RMI) 4.1 RMI의소개 257 4.2 RMI 예제 259 4.2.1 예제프로그램설명 259 4.2.2 예제프로그램실행 261 4.2.3 소켓사용하여다시작성한예제프로그램 262 4.2.4 RMI 를실행하기위해필요한파일 267 4.2.5 객체를인자로사용하는 RMI 273 4.2.6 RMI 예제프로그램 277 4.2.7 RMI CallBack 294 5 장코바 (Corba) 5.1 코바 (Corba) 의소개 311 5.2 ORB 구조 : ORB 는통신을담당하는일종의소프트웨어버스또는미들웨어 (middle ware) 315 5.3 간단한 CORBA 예제의실행 318 5.4 HelloClient.java 프로그램의이해 324 5.5 애플릿으로작성한 CORBA 클라이언트 329 5.6 orbd(object Request Broker Daemon) 332
1 장자바스레드 1.1 기본개념 대부분의프로그래밍언어 ( 예를들어, C 언어또는 Pascal) 는단일스레드만을지원한다. 즉지금까지의모든프로그램문장들은하나씩순차적으로실행된다는것을가정하였다. 대부분컴퓨터언어는단일스레드만을지원하지만, 이와는달리자바는멀티스레드를지원한다. 단일스레드란하나의프로그램에하나의실행점을가지는것을의미하며, 이는 C, C++, Pascal 등의기존컴퓨터언어에서일반적인것이다. 여기서스레드란프로그램실행흔적 (execution trace) 을의미한다. 멀티스레드를지원한다는의미는하나의프로그램에여러개의실행흔적을가진다는의미이다. [ 그림 1-1] 은전통적인프로그램의실행을나타낸그림이다. [ 그림 1-1] 은하나의프로그램안에하나의스레드가존재하는것을보여주고있다. 즉 [ 그림 1-1] 의프로그램은하나의실행흔적 (program execution trace) 을가진다. 프로그램 스레드 [ 그림 1-1] 전통적인프로그램실행 [ 그림 1-2] 는두개의스레드가하나의프로그램을공유하면서실행하는것을보인것이 1 장자바스프레드 7
다. 즉하나의프로그램안에서두개의스레드가서로다른부분을병행실행하는것이다. 멀티스레딩기법을사용하면, 논리적상관관계가없는두개의함수를동시에병렬적으로실행할수있다. 종료 프로그램 스레드 [ 그림 1-2] 두개의스레드가실행되는프로그램 자바프로그램에서스레드를사용하기위해서는 java.lang.thread 클래스를제공한다. 프로그래머는 Thread 클래스를사용하여멀티스레드프로그램을작성할수있다. 자바프로그램에서사용되는스레드는생성되어소멸될때까지 life cycle을가진다. [ 그림 1-3] 은자바스레드의 life cycle을나타낸것이다. running state 종료 시작 생성 start( yield( CPU 할당 runnable state sleep( ), wait( ) notify( ) 대기상태 [ 그림 1-3] 자바스레드의 life cycle 자바에서지원되는스레드또한클래스이므로객체를저장할메모리공간을할당하여야한다. 메모리공간을할당받은스레드 ( 생성상태 ) 는 start() 메소드에의해서실행가능한상태 ( 또는준비상태 : runnable state or ready state) 로전이한다. 준비상태의스레드에 CPU가할당되면, 스레드는실행된다 (running state). 일반적으로컴퓨터시스템에는 CPU 가하나뿐이므로여러개의준비된스레드중에서하나만이 CPU를할당받아실행상태에있게된다. 자바시스템은준비상태의여러스레드를일정시간동안번갈아가면서실행하 8 자바네트워크프로그래밍
여, 마치여러개의스레드가동시에실행되는것과같은효과를나타낸다. 동일한우선순위를가지는스레드들의실행순서는정해져있지않으며, 이를스레드의비동기 (asynchronous) 적인실행이라고한다. 실행중인스레드가 sleep() 이나 wait() 메소드를실행하거나, System.out.print() 와같은 I/O 명령어를실행하면스레드는대기상태로전환된다. 대기상태의스레드는원하는이벤트가발생하면다시준비상태로전이하여, CPU 할당을기다리게된다. 실행상태와대기상태, 준비상태를이동하던스레드는궁극적으로실행을마치고종료상태로전이하게된다. 멀티스레딩 / 멀티프로그래밍 / 멀티프로세싱의비교 1 멀티프로세싱 (multi-processing) 개인용컴퓨터에는일반적으로컴퓨터에 1개의 CPU가장착되어있지만, 서버용컴퓨터에는성능향상을위해서 2개이상의 CPU가장착되어있다. 이와같이컴퓨터시스템에 2 개이상의 CPU가장착되어데이터를처리할때, 이를멀티프로세싱이라고한다. 2 멀티프로그래밍 (multi-programming) 일반적으로멀티태스킹과동일한의미를가지며, 컴퓨터시스템이두개이상의프로그램을동시에주기억장치에적재하여실행하는처리기법을뜻한다. 일반적으로컴퓨터시스템에는하나의 CPU가설치되어있으므로, 한순간에하나의작업만이실행상태에있다. 운영체제가 CPU를프로그램에서다른프로그램으로할당하는작업전환을책임진다. 윈도우 95/98, 유닉스등의운영체제가멀티프로그래밍을지원하는운영체제들이다. 3 멀티스레딩 (multi-threading) 기존의단일스레드프로그램은하나의실행흐름을가지고실행된다. 프로그램실행명령의흐름을스레드라고하며, 멀티스레딩이란하나의프로그램에서실행명령의흐름이 2 개이상인경우를말한다. 멀티스레딩기법은멀티프로그래밍기법과유사한점이많다. 그러나멀티스레딩기법에서스레드들은하나의주소공간을공유하여실행되지만, 프로세스들은각프로세스마다별도의주소공간을가진다는차이점이있다. 주소공간을공유한스레드간의작업전환 (context switching) 비용이프로세스간의작업전환비용보다저렴하므로멀티스레딩기법이보다효율적이다. 1 장자바스프레드 9
Concurrent Program o Two or more threads or processes that work together. o Communication and synchronization via shared variables and/or message passing Multi-threaded application o more than 1 thread (may share the same CPU) o A good way to organize modern software systems. o Examples - OS: timesharing, servers - PC: GUI - Browser: applet - User: UNIX pipes(latex dvips) dvips: div ps 로변경 Parallel Application o processes or threads execute on their own processor o To solve a problem faster by dividing the work into pieces that can be solved simultaneously on multiple CPUs o or solve a large problem by utilizing memory of multiple CPUs. Distributed Applications o processes communicate over a network o offload work - servers o connect to remote data o use remote resources - grid computing o Scalable parallel computing on multi-computers & networks - SETI@home (www.computer.org/cise/articles/seti.htm) o Concurrent programming includes all of these (multi-thread, parallel, distributed). 10 자바네트워크프로그래밍
o Concurrent programming is more difficult than sequential programming - More difficult to understand - safety, liveness, and performance - More things to go wrong Dangers of concurrency o Safety hazards - safety means "nothing bad happens" - safety failures can lead to incorrect results o Liveness hazards - liveness means "something good eventually happens" - liveness failure leads to lack of any progress toward a result o Performance hazards - threads introduce overhead for thread startup, context switches, cache sharing costs, etc. - progress failures lead to unacceptable performance. 1) Example of a safety problem - The program returns a unique sequence number every time its method is called. - It is fine in a sequential setting. class Sequencer { private int val; public int getnext() { return val++; // load val; add 1; store val; return; 1 장자바스프레드 11
load val 4 add 1 store val 5 return 4 load val 4 add 1 store val 5 return 4 o Use synchronization to prevent the safety problem - In Java, one way to do this is using the synchronized keyword. - Ensure entire method executed atomically class Sequencer { private int val; public synchronized int getnext() { return val++; 2) Example of a liveness problem 1 Deadlock a.lock(); b.lock(); b.lock(); a.lock(); 2 Starvation/ poor responsiveness a.lock() very long computation a.lock(); 12 자바네트워크프로그래밍
3 livelock attempt to update x detect interference abort & retry attempt to update x detect interference abort & retry 1.2 사용자 Thread 의생성 자바에서멀티스레딩프로그램을작성하려면먼저프로그래머가사용자정의스레드클 래스를작성하여야한다. 그방법은 1 Thread 클래스를상속받아새로운클래스를정의 하는방법, 2 Runnable 인터페이스를구현하는클래스를정의하는것두가지가있다. 방법 1: 스래드클래스상속하기 먼저스레드클래스를상속받아새로운사용자정의스레드를작성하는방법에대해서 설명한다. Thread 클래스를상속하는경우는다음두가지의생성자를사용할수있다. Thread() Thread(String name) 위의생성자에서사용된 name 은스레드를구별할수있는이름이다. Runnable 인터페 이스를구현하였을경우에는다음두가지의생성자를사용할수있다. Thread(Runnable r) Thread(Runnable r, String name) 변수 'r' 은 Runnable 인터페이스를구현한객체를의미한다. 1 장자바스프레드 13
스레드를실행하기위해서는객체메소드 start() 를호출하여야한다. start() 메소드는 Thread 클래스로부터상속받은메소드로서, 먼저스레드의실행준비를한다음 run() 메소드를자동호출한다. 그러므로프로그래머는스레드가할일을 run() 메소드안에기술하여야한다. 다음은 Thread 클래스를상속받은사용자정의클래스를사용하는예이다. class MyThread extends Thread {... public void run() { // 스레드가수행할일을여기에기술한다. 앞에서정의한 MyThread 클래스의객체를생성하여실행하는코드는다음과같다. MyThread t1 = new MyThread(); t1.start(); 방법 2:Runnable 인터페이스구현하기 스레드를생성하는다른방법은 Runnable 인터페이스를구현하는클래스를정의하는것이다. 이는사용자가정의하는클래스가이미다른클래스를상속받았을경우자바언어의특성상다중상속이지원되지않으므로, Runnable 인터페이스를구현한클래스를정의하여야한다. 다음은애플릿클래스를상속받고, Runnable 인터페이스를구현하는예이다. ( 즉애플릿에서스레드를사용할때, 다음과같이정의한다.) Class MyRunnable extends Applet implements Runnable {... public void run() { // 스레드가수행할일을여기에기술한다. 앞에서선언한 MyRunnable 클래스의객체를생성하여실행하는예는다음과같다. 14 자바네트워크프로그래밍
MyRunnable r1 = new MyRunnable(); Thread t1 = new Thread(r1); t1.start(); 다음은자바의스레드의기능을설명하는간단한예로서많이소개된예제프로그램이다. 프로그램의내용은스레드의병렬성및비동기성을이용하여여름휴가여행지를임의로결정하는예제프로그램이다. 휴가예정지는디즈니랜드와홍콩이며, 가장마지막에출력된장소로결정하기로한다. 사용자정의스레드 SimpleThread 의 run() 메소드에서기술된작업은스레드이름을화면에출력하고, 0에서 999 밀리초사이의휴지상태를유지하는간단한것이다. 예제프로그램 class SimpleThread extends Thread { public SimpleThread(String s) { super(s); // 스레드이름설정 public void run() { for(int i=0; i<5; i++) { // 5회반복실행 System.out.println(getName()); // 스레드이름출력 try { sleep((int) (Math.random() * 1000)); catch (Exception e) { public class ThreadTest { public static void main(string[] a) { SimpleThread t1= new SimpleThread("Disney Land"); SimpleThread t2= new SimpleThread("\t\t Hong Kong"); t1.start(); t2.start(); 1 장자바스프레드 15
실행결과 C:\java\j8>java ThreadTest Disney Land Hong Kong Disney Land Disney Land Hong Kong Disney Land Hong Kong Disney Land Hong Kong Hong Kong TIP 실행결과에의하면이번여름의휴가지로홍콩을선택하였다. 실행결과가항상동일하지않도록하기위해서 Math.random() 메소드를사용하여, 0부터 1000미만의난수를발생시킨후, sleep() 메소드를사용하여스레드의실행을일시중지시켰다. Thread 클래스의 sleep() 메소드는실행중인스레드를주어진시간 ( 단위밀리초 ) 동안휴지상태를유지시키는메소드로서반드시예외처리 (try, catch문사용 ) 를해주어야한다. 위의프로그램을 Runnable 인터페이스를이용하여다시구현하면다음과같다. ( 앞의 예제와동일한기능을수행하므로주의깊게차이점을비교하기바란다.) 예제프로그램 class SimpleRun implements Runnable { String name; public SimpleRun(String str) { // Thread 클래스의생성자를사용할수 16 자바네트워크프로그래밍
name = str; // 없으므로이렇게작성한다. public void run() { for(int i=0; i<5; i++) { System.out.println(this.name); try { Thread.sleep((int) (Math.random() * 1000)); catch (Exception e) { public class RunnableTest { public static void main(string[] a) { SimpleRun r1= new SimpleRun("Disney Land"); SimpleRun r2= new SimpleRun("\t\t Hong Kong"); Thread t1= new Thread(r1); Thread t2= new Thread(r2); t1.start(); t2.start(); 실행결과 C:\java\j8>java RunnableTest Disney Land Hong Kong Disney Land Disney Land Hong Kong Hong Kong Disney Land Disney Land Hong Kong Hong Kong 1 장자바스프레드 17
TIP 이번실행결과도홍콩이선택되었다. 홍콩이선택될확률이약간더높은이유는 t2. start() 메소드가나중에실행되었기때문이다. TIP Runnable 인터페이스를구현하는클래스는불행스럽게도 Thread 클래스를상속받지않았으므로 Thread 클래스메소드를사용할수없다. Runnable 인터페이스를사용한앞의예제프로그램에서 SimpleRun 클래스는 String name이라는변수가추가되어정의되어있으며, sleep() 메소드를 Thread.sleep() 으로호출하고있다는차이점에주의해야한다. [ 코딩연습 ] 세여자 (Sophia, Diana, Julia) 중에하나를선택하는멀티스레드프로그램을작성하시오. [ 연습문제 ] 다음프로그램은앞의예제에서 start() 메소드를 run() 메소드로변경한것이다. 실행결과를예측하고설명하시오. 소스코드 class SimThread extends Thread { public SimThread(String s) { super(s); public void run() { for(int i=0; i<5; i++) { // 5회반복실행 System.out.println(getName()); // 스레드이름출력 try { sleep((int) (Math.random() * 1000)); catch (Exception e) { public class SimTest { public static void main(string[] a) { SimThread t1= new SimThread("Disney Land"); 18 자바네트워크프로그래밍
SimThread t2= new SimThread("\t\t Hong Kong"); t1.run(); // 변경된부분 t2.run(); // 변경된부분 [ 연습문제 ] 다음소스코드를살펴보고물음에답하시오. 멀티스레드는하나의주소공간을공유하므로, 두개의스레드객체 s와 t는공유변수를가질수있다. Simple 클래스에서정의된변수 (cnt, val) 중에서공유되는변수는어느것인가? 실행결과를설명하시오. 소스코드 class Simple implements Runnable { static int cnt=0; // per class variable int val; // per thread variable public Simple(int x){ cnt++; val = x; public void run(){ System.out.println("cnt="+cnt+", val="+val); public class Exam_01 { public static void main(string[] args) { Thread s=new Thread(new Simple(10)); Thread t=new Thread(new Simple(12)); s.start(); t.start(); 실행결과 cnt=2, val=10 cnt=2, val=12 1 장자바스프레드 19
Simple 클래스의정적변수 (static variable) cnt 는모든 Simple 클래스객체가공유한다. Simple 클래스의객체변수 val 은객체가생성될때마다새롭게할당되므로공유되지않는다. 그렇다면두개의스레드가하나의객체를공유하기위해서는어떻게하여야되겠는가? 답 ) 공유하고자하는객체 (object reference) 를스레드멤버변수가가리키도록해야한다. 그러기위해서는스레드생성시에공유객체의참조 (reference) 를넘겨받아야한다. ( 추후의예제참조바람 ) 1.3 스레드클래스지원메소드 여러스레드가준비상태에있을때, CPU는그중에하나를선택해서실행한다. 이때실행할스레드를선택하는기준이바로우선순위 (priority) 이다. 자바에서우선순위는정수로표현되며 1부터 10까지의값을가진다. 10이가장높은우선순위이며, 1이가장낮은우선순위를나타낸다. 일반스레드의우선순위는 5이다. java.lang 패키지에속한 Thread 클래스는스레드의우선순위를지정하는데사용할수있는다음의세정수형상수를지원한다. MAX_PRIORITY, MIN_PRIORITY, NORM_PRIORITY 가바로지원되는세가지상수이다. 시스템관리자는준비상태의여러스레드중에서가장우선순위가높은스레드를선택하여 CPU를할당하게된다. 스레드는 CPU가할당되어야만실행될수있다. 다음표는 Thread 클래스에서지원되는정적메소드를정리한것이다. [ 표 1-1] Thread 클래스의메소드 static( 정적 ) method 설명 Thread currentthread() int enumerate(thread[] tarray) void sleep(long msec) throws InterruptedException void sleep(long msec, int nsec) thorws InterruptedException void yield() 현스레드를반환현스레드의그룹, 서브그룹에속한스레드를배열에복사현스레드를 msec 밀리초동안대기현스레드를 msec 밀리초와 nsec 나노초동안대기현스레드가다른스레드에게실행권을양도 20 자바네트워크프로그래밍
객체메소드 설명 String getname() long getid() 스레드의이름을반환 스레드의 ID 를반환 void start() 스레드실행준비한후 run() 메소드를호출. int getpriority() 스레드의우선순위를반환. boolean isalive() 스레드가시작되어종료전이면 true, 아니면 false 를반환 void interrupt() 스레드객체에인터럽트한다. boolean isinterrupted() 현스레드가인터럽트되었는지테스트한다. void join() throws InterruptedException void join(long msec) throws InterruptedException 현재스레드가종료될때까지스레드를대기. 현재스레드가종료될때까지스레드를최대 msec 밀리초까지대기. void run() 하위클래스에서재정의되어, 실행할내용을기술. void setname(string s) 스레드의이름을설정. void setpriority(int p) 스레드의우선순위를 p 로설정. boolean isdaemon() void setdaemon(boolean b) 스레드의데몬여부를반환 데몬여부를결정. 반드시 start() 전에수행. JVM exits only threads are all daemon. 초기버전의자바에서지원되었던 resume(), stop(), suspend() 는더이상사용을권장하지않지만, 기존의자바프로그램을이해하기위해서소개한다. 1.2 버전이후의 JDK를사용하여컴파일하면, resume(), stop(), suspend() 등은 deprecated API를사용하였다는경고문장이출력된다. [ 표 1-2] deprecated 메소드 ( 권장하지않는메소드 ) 메소드 설명 void suspend() 스레드의실행을잠시중단시킴. void destroy() void resume() 할당된자원의회수없이스레드를 destroy 시킴 suspend() 에의해중단된스레드의실행을재개시킴 void stop() 스레드의실행을중지시킴. 1 장자바스프레드 21
다음은자바 API 를확인하는예제프로그램이다. 예제프로그램 public class APITest extends Thread { public void run() { System.out.println("--- in child ----"); System.out.println("count = "+ activecount()); Thread mythread = Thread.currentThread(); System.out.println("current Thread = "+ mythread); System.out.println("priority = " + mythread.getpriority()); System.out.println("alive? = " + mythread.isalive()); System.out.println("Daemon? = "+mythread.isdaemon()); public static void main(string[] args) { System.out.println("count = "+Thread.activeCount()); Thread mythread = Thread.currentThread(); System.out.println("current Thread = "+ mythread); System.out.println("priority = " + mythread.getpriority()); System.out.println("alive? = " + mythread.isalive()); System.out.println("Daemon? = "+mythread.isdaemon()); APITest child = new APITest(); child.setdaemon(true); child.setpriority(8); child.start(); try { child.join(); // 나중에설명 catch(interruptedexception e){ 22 자바네트워크프로그래밍
실행결과 count = 1 current Thread = Thread[main,5,main] priority = 5 alive? = true Daemon? = false --- in child ---- count = 2 current Thread = Thread[Thread-0,8,main] priority = 8 alive? = true Daemon? = true 계속하려면아무키나누르십시오... 데몬스레드를제외한응용프로그램안의모든스레드가종료해야 JVM 이종료된다. 다음예제프로그램은데몬스레드와일반스레드와의차이점을설명한다. 그대로실 행한경우와데몬설정후 ( 주석표시를삭제 ) 실행하여그차이점을비교하시오. 예제프로그램 public class DaemonThread extends Thread { public void run(){ try{ System.out.println("Thread Start.."); Thread.sleep(2000); catch(interruptedexception e){ finally{ System.out.println("Thread Dead.."); public static void main(string[] args) { DaemonThread t = new DaemonThread(); // t.setdaemon(true); 1 장자바스프레드 23
t.start(); System.out.println("Main 스레드종료 "); 실행결과 Thread Start.. Main 스레드종료 Thread Dead.. 계속하려면아무키나누르십시오... 스레드 t 가데몬으로설정되면, t 의실행이종료되기전에 JVM 이먼저종료될수있 다. 즉데몬스레드 t의실행이완료되기전에먼저 JVM이먼저종료되므로, Thread Dead.. 가출력되지않을수도있다. [ 프로그래밍연습 ] 예제프로그램 APITest.java 를참조하여, deprecated method 인 suspend(), resume() 메소드의역할을보여주는예제 APITest2.java 를작성하시오. ( 박스안의코드참조 ) APITest2 child = new APITest2(); child.start(); child.suspend(); Thread.sleep(2000); child.resume(); 다음은우선순위의사용을보여주는예제프로그램이다. 현재자바가제공하는우선 순위는 1 부터 10 까지이며, 큰정수값이우선순위가높음을나타낸다. 예제프로그램 class SimpleThread2 extends Thread { 24 자바네트워크프로그래밍
public SimpleThread2(String s) { super(s); public void run() { for(int i=0; i<5; i++) { System.out.println(getName()+"\t 우선순위 :"+getpriority()); try { sleep((int) (Math.random() * 4)); catch (Exception e) { public class PriorityTest { public static void main(string[] a) { SimpleThread2 t1= new SimpleThread2("Max Priority thread"); SimpleThread2 t2= new SimpleThread2("Normal Priority thread"); SimpleThread2 t3= new SimpleThread2("Min Priority thread"); t1.setpriority(thread.max_priority); t2.setpriority(thread.norm_priority); t3.setpriority(thread.min_priority); t1.start(); t2.start(); t3.start(); 실행결과 C:\java\j8>java PriorityTest Max Priority thread 우선순위 :10 Normal Priority thread 우선순위 :5 Min Priority thread 우선순위 :1 Normal Priority thread 우선순위 :5 Min Priority thread 우선순위 :1 Max Priority thread 우선순위 :10 1 장자바스프레드 25
Normal Priority thread 우선순위 :5 Max Priority thread 우선순위 :10 Max Priority thread 우선순위 :10 Min Priority thread 우선순위 :1 Min Priority thread 우선순위 :1 Normal Priority thread 우선순위 :5 Max Priority thread 우선순위 :10 Normal Priority thread 우선순위 :5 Min Priority thread 우선순위 :1 계속하려면아무키나누르십시오... [ 프로그램연습 ] 위의실행결과는스레드우선순위가적절히반영되어있지않다고생각된다. 1 sleep() 시간이현재 4 msec 로설정되어있는데, 이를 2msec 로변경한다음실행해보시오. 2 현재프로그램에서생성되는자식스레드의수가 3개이다. 이를 6개로늘리고적절한우선순위를주어실행하시오. 스레드우선순위가실행결과에보다적절히반영됨을알수있다. 그이유는무엇이라고생각하는가? TIP UNIX 및윈도우 NT와같은 multi-user 운영체제에서일반사용자로로그인하면, 스레드의우선순위를변경하지못하는경우가발생한다. 스레드의우선순위를높이는것은다른사용자의프로그램실행에많은영향을주기때문에권한을갖지않은일반사용자프로그램이우선순위변경은일반적으로허용되지않는다. Join() 메소드 join() 메소드는 start() 로시작시킨스레드가종료되기를기다리는메소드이다. 예를들 어메인에서생성한스레드 t1 을실행시키려면, t1.start(); 를사용하여야한다. 이순간부터프로그램에는메인스레드와 t1 스레드가병렬적으로실행된다. 스레드 t1이종료될때까지메인스레드 (main thread) 가대기하고싶다면 t1.join(); 26 자바네트워크프로그래밍
을사용하여야한다. 만일 t1.join() 을사용하지않으면메인스레드는스레드 t1과는무관하게계속진행한다. 이를그림으로설명하면다음과같다. 스레드 t1은 main 스레드가 t1.start() 를호출함으로써, run() 메소드를실행한다. 이때 main 스레드와 t1 스레드는서로비동기적으로실행된다. main 스레드가 t1.join() 를호출하면, t1 스레드가 run() 의실행을종료할때까지대기한다. 여기서중요한요점은자바프로그램실행을위해서기본적으로 main 스레드가존재한다는점이다. 그러므로자바프로그램에서스레드객체 t1을생성하여 t1.start() 를실행하면 main 스레드와 t1 스레드가비동기적으로실행된다는점을유의하여야한다. main 스레드 t1.start() t1 스레드 main 스레드는 t1.start() 와 t1.join() 사이의명령어수행 t1 은 run() 실행 t1.join() 다음예제에서 join() 메소드의역할을확인해보자. 예제프로그램 : join() 없음 - main 스레드와자식의실행순서없음 class Simple2 implements Runnable { int val; public Simple2(int x){ val = x; 1 장자바스프레드 27
public void run(){ System.out.println("val="+val); public class NoJoin { public static void main(string[] args) { Thread s=new Thread(new Simple2(10)); Thread t=new Thread(new Simple2(12)); s.start(); t.start(); System.out.println("main thread end"); 실행결과 val=10 main thread end val=12 예제프로그램 -join() 사용 : 자식종료대기 class Simple2 implements Runnable { int val; public Simple2(int x){ val = x; public void run(){ System.out.println("val="+val); public class WithJoin { public static void main(string[] args) { 28 자바네트워크프로그래밍
Thread s=new Thread(new Simple2(10)); Thread t=new Thread(new Simple2(12)); s.start(); t.start(); try { s.join(); t.join(); catch(interruptedexception e){ System.out.println("main thread end"); 실행결과 val=12 val=10 main thread end 다음예제프로그램은앞의휴가목적지선택프로그램을 join() 을사용하여변경한것이다. join() 메소드와정적변수를사용하여앞의예제프로그램보다효과적으로휴가목적지를선택하고있다. ( 정적변수는두개의스레드에서공유가능함 ) 예제프로그램 class SimThread2 extends Thread { public SimThread2(String s) { super(s); public void run() { for(int i=0; i<5; i++) { if(getname().equals("disney Land")) JoinTest.tour=0; else 1 장자바스프레드 29
JoinTest.tour = 1; try { sleep((int) (Math.random() * 1000)); catch (Exception e) { public class JoinTest { static int tour; // JoinTest의정적변수 public static void main(string[] a) { SimThread2 t1= new SimThread2("Disney Land"); SimThread2 t2= new SimThread2("\t\t Hong Kong"); t1.start(); t2.start(); try { t1.join(); // t1이종료할때까지대기 t2.join(); catch(exception e) { e.printstacktrace(); if(tour==0) System.out.println("Let's go! Disney Land!"); else System.out.println("Let's go! Hong Kong!"); 실행결과 C:\java\j8>java JoinTest Let's go! Disney Land! C:\java\j8>java JoinTest Let's go! Hong Kong! [ 코딩연습 ] join() 메소드를사용하여, 세여자 (Sophia, Diana, Julia) 중에하나를선택하는멀티스레드프로그램을작성하시오. 30 자바네트워크프로그래밍
Thread.yield() 메소드 Thread 클래스의정적메소드 yield() 는현재실행중인스레드의실행을일시중단하여다른스레드가실행할수있도록한다. CPU를양보 (yield) 하여다른스레드가실행되도록한다는의미이다. 다음예제를실행하여 Thread.yield() 의기능을이해하자. 프로그램소스 class S10 implements Runnable { public void run(){ System.out.println("S10"); // Thread.yield(); System.out.println("S10"); // Thread.yield(); System.out.println("S10"); class S20 implements Runnable { public void run(){ System.out.println("S20"); // Thread.yield(); System.out.println("S20"); // Thread.yield(); System.out.println("S20"); class S30 implements Runnable { public void run(){ System.out.println("S30"); // Thread.yield(); System.out.println("S30"); // Thread.yield(); 1 장자바스프레드 31
System.out.println("S30"); public class Yield { public static void main(string[] args) { Thread s=new Thread(new S10()); Thread t=new Thread(new S20()); Thread u=new Thread(new S30()); s.start(); t.start(); u.start(); 실행결과 D:\Java\Day002>java Yield S10 S10 S10 S30 S30 S30 S20 S20 S20 위의예제에서 Thread.yield() 메소드가작동하도록, 주석 (//) 을모두삭제하고실행 한결과는다음과같다. 앞의실행결과와비교하여여러스레드가번갈아실행되었 음을알수있다. 32 자바네트워크프로그래밍
실행결과 D:\Java\Day002>java Yield S10 S20 S10 S30 S10 S20 S30 S20 S30 다음은 1부터 4000까지의정수의합을구하는계산을멀티스레드로구현한예제프로그램이다. 4개의스레드를생성하여각각 1부터 1000까지의합, 1001부터 2000까지의합, 2001부터 3000까지의합, 3001부터 4000까지의합을계산하도록하였다. 동일한기능을수행하는단일스레드프로그램을작성하고계산시간을비교하시오. 예제프로그램 class MyInteger { private int value; public int getvalue() { return value; public void setvalue(int val) { value = val; 1 장자바스프레드 33
class Sum implements Runnable { private int low, upper; private MyInteger result; public Sum(int low, int upper, MyInteger res) { this.low = low; this.upper = upper; result = res; public void run() { int sum = 0; for(int i= low; i<= upper; i++) sum += i; result.setvalue(sum); public class AddDriver { public static void main(string[] args) throws Exception { int total; long start, end; start = System.nanoTime(); MyInteger m1 = new MyInteger(); MyInteger m2 = new MyInteger(); MyInteger m3 = new MyInteger(); MyInteger m4 = new MyInteger(); Thread t1 = new Thread(new Sum(1,1000, m1)); Thread t2 = new Thread(new Sum(1001,2000, m2)); Thread t3 = new Thread(new Sum(2001,3000, m3)); Thread t4 = new Thread(new Sum(3001,4000, m4)); t1.start(); t3.start(); t2.start(); t4.start(); 34 자바네트워크프로그래밍
t1.join(); t2.join(); t3.join(); t4.join(); total = m1.getvalue() + m2.getvalue() + m3.getvalue() +m4.getvalue(); end = System.nanoTime(); System.out.println("Total = "+total+" Compute Time nano="+(end-start)); 실행결과 D:\Java\Day002>java AddDriver Total = 8002000 Compute Time nano=1736589 앞의예제프로그램을반복수행해보면, 수행할때마다계산시간이다르게나온다. 그이유에대해서조사해보시요. ( 자바에서는가비지콜렉션을 JVM 이스스로처리 한다.) [ 코딩연습 ] 앞의예제를다음과같이수정하시오. 1개의스레드만을생성하여 1부터 8000 까지의정수합을계산하도록하시오. 실행시간을비교하시오. [Home Work] 멀티스레드프로그래밍 1) 자식스레드를생성하여, 1부터 20000 까지의정수의합을계산하도록하고, 그계산시간 (nono 초단위 ) 을출력하시오. 2) 두개의자식스레드를생성하여한스레드는 1부터 10000 까지의정수합을계산하고, 다른스레드는 10001 부터 20000 까지의정수합을계산하도록하시오. 그리고다음두결과를합하여 1부터 20000 까지의정수합을계산하고계산시간 (nono 초단위 ) 을출력하시오. 연산위주의 CPU-bound job을굳이나누어멀티스레딩을할필요는없다. 이렇게할경우, 실행시간이오히려더걸리기도한다. 다음예제는계산도중에 IO를실행한다고가정하였다. 다음예제를단일스레드와멀티스레드로실행하고그실행시간을비교해보자. 1 장자바스프레드 35
예제프로그램코드 /* class MyInteger { private int value; public int getvalue() { return value; public void setvalue(int val) { value = val; */ // 기존에 MyInteger.class가없어서오류가발생하면, /* 와 */ 를삭제하고실행 class Sum2 implements Runnable { private int low, upper; private MyInteger result; static void executeio() { // no actual IO, just wait IO time. try{ Thread.sleep(2); catch(interruptedexception e){ public Sum2(int low, int upper, MyInteger res) { this.low = low; this.upper = upper; result = res; public void run() { int sum = 0; for(int i= low; i<= upper; i++) { executeio(); sum += i; 36 자바네트워크프로그래밍
result.setvalue(sum); public class DriverIO { public static void main(string[] args) throws Exception { int total; long start, end; start = System.nanoTime(); MyInteger m1 = new MyInteger(); MyInteger m2 = new MyInteger(); Thread t1 = new Thread(new Sum2(1,500, m1)); Thread t2 = new Thread(new Sum2(501,1000, m2)); t1.start(); t1.join(); t2.start(); t2.join(); total = m1.getvalue() + m2.getvalue(); end = System.nanoTime(); System.out.println("Total = "+total+" Compute Time msec="+ ((end-start)/1000000)); [ 스레드 2개생성실행시간 ] Total = 500500 Compute Time msec=1465 자바예제코드 : 단일스레드실행소스 public class DriverIO2 { public static void main(string[] args) throws Exception { int total; long start, end; 1 장자바스프레드 37
start = System.nanoTime(); MyInteger m1 = new MyInteger(); Thread t1 = new Thread(new Sum2(1,1000, m1)); t1.start(); t1.join(); total = m1.getvalue(); end = System.nanoTime(); System.out.println("Total = "+total+" Compute Time msec="+ ((end-start)/1000000)); 실행결과 Total = 500500 Compute Time msec=2930 Thread 실행취소스레드의실행취소는실행이정상종료되기전에강제적으로중단시키는것을의미한다. 예를들어, 여러스레드가데이터를병행검색하여하나의스레드가원하는정보를얻게되면다른스레드의정보검색은더이상필요가없어진다. 이러한경우에나머지스레드의작업은취소하는것이바람직하다. 스레드실행을취소하는시나리오는두가지이다. 1 비동기적취소 (Asynchronous cancellation) 중단을원하는스레드를즉시중단시키는것을말한다. 자바에서는이미 deprecated 된 stop() 메소드를사용하면가능하다. 스레드가자원을소유한상태에서갑자기실행중단이이루어지므로, 교착상태가발생할수있다. 그러므로비동기적취소를실행하려면주의가필요하다. 2 연기된취소 (Defered cancellation) 실행중인스레드가중단되어야하는지주기적으로체크한다. 자바에서는 interrupt() 와 isinterrupted() 메소드를사용하면가능하다. 38 자바네트워크프로그래밍
비동기적취소예제프로그램 class StoppedThread implements Runnable { public void run() { while (true) { Stopper.sum = Stopper.sum + 1; // execute this thread should do public class Stopper { static public long sum=0; public static void main(string[] args) { Thread worker = new Thread (new StoppedThread()); worker.start(); try { Thread.sleep(2000); catch (InterruptedException ie) { worker.stop(); // stop worker thread System.out.println("worker thread stopped: sum = "+sum); 실행결과 worker thread stopped: sum = 572517984 연기된취소예제프로그램 class InterruptibleThread implements Runnable { 1 장자바스프레드 39
public void run() { while (true) { if (Thread.currentThread().isInterrupted()) { System.out.println("I'm interrupted!"); break; // clean up and terminate public class Interrupter { public static void main(string[] args) { Thread worker = new Thread (new InterruptibleThread()); worker.start(); // now wait 3 seconds before interrupting it try { Thread.sleep(3000); catch (InterruptedException ie) { worker.interrupt(); // send interrupt signal to child thread 실행결과 I'm interrupted! [Home Work] 스레드의실행을취소시키는경우, Asynchronous Calcellation 보다는 Defered Cancellation 이더바람직하다. 그이유에대해서조사해보시오. ( 소유한자원의 clean-up, 교착상태...) 40 자바네트워크프로그래밍
1.4 스레드동기화 멀티스레드프로그래밍환경에서는여러개의스레드가실행하면서데이터를공유할수있다. 여러스레드가데이터를공유하면서데이터를갱신하는경우에는프로그램의실행에유의하여야한다. 멀티스레드프로그램을올바르게작성하지않을경우에입력된데이터가손실될수있으며, 이로인하여틀린결과를유발시킬수도있다. (1.1의 safty 문제참조 ) 다음예제프로그램은 deposit( 예금 ) 스레드와 withdraw( 출금 ) 스레드가있으며각각 1000회식입금하거나출금하는기능을수행한다 ( 매회 100원씩 ). 예제프로그램 class Account { int bal=10000; class Deposit extends Thread { Account account; int temp; Deposit(Account a) { // 생성자 account = a; // synchronized int add(int amt) { int temp = account.bal; account.bal = temp + amt; return (account.bal); public void run() { for(int i=1; i<=1000; i++) { temp = add(100); 1 장자바스프레드 41
class Withdraw extends Thread { Account account; int temp; Withdraw(Account a) { // 생성자 account = a; // synchronized int sub(int amt) { int temp = account.bal; account.bal = temp - amt; return (account.bal); public void run() { for(int i=1; i<=1000; i++) { temp = sub(100); public class ConCurrent { public static void main(string args[]) { Account acc = new Account(); // 공유객체생성 Deposit deposit = new Deposit(acc); Withdraw withdraw = new Withdraw(acc); deposit.start(); // 스레드실행 withdraw.start(); // 스레드실행 try { deposit.join(); withdraw.join(); catch(interruptedexception e) { e.printstacktrace(); System.out.println("Final balance=" + acc.bal); 42 자바네트워크프로그래밍
실행결과 C:\java\j8>java ConCurrent (JDK 1.5로실행한여러결과중의하나임 ) Final balance=6400 실행결과를살펴보면각기 1000회씩입금과출금을하였으므로, 원래예금잔액과동일한 10000원이되어야하겠지만, 결과는 6400원이되었다. 여러번실행해보면결과가 6400원이아닌여러가지경우가발견된다. 이는 deposit 스레드와 withdraw 스레드가동일한잔액 (bal) 값을읽고연산을행하기때문에두개중에하나의연산을잃어버리는결과가발생하기때문이다. [ 그림 1-4] 에서알수있듯이, 두개의스레드실행을동기화하지않으면나중에 bal값을쓴스레드의결과가앞의스레드결과를 overwrite 하고있다. bal=10000 읽기 deposi bal+=100 읽기 withdra bal-=100 쓰기 쓰기 bal=10100 bal=9900 [ 그림 1-4] 비동기적인갱신으로인한오류 여러스레드가하나의데이터를공유하기위해서는약간의주의가필요하다. 일반적으로스레드를생성하면각스레드마다변수들이새롭게생성된다. 그러므로스레드간에데이터를공유하기위해서는예제프로그램에서와같이메인메소드에서공유할객체를생성하고공유할객체를스레드생성자의인자로넘겨주어야한다. 객체를인자로사용하면참조값 ( 주소값 ) 을넘겨주므로, 각스레드가동일한객체를공유하는것이가능하다. 만일정수나 1 장자바스프레드 43
실수와같은객체속성변수를인자로넘겨준다면, 변수의값이넘어가므로공유할수없 다. [ 그림 1-5] 는앞의예제프로그램에서 withdraw 스레드와 deposit 스레드가 account 객체를공유하는것을표현한것이다. account 객체 withdraw 객체참조변수 acc deposit 객체참조변수 acc [ 그림 1-5] 두스레드의 account 객체공유 다음예제코드는앞에서설명한스레드객체간의공유를설명하기위한것이다. Account 객체 acc를두개의스레드클래스객체 withdraw 와 deposit가공유하고있음을 유의한다. 예제프로그램 class Account {... // 세부사항생략 class Withdraw extends Thread { Account acc; Withdraw(Account a) { // 생성자 acc = a;... // 세부사항생략 class Deposit extends Thread { Account acc; Deposit(Account a) { // 생성자 acc = a; 44 자바네트워크프로그래밍
... // 세부사항생략 public class MainClass { public static void main(string[] args) { Account acc = new Account(); Deposit deposit = new Deposit(acc); Withdraw withdraw= new Withdraw(acc);... 앞의예제프로그램의수행결과가올바르게나오려면, 동시에두개메소드가공유변수에동시접근하는것을금지하여야한다. 자바에서는동시에실행되서는안되는메소드를선언할수있도록하기위해서 synchronized 키워드를제공하고있다. 스레드가 synchronized 메소드의실행을시작하면자동으로메소드에 lock을설정하고, 메소드가완료될때까지 lock 을유지한다. Synchronized 키워드는메소드나객체와같이사용될수있다. 예를들어 Account 클래스의메소드 deposit() 와 withdraw() 의동시실행을방지하고싶으면, class Account { int bal; public synchronized void deposit(int amount) {... public synchronized void withdraw(int amount) {... 와같이명시하면된다. 공통의객체에여러액세스를동기화하는다른방법은 synchronized를객체에사용하는것이다. 그형식은다음과같다. 1 장자바스프레드 45
synchronized(obj) { // 명령문 예를들어, 인자로전달된객체를변경하고자할때, 액세스를동기화하고싶다면다음과 같이표현한다. public void updatebank(bank mybank) { synchronized(mybank) { // mybank 객체를갱신하는명령어 Synchronized 키워드를사용해서앞의예제프로그램을올바르게수정하면다음과같 다. Synchronized 키워드는여러스레드가공유객체에액세스하는것을방지하여준다. 실행결과를비교하여보자. 예제프로그램 class Account { int bal=10000; class Deposit2 extends Thread { Account account; int temp; Deposit2(Account a) { // 생성자 account = a; synchronized int add(int amt) { // 수정된곳 int temp = account.bal; account.bal = temp + amt; return (account.bal); 46 자바네트워크프로그래밍
public void run() { for(int i=1; i<=10; i++) { temp = add(100); class Withdraw2 extends Thread { Account account; int temp; Withdraw2(Account a) { // 생성자 account = a; synchronized int sub(int amt) { // 수정된곳 int temp = account.bal; account.bal = temp - amt; return (account.bal); public void run() { for(int i=1; i<=10; i++) { temp = sub(100); public class SynConCurrent { public static void main(string args[]) { Account acc = new Account(); Deposit2 deposit = new Deposit2(acc); Withdraw2 withdraw = new Withdraw2(acc); deposit.start(); withdraw.start(); try { deposit.join(); withdraw.join(); catch(interruptedexception e) { 1 장자바스프레드 47
e.printstacktrace(); System.out.println("Final balance=" + acc.bal); 실행결과 C:\java\j8>java SynConCurrent Final balance=10000 실행결과가올바르게출력되었음을알수있다. 그러나앞의예제프로그램은 Account 클래스내의변수 bal을외부에서조작하고있으므로객체지향적이지못하다. Account 클래스의내부변수들을조작하는메소드를 Account 클래스안에서정의하는것이바람직하다. 앞의예제프로그램을객체지향적으로변경하면다음과같다. 예제프로그램 class Account3 { private int bal=10000; public int getbal() { return bal; public synchronized void add(int amt) { bal += amt; public synchronized void sub(int amt) { bal -= amt; class Deposit3 extends Thread { Account3 account; Deposit3(Account3 a) { // 생성자 account = a; 48 자바네트워크프로그래밍
public void run() { for(int i=1; i<=10; i++) { account.add(100); System.out.println("dep # " + i + " Current = " + account.getbal()); class Withdraw3 extends Thread { Account3 account; Withdraw3(Account3 a) { // 생성자 account = a; public void run() { for(int i=1; i<=10; i++) { account.sub(100); System.out.println("with# " + i + " Current = " + account.getbal()); public class SynCon { public static void main(string args[]) { Account3 acc = new Account3(); Deposit3 deposit = new Deposit3(acc); Withdraw3 withdraw = new Withdraw3(acc); deposit.start(); withdraw.start(); try { deposit.join(); withdraw.join(); catch(interruptedexception e) { e.printstacktrace(); System.out.println("The balance = " + acc.getbal()); 1 장자바스프레드 49
이예제에서는 Account 클래스안에서내부변수 bal 을조작하는 add(), sub() 메소드 가정의되어있으므로앞의예제보다객체지향적이라고할수있다. 1.5 스레드간의통신 앞에서는여러스레드가순서없이공유데이터에접근하는방식을살펴보았다. synchronized 키워드는공유데이터에동시에접근하는실행오류를방지하여준다. 그러나만일두개또는그이상의스레드가특정순서에따라실행되기를원한다면앞의 synchronized 키워드만가지고는불가능하다. 예를들어생산자와소비자문제와같이먼저데이터를생산한후에데이터를소비하는프로그램의경우에는별도의키워드가필요하다. 최상위클래스인 Object 클래스에서정의된메소드이지만, 스레드간의통신에서사용되는중요한메소드만을정리하면다음과같다. [ 표 1-4] 스레드통신을위한메소드 메소드 void wait() throws InterruptedException void wait(long msec) throws InterruptedException void wait(long msec,int nsec) throws InterruptedException 설명 현재스레드를대기시킨다. notify() 로실행을재개할수있다. 현재스레드를최대 msec 밀리초동안대기시킨다. 현재스레드를최대 msec 밀리초와 nsec 나노초동안대기시킨다. void notify() 대기중인스레드의실행을재개시킨다.( 스레드하나를재개시킴 ) void notifyall() 대기중인모든스레드의실행을재개시킨다. notify() 와 wait() 메소드는동일한객체를수행하는멀티스레드사이의통신에사 용되므로, 다음예제프로그램과같이서로다른객체간에 notify(), wait() 는통신 하지않는다. 50 자바네트워크프로그래밍
예제프로그램 class SCom11 implements Runnable { public void run(){ System.out.println("Im Waiting"); try { wait(); catch(interruptedexception e){ System.out.println("Waked up"); class SCom12 implements Runnable { public void run(){ System.out.println("about notify"); notify(); System.out.println("notifyed"); public class ThreadCom1 { public static void main(string[] args) { Thread s=new Thread(new SCom11()); Thread t=new Thread(new SCom12()); s.start(); // 서로다른스레드 s와 t 실행 t.start(); 실행결과 D:\Java\Day002>java ThreadCom1 Im Waiting about notify 1 장자바스프레드 51
Exception in thread "Thread-0" Exception in thread "Thread-1" java.lang. IllegalMonitorStateException at java.lang.object.notify(native Method) at S2.run(ThreadCom.java:13) at java.lang.thread.run(unknown Source) java.lang.illegalmonitorstateexception at java.lang.object.wait(native Method) at java.lang.object.wait(object.java:485) at S1.run(ThreadCom.java:5) at java.lang.thread.run(unknown Source) 위의실행오류를없애기위해서는 synchronized 키워드를사용하여야한다. wait() 와 notify() 를포함하고있는메소드는 synchronized 메소드이어야오류가발생하지않는다. 여기서중요한것은 synchronized 메소드안에서한스레드가 wait() 로대기하는경우, 다른스레드가 synchronized 메소드를실행하는것이가능하다. 다음예제를살펴보자. 예제프로그램 class SCom21 implements Runnable { synchronized void w(){ // 다시작성된메소드 try { wait(); catch(interruptedexception e){ public void run(){ System.out.println("Im Waiting"); w(); System.out.println("Waked up"); class SCom22 implements Runnable { synchronized void n() { // 다시작성된메소드 52 자바네트워크프로그래밍
notify(); public void run(){ System.out.println("about notify"); n(); System.out.println("notifyed"); public class ThreadCom2 { public static void main(string[] args) { Thread s = new Thread(new SCom21()); Thread t = new Thread(new SCom22()); s.start(); t.start(); 실행결과 실행오류는사라졌지만, 스레드간통신은되지않는다. 스레드 t가 notify () 를실행하였지만, wait() 중인스레드 s는깨어나지않는다. D:\Java\Day002>java ThreadCom2 Im Waiting about notify notifyed < 첫째스레드가계속기다리므로실행이종료되지않는다.> wait() 로대기중인스레드를 notify() 로깨워주기위해서는스레드생성시에하나의객체를공유하도록하여야한다. 멀티스레드가공유하는객체의클래스 Common을정의하고, Common 클래스안의메소드안에서 wait() 와 notify() 메소드를사용한다. 1 장자바스프레드 53
예제프로그램 class Common { synchronized void w() { try { System.out.println("Waiting"); wait(); System.out.println("Finished"); catch(interruptedexception e){ synchronized void n() { System.out.println("About to Notify"); notify(); System.out.println("Notified"); class S11 implements Runnable { Common com; S11(Common c) { com=c; public void run(){ com.w(); class S22 implements Runnable { Common com; S22(Common c) { com=c; public void run(){ com.n(); 54 자바네트워크프로그래밍
public class ThreadCom3 { public static void main(string[] args) { Common com = new Common(); Thread s=new Thread(new S11(com)); Thread t=new Thread(new S22(com)); s.start(); t.start(); 실행결과 wait() 중인스레드가다른스레드가 notify() 를실행함으로서깨워짐을알수있다. D:\Java\Day002>java ThreadCom3 Waiting About to Notify Notified Finished notify() 와 notifyall() notify() 메소드는 wait() 로대기중인스레드 1개만을깨워준다. 만일대기중인스레드가없다면, notify() 메소드는무시된다. notifyall() 은대기중인모든스레드를깨워준다. 다음예제프로그램의실행은 notify() 메소드가 wait() 로대기중인두개의스레드중에서 1개만을깨운다는것을보여준다. ( 세개의자식스레드를생성하였고, 그중두개는 wait() 를먼저실행하고마지막스레드가 notify() 를실행하였음 ) 예제프로그램 class Common2 { synchronized void w() { 1 장자바스프레드 55
try { System.out.println(Thread.currentThread()+" Waiting"); wait(); System.out.println(Thread.currentThread()+" Finished"); catch(interruptedexception e){ synchronized void n() { System.out.println("About to Notify"); notify(); // 재실행시에 notifyall() 로변경 System.out.println("Notified"); class S112 implements Runnable { Common2 com; S112(Common2 c) { com=c; public void run(){ com.w(); class S222 implements Runnable { Common2 com; S222(Common2 c) { com=c; public void run(){ com.n(); public class ThreadCom33 { public static void main(string[] args) { Common2 com = new Common2(); 56 자바네트워크프로그래밍
Thread s=new Thread(new S112(com)); Thread t=new Thread(new S112(com)); Thread u=new Thread(new S222(com)); s.start(); t.start(); try { Thread.sleep(20); catch(interruptedexception e){ u.start(); 실행결과 D:\Java\Day002>java ThreadCom33 Thread[Thread-0,5,main] Waiting About to Notify Notified Thread[Thread-1,5,main] Waiting Thread[Thread-0,5,main] Finished < 스레드 Thread[Thread-1,5,main] 가계속기다리므로실행이종료되지않는다.> 앞의예제프로그램에서 notify() 를 notifyall() 로변경하고실행해보자. notifyall() 메소드는 wait() 로대기중인모든스레드를깨워준다. 다음의실행결과를확인해보자. 실행결과 D:\Java\Day002>java ThreadCom4 Thread[Thread-0,5,main] Waiting Thread[Thread-1,5,main] Waiting About to Notify Notified 1 장자바스프레드 57
Thread[Thread-1,5,main] Finished Thread[Thread-0,5,main] Finished < 대기중인두개의스레드가모두깨워졌음을알수있다 > 생산자 / 소비자문제 생산자 / 소비자 (producer/consumer) 문제는병렬처리에서매우간단하면서도유명한문 제이다. 생산자 / 소비자문제를간단히설명하면다음과같다. 생산 Queu 소비 데이터생산 저장 회수 데이터소비 1 생산자스레드 ( 또는프로세스 ) 와소비자스레드는서로독립적으로실행된다. 2 생산자는데이터를생산해서 Queue에저장한다. 3 소비자는 Queue에서데이터를읽어사용한다. 4 Queue에데이터를추가저장할공간이없을때, 생산자는데이터생산을멈추고대기한다. 5 Queue에저장된데이터가없으면소비자는대기한다. 6 Queue는생산자와소비자가공유하는객체로서어느한순간에는하나의스레드만접근할수있다. 생산자는생산된데이터를공유메모리에저장하고소비자는공유메모리에서데이터를읽어와사용한다. 생산자는공유메모리의여유공간이없을때에는대기하여야하고, 소비자는공유메모리에저장된데이터가없을때에는대기하여야한다.(wait 메소드사용 ). 다음은공유메모리의버퍼공간이하나인경우, 생산자소비자프로그램을자바스레드를사용하여구현한예제프로그램이다. 주의할점은생산자가데이터를큐에저장한후에는반드시대기하고있을지도모르는소비자스레드를깨워주어야한다 (notify 메소드사용 ). 또한소비자가큐에서데이터를회수한후에는반드시대기하고있을지도모르는생산자스레드를깨워주어야한다. 생산자 / 소비자문제를프로그램하면다음과같다. 58 자바네트워크프로그래밍
예제프로그램 class Producer extends Thread { Queue queue; Producer(Queue queue) { // 생성자 this.queue = queue; public void run() { int x = 1; int idx = 0; while(++idx <= 10) { queue.add(x); System.out.println("Producer: "+ x); x *= 2; // 새로운데이터생산 class Consumer extends Thread { Queue queue; Consumer(Queue queue) { // 생성자 this.queue = queue; public void run() { int idx = 0; while(++idx <= 10) { System.out.println("\tConsumer: " + queue.remove()); class Queue { int buf; boolean available = true; synchronized void add(int data) { 1 장자바스프레드 59
while(!available) { // 여유공간이없으면대기 try { wait(); catch(exception e) { buf = data; available = false; notify(); // 대기하고있는스레드를깨움 synchronized int remove() { while(available) { // 데이터가없으면대기 try { wait(); catch(exception e) { available = true; // 4 notify(); // 대기하고있는스레드를깨움 return buf; public class SimplePC { public static void main(string args[]) { Queue q1 = new Queue(); new Producer(q1).start(); new Consumer(q1).start(); 실행결과 C:\java\j8>java SimplePC 60 자바네트워크프로그래밍
Producer: 1 Producer: 2 Producer: 4 Producer: 8 Producer: 16 Producer: 32 Producer: 64 Producer: 128 Producer: 256 Producer: 512 Consumer: 1 Consumer: 2 Consumer: 4 Consumer: 8 Consumer: 16 Consumer: 32 Consumer: 64 Consumer: 128 Consumer: 256 Consumer: 512 실행과정설명 이프로그램의실행과정을그림으로간단하게설명하면다음과같다. Producer 스레드 는 Queue 클래스의 add() 메소드를호출하며, Consumer 스레드는 Queue 클래스의 remove() 메소드를호출한다. 1 장자바스프레드 61
Producer 스레 Consumer 스레드 호출 호출 add() { wait();......... notify(); remove() { wait();......... notify(); Queue 프로그램실행이시작되고, Consumer 스레드가먼저 remove() 를호출한다고가정해보자. 1 Consumer 스레드가 remove() 를실행하면 available 초기값이 true이므로, wait() 를실행한다. 즉 Producer 스레드가 notify() 메소드를실행하여자신을깨워주기를기다리게된다. 2 컴퓨터 CPU는여러스레드를번갈아가며실행하므로, Consumer 스레드가대기중이면, Producer 스레드가실행할기회를갖게된다. Producer 스레드는 add() 메소드를호출한다. 이경우!available 값은거짓이므로, while 루프를실행하지않고, data 값을 buf에저장하고 available = false; 를실행한다. 그리고 notify() 를실행함으로써, 대기중인 Consumer 스레드를깨워준다. 이로써, Consumer 스레드는실행가능한상태가된다. 3 이시점에서는 Producer 스레드와 Consumer 스레드모두실행가능한상태에있게된다. 즉두스레드중에어느스레드라도실행될수있지만, 여기서는 Producer 스레드가계속실행된다고가정한다. 4 Producer 스레드는다음데이터를생산해서계속 add() 메소드를호출한다. 이때!available의값이참이므로, while 루프안의 wait() 를실행한다. 즉 Consumer 스레드가 notify() 메소드를실행하여자신 (Producer 스레드 ) 을깨워주기를기다리게된다. 62 자바네트워크프로그래밍
5 이번에는 Producer 스레드가대기중이므로 Consumer 스레드가실행할기회를갖게된다. wait 상태에서깨어난 Consumer 스레드는 available 값을참으로변경하고, notify() 를실행하여대기중인 Producer 스레드를깨워주며, buf 값을반환한다. 6 이렇게진행됨으로써, Producer 스레드와 Consumer 스레드는번갈아가면서, buf에접근하게된다. 연습문제 앞의예제프로그램에서 4가표시된문장이 available = false;" 로변경되었다면실행결과가어떻게변경될것인지예측하시오. 앞의생산자 / 소비자프로그램은공유하는버퍼공간의크기가 1이므로프로그램실행이효율적이지못하다는단점이있었다. 다음은공유메모리의버퍼공간을 n개로일반화하여프로그램의성능을향상시킨예제프로그램이다. 또한다음예제프로그램에서하나의생산자스레드와두개의소비자스레드가실행된다는점을유의하기바란다. 예제프로그램 class Producer2 extends Thread { Queue2 queue; Producer2(Queue2 queue) { this.queue = queue; public void run() { int x = 1; int idx = 0; while(++idx <= 20) { queue.add(x); x *= 2; 1 장자바스프레드 63
class Consumer2 extends Thread { String str; Queue2 queue; Consumer2(String str, Queue2 queue) { this.str = str; this.queue = queue; public void run() { int idx = 0; while(++idx <= 10) { System.out.println(str + ": " + queue.remove()); class Queue2 { private final static int SIZE = 5; int array[] = new int[size]; int front = 0; int rear = 0; int count = 0; synchronized void add(int data) { while(count == SIZE) { // 여유공간이없으면대기 try { wait(); catch(interruptedexception e) { e.printstacktrace(); array[rear++] = data; if(rear >= SIZE) rear = 0; // circular queue임 ++count; 64 자바네트워크프로그래밍
notify(); synchronized int remove() { while(count == 0) { // 데이터가없으면대기 try { wait(); catch(interruptedexception e) { e.printstacktrace(); int element = array[front++]; if(front >= SIZE) front = 0; --count; notify(); return element; public class ProducerConsumer { public static void main(string args[]) { Queue2 q1 = new Queue2(); new Producer2(q1).start(); new Consumer2("Consumer-1", q1).start(); new Consumer2("Consumer-2", q1).start(); 실행결과 C:\java\j8>java ProducerConsumer Consumer-1: 1 Consumer-1: 2 Consumer-1: 4 1 장자바스프레드 65
Consumer-1: 8 Consumer-2: 16 Consumer-1: 32 Consumer-1: 64 Consumer-1: 128 Consumer-1: 256 Consumer-1: 512 Consumer-1: 1024 Consumer-2: 2048 Consumer-2: 4096 Consumer-2: 8192 Consumer-2: 16384 Consumer-2: 32768 Consumer-2: 65536 Consumer-2: 131072 Consumer-2: 262144 Consumer-2: 524288 66 자바네트워크프로그래밍
프로그램연습 다음예제프로그램을참조하여, 햄버거의생산 / 소비과정을자바의멀티스레드를이용하여시뮬레이션하시오. 프로그램제목 : 햄버거가게시뮬레이션 -햄버거가게는 2명의요리사 ( 생산자 ) 와 3명의손님 ( 소비자 ) 으로구성되며, 한번에하나의햄버거를생산하거나소비할수있다. -각요리사의임무는 40개의햄버거를생산하는것이고, 각손님의임무는 20개의햄버거를소비하는것이다. -요리사와손님은햄버거를생산하거나소비하고나서임의의휴식시간을갖는다. ( 손님의평균휴식시간은요리사의 2배로한다.) -선반에는 5개의햄버거를저장할수있다. ( 햄버거는 10-20 칼로리 ) - 프로그래밍은다음참조프로그램에비어있는메소드를구현하면완성된다. (// do something 부분 ) 참조프로그램 import java.util.random; class Semaphore { private int cnt, pin, pout, max; private int[] buf; public Semaphore(int m) { cnt = pin = pout = 0; max = m; buf = new int[max]; synchronized public int p() throws Exception { if (cnt == 0) wait(); --cnt; int tmp = get(); notifyall(); return tmp; synchronized public void v(int cal) throws Exception { if (cnt == max) wait(); ++cnt; 1 장자바스프레드 67
put(cal); notifyall(); private void put(int cal) { buf[pin] = cal; pin = ++pin % max; private int get() { int tmp = buf[pout]; pout = ++pout % max; return tmp; class HamProducer extends Thread { private Semaphore s; private int id, MAX=40; private Random rand; public HamProducer(Semaphore s, int id) { this.s = s; this.id = id; rand = new Random(); public void run() { int cal; for(int n=0; n<max; n++) { try { cal = rand.nextint(11)+10; s.v(cal); System.out.println("P"+id+" made "+cal+" calories"); sleep((int)(400*math.random())); catch(exception e) { class HamConsumer extends Thread { private Semaphore s; private int id, MAX=20; 68 자바네트워크프로그래밍
private int sum = 0; public HamConsumer(Semaphore s, int id) { this.s = s; this.id = id; public int getsum() { return sum; public void run() { // You need to code here!!! public class HambugerP { public static void main(string args[]) throws Exception { Semaphore s = new Semaphore(5); HamProducer p1 = new HamProducer(s, 1); HamConsumer c1= new HamConsumer(s, 1); HamConsumer c2= new HamConsumer(s, 2); p1.start(); c1.start(); c2.start(); c1.join(); c2.join(); System.out.println("C1 consumed "+c1.getsum()+"calories"); System.out.println("C2 consumed "+c2.getsum()+"calories"); 다음예제프로그램은운영체제의유명한교착상태문제인 Dining Philosopher( 철학자들의저녁식사 ) 를자바스레드로구현한것이다. Dining Philosopher에관한사항은운영체제책을참고하기를바라며, 이예제에는 4명의철학자와 4개의포크를구현하였으니참조하기를바란다. ( 파일이름 : DinningP.java) 1 장자바스프레드 69
Dining Philosopher 프로그램소스 class Fork { // 포크클래스선언 private boolean taken=false; private int id; Fork(int id) { this.id=id; synchronized void put() { taken=false; notify(); synchronized void get() throws Exception { while(taken) wait(); taken=true; class Philosopher extends Thread { private int id; private Fork left, right; Philosopher(int id, Fork l, Fork r) { this.id = id; left=l; right=r; void display(string m) { for(int i=0; i<id; i++) System.out.print("\t\t"); System.out.println(id+": "+ m); public void run() { int cnt=0; try { 70 자바네트워크프로그래밍
while(++cnt<=100) { display("thinking"); sleep((int)(math.random()*1000)); display("hungry"); left.get(); display("got left fork"); sleep(300); // 교착상태를유도하는시간 right.get(); display("eating"); sleep(300); left.put(); right.put(); catch(exception e) { public class DinningP { public static void main(string args[]) { Fork f0 = new Fork(0); // 네개의포크 Fork f1 = new Fork(1); Fork f2 = new Fork(2); Fork f3 = new Fork(3); Philosopher p0 = new Philosopher(0, f0, f1); Philosopher p1 = new Philosopher(1, f1, f2); Philosopher p2 = new Philosopher(2, f2, f3); Philosopher p3 = new Philosopher(3, f3, f0); p0.start(); // 네명의철학자스레드실행 p1.start(); p2.start(); p3.start(); 1 장자바스프레드 71
교착상태가발생한실행결과 0: eating 2: hungry 2: got left fork 0: thinking 1: hungry 1: got left fork 0: hungry 2: eating 0: got left fork 2: thinking 1: eating 1: thinking 0: eating 1: hungry 2: hungry 2: got left fork 0: thinking 1: got left fork 0: hungry 0: got left fork < 교착상태발생 > 3: eating 3: thinking 3: hungry 3: got left fork [Home Work] 철학자들의저녁식사프로그램수정 1 식사하는철학자들의수를 6명으로늘리시오. 2 교착상태가발생하지않도록프로그램을수정하시오 [Hint] 각철학자들에게순서대로번호를붙이고, 짝수번호철학자는왼쪽포크부터홀수번호철학자는오른쪽포크부터잡도록한다. 72 자바네트워크프로그래밍
1.6 자바 1.5 에추가된기능 Java 5 이전에는스레드병행처리를위한기능이 synchronized, wait(0, notify() 명 령어정도였다. Java 5 에서는새로운 java.util.concurrent.* 패키지를포함되었으며스레드 처리와동기화를위한다양한 API 가소개되었다. 1) Reentrant Lock - java.util.concurrent.locks.reentrantlock - Object 상속, Serializable, Lock 인터페이스구현 ReentrantLock 클래스는가장단순한 lock 메커니즘을제공한다. 기존의 lock 메커니즘 을제공하였던 synchronized 와유사하지만, 여러가지부가기능도제공한다. 예를들어 fairness 인자를사용하여가장오래대기한스레드에게 lock 을부여할수도있다. 생성자 1 ReentrantLock(): Creates an instance of ReentrantLock. 2 ReentrantLock(boolean fair): Creates an instance of ReentrantLock with the given fairness policy. 주요메소드 1 int getholdcount(): Queries the number of holds on this lock by the current thread. 2 boolean hasqueuedthread(thread thread): Queries whether the given thread is waiting to acquire this lock. 3 boolean hasqueuedthreads(): Queries whether any threads are waiting to acquire this lock. 4 boolean haswaiters(condition condition): Queries whether any threads are waiting on the given condition associated with this lock. 5 boolean isfair(): Returns true if this lock has fairness set true. 6 void lock(): Acquires the lock. 1 장자바스프레드 73
7 Condition newcondition(): Returns a Condition instance for use with this Lock instance. 8 boolean trylock(): Acquires the lock only if it is not held by another thread at the time of invocation. 9 void unlock(): Attempts to release this lock. ReentrantLock의일반적인사용형식 class X { private final ReentrantLock key = new ReentrantLock(); //... public void m() { key.lock(); // block until condition holds. try { // This is critical Section. finally { key.unlock() 해설 : ReentrantLock 객체는하나의스레드에의해서만소유될수있으며, 공유자원에대한독점적인접근권한을보장받을때사용된다. 만일 ReentrantLock 객체 key 에대한소유스레드가없을때한스레드가 key.lock() 메소드를실행하면소유권을갖게된다. ( 여기서 Reentrant라는것은이미 key 객체에대한소유권을가지고있을때에도소유권을가진다는의미이다.) 공유자원에대한독점접근을마친후에 key.unlock() 을시행하면소유권을반납하게된다. 프로그램예제 다음예제는두개의스레드를생성하여각기공유변수 ( 초기값 ) 에 120을 100회더하는프로그램이다. 올바른실행결과는 240000이출력되어야한다. 74 자바네트워크프로그래밍
import java.util.concurrent.locks.*; class S14 implements Runnable { ReentrantLock key; int temp; public S14(ReentrantLock key){ this.key = key; public void run(){ for(int n=0; n<1000; n++) { // key.lock(); try{ temp= ReentrantTest.val; temp= temp + 120; ReentrantTest.val = temp; finally { // key.unlock(); class S15 implements Runnable { ReentrantLock key; int temp; public S15(ReentrantLock key){ this.key = key; public void run(){ for(int n=0; n<1000; n++) { // key.lock(); try{ temp= ReentrantTest.val; temp= temp + 120; ReentrantTest.val = temp; 1 장자바스프레드 75
finally { // key.unlock(); public class ReentrantTest { static public int val=0; public static void main(string[] args) throws InterruptedException { ReentrantLock key = new ReentrantLock(true); Thread s=new Thread(new S14(key)); Thread t=new Thread(new S15(key)); s.start(); t.start(); s.join(); t.join(); System.out.println("val="+val); 앞의예제에는모든 lock(), unlock() 에주석처리가되었으므로다음과같은엉뚱 한값이출력된다. 실행결과 D:\Java\Day002>java ReentrantTest (JDK1.5에서의실행결과중하나임 ) val=214440 앞의예제에서모든 lock(), unlock() 앞에있는주석표시 (//) 를삭제하고다시실행 시키면다음과같은결과가출력된다. 임계구역에대한상호배제가적절히이루어졌 으므로올바른값이출력된다. 76 자바네트워크프로그래밍
실행결과 D:\Java\Day002>java ReentrantTest val=240000 2) Condition 변수컨디션 (Condition) 변수는자바객체에서지원되는 wait(), notify(), notifyall() 등과유사한통신메소드 await(), signal(), signalall() 등을제공한다. 자바객체에서제공되는메소드는객체에하나의 wait(), notify() 만을사용할수있다. 그러나컨디션변수를사용하면여러세트의 await(), signal() 통신이가능하다는차이점이있다. 컨디션변수생성하기 1 먼저 ReentrantLock 객체를생성한다. Lock key = new ReentrantLock(); 2 ReentrantLock 객체를사용하여 newcondition() 메소드를호출한다. 하나의 ReentrantLock 객체에서여러개의컨디션객체를생성할수있다. Condition cond1 = key.newcondition(); Condition cond2 = key.newcondition(); 컨디션객체를사용하기위해서는연관된 ReentrantLock 객체의 lock(), unlock() 메소 드를사용하여상호배제를보장하여야한다. java.util.concurrent.locks.condition 인터페이스에서정의된메소드 1 void await(): Causes the current thread to wait until it is signalled or interrupted. 2 long awaitnanos(long nanostimeout): Causes the current thread to wait until it is signalled or interrupted, or the specified waiting time elapses. 1 장자바스프레드 77
3 void signal(): Wakes up one waiting thread. 4 void signalall(): Wakes up all waiting threads. 다음은 Condition 객체를사용하여자식스레드에게 signal 을보내는예제프로그램 이다. 메인스레드는 4 초정도 sleep 한다음 signal 을보낸다. Condition 객체는연관 된 ReentrantLock 객체를사용하여상호배체를보장받고있음에주의한다. 예제프로그램 import java.util.concurrent.locks.*; class S24 implements Runnable { Condition cond; Lock key; public S24(Lock key, Condition cond){ this.key = key; this.cond = cond; public void run(){ key.lock(); try { System.out.println("thread S24"); cond.await(); System.out.println("got signal"); catch(interruptedexception e){ finally{ key.unlock(); public class CondTest { public static void main(string[] args) throws Exception { 78 자바네트워크프로그래밍
Lock key = new ReentrantLock(); Condition cond = key.newcondition(); Thread s=new Thread(new S24(key, cond)); s.start(); Thread.sleep(4000); System.out.println("main: signal"); key.lock(); try { cond.signal(); finally { key.unlock(); 실행결과 D:\Java\Day002>java CondTest thread S24 main: signal got signal 다음은유사한예제이나, 하나의 ReentrantLock 객체를사용하여두개의 Condition 객체를생성하여통신한다는점이다르다. 예제프로그램 import java.util.concurrent.locks.*; class S26 implements Runnable { Condition con1, con2; Lock key; public S26(Lock key, Condition c1, Condition c2){ this.key = key; con1 = c1; 1 장자바스프레드 79
con2 = c2; public void run(){ key.lock(); try { System.out.println("thread S24"); con1.await(); System.out.println("th:got signal"); Thread.sleep(3000); con2.signal(); System.out.println("th:sent signal"); catch(interruptedexception e){ finally{ key.unlock(); public class CondTest2 { public static void main(string[] args) throws Exception { Lock key = new ReentrantLock(); Condition con1 = key.newcondition(); Condition con2 = key.newcondition(); Thread s=new Thread(new S26(key,con1,con2)); s.start(); Thread.sleep(3000); System.out.println("main: signal"); key.lock(); try { con1.signal(); System.out.println("main: waiting"); con2.await(); 80 자바네트워크프로그래밍
System.out.println("main: got signal"); finally { key.unlock(); 실행결과 D:\Java\Day002>java CondTest2 thread S24 main: signal main: waiting th:got signal th:sent signal main: got signal [Home Work] 앞에서 wait()/notify() 를사용하여작성한햄버거생산자 / 소비자프로그램을 Condition 변수를사용하여다시작성하시오. 3) 세마포어 (Semaphore) 클래스 Java 5 에서는 Semaphore(java.util.concurrent.Semaphore) 도지원된다. 세마포어에대한 지식이부족하다면, 운영체제교과서의프로세서관리및병행처리부분을참조하기바란다. 세마포어의생성자는다음과같다. 1 public Semaphore(int val): Creates a Semaphore with the given number of permits and nonfair fairness setting. 2 public Semaphore(int permits, boolean fair): Creates a Semaphore with the given number of permits and the given fairness setting. Semaphore 에서지원되는주요메소드는다음과같다. 1 void release(): Releases a permit, returning it to the semaphore. 2 void acquire() throws InterruptedException: Acquires a permit from this semaphore, blocking until one is available, or the thread is interrupted. 1 장자바스프레드 81
세마포어를사용하여상호배제를구현하는코드형식은다음과같다. import java.util.concurrent.semaphore; Semaphore sem = new Semaphore(1); try { sem.acquire(); // Critical Section; catch(interruptedexception e){ finally { sem.release(); [ 연습문제 ] 다음두프로그램은자바지원세마포어클래스를사용하여상호배제를구현하였다. 두개의프로그램중 1개의실행결과가잘못되었다. 그이유를설명하시오. 프로그램 1 : "SemTest.java" import java.util.concurrent.semaphore; class Th extends Thread { static public int sum=0; Semaphore sem; int temp; Th() { sem = new Semaphore(1); public void run() { for(int n=0; n<1000; n++) { try { sem.acquire(); temp = sum; temp = temp + 120; sum = temp; 82 자바네트워크프로그래밍
catch(interruptedexception e){ finally { sem.release(); public class SemTest { public static void main(string[] args) throws Exception { Th t1 = new Th(); Th t2 = new Th(); Th t3 = new Th(); t1.start(); t2.start(); t3.start(); t1.join(); t2.join(); t3.join(); System.out.println("sum="+Th.sum); 실행결과 (JDK 1.5에서실행한결과중의하나임 ) sum=359880 프로그램 1 : "SemTest2.java" import java.util.concurrent.semaphore; class Th2 extends Thread { static public int sum=0; Semaphore sem; 1 장자바스프레드 83
int temp; Th2(Semaphore s) { sem = s; public void run() { for(int n=0; n<1000; n++) { try { sem.acquire(); temp = sum; temp = temp + 120; sum = temp; catch(interruptedexception e){ finally { sem.release(); public class SemTest2 { public static void main(string[] args) throws Exception { Semaphore sem = new Semaphore(1); Th2 t1 = new Th2(sem); Th2 t2 = new Th2(sem); Th2 t3 = new Th2(sem); t1.start(); t2.start(); t3.start(); t1.join(); t2.join(); t3.join(); System.out.println("sum="+Th2.sum); 84 자바네트워크프로그래밍
실행결과 sum=360000 [Home Work] Readers-Writers 문제 ( 운영체제교재참조 ) 데이터베이스에여러개의스레드가동시에읽기 (read) 접근이가능하지만, 오직하나의쓰기 (write) 접근만이가능하다. 다음은자바의스레드기능을사용하여, 데이터베이스에쓰기 / 읽기접근동기화예제프로그램이다. 예제프로그램을실행하고, 내용을분석해보시오. 한스레드가데이터에읽기접근중인경우에데이터에쓰기접근은불가능하다. 한 스레드가읽기접근중에다른스레드의추가적인읽기접근은허용된다. 이예제프로그램의문제점은읽기스레드가쓰기스레드에비하여우선권이높다는것이다. 즉기존의스레드가읽기접근중일때쓰기접근은대기하여야하지만, 추가적인읽기접근은즉시허용된다. 이는쓰기접근스레드의기아 (starvation) 현상이발생할수있음을나타낸다. ( 실행하는읽기스레드의수를많이늘려서실행하면이러한현상이발생한다.) 이러한문제점을해결하여쓰기접근스레드가기아현상이발생하지않도록예제프로그램을수정하시오. 예제코드 class DB { private int readercnt; private boolean dbwriting; public DB() { readercnt = 0; dbwriting = false; 1 장자바스프레드 85
public static void rest() { int resttime = (int) (1800 * Math.random()); try{ Thread.sleep(restTime+200); catch(interruptedexception e){ public synchronized void acquirereadlock(int readernum) { while (dbwriting == true) { try { wait(); catch(interruptedexception e) { ++readercnt; System.out.println("R" + readernum + " is reading. Reader#:" + readercnt); public synchronized void releasereadlock(int readernum) { --readercnt; if (readercnt == 0) notify(); System.out.println("R" + readernum + " finished reading. Reader#:"+ readercnt); public synchronized void acquirewritelock(int writernum) { while (readercnt > 0 dbwriting == true) { try { wait(); catch(interruptedexception e) { dbwriting = true; System.out.println("W" + writernum + " is writing."); public synchronized void releasewritelock(int writernum) { dbwriting = false; 86 자바네트워크프로그래밍
System.out.println("W" + writernum + " finished writing."); notifyall(); class Writer implements Runnable { private DB db; private int writernum; public Writer(int w, DB mydb) { writernum = w; db = mydb; public void run() { while (true) { DB.rest(); // doing other works System.out.println("W" + writernum + " wants to write."); db.acquirewritelock(writernum); DB.rest(); // doing DB write access db.releasewritelock(writernum); class Reader implements Runnable { private DB db; private int readernum; public Reader(int readernum, DB mydb) { this.readernum = readernum; db = mydb; 1 장자바스프레드 87
public void run() { while (true) { DB.rest(); System.out.println("R" + readernum + " wants to read."); db.acquirereadlock(readernum); DB.rest(); db.releasereadlock(readernum); public class RWLockDemo { static final int NUM_READERS = 12; static final int NUM_WRITERS = 3; public static void main(string args[]) { DB mydb = new DB(); Thread[] readers = new Thread[NUM_READERS]; Thread[] writers= new Thread[NUM_WRITERS]; for (int i = 0; i < NUM_READERS; i++) { readers[i] = new Thread(new Reader(i, mydb)); readers[i].start(); for (int i = 0; i < NUM_WRITERS; i++) { writers[i] = new Thread(new Writer(i, mydb)); writers[i].start(); 88 자바네트워크프로그래밍
1.7 애플릿과스레드 자바언어는다중상속을지원하지않으므로, 애플릿에서스레드를사용하기위해서는 Runnable 인터페이스를구현해야한다.( 사용자가정의하는클래스는반드시애플릿클래스를확장해서정의해야하므로 ) 애플릿에서애니메이션이나시간의흐름에따라동적으로결과가바뀌는경우에는스레드를사용하는것이필요하다. 다음은애플릿에서스레드를사용한예제프로그램이다. start() 메소드에서새로운스레드 th1을생성한후, th1.start() 를호출함으로써 run() 메소드를실행시킨다. run() 메소드에서는 400 밀리초마다 repaint() 메소드를호출함으로서 paint() 메소드가실행되도록하고있다. repaint() 메소드는화면을클리어한뒤에 paint() 메소드호출을요구하는메소드이다. 이예제프로그램에서중요한사실은애플릿을실행시키는메인스레드와스레드 th1 이하나의프로그램공간을완전히공유하고있다는사실을인지하고있어야한다. 예제프로그램 import java.applet.*; import java.awt.*; public class ThApplet extends Applet implements Runnable { static final long serialversionuid=40607293l; Thread th1; int loc; public void start() { th1= new Thread(this); // 스레드 th1 생성 th1.start(); public void run() { try { for(loc=0 ; loc<25 ; loc++) { repaint(); Thread.sleep(400); // 400 밀리초간격으로 repaint() 25번호출 1 장자바스프레드 89
catch(exception e) { public void paint(graphics g) { Dimension d = getsize(); Font font = new Font("Serif", Font.BOLD, 28); g.setfont(font); String st = "JAVA 1.2"; g.drawstring(st, 15*loc, d.height/2); 실행결과 다음은애플릿으로구현한디지털시계예제애플릿이다. 많은자바서적에서인용하고있는유명한예제프로그램이다. 이시계애플릿은매초마다시각을 drawstring() 메소드를이용해서새로이그려주고있다. 또한사용자가애플릿을떠나면스레드변수에 null값을넣어서스레드실행을중지시키고, 다시애플릿으로되돌아오면다시스레드를실행시키고있다. 예제프로그램 import java.awt.*; import java.util.date; import java.applet.applet; public class DigitalClock extends Applet implements Runnable { static final long serialversionuid=123123l; 90 자바네트워크프로그래밍
Font afont = new Font("TimesRoman",Font.BOLD,22); Date adate; Thread runner; public void start() { runner = new Thread(this); runner.start(); public void stop() { if (runner!= null) { runner = null; public void run() { Thread thisthread = Thread.currentThread(); while (runner == thisthread) { repaint(); try { Thread.sleep(1000); catch (InterruptedException e) { public void paint(graphics screen) { adate = new Date(); screen.setfont(afont); screen.drawstring("" + adate, 24, 50); 실행결과 1 장자바스프레드 91
프로그램연습 다음은애플릿에서두개의스레드를생성하여실행하는경마프로그램이다. 두마리의말 ( 스레드 ) 은임의의시간만큼 sleep() 한후에달린다. 다음애플릿프로그램을각자분석하고실행오류가있다면수정하시오. 또한프로그램을변경하여말을 5마리로변경하여실행하시오. 예제프로그램 import java.applet.*; import java.awt.*; public class HorseApplet extends Applet implements Runnable { static final long serialversionuid=4053221l; Thread t1; Thread t2; public int a=0, b=0; Font font; String s1 = "HorseA"; String s2 = "HorseB"; int rank = 1; // public void start() { font = new Font("Serif",Font.BOLD,24); t1= new Thread(this); t2= new Thread(this); t1.start(); t2.start(); public void run() { 92 자바네트워크프로그래밍
try { if(thread.currentthread()==t1) // 현스레드가 t1인지체크 for(a=0 ; a<=70 ; a++) { if (a==70) { s1 = s1 + " " + rank+" 등 "; rank++; repaint(); Thread.sleep((int)(Math.random()*1204)); else for(b=0 ; b<=70 ; b++) { if (b==70) { s2 = s2 + " " + rank+" 등 "; rank++; repaint(); Thread.sleep((int)(Math.random()*1204)); catch(exception e) { public void paint(graphics g) { Dimension d = getsize(); g.setfont(font); g.drawstring(s1, 5*a, d.height/2-20); g.drawstring(s2, 5*b, d.height/2 + 20); 실행결과 1 장자바스프레드 93
[ 영문해석 ] 1. 다음은스레드와멀티스레드에대한영문설명이다. 이를해석하시오. A thread in computer science is short for a thread of execution. Threads are a way for a program to fork (or split) itself into two or more simultaneously (or pseudo-simultaneously) running tasks. Threads and processes differ from one operating system to another but, in general, a thread is contained inside a process and different threads in the same process share some resources while different processes do not. Multithreading generally occurs by time-division multiplexing ("time slicing") in very much the same way as the parallel execution of multiple tasks (computer multitasking): the processor switches between different threads. This context switching can happen so fast as to give the illusion of simultaneity to an end user. On a multiprocessor or multi-core system, threading can be achieved via multiprocessing, wherein different threads and processes can run literally simultaneously on different processors or cores. 2. 다음은자바스레드에대한영문설명이다. 이를해석하시오. A thread is a thread of execution in a program. The Java Virtual Machine allows an application to have multiple threads of execution running concurrently. Every thread has a priority. Threads with higher priority are executed in preference to threads with lower priority. Each thread may or may not also be marked as a daemon. When code running in some thread creates a new Thread object, the new thread has its priority initially set equal to the priority of the creating thread, and is a daemon thread if and only if the creating thread is a daemon. When a Java Virtual Machine starts up, there is usually a single non-daemon thread (which typically calls the method named main of some designated class). The Java Virtual Machine continues to execute threads until either of the following occurs: 94 자바네트워크프로그래밍
o The exit method of class Runtime has been called and the security manager has permitted the exit operation to take place. 1. o All threads that are not daemon threads have died, either by returning from the call to the run method or by throwing an exception that propagates beyond the run method. 1 장자바스프레드 95
2 장중요사항 Review 2 장에서는자바분산프로그래밍에필요한자바언어의기본사항을복습하도록한다. 2.1 이벤트의멀티캐스트 (multicast) 일반적으로이벤트를처리하는리스너객체는 1개이다. 그러나하나의이벤트에여러개의리스너객체를등록하는것도가능하다. 하나의이벤트가여러개의리스너객체에전송되어처리되는것을이벤트의멀티캐스트라고한다. 다음은하나의종료버튼을사용하여개방된여러개의프레임을종료시키는예제프로그램이다. 새로운프레임이생성될때마다, 종료버튼 (closebutton) 에리스너를등록하고있다는사실에유의한다. 프로그램소스 import java.awt.event.*; import javax.swing.*; public class MulticastTest extends JFrame { static final long serialversionuid=1101; public MulticastTest() { settitle("multicasttest"); 2 장중요사항 Review 99
setsize(200, 150); addwindowlistener(new WindowAdapter() { public void windowclosing(windowevent e) { System.exit(0); ); public static void main(string[] args) { MulticastTest f = new MulticastTest(); MulticastPanel mp = new MulticastPanel(); f.getcontentpane().add(mp); f.setvisible(true); class MulticastPanel extends JPanel implements ActionListener { static final long serialversionuid=1102; private int counter = 0; private JButton closebutton; public MulticastPanel() { JButton newbutton = new JButton("New"); add(newbutton); newbutton.addactionlistener(this); closebutton = new JButton("Close all"); add(closebutton); public void actionperformed(actionevent evt) { // create new frame SimpleFrame f = new SimpleFrame(++counter); closebutton.addactionlistener(f); 100 자바네트워크프로그래밍
class SimpleFrame extends JFrame implements ActionListener { static final long serialversionuid=1103; SimpleFrame(int cnt) { settitle("window " + cnt); setsize(200, 100); setlocation(9*cnt+200, 9*cnt+120); setvisible(true); public void actionperformed(actionevent evt) { // close frame dispose(); 실행결과 [ 프로그램설명 ] - [New] 버튼으로생성된 SimpleFrame 객체는 [Close All] 버튼의 ActionListener에등록 ( 코드 ) 다음은 [New] 버튼을클릭하였을때, 실행되는 actionperformed 코드 public void actionperformed(actionevent evt) { // 새로운프레임을생성하고, 리스너를등록 2 장중요사항 Review 101
SimpleFrame f = new SimpleFrame(++counter); closebutton.addactionlistener(f); // close버튼의처리를위해 f 객체에등록 ( 코드 ) 다음은 [CloseAll] 을클릭하였을때, 실행되는 actionperformed 코드 public void actionperformed(actionevent evt) { // 프레임종료 dispose(); // 프레임객체를종료함 [ 프로그래밍연습 ] [Clear] 버튼을추가한다. 클릭시에모든윈도우타이틀의번호를 0으로변경한다. ActionEvent 클래스의 getactioncommand() 메소드를사용하여, 이벤트를발생시킨버튼의문자열을알아낸다. equals(object obj) 메소드를사용하여문자열을비교하면알수있다. 이벤트를발생한객체의문자열이 Clear" 인경우에적절한액션을취한다. 2.2 메뉴를사용한간단한문서편집기 Swing을사용한 GUI는 AWT를사용한 GUI에비하여보다화려하고다양한컴포넌트를사용하게한다. 다음은 Swing이제공하는메뉴와 JFileChooser를사용하여작성한문 서편집기이다. 프로그램소스 // Simple Text Editor import java.io.*; import java.awt.event.*; import javax.swing.*; public class Editor extends JFrame implements ActionListener { 102 자바네트워크프로그래밍
static final long serialversionuid=1108; final static int EOF = -1; //Create a file chooser final JFileChooser fc = new JFileChooser(); JMenuItem new_doc, open, save, exit; JMenuItem copy, cut, paste; JTextArea ta; Editor() { super( "Simple Text Editer" ); JMenuBar mb = new JMenuBar(); // 메뉴바생성 // File 메뉴생성 JMenu file = new JMenu( "File" ); file.add(new_doc = new JMenuItem( "New" ) ); file.add(open = new JMenuItem( "Open" ) ); file.add(save = new JMenuItem( "Save" ) ); file.addseparator(); file.add(exit = new JMenuItem( "Exit",'X' ) ); new_doc.addactionlistener( this ); open.addactionlistener( this ); save.addactionlistener( this ); exit.addactionlistener( this ); mb.add(file); // Edit 메뉴생성 JMenu edit = new JMenu( "Edit" ); edit.add(copy = new JMenuItem( "Copy" ) ); edit.add(cut = new JMenuItem( "Cut" ) ); edit.addseparator(); edit.add(paste = new JMenuItem( "Paste" ) ); copy.addactionlistener( this ); cut.addactionlistener( this ); paste.addactionlistener( this ); mb.add( edit ); 2 장중요사항 Review 103
setjmenubar( mb ); ta = new JTextArea(); getcontentpane().add( new JScrollPane( ta ), "Center" ); setsize( 400, 300 ); setvisible(true); public void actionperformed( ActionEvent e ) { String cmd = e.getactioncommand(); if( cmd.equals( "New" )) // New Documents ta.settext(" "); // Open Menu Clicked Event Handling else if( cmd.equals( "Open" ) ) { int returnval = fc.showopendialog( Editor.this ); if ( returnval == JFileChooser.APPROVE_OPTION ) { File file = fc.getselectedfile(); try{ FileReader f = new FileReader( file ); StringBuffer sb = new StringBuffer(); int ch; // s = s+ch; while( ( ch = f.read() )!= EOF ) sb.append( (char)ch ); f.close(); ta.settext( sb.tostring() ); catch( IOException IOe ){ JDialog dialog = new JDialog( this, "Error", true ); dialog.setvisible(true); // Save Menu Clicked Event Handling else if( cmd.equals( "Save" ) ) { int returnval = fc.showsavedialog(editor.this ); if ( returnval == JFileChooser.APPROVE_OPTION ) { File file = fc.getselectedfile(); try{ FileWriter f = new FileWriter( file ); f.write( ta.gettext() ); 104 자바네트워크프로그래밍
f.close(); catch( IOException IOe ){ JDialog dialog = new JDialog( this, "Error", true ); dialog.setvisible(true); else if( cmd.equals( "Exit" ) ) System.exit( 0 ); else if( cmd.equals( "Copy" ) ) ta.copy(); else if( cmd.equals( "Cut" ) ) ta.cut(); else if( cmd.equals( "Paste" ) ) ta.paste(); repaint(); public static void main( String args[] ) { Editor fm = new Editor(); fm.addwindowlistener( new WindowAdapter() { public void windowclosing( WindowEvent e ) { System.exit( 0 ); ); 실행결과 2 장중요사항 Review 105
많은윈도우프로그램들은메뉴기능을포함한다. 메뉴기능은고급 GUI 프로그램에서 매우중요하다. Swing 에서메뉴를생성하는과정을정리하면다음과같다. o Swing 에서메뉴작성과정 1 메뉴바를생성한다. JMenuBar mb = new JMenuBar(); 2 메뉴를생성하여메뉴바에부착한다. JMenu filemenu = new JMenu("File"); mb.add(filemenu); 3 메뉴아이템을생성한다. JMenuItem newfile = new JMenuItem("New"); JMenuItem openfile = new JMenuItem("Open", new ImageIcon("open.gif")); // 이미지사용시 4 메뉴아이템을메뉴에부착한다. filemenu.add(newfile); filemenu.addseparator(); // 필요에따라분리선을긋는다. filemenu.add(openfile); 5 메뉴아이템에이벤트리스너등록을한다. newfile.addactionlistener(actionlistenerobject); openfile.addactionlistener(actionlistenerobject); 6 메뉴바를 setjmenubar() 메소드를사용하여컨테이너에부착한다. frame.setjmenubar(mb); [ 프로그래밍연습 ] 앞의편집기프로그램에서메뉴바에 [Help] 메뉴를추가한다. [Help] 메뉴를선택하면 [version] 메뉴아이템이표시된다. [version] 메뉴아이템을선택하면, 대화상자에 "Simple Editor V1.0" 이란메시지를출력한다. 106 자바네트워크프로그래밍
다음코드를이용한다. JOptionPane.showMessageDialog(null, "Simple Editor V1.0", "Version Info.", JOptionPane.INFORMATION_MESSAGE); 2.3 파일복사프로그램 자바프로그램에서파일입출력을하기위해서는 java.io.* 패키지를 import 하여야한다. 다음은파일을복사하는기능을수행하는예제프로그램이다. 첫번째명령행인자는소스 파일로인식하고, 두번째인자를목적파일또는디렉터리로인식하여파일을복사한다. 프로그램소스 import java.io.*; public class FileCopy { public static void main(string[] args) throws Exception { if (args.length!= 2) { System.out.println("Specify souce & destination"); System.exit(0); File from_file = new File(args[0]); File to_file = new File(args[1]); if (!from_file.exists()) abort("no such source file: " + args[0]); if (!from_file.isfile()) abort("can't copy directory: " + args[0]); if (to_file.isdirectory()) to_file = new File(to_file, from_file.getname()); 2 장중요사항 Review 107
if (to_file.exists()) { if (!to_file.canwrite()) abort("dest. file is unwriteable: " + args[1]); FileInputStream from = new FileInputStream(from_file); FileOutputStream to = new FileOutputStream(to_file); byte[] buffer = new byte[(int) from_file.length()]; from.read(buffer, 0, buffer.length); from.close(); to.write(buffer, 0, buffer.length); to.flush(); to.close(); private static void abort(string msg) { System.out.println(msg); System.exit(0); 실행결과 E:\newBook\review>java FileCopy test.txt.. E:\newBook\review>java FileCopy test.txt copyed.txt [ 프로그래밍연습 ] - 파일을이동시키는 FileMove.java 를작성하시오. (File 클래스의 delete() 메소드를사용하여원본파일을삭제한다.) [Home Work] - 와일드카드를사용하여소스파일여럿을명시하고, 명시된파일들을동시에복사하는기능을추가하시오. ( 예 : java FileCopy *.java sub 모든자바소스파일을서브디렉토리 sub 에복사한다.) 108 자바네트워크프로그래밍
2.4 테이블사용 JTable 은테이블형태의데이터를출력하는데매우유용하다. 특히데이터베이스의질의 결과를출력한다든지, 여러레코드를출력할때사용하면편리하다. 다음은 JTable 을사용하 여단순한데이터를출력하는예제이다. 프로그램예제 import java.awt.*; import javax.swing.*; public class TableDemo extends JFrame{ static final long serialversionuid=11090; public TableDemo(){ super("table Demo"); String colnames[] = { "Name", "Tel. No", "Address" ; String data[][] = { {" 김정수 ", "123-7788", "Seoul, Korea", {" 김정훈 ", "234-1111", "TaeJun, Korea", {" 서지수 ", "123-2222", "Pusan, Korea", {" 조수정 ", "760-4315", "SeongBook, Seoul" ; Font fon = new Font("Dialog",Font.BOLD, 15); JTable table = new JTable(data, colnames); table.setfont(fon); 2 장중요사항 Review 109
JScrollPane sp = new JScrollPane(table); getcontentpane().add(sp, BorderLayout.CENTER); setdefaultcloseoperation(exit_on_close); setsize(480, 120); setvisible(true); public static void main(string args[]){ new TableDemo(); 실행결과 2.5 테이블모델을사용한예제프로그램 JTable을이용하여단순히배열의데이터를 2차원테이블에출력할수있다. 테이블모델을사용하면보다고급스러운기능을사용하여테이블에데이터를출력할수있다. 테이블모델을사용하기위해서는 AbstractTableModel을확장하여클래스를정의하여야한다. 또한다음메소드를정의하여야한다. 1 int getrowcount() - 이메소드의반환값이테이블행의수를결정 2 int getcolumncount(0 - 이메소드의반환값이테이블의컬럼수를결정 110 자바네트워크프로그래밍
3 Object getvalueat(int r, int c) - 이메소드의반환값이격자 (r, c) 의값을결정. 4 String getcolumnname(int c) - 이메소드의반환값이컬럼 c의이름을결정 다음예제프로그램은 AbstractTableModel 을확장하여구구단을출력하고있다. 예제프로그램 import java.awt.*; import javax.swing.*; import javax.swing.table.*; class Mul_TableModel2 extends AbstractTableModel { static final long serialversionuid=11091; private int min; private int max; public Mul_TableModel2(int r1, int r2) { min = r1; max = r2; public int getrowcount() { return 8; public int getcolumncount() { return max - min + 1; public Object getvalueat(int r, int c) { int n = min+c; int m = r + 2; String tmp = " "+n+" * " + m + " = " + n * m; return tmp; 2 장중요사항 Review 111
public String getcolumnname(int c) { return (min+c)+" 단 "; public class TableModelDemo extends JFrame { static final long serialversionuid=11092; public TableModelDemo() { settitle("mul_table"); setsize(500,185); setdefaultcloseoperation(exit_on_close); TableModel model = new Mul_TableModel2(2, 8); JTable table = new JTable(model); Container contentpane = getcontentpane(); contentpane.add(new JScrollPane(table), "Center"); public static void main(string[] args) { JFrame frame = new TableModelDemo(); frame.setvisible(true); 실행결과 [ 프로그래밍연습 ] 앞의자바프로그램예제를수정하여, 2단부터 12 단까지구구단표를출력하시오 ( 기존예제의프레임크기를확대해야함 ). 112 자바네트워크프로그래밍
2.6 Properties 클래스 Properties는 Key와 value값이모두문자열로만제한되는컬렉션 (Collection) 이다. Properties 객체에키와 value를저장하기위해서는 setproperty(key, value) 메소드를사용한다. 키를사용하여 value를검색하기위해서는 getproperty(key) 메소드를사용한다. 특히 Properties 객체의데이터를 load() 와 store() 메소드를사용하여손쉽게디스크파일에저장하거나, 가져올수있으므로편리하다. o Properties 클래스의생성자 1 Properties() - 비어있는 Property 객체생성 2 Properties(Properties defaults) - 특정 Property 객체를기본값으로가진 Property 객체생성 o Properties 클래스의메소드 1 String getproperty(string key), String getproperty(string key, String defaultvalue) - 명시된키에해당되는값을찾아서반환 2 void list(printstream out), void list(printwriter out) - 명시된출력스트림에 Property 리스트를출력 3 void load(inputstream instream) - 명시된입력스트림으로부터 Property 리스트를적재 4 Enumeration propertynames() - Property 리스트의모든킷값을가진나열형객체를반환 5 void save(outputstream out, String header) - deprecated 메소드, 출력스트림으로 Property 리스트출력 6 Object setproperty(string key, String value) - 키와값을설정 7 void store(outputstream out, String header) - 출력스트림으로헤더메시지와 Property 리스트출력 2 장중요사항 Review 113
다음은 Properties 클래스를사용하여이름과전화번호를저장하고, 검색하는간단한예제 프로그램이다. 예제프로그램 import java.io.*; import java.util.*; import java.awt.*; import java.awt.event.*; public class PropertyDemo extends Frame implements ActionListener { static final long serialversionuid=11093; private TextField name, tel; private Button enter, find; private FileInputStream fis; private FileOutputStream fos; private Properties props; PropertyDemo() { super("property Demo"); props = new Properties(); try { fis = new FileInputStream("addr.dat"); props.load(fis); catch(ioexception e) { System.out.println("First execution"); setsize(260, 120); setlayout(new GridLayout(3, 2, 4, 2)); add(new Label("Name")); 114 자바네트워크프로그래밍
add(name=new TextField(10)); add(new Label("Tel. Number")); add(tel=new TextField(10)); add(find=new Button("Find")); find.addactionlistener(this); add(enter=new Button("Enter")); enter.addactionlistener(this); setvisible(true); public void actionperformed(actionevent e) { if(e.getsource() == find) { String key = name.gettext().trim(); String telnum = props.getproperty(key); tel.settext(telnum); else { String key = name.gettext().trim(); String val = tel.gettext().trim(); props.setproperty(key, val); name.settext(""); tel.settext(""); try { fos=new FileOutputStream("addr.dat"); props.store(fos,"tel. Book"); catch(ioexception exp) { public static void main(string arg[]) { PropertyDemo f = new PropertyDemo(); f.addwindowlistener(new WindowAdapter() { public void windowclosing(windowevent e) { System.exit(0); ); 2 장중요사항 Review 115
실행결과 다음은 Properties 객체를사용하여간단한내용을설정하고, 화면에출력하는자바예제 프로그램이다. 프로그램소스 import java.util.*; public class PropTest { public static void main(string args[]) throws Exception { Properties p = new Properties(); p.setproperty("name", "JungSu Kim"); p.setproperty("sex", "Male"); p.setproperty("age", "5"); p.store(system.out, "My Lovely Son"); 실행결과 #My Lovely Son #Sun Sep 09 20:11:12 KST 2007 Name=JungSu Kim 116 자바네트워크프로그래밍
Sex=Male Age=5 계속하려면아무키나누르십시오... 2.7 Timer 클래스 javax.swing.timer 클래스를사용하면, 주기적으로지정된함수를호출할수있다. 즉 일정한시간간격마다특정함수를반복수행할수있다. Timer 클래스의생성자는다음과 같다. Timer(int delay, ActionListener listener) 생성된 Timer 객체는정수값으로명시된 delay 밀리초마다액션이벤트를발생시키는 데사용된다. 발생된액션이벤트는생성시명시된 listener 객체의 actionperformed() 메 소드에서처리된다. Timer 객체를생성하고, 실행시키는코드는다음과같다. Timer t=new Timer(1000, listener); t.start(); // 1초마다액션이벤트발생및처리시작 다음은 Timer 클래스를사용하는간단한예제프로그램이다. JFrame 객체에초기값이 20 이표시되고, 매초마다 1 씩감소하여출력된값이 0 이되면종료된다. 프로그램소스 import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.timer; 2 장중요사항 Review 117
interface TimerListener { void timeelapsed(timer t); public class TimerTest extends JFrame implements ActionListener { static final long serialversionuid=11094; int cnt = 20; JLabel label; public TimerTest() { setsize(300, 200); settitle("timertest"); setdefaultcloseoperation(exit_on_close); label=new JLabel(""+cnt, JLabel.CENTER); label.setfont(new Font("Dialog", Font.PLAIN, 64)); Container c = getcontentpane(); c.add(label); setvisible(true); Timer t = new Timer(1000, this); t.start(); // every 1 sec, actionperformed() will be called public void actionperformed(actionevent e) { if (cnt > 0) { cnt--; label.settext(""+cnt); else System.exit(0); public static void main(string[] args) { new TimerTest(); 118 자바네트워크프로그래밍
실행결과 [ 프로그래밍연습 ] - 앞의예제를수정하여 1초마다현재시간을 display 하도록하시오. java.util.date 또는 java.util.calendar 클래스를이용한다. - 앞의프로그램을변경하여, 다음코드를포함하도록하시오. 앞의예제프로그램의 TimerTest 클래스는액션이벤트를처리하는 ActionPerformed 메소드를포함하고있다. 그러므로 Timer 객체를생성하는코드는다음과같다. Timer t = new Timer(1000, this) 이를수정하여, 액션이벤트를처리하는액션리스너구현클래스를별도로정의하여, 이벤트처리객체를생성한후, 다음코드와같이 this 대신에등록하도록한다.... MyListener my = new MyListener(label); Timer t = new Timer(1000, my);... class MyListener implements ActionListener { JLabel label; int cnt = 20; public MyListener(JLabel jl) { label = jl;... 2 장중요사항 Review 119
[ 영문해석 ] 자바 Properties 클래스에대한영문설명이다. 이를해석하시오. The Properties class represents a persistent set of properties. The Properties can be saved to a stream or loaded from a stream. Each key and its corresponding value in the property list is a string. A property list can contain another property list as its "defaults"; this second property list is searched if the property key is not found in the original property list. Because Properties inherits from Hashtable, the put and putall methods can be applied to a Properties object. Their use is strongly discouraged as they allow the caller to insert entries whose keys or values are not Strings. The setproperty method should be used instead. If the store or save method is called on a "compromised" Properties object that contains a non-string key or value, the call will fail. Similarly, the call to the propertynames or list method will fail if it is called on a "compromised" Properties object that contains a non-string key. The load(reader) / store(writer, String) methods load and store properties from and to a character based stream in a simple line-oriented format specified below. The load(inputstream) / store(outputstream, String) methods work the same way as the load(reader)/store(writer, String) pair, except the input/output stream is encoded in ISO 8859-1 character encoding. Characters that cannot be directly represented in this encoding can be written using Unicode escapes ; only a single 'u' character is allowed in an escape sequence. The native2ascii tool can be used to convert property files to and from other character encodings. The loadfromxml(inputstream) and storetoxml(outputstream, String, String) methods load and store properties in a simple XML format. 120 자바네트워크프로그래밍
3 장네트워크프로그래밍 자바언어는네트워크프로그래밍에필요한소켓을포함하고있다. 자바프로그램에서 소켓을사용하기위해서는먼저 import java.net.*; 문을사용하여네트워크관련클래스를 수입하여야한다. 3.1 IP 주소와 Port 인터넷에연결되어데이터교환이가능한컴퓨터시스템을호스트라고부르며, 모든호스트는 IP 주소를가진다. IP 주소는 4 바이트로구성되며, 모든호스트는유일한 IP를가진다. 예를들어 128.134.168.158은컴퓨터 IP이다. 인터넷에연결하기위해서모든호스트는유일한 IP를할당받아야하며, 인터넷상에유통되는모든메시지는 IP를사용하여목적지로이동한다. IP 는 0 부터 255 사이의정수 4 개로구성되기때문에사람이기억하기는쉽지않다. IP 는 사용이불편하므로, 사람이기억하기쉬운영문이름을가지는데이를도메인이름이라고한 다. www.hansung.ac.kr 은한성대학교의도메인이름이다. 도메인이름은사람에게는편리 하지만컴퓨터가이를처리하기위해서는먼저 IP 주소로변경해야한다. 도메인이름을 IP 로변경해주는시스템을 DNS(Domain Name Server) 라고한다. 인터넷에연결된모든호 스트들은 DNS 에접속할수있어야한다. DNS 도메인이름 IP 주소 3 장네트워크프로그래밍 123
IP주소는인터넷에연결된호스트시스템을결정한다. 한호스트에는여러개의포트가있어, 여러개의프로세스가동시에데이터를주고받을수있다. 만일 IP주소만을가지고통신을한다면, 하나의프로세스만이통신가능하므로불편할것이다. 즉포트는컴퓨터내의여러통신프로세스중에서원하는프로세스에게만전달하기위한번호이다. 포트번호는 16비트로표현되기때문에 0부터 65525까지의포트번호가가능하다. 0부터 1023까지의포트번호는시스템만이사용하도록예약되어있으며, 사용자는 1024부터 65535까지의포트를자유롭게사용할수있다. 인터넷통신을위해서포트번호도 IP 주소와함께반드시명시하여야하지만, 통신프로 토콜에따라기본포트번호를가지고있으므로생략이가능하다. 다음은주요인터넷프로 토콜이사용하는기본포트번호를정리한것이다. [ 표 3-1] 주요프로토콜의기본포트 프로토콜 포트번호 echo 7 ftp 21 telnet 23 smtp 25 DNS 42 Bootstrap 서버 67 Bootstrap 클라이언트 68 gopher 70 finger 79 http 80 그러므로우리가홈페이지를액세스할때, http://www.hansung.ac.kr 이라고명시하는것은묵시적으로 80 포트를사용하므로 http://www.hansung.ac.kr:80 이라고표시하는것과동일하다. 자바에서는인터넷주소클래스, TCP 통신을위한소켓클래스, UDP 통신을위한소켓클래스등풍부한네트워킹기능을지원한다. 다음절부터간단히살펴보기로한다. 124 자바네트워크프로그래밍
3.2 InetAddress 클래스 InetAddress 클래스는자바프로그래머에게 IP 주소에관련된메소드를지원하여준다. 그러므로 InetAddress 객체를사용하면 IP 주소와관련된다양한연산을손쉽게할수있다. InetAddress 클래스의생성자와메소드를정리하면다음과같다. o 생성자 InetAddress 클래스의생성자는없다. 객체를만들기위해서는클래스정적메소드인 getbyname(), getlocalhost(), getallbyname() 을사용하여야한다. InetAddress 클래스에서지원되는메소드를정리하면다음과같다. [ 표 3-2] InetAddress 클래스의메소드 메소드 static InetAddress getlocalhost() throws UnknownHostException static InetAddress getbyname(string name) thorws UnknownHostException static InetAddress[] getallbyname (String name) thorws UnknownHostException boolean equals(inetaddress adr) byte[] getaddress() String gethostaddress() String gethostname() 설명 로컬컴퓨터를나타내는 InetAddress 객체를반환 name 으로명시된컴퓨터를나타내는 InetAddress 객체를반환 name 으로명시된모든컴퓨터를나타내는 InetAddress 배열을반환 두객체가동일하면 true, 아니면 false 를반환 32 비트 IP 주소를나타내는 4 바이트배열반환 현객체의주소정보를문자열반환 현객체의컴퓨터이름을반환 String tostring() IP 주소를문자열로반환한다. 다음은 InetAddress 클래스를설명하기위한간단한예제프로그램이다. 실행결과는 로컬호스트의 IP 와검색엔진 naver 사이트에대한정보를출력한다. 3 장네트워크프로그래밍 125
예제프로그램 import java.net.*; public class InetDemo{ public static void main(string a[]) throws Exception{ InetAddress ia; ia= InetAddress.getLocalHost(); System.out.println("LocalHost의 IP 주소 : " + ia.gethostaddress()); ia = InetAddress.getByName("www.naver.com"); System.out.println("naver의 IP 주소 : " + ia.gethostaddress()); System.out.println("naver의호스트이름 : " + ia.gethostname()); 실행결과 C:\JAVA\newED\ch16>java InetDemo LocalHost의 IP 주소 : 128.134.168.158 naver의 IP 주소 : 211.218.150.200 naver의호스트이름 : www.naver.com 다음은 InetAddress 클래스의사용법을설명하기위한 Swing 예제프로그램이다. 텍스트필드에도메인이름이나 IP 주소를입력하면, 텍스트영역에관련정보를출력한다. 이예제에서주의할점은 IP주소나도메인이름중하나만을알고있을경우에도, 다른정보를손쉽게알아낼수있다는것이다. 예제프로그램 import java.net.*; import java.awt.*; 126 자바네트워크프로그래밍
import java.awt.event.*; import javax.swing.*; import javax.swing.border.*; class InetExample extends JFrame implements ActionListener { JTextField inip = new JTextField(); JTextArea ta = new JTextArea(); public InetExample() { super("inetaddress Example"); Container c = getcontentpane(); c.setlayout(new BorderLayout()); inip.setborder(new TitledBorder("IP or Domain Name")); inip.addactionlistener(this); c.add(inip, BorderLayout.SOUTH); c.add(new JScrollPane(ta), BorderLayout.CENTER); setlocation(200, 200); setsize(400, 250); setvisibile(true); public void actionperformed(actionevent e) { try { String tmp = inip.gettext(); ta.append("look up "+tmp); InetAddress adr = InetAddress.getByName(tmp); ta.append("\nhost name: "+ adr.gethostname()); ta.append("\nhost IP: "+ adr.gethostaddress()+"\n\n"); catch(exception ex) { inip.settext(""); 3 장네트워크프로그래밍 127
public static void main( String args[] ) { InetExample fm = new InetExample(); fm.addwindowlistener( new WindowAdapter() { public void windowclosing( WindowEvent e ) { System.exit( 0 ); ); 실행결과 [ 실습 ] 한성대학교홈페이지의도메인이름 www.hansung.ac.kr" 의 IP 주소를알아보시오. 그와반대로한성대학교홈페이지의 IP 주소를입력하여, 도메인이름을알아낼수있는지알아보시오. 128 자바네트워크프로그래밍
o InetAddress.getAllByName() 메소드예제접근빈도수가매우많은도메인은업무량을분산시키기위하여두개이상의 IP 주소로번역될수있다. 이러한경우에하나의도메인이름에대응되는여러 IP 주소를모두알고싶다면, getallbyname() 메소드를사용한다. 이메소드는도메인에대응되는모든 IP를포함하는 InetAddress 배열을반환한다. 이메소드의사용예는다음과같다. InetAddress[] addr = InetAddress.getAllByName("www.naver.com"); 위코드를사용한예제프로그램은다음과같다. ( 주석처리된다른사이트도테스트해보기바랍니다.) 예제프로그램 import java.net.*; class AllAddress { public static void main (String[] args) { try { String domain = // "www.yahoo.com"; "www.naver.com"; // "www.google.com"; InetAddress[] addresses = InetAddress.getAllByName(domain); for (int i = 0; i < addresses.length; i++) { System.out.println(addresses[i]); System.out.println("----- one name ----"); System.out.println(InetAddress.getByName(domain)); 3 장네트워크프로그래밍 129
catch (UnknownHostException e) { System.out.println("No DNS found"); 실행결과 www.naver.com/61.247.208.7 www.naver.com/222.122.84.200 www.naver.com/222.122.84.250 www.naver.com/61.247.208.6 ----- one name ---- www.naver.com/61.247.208.7 계속하려면아무키나누르십시오... 3.3 URL 클래스 URL(Uniform Resource Locator) 는원래 WWW 상에존재하는자원의위치를표시하 기위한것이다. URL 은프로토콜, 호스트 ID, 포트, 디렉토리, 파일로구성된다. URL 의사 용형식은다음과같다. protocol://domain_name:port_no/path/file_name 예를들어, 다음 URL 은 http://ikim.hansung.ac.kr/cgi/index.html/#a12.2.2 아래와같은정보를포함하고있다. 1 프로토콜 : http 2 호스트 : ikim.hansung.ac.kr 3 포트 (port) : 80 (http 프로토콜의기본값 ) 130 자바네트워크프로그래밍
4 디렉토리 ( 경로 ) : cgi 5 파일명 : index.html 6 참조 : A12.2.2 참조는 # 를사용하여표시할수있으며, 파일내의참조위치를나타낸다. 자바의 URL 클래스는인터넷자원주소를나타내는 URL 을편리하게사용할수있게 해준다. URL 클래스는다음과같은생성자를가진다. o 생성자 1 URL(String protocol, String host, int port, String file) throws MalformedURLException 2 URL(String protocol, String host, String file) throws MalformedURLException 3 URL(String urlstring) throws MalformedURLException URL 클래스에서지원되는메소드를정리하면다음과같다. [ 표 3-3] URL 클래스의메소드 메소드 설명 String getfile() URL 의파일이름을반환 String gethost() URL 의호스트이름을반환 String getprotocol() URL 의프로토콜을반환 String toref() URL 의참조를반환 String tostring() URL 을나타내는문자열을반환 Object getcontent() URL이가리키는객체를반환 URLConnection openconnection() throws IOException 지정된 URL과접속후 URLConnection 객체를반환 InputStream openstream() throws IOException 지정된 URL 과접속후 InputStream 객체를반환 다음은 URL 클래스를설명하기위한예제프로그램이다. 예제프로그램을실행시킨후, 하단의텍스트필드에 URL 을입력하면텍스트영역에 URL 정보를출력한다. 3 장네트워크프로그래밍 131
예제프로그램 import java.net.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.border.*; class URLTest extends JFrame implements ActionListener { static final long serialversionuid=1234560011; JTextField tf = new JTextField(); JTextArea ta = new JTextArea(); URL url; public URLTest() { super("url Test"); Container c = getcontentpane(); c.setlayout(new BorderLayout()); tf.setborder(new TitledBorder("Enter URL here")); tf.addactionlistener(this); c.add(tf, BorderLayout.SOUTH); c.add(new JScrollPane(ta), BorderLayout.CENTER); setlocation(200, 300); setsize(400, 300); setvisible(true); public void actionperformed(actionevent e) { 132 자바네트워크프로그래밍
try { String tmp = tf.gettext(); url = new URL(tmp); ta.append("\nprotocal: "+ url.getprotocol()); ta.append("\nhost : "+ url.gethost()); ta.append("\nport : "+ url.getport()); ta.append("\nfile : "+ url.getfile()); ta.append("\ncontent : "+ url.getcontent()); ta.append("\nref : "+ url.getref()); catch(exception ex) { ta.append("\n Wrong Input"); tf.settext(""); public static void main( String args[] ) { URLTest fm = new URLTest(); fm.addwindowlistener( new WindowAdapter() { public void windowclosing( WindowEvent e ) { System.exit( 0 ); ); 3 장네트워크프로그래밍 133
실행결과 o URL에서데이터읽기 URL 객체는일반적으로원격컴퓨터의자원객체를가리킨다. 자바의 URL 클래스에서 URL 객체가가리키는자원객체에서스트림을읽어오기위해서는 openstream() 메소드를사용한다. try { int ch; URL url = new URL("http://www.naver.com"); InputStream in = url.openstream(); while((ch = in.read())!= -1) System.out.print(ch); catch(ioexception e) { // IOException 처리 134 자바네트워크프로그래밍
다음은 URL 클래스의 openstream() 메소드를사용하는예제프로그램이다. 사용자가 URL을입력하면, URL이가리키는파일을읽어로컬호스트에출력하는예제프로그램이다. URL로명시된호스트에는물론 URL에명시된프로토콜을서비스하는서버가설치되어있어야한다. 예제프로그램 import java.net.*; import java.io.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.border.*; class URLDemo extends JFrame implements ActionListener { static final long serialversionuid=1234560012; JTextField tf = new JTextField(); JTextArea ta = new JTextArea(); URL url; public URLDemo() { super("url Test"); Container c = getcontentpane(); c.setlayout(new BorderLayout()); tf.setborder(new TitledBorder("Enter URL here")); tf.addactionlistener(this); c.add(tf, BorderLayout.SOUTH); c.add(new JScrollPane(ta), BorderLayout.CENTER); 3 장네트워크프로그래밍 135
setlocation(200, 300); setsize(400, 400); setvisible(true); public void actionperformed(actionevent e) { try { String tmp = tf.gettext(); url = new URL(tmp); InputStream is = url.openstream(); BufferedReader r = new BufferedReader( new InputStreamReader(is)); String input; while((input=r.readline())!= null) // EOF Test is simple ta.append(input+"\n"); catch(exception ex) { ta.append("\n Wrong Input"); tf.settext(""); public static void main( String args[] ) { URLDemo fm = new URLDemo(); fm.addwindowlistener( new WindowAdapter() { public void windowclosing( WindowEvent e ) { System.exit( 0 ); ); 136 자바네트워크프로그래밍
실행결과 3.4 URLConnection URLConnection 클래스는 URL이명시하는자원에접속한후, 원격자원에대한정보를손쉽게알아볼수있는여러가지메소드를지원하는클래스이다. 그러므로 URLConnection 클래스는 URL 클래스보다더많은서버관련메소드를지원한다. URLConnection을사용하여서버와연결을하기위해서는다음과같은절차를필요로한다. 1 URL 클래스를사용하여연결하고자하는원격자원의 URL객체를생성 2 연결에필요한여러파라메터를설정 3 URL객체의 openconnection() 메소드를이용해서원격자원과연결 4 URLConnection 클래스의다양한메소드사용 3 장네트워크프로그래밍 137
URLConnection 객체를생성하기위해서는생성자를사용하거나 URL객체의 open- Connection() 메소드를사용하여야한다. URLConnection 클래스생성자는다음하나만을 지원한다. o 생성자 1 protected URLConnection(URL url) - 접근제한자가 protected 이므로 URLConnection 클래스를확장한서브클래스에서만호출가능하다. 다음은 URLConnectin 클래스에서지원되는메소드를정리한것이다. 1 URL geturl() - URLConnection 객체의 URL을반환한다. 2 int getcontentlength() - 연결된문서의길이를반환한다. 3 String getcontenttype() - 연결된문서의타입을반환한다. 4 long getdate() - header date 항목의값을반환한다. 5 long getexpiration() - 헤더의 expires 항목값을반환한다. 6 long getlastmodified() - 해당문서의마지막수정날짜를반환한다. 7 InputStream getinputstream() throws IOException - 연결된문서를읽기위한 InputStream객체를반환 8 OutputStream getoutputstream() thorws IOException - 연결된문서에출력하기위한 OutputStream 객체를반환 9 String getheaderfield(int n) - n 번째헤더항목을얻는다. ( 시작은 0부터 ) 10 String getheaderfield(string name) - 주어진헤더에해당되는필드값을얻는다. 138 자바네트워크프로그래밍
11 String getheaderfieldkey(int n) - n 번째헤더항목의키를반환한다. 다음은간단한 URLConnection 클래스예제프로그램이다. 하단의텍스트필드에 URL 을입력하면입력된 URL 에대한정보와 URL 파일내용을텍스트영역에출력한다. 예제프로그램 import java.net.*; import java.io.*; import java.util.date; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.border.*; class URLConDemo extends JFrame implements ActionListener { static final long serialversionuid=1234560013; JTextField tf = new JTextField(); JTextArea ta = new JTextArea(); URL url; URLConnection urlcon; public URLConDemo() { super("urlconnection Test"); Container c = getcontentpane(); c.setlayout(new BorderLayout()); tf.setborder(new TitledBorder("Enter URL here")); tf.addactionlistener(this); 3 장네트워크프로그래밍 139
c.add(tf, BorderLayout.SOUTH); c.add(new JScrollPane(ta), BorderLayout.CENTER); setlocation(120, 300); setsize(500, 400); setvisible(true); public void actionperformed(actionevent e) { try { String tmp = tf.gettext(); url = new URL(tmp); urlcon = url.openconnection(); ta.append("\n-------- Document Information --------"); ta.append("\ndocument Type : "+ urlcon.getcontenttype()); ta.append("\ndocument length: "+ urlcon.getcontentlength()); ta.append("\nlast Modified : "+ new Date(urlCon.getLastModified())); ta.append("\nserver's clock :"+ new Date(urlCon.getDate())); ta.append("\n-------- Document Contents ----------\n"); InputStream is = urlcon.getinputstream(); BufferedReader r = new BufferedReader(new InputStreamReader(is)); String input; while((input=r.readline())!= null) // EOF Test is simple ta.append(input+"\n"); catch(exception ex) { ta.append("\n Wrong Input"); tf.settext(""); public static void main( String args[] ) { URLConDemo fm = new URLConDemo(); fm.addwindowlistener( 140 자바네트워크프로그래밍
new WindowAdapter() { public void windowclosing( WindowEvent e ) { System.exit( 0 ); ); 실행결과 3 장네트워크프로그래밍 141
3.5 TCP Socket Socket이란컴퓨터네트워크통신프로그램을위한소프트웨어메커니즘을나타낸다. 소켓은네트워크통신에대한세부사항을감추어주고, 메시지교환을위한고급메소드를지원한다. 그러므로프로그래머가소켓을사용하면통신프로그램을간단하게작성할수있다. 다음그림은소켓을통해서두원격프로세스가데이터를주고받는것을나타낸것이다. 네트워크통신에관한세부사항은소켓을구현한시스템프로그래머가담당하고, 응용프로그래머에게는감추어진다. 프로세스 A와 B가데이터 x를송수신하기위해서는먼저소켓을설정한후에, 프로세스 A는소켓에데이터 x를쓰고, 프로세스 B는소켓에서데이터읽기를실행하면된다. 프로세스 A 프로세스 B write x read x 소켓 통신 소켓 [ 그림 3-1] 소켓프로그램의예 자바언어의소켓은클래스로정의되어있어서기존시스템과비교할때생성과설정이간편하다. 특히자바소켓은플랫폼에무관하게생성하여사용할수있으며, 다양한고급메소드를지원한다는특징이있다. 또한자바의소켓은기존의시스템과는달리 TCP 프로토콜만을지원하며, UDP 프로토콜을사용하기위해서는별도의데이터그램소켓 (DatagramSocket) 을사용한다. o TCP 와 UDP TCP 와 UDP 는 TCP/IP 프로토콜의전송방식을나타낸다. 여기서 TCP 와 UDP 를간단 히비교설명하면다음과같다. 142 자바네트워크프로그래밍
1 TCP TCP는연결성통신방식을의미한다. 데이터를송수신하고자하는호스트간에먼저가상연결선을구성한다음데이터를전송하는것이다. TCP는일상생활의전화통신과비슷하다. 전화를거는사람은먼저다이얼을돌려수신자를결정한다. 수신자가전화를받아서로의신원을확인하면데이터를주고받는다. 더이상송수신할데이터가없으면전화를끊어연결을끊게된다. TCP 전송방식의특징은다음과같다. - 신뢰성이높은프로토콜이다. - 전송채널을설정해야하므로짧은메시지전송시오버헤드가크다. - 동일한경로를통해전송되므로전송된메시지순서가송신측과항상일치한다. - 데이터를전송하지않아도채널이유지되므로전송라인이낭비될수있다. 2 UDP UDP는비연결성통신방식이다. 전송하고자하는데이터를일정크기의패킷으로나누어전송한다. 전송채널을확립하지않기때문에모든패킷은목적지정보를포함하여야한다. UDP 방식은일상생활의편지전송과비슷하다. 만일한편지의내용의 200자를넘지못한다면, 10000자의데이터는 50통의편지에나누어져전송되어야할것이다. 모든편지에는수신자와송신자를명시해야한다. 50통의편지를메시지순서대로전송하였다고해서, 우체부아저씨가그순서를지켜주지는않을것이다. 일이잘못될경우에는편지한두통이분실될수도있을것이다. UDP 통신에서도이러한일이발생할수있다. - 신뢰성이낮은프로토콜이다. - 전송되는메시지의순서와수신되는메시지의순서가일치하지않을가능성이있다. 메시지순서를다시맞추어야하는불편함이있다. - 모든메시지에목적지를명시하여야한다. - 각메시지는통신라인의 busy여부에따라다른경로를통해전송될수있다. TCP와비교해서전송라인의효율이좋다. - 통신채널을설정하는오버헤드가없다. 본교재에서는 TCP 소켓을먼저살펴보고, UDP 소켓에대해서는이장의마지막부분에서 살펴보기로한다. 3 장네트워크프로그래밍 143
o TCP 소켓을사용한클라이언트- 서버간의접속순서소켓은일반소켓과서버소켓으로구분된다. 서버소켓은네트워크를통해자신에게오는요청을기다린다. 만일클라이언트측에서통신요구가있으면, 새로운소켓을생성 ( 서버의 IP와포트를명시해야함 ) 해서서버소켓과연결하게되고, 데이터를주고받게된다. 이러한소켓통신절차를간단히기술하면다음과같다. Server 1 서버소켓을생성한후, accept() 실행, client요구를대기 3 client요구를받아들여소켓객체생성 4 소켓객체를이용입출력스트림생성 Client 2 서버 IP, port를사용하여 Socket객체생성 ( 접속요구 ) 4 소켓객체를이용입출력스트림생성 5 통신수행 6 소켓을닫는다 5 통신을수행 6 소켓을닫는다 [ 그림 3-2] 클라이언트서버접속절차 o ServerSocket 클래스 ServerSocket 클래스는통신서버에서생성하는소켓이다. ServerSocket 은클라이언트로부터접속요구를기다리고있다가, 접속요구가있을경우새로운통신소켓을생성하는일을한다. ServerSocket 클래스의생성자는다음과같다. o 생성자 1 ServerSocket(int port) - 주어진포트를사용하는서버소켓을생성한다. 2 ServerSocket(int port, int backlog) - 주어진포트와입력버퍼의크기를사용하는서버소켓을생성한다. 3 ServerSocket(int port, int backlog, InetAddress bindaddr) - bindaddr은서버의주소가두개이상인경우에서버소켓이사용할인터넷주소를명시한것이다. 144 자바네트워크프로그래밍
다음은 ServerSocket 클래스에서지원되는메소드를정리한것이다. [ 표 3-4] ServerSocket 클래스의주요메소드 메소드 Socket accpet() throws IOException void close() throws IOException 설명 정해진포트로클라이언트의접속요구가있을시새로운소켓을생성반환한다. 서버소켓을닫는다. InetAddress getinetaddress() 서버소켓이사용중인인터넷주소를반환한다. int getlocalport() 서버소켓이사용중인포트를반환한다. protected void implaccept(socket s) void close() 소켓을닫음 ServerSocket 의서브클래스에서 accept() 를오버라이드하여 Socket 의서브클래스반환시킴 void setsotimeout(int timeout) 명시된 timeout 을사용하여 SO_TIMEOUT 설정 서버소켓은 accept() 메소드를수행함으로써클라이언트의요청에따라실제통신을수 행할소켓을생성하여반환한다. ServerSocket 객체는클라이언트와통신할소켓을생성할 뿐이고실제통신은수행하지않는다. o Socket 클래스 TCP 방식을사용해서통신을하기위해서는 Socket 클래스객체를생성해야한다. Socket 클래스는클라이언트와서버간에실질적인통신메소드를지원하고있다. Socket 클래스의생성자는다음과같다. o 생성자 1 Socket(String host, int port) throws IOException, UnknownHostException - 소켓을생성하고주어진 host와 port에접속한다. 접속이안이루어지는경우 IOException 이발생한다. 인자로서버의 IP와서버의포트번호를명시하여야한다. 2 Socket(InetAddress address, int port) throws IOException - 소켓을생성하고주어진 address와 port에접속한다. 접속이안이루어지는경우 IOException이발생한다. 3 장네트워크프로그래밍 145
3 Socket(SocketImpl impl) throws SocketException - 사용자명시 SocketImpl 클래스의 Socket 객체생성 4 Socket() - 연결안된소켓을생성 다음은 Socket 클래스에서지원되는메소드를정리한것이다. [ 표 3-5] Socket 클래스의주요메소드 메소드 InputStream getinputstream() throws IOException OutputStream getoutputstream() throws IOException 설명 현소켓의 InputStream 객체를생성하여반환 현소켓의 OutputStream 객체를생성하여반환 void close() throws IOException 현소켓을닫는다. InetAddress getinetaddress() 원격호스트의인터넷주소를반환한다. InetAddress getlocaladdress() 로컬호스트의인터넷주소를반환한다. int getport() 원격호스트의포트를반환한다. int getlocalport() 로컬호스트의포트를반환한다. void connect(socketaddress target) throws IOException 서버소켓에현소켓을연결한다. void connect(socketaddress target, int timeout) throws IOException timeout 밀리초안에서버소켓에현소켓을연결한다. 다음은 Socket 의메소드사용예제프로그램이다. 여기서주의할것은원격서버의포트 와로컬호스트의포트번호가동일하지않다는점이다. 예제프로그램 import java.net.*; import java.io.*; public class SocketInfo { public static void main(string[] args) { 146 자바네트워크프로그래밍
try { Socket sock = new Socket(args[0], 80); System.out.println("Remote Addr: " + sock.getinetaddress()); System.out.println("Remote port: " + sock.getport()); System.out.println("Local Addr : " + sock.getlocaladdress()); System.out.println("Local port : " + sock.getlocalport()); catch (UnknownHostException e) { System.err.println("UnKnown Host : " + args[0]); catch (SocketException e) { System.err.println("Socket Exception " + e); catch (IOException e) { 실행결과 Remote Addr: www.hansung.ac.kr/128.134.165.1 Remote port: 80 Local Addr : /211.48.44.82 Local port : 2447 [Question] 위의실형결과에서는 Local port 가 2247 로출력되었다. 원격포트와로컬포트가다른이유는무엇인가? 시스템이 1024 번이후의사용가능한로컬시스템의포트를임의로선택한것임. 하나의소켓를사용하여동시에입력스트림과출력스트림객체를생성하는것이가능하다 ( 소켓객체의 getinputstream 메소드와 getoutputstream 메소드실행 ). 입출력객체를생성한후에는스트림입출력객체에적용할수있는모든메소드를사용할수있다. 그러므로자바의네트워크프로그래밍을위해서는먼저입출력스트림을이해하는것이필요하다. 3 장네트워크프로그래밍 147
o Telnet을사용한웹서버연동 - 본실습에서는 Telnet 이라는시스템제공유틸리티를사용한다. Telnet 은메인컴퓨터와원격으로연결하여명령어를전송하는툴이다. ( 소켓을사용하여구현되었음.) 일반적으로유닉스운영체제를채택한컴퓨터에서원격으로명령어를전송하고, 실행결과를출력하는데사용되었다. MS 윈도우운영체제에도 Telnet 은내장되어있다. Telnet - telnet을사용하여웹서버에명령어를주고, html 문서를출력하는것이가능하다. MS 윈도우에서도스창을열고, C:\> telnet www.hansung.ac.kr 80 을입력하고엔터키를치면, 텔넷창이생성되고, 한성대학교웹서버와연결된다. ( 한성대학교의경우, JSP를사용하여웹서비스를제공하므로시작문서가 index.jsp 이다.) - 텔넷창에서다음을입력한다. ( 화면에 echo 되지는않는다.) GET /index.jsp HTTP/1.0 <enter> <enter> - 한성대학교홈페이지의 html문서가출력된다. Telnet은사용자키보드입력을원격호스트에전송하고, 원격호스트의실행결과를출력하는단순터미널기능을수행한다. ( 검색엔진에서 telnet에대해조사하기바랍니다.) 즉홈페이지를내용을받기위해서는 www.hansung.ac.kr 사이트의 80번포트로연결되면, 클라이언트가 GET /index.html HTTP/1.0 <enter> <enter> 문자열을전송하면웹서버는 <HTML> 문서를클라이언트에게전송한다. 148 자바네트워크프로그래밍
< 텔넷의실행결과 > 다음예제 WebGet.java 는앞의텔넷실습내용을자바소켓을사용하여프로그래밍 한것이다. 명령행인자로명시한 URL 의웹문서의내용을화면에출력할수있다. 예제코드 : WebGet.java import java.io.bufferedreader; import java.io.inputstream; import java.io.inputstreamreader; import java.io.ioexception; import java.io.outputstream; import java.io.printwriter; import java.net.socket; /** This program demonstrates how to use a socket to communicate with a web server. Supply the name of the host and the resource on the command-line, for example java WebGet java.sun.com index.html */ public class WebGet 3 장네트워크프로그래밍 149
{ public static void main(string[] args) throws IOException { if (args.length!= 2) // get command-line arguments { System.out.println("usage: java WebGet host resource"); System.exit(0); String host = args[0]; String resource = args[1]; final int HTTP_PORT = 80; // open socket Socket s = new Socket(host, HTTP_PORT); // get streams InputStream in = s.getinputstream(); OutputStream out = s.getoutputstream(); // turn streams into readers and writers BufferedReader reader = new BufferedReader(new InputStreamReader(in)); PrintWriter writer = new PrintWriter(out); // send command String command = "GET /" + resource + " HTTP/1.0\n\n"; writer.print(command); writer.flush(); // read server response boolean done = false; while (!done) { String input = reader.readline(); 150 자바네트워크프로그래밍
if (input == null) done = true; else System.out.println(input); // always close the socket at the end s.close(); 실행예 C:> java WebGet java.sun.com index.html 썬사의자바홈페이지 html 문서를출력한다. o 텔넷 (telnet) 과연동하는자바소켓프로그램텔넷을사용하여원격명령어를입력하고, 이를받아들여수행하는자바소켓프로그램을작성한다. 사용자는먼저 telnet을사용하여접속할컴퓨터의 IP와포트번호를명시한다. Telnet과서버와접속되면, 사용자가입력한명령어는 echo없이서버에서실행되는자바프로그램에게전달된다. 이때서버의자바프로그램은접속요구를받아들여야하므로, ServerSocket 클래스를사용하여야한다. 다음그림을참조하여프로그램의기본적인실행방식을이해한다. Telnet 키보드 /display - Telnet: 명령어입력 - 원격실행결과출력 - 자바프로그램 : 텔넷으로부터받은명령어를받아들여, 수행 - 실행결과를전송또는출력 [ 프로그램의실행과정 ] 1 작성하는프로그램은은행계좌관리프로그램으로서, 클라이언트와서버로구성된다. 2 서버프로그램은자바의 ServerSocket 을사용하여작성하며, 클라이언트로부터은행구좌에관련된명령어를수신받아실행한다. 3 장네트워크프로그래밍 151
3 클라이언트프로그램은 telnet 을사용하며, 키보드로받아들인명령어를서버프로그램에전송한다. 4 텔넷을통해입력할명령어는다음세가지이며, 초기잔액은 0이다. deposit <amount> // 명시된금액을예금 withdraw <amount> // 명시된금액을출금 quit // 프로그램종료 예제코드 : BankServer.java import java.io.*; import java.net.serversocket; import java.net.socket; import java.util.stringtokenizer; public class BankServer { public static void main(string[] args) throws IOException { int balance = 0; ServerSocket server = new ServerSocket(8888); System.out.println("Waiting for clients to connect..."); while(true) { Socket s = server.accept(); Bank bank = new Bank(s, balance); balance = bank.execute(); s.close(); class Bank { Socket s; int bal ; public Bank(Socket asocket, int abal) { s = asocket; 152 자바네트워크프로그래밍
bal = abal; public int execute() throws IOException { String line; StringTokenizer tokens; BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream())); // PrintWriter out = new PrintWriter(s.getOutputStream()); while(true) { line = in.readline(); System.out.println("Received: "+ line); if(line.equals("") line.equals("quit")) return bal; tokens = new StringTokenizer(line); String command = tokens.nexttoken(); if (command.equals("deposit")) { int amount = Integer.parseInt(tokens.nextToken()); bal += amount; else if (command.equals("withdraw")) { int amount = Integer.parseInt(tokens.nextToken()); bal -= amount; System.out.println("Current Balance = "+bal); // out.println("current Balance = "+bal); // out.flush(); [ 실행예 ] telnet 에서입력받은입출금명령어를서버는실행한다. 도스창을하나열어 BankServer 를실행하고, 다른도스창을열어 telnet 을실행한다. C:> java BankServer - 다른도스창이나원격컴퓨터에서텔넷을실행한다. ( 텔넷실행시에 IP와 port를명시 ) 3 장네트워크프로그래밍 153
C:> telnet 127.0.0.1 8888 deposit 1000 withdraw 200 withdraw 200 quit [ 실행결과 ] [ 코딩연습 ] 1. 앞의 BankServer 예제는실행결과를서버에서만출력하였다. 실행결과를클라이언트에게전송하여텔넷에서도출력할수있도록프로그램을수정하시오. o 웹브라우저 (internet explorer) 와연동하는자바소켓프로그램웹브라우저는 IP 주소와 80포트를사용하여웹서버에게 request 문자열을전송한다. 웹서버를이요구를받아들여, 적절한 response를웹브라우저에게전송한다. 다음웹서버예제프로그램은웹브라우저의 request를무시하고, 웹브라우저에정수 n값을입력하는화면을출력시킨다. ( 실습하고자하는컴퓨터에웹서버가이미실행중인경우를대비하여, 154 자바네트워크프로그래밍
다음예제에서는 8000 번포트를사용하였다. 만일 80 포트가사용가능하다면, HTTP 프로토 콜의기본포트가 80 번이므로 80 포트를사용하는것이바람직하다.) 소스코드 : WebBank.java import java.io.*; import java.net.*; public class WebBank { public static void main(string[] args) throws Exception { ServerSocket server = new ServerSocket(8000); System.out.println("Waiting for Web-Browsers..."); while(true) { Socket s = server.accept(); BufferedReader in= new BufferedReader( new InputStreamReader(s.getInputStream())); for(int a = 0; a<7; a++) System.out.println(in.readLine()); System.out.println("------------------------------"); OutputStreamWriter out= new OutputStreamWriter(s.getOutputStream()); out.write("http/1.1 200 OK\r\n"); out.write("\n"); out.write("<html><head><title>summing</title></head>"); out.write("<body><h3> 1.. n summing</h3><hr>"); out.write("<form method=get>"); out.write("key a positive integer: <input type=text name=num>"); out.write("<br><hr><input type=submit value=\"compute\">"); out.write("</form></body></html>"); out.flush(); in.close(); out.close(); // what if this line is missed? 3 장네트워크프로그래밍 155
실행결과 c:\dist\socket> java WebBank 웹브라우저를실행해서, 로컬호스트의 8000 번포트 (http://127.0.0.1:8000) 에접속하면 다음화면과같다. 156 자바네트워크프로그래밍
[Home Work] 앞의웹서버예제프로그램을변경하여, 웹브라우저에서 2개의정수를입력한다음 [Add] 버튼을클릭하면, 두수의합이출력되도록하시오. o 단순한메시지전송예제다음은 TCP 소켓을설명하기위한예제프로그램이다. 서버프로그램을먼저실행한후에클라이언트프로그램을실행하여야한다. 클라이언트프로그램을실행할때명령행인자로서버의 IP 주소나도메인이름을명시해야한다. 서버프로그램은 swing을사용하였으며, 텍스트필드에입력된문자열을클라언트로전송한다. 문자열을전송하기위해서 DataInputStream / DataOutputStream 클래스와 readutf() / writeutf() 메소드를사용하였다. 클라이언트프로그램은전송받은문자열을화면에출력한후, 서버로되돌려보낸다. 서버는되돌아온문자열을텍스트영역에출력한다. 한소켓을사용하여, 클라이언트와서버간에양방향통신이가능하다는점에유의한다. 예제프로그램 1) 서버예제코드 import java.io.*; import java.net.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.border.*; class ServerTest extends JFrame implements ActionListener { final static long serialversionuid=1234560012; JTextField tf = new JTextField(); JTextArea ta = new JTextArea(); ServerSocket ss; Socket s; 3 장네트워크프로그래밍 157
DataOutputStream w; DataInputStream r; public ServerTest() { super("server Socket Test"); Container c = getcontentpane(); c.setlayout(new BorderLayout()); tf.setborder(new TitledBorder("Enter Message")); tf.addactionlistener(this); c.add(tf, BorderLayout.SOUTH); c.add(new JScrollPane(ta), BorderLayout.CENTER); setlocation(200, 200); setsize(400, 250); setvisible(true); try { ss = new ServerSocket(8888); // 포트 8888을사용서버소켓생성 s = ss.accept(); // client의접속요구를기다림 ta.append("a client connented\n"); // client와연결되었음을표시 // 문자열송수신을위한데이터입출력스트림생성 w = new DataOutputStream(s.getOutputStream()); r = new DataInputStream(s.getInputStream()); catch(exception e) { public void actionperformed(actionevent e) { try { w.writeutf(tf.gettext()); // 문자열전송 String tmp = r.readutf(); // 되돌아올문자열수신 ta.append("echo: "+ tmp +"\n"); // 텍스트영역에문자열표시 158 자바네트워크프로그래밍
catch(exception ex) { tf.settext(""); // 새로운입력을받기위해텍스트필드 reset public static void main( String args[] ) { ServerTest fm = new ServerTest(); fm.addwindowlistener( new WindowAdapter() { public void windowclosing( WindowEvent e ) { System.exit( 0 ); ); 클라이언트자바코드 import java.io.*; import java.net.*; class ClientTest { public static void main(string args[]) throws Exception { if(args.length!= 1) { // 명령행인자가 1개있음을체크 System.out.println("specify Server IP"); System.exit(1); Socket c = new Socket(args[0], 8888); // 서버와접속 DataInputStream r = new DataInputStream(c.getInputStream()); DataOutputStream w = new DataOutputStream(c.getOutputStream()); String mesg; while(true) { 3 장네트워크프로그래밍 159
mesg = r.readutf(); // 메시지수신 w.writeutf(mesg); // 수신된메시지송신 System.out.println("From Server: "+mesg); // 수신된메시지출력 if(mesg.equals("end")) break; // 수신된메시지가 end 이면종료 실행결과 1) 서버측실행결과 2) 클라이언트측실행결과 C:\JAVA\newED\ch16>java ClientTest ikim.hansung.ac.kr From Server: is it working? From Server: I love Java Network Programming From Server: See You Later!!!!! [Home Work] 위의예제프로그램은 end 를입력하면클라이언트가종료되고, 서버도정지된다. end 입력후, 다시실행한클라이언트의접속요구에서버는응답하지않는다. 서버프로그램을수정하여, 클라이언트가재접속을요구하면, 계속동작하도록서버프로그램을변경하시오. (ServerTest2.java, ServerTest3.java 참조 ) 160 자바네트워크프로그래밍
o 파일송수신예제자바에서파일입출력프로그래밍과소켓프로그래밍은매우비슷하다. 여기서소개하는예제는파일스트림과소켓을이용하여파일을송수신하는프로그램이다. 파일이름을먼저송신한다음, 파일내용을전송한다. 매우단순한프로그램이지만, 모든종류의파일을오류없이송수신할수있다. 파일수신코드 : FileReceiver.java import java.io.*; import java.net.*; class FileReceiver { public static void main(string[] args) throws Exception { ServerSocket ss = new ServerSocket(8888); Socket s = ss.accept(); System.out.println("Client Connected"); DataInputStream r = new DataInputStream(s.getInputStream()); String fname = r.readutf(); System.out.println("File: "+fname+" receiving"); DataOutputStream w = new DataOutputStream(new FileOutputStream(fname)); try { while(true) w.writechar(r.readchar()); catch(eofexception exp) { System.out.println("File Transfer End"); 3 장네트워크프로그래밍 161
r.close(); w.close(); 파일송신코드 : FileSender.java import java.io.*; import java.net.*; class FileSender { public static void main(string[] args) throws Exception { if(args.length < 2) { System.out.println("Specify Server IP & File Name"); System.exit(1); Socket s=new Socket(args[0], 8888); DataOutputStream w = new DataOutputStream(s.getOutputStream()); DataInputStream r = new DataInputStream(new FileInputStream(args[1])); w.writeutf(args[1]); try { while( true) w.writechar(r.readchar()); catch(eofexception exp) { System.out.println("File Transfer End"); r.close(); w.close(); 162 자바네트워크프로그래밍
실행결과 1 파일수신측 C:\JAVA\test\server>java FileReceiver Client Connected // 출력된메시지임 File: 08.jpg receiving File Transfer End C:\JAVA\test\server> 2 파일송신측 C:\JAVA\test>java FileSender localhost 08.jpg File Transfer End C:\JAVA\test> [Home Work] 앞의파일송수신예제프로그램은서버가하나의파일만을수신하고종료한다. 이를변경하여, 파일수신후서버가종료되지않고다른파일을수신할수있도록수정하시오. ( 멀티스레드사용 : 서버소켓이 accept() 한후에, 스레드를생성하여파일을수신하도록한다 ) o 시스템클래스객체의송수신클래스가정의될때, java.io.serializable 인터페이스를구현 (implements) 을명시하였다면, 그객체는 TCP 소켓을통해전송할수있다. 다음은 java.util.date 객체를 TCP 소켓을통해서송수신하는간단한예제이다. Date 클래스도 Serializable 인터페이스를구현하고있으므로 TCP 소켓을통해송수신이가능하다. 예제코드 : ReceiveDate.java import java.io.*; import java.net.*; import java.util.date; 3 장네트워크프로그래밍 163
public class ReceiveDate { public static void main(string args[]) throws Exception { ServerSocket ss =new ServerSocket(7788); Socket sock = ss.accept(); ObjectInputStream is = new ObjectInputStream(sock.getInputStream()); Date date = (Date) is.readobject(); System.out.println("Today is "+date); is.close(); sock.close(); 예제코드 : SendDate.java import java.io.*; import java.net.*; import java.util.date; public class SendDate { public static void main(string args[]) throws Exception { String ip=null; if (args.length==0) ip="localhost"; else ip=args[0]; Socket sock = new Socket(ip, 7788); ObjectOutputStream os = new ObjectOutputStream(sock.getOutputStream()); Date date = new Date(); os.writeobject(date); os.flush(); os.close(); 164 자바네트워크프로그래밍
sock.close(); System.out.println("We have sent a Date"); 실행결과 ReceiveDate가서버소켓을포함하고있으므로, 먼저실행되어야한다. SendDate 실행컴퓨터와 ReceiveDate 실행컴퓨터가서로다르다면, 실행시 ReceiveDate 실행컴퓨터의 IP 또는도메인주소를명시해야한다. 1) 서버소켓측 E:\test>java ReceiveDate Today is Wed Jan 23 14:15:47 KST 2002 2) 클라이언트측 E:\test>java SendDate 127.0.0.1 We have sent a Date o 자바제공유틸리티 : Serialver 앞의예제에서사용한 Date는 Serializable 인터페이스를구현한클래스이지만, 자바가제공하는모든클래스들이 Serializable 인터페이스를구현하고있는것은아니다. 그러므로자바의객체를파일에저장하거나소켓으로전송하는프로그램을작성할때에는그클래스가 Serializable 인터페이스를구현하고있는지확인할필요가있다. 이를위해서 JDK에서제공되는유틸리티 serialver" 를사용할수있다. C:\java>serialver -show 위의명령어를입력하면, 다음과같은프레임이표시되고, 검사를원하는클래스의이름 ( 경로포함 ) 을입력하고 [show] 버튼을클릭한다. 다음은 Date 클래스를입력한결과 Date 클래스의 serialversionuid 값이출력되었음을보여준다. 3 장네트워크프로그래밍 165
다음은 java.awt.graphics 클래스를입력한결과이다. Graphics 클래스는 Serializable 인터페이스를구현하고있지않음을알수있다. o 프로그래머정의객체의송수신프로그래머가정의한객체를소켓을통해전송하려면, 클래스를정의할때 Serializable 인터페이스를구현하여야한다. 예를들어프로그래머가 Person이라는클래스를다음과같이정의하였다고가정한다. class Person { // 정의내용은생략 위의 Person 클래스의객체를소켓으로전송하기위해서는다음과같이변경되어야한 다. 새롭게구현되어야하는메소드는없다. class Person implements Serializable { // 정의내용은동일 다음은 TCP 소켓을통하여 Person 객체를송수신하는간단한예제이다. SendPerson 클래스가서버소켓을포함하고있으므로먼저실행하여야한다. GetPerson 클래스를실행할때명령행인자로서 SendPerson이실행되고있는컴퓨터의 IP나도메인이름을명시해야한다. 이를생략하면, 두프로그램이동일한컴퓨터에서실행되는것으로간주한다. 166 자바네트워크프로그래밍
자바소스프로그램 : SendPerson.java import java.io.*; import java.net.*; class Person implements Serializable { static final long serialversionuid=1122339; String name; int id; Person(String name, int id) { this.name = name; this.id = id; public String tostring() { String s = name + " " + id; return s; public class SendPerson { public static void main(string args[]) { Person p[] = new Person[3]; p[0] = new Person("Kim", 7); p[1] = new Person("Lee", 8); p[2] = new Person("Park", 12); try { ServerSocket ss = new ServerSocket(7788); Socket sock = ss.accept(); ObjectOutputStream os = new ObjectOutputStream(sock.getOutputStream()); for(int i=0; i<3; i++) os.writeobject(p[i]); os.close(); 3 장네트워크프로그래밍 167
System.out.println("3 Person Sent"); catch (Exception e) { e.printstacktrace(); GetPerson.java import java.io.*; import java.net.*; public class GetPerson { public static void main(string args[]) { String ip = null; if (args.length == 0) ip = "localhost"; else ip=args[0]; Person p[] = new Person[3]; try { Socket sock = new Socket(ip, 7788); ObjectInputStream is = new ObjectInputStream(sock.getInputStream()); for(int i=0; i<3; i++) p[i] = (Person) is.readobject(); is.close(); System.out.println("Three Person received"); for(int i=0; i<3; i++) System.out.println(p[i]); catch (Exception e) { e.printstacktrace(); 168 자바네트워크프로그래밍
실행과정및결과 1) 서버소켓측 E:\test>java SendPerson 3 Person Sent 2) 클라이언트측 E:\test>java GetPerson 127.0.0.1 Three Person received Kim 7 Lee 8 Park 12 [Home Work] 다음 Acc_Transaction 클래스객체를소켓으로송수신하는프로그램을작성하시오. ➀ 클라이언트는은행구좌트랙잭션정보를객체로만들어, 서버로전송한다. 트랙잭션의객체는예금또는출금으로구분되며, 금액을명시하여서버에게전송한다. ➁ 서버는클라이언트의요구를소켓으로받아들여, 입출금업무를수행한다. 고객의처음예금액은 0원이다. 서버는항상고객의잔액을화면에출력한다. 다음은뱅크서버프로그램이다. 뱅크클라이언트프로그램을작성하여, Acc_Transaction 객체를서버에전송하시오. BankTrans.java import java.io.*; import java.net.*; class Acc_Transaction implements Serializable { public static final long serialversionuid=1234567890; final int deposit = 1; final int withdraw = 2; int trans_type; 3 장네트워크프로그래밍 169
int amount; String name; public Acc_Transaction(int type, int amt, String name) { trans_type = type; amount = amt; this.name = name; int get_type() { return trans_type; int get_amount() { return amount; String get_name() {return name; public class BankTrans { public static void main(string[] args)throws Exception { Acc_Transaction at=null; int balance=0; ServerSocket ss = new ServerSocket(7799); while(true){ Socket sock = ss.accept(); ObjectInputStream ois = new ObjectInputStream(sock.getInputStream()); at= (Acc_Transaction) ois.readobject(); if (at.get_type()==1){ balance += at.get_amount(); else { balance -= at.get_amount(); System.out.println("Current Balance="+balance+" by "+at.get_name()); ois.close(); sock.close(); 클라이언트 요구객체 서버 170 자바네트워크프로그래밍
[ 영문해석 ] 다음은 TCP와소켓에대한설명이다. 이를해석하시오. 1. TCP o Transmission Control Protocol, a transportation protocol that is one of the core protocols of the Internet protocol suite o Trusted Computing Platform, a cross-platform hardware-based platform for improved security 2. Socket o An Internet socket (or commonly, a socket or network socket), is an end-point of a bidirectional communication link that is mapped to a computer process communicating on an Internet Protocol-based network, such as the Internet. A socket is an interface between an application process or thread (child process) and the TCP/IP protocol stack provided by the operational system. o An Internet socket is identified by the operational system as a unique combination of the following: - Protocol (TCP, UDP or raw IP) - Local IP address - Local port number - Remote IP address (Only for established TCP sockets) - Remote port number (Only for established TCP sockets) o The operational system is forwarding incoming IP data packets to the corresponding application process by extracting the above socket address information from the IP, UDP and TCP headers. o A communicating local and remote socket are called a socket pair. o A somewhat simplified definition occurring in the literature follows: "The combination of an IP address and a port number is referred to as a socket."[1] 3 장네트워크프로그래밍 171
See also RFC 147 for the original definition of socket as it was related to the ARPA network in 1971. o The netstat -an command-line tool shows a list of all sockets that are currently defined by the operational system. The netstat -b command shows a list of which socket that was created by what application program. For a listening TCP socket, the remote address presented by netstat may be denoted 0.0.0.0 and the remote port number 0. 172 자바네트워크프로그래밍
o 간단한웹서버프로그램 HTTP 프로토콜은웹에서가장많이사용되는프로토콜이다. 웹브라우저가특정사이트의홈페이지를접속하기위해서는 URL을명시하여야한다. 웹브라우저와웹서버사이의 HTTP 통신은 TCP/IP 기반으로클라이언트가요청하고서버가응답함으로써완료된다. 1) HTTP 요청 웹클라이언트가서버에게전송하는메시지의첫째라인은매우중요하며, 를포함하고있다. 1 GET/POST 방식의선택 2 접속하고자하는리소스의경로명과파일이름 3 HTTP 프로토콜의버전 다음의정보 예 ) 클라이언트메시지의첫째라인의예 1 GET / HTTP/1.1 [CR LF] // default 페이지전송요구 2 GET /index.html HTTP/1.1 [CR LF] // index.html 문서요구 3 GET /MyPage/index.html HTTP/1.1 [CR LF] // /MyPage/index.html 문서요구 4 POST /cgi-bin/score.cgi HTTP/1.1 [CR LF] // /cgi-bin/score.cgi 실행결과요구 웹클라이언트의요청에는웹브라우저, 클라이언트도메인, 사용언어, 문자셋, 코딩형식, 수식가능파일형식에관한정보를포함한다. 다음은웹클라이언트의요청메시지의예이다. [ 웹클라이언트의요청메시지예 ] GET / HTTP/1.1 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */* Accept-Language: ko Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows 98; Win 9x 4.90) Host: ikim.hansung.ac.kr:8000 Connection: Keep-Alive 3 장네트워크프로그래밍 173
2) HTTP 응답 웹서버가클라이언트요구에응답하는메시지로서, HTTP 응답부분의시작형식은 다음과같다. [ 응답형식 ] HTTP/protocol-version response-code [CR LF] [LF] HTML_Document [ 실제예 ] HTTP/1.1 200 OK [CRLF] [LF] <html><body><h1>hello! HTTP</h1></body></html> 다음은단순한웹서버예제이다. 이예제를실행하고, 웹브라우저로접속하면, 서버측에서는 HTTP 요청내용을출력하고, 웹브라우저에는 Hello 메시지를표시한다. 실행시에명령행인자로서웹서비스포트를지정할수있다. 명시하지않으면, 기본값인 8000번포트로접속요구를받는다. 컴퓨터시스템이이미선점하여사용하고있는포트를지정하는경우에는실행오류가발생한다. 이경우사용하지않는다른포트번호를명시하여야한다. 소스코드 import java.net.*; import java.io.*; public class MyWebServer { public static void main(string[] args) { int port = 8000; try { port = Integer.parseInt(args[0]); catch (Exception e) { try { ServerSocket server = new ServerSocket(port); 174 자바네트워크프로그래밍
System.out.println("My Web Server is Ready"); int count = 0; while (true) { count++; Socket conn = server.accept(); try { System.out.println("HTTP Req.: " + conn); BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); try { int cnt =1; while (cnt <= 7) { String str = in.readline(); System.out.println(cnt+":"+str); cnt++; catch (SocketException e) { catch (IOException e) { System.err.println(e); Thread outthread = new OutputThread(conn.getOutputStream(), count); outthread.start(); try { outthread.join(); catch (InterruptedException e) { catch (IOException e) { System.err.println(e); finally { try { if (conn!= null) conn.close(); catch (IOException e) { 3 장네트워크프로그래밍 175
catch (IOException e) { e.printstacktrace(); class OutputThread extends Thread { Writer out; int cnt; public OutputThread(OutputStream out, int num) { this.out = new OutputStreamWriter(out); cnt = num; public void run() { try { String[] line = {"HTTP/1.0 200 OK\r\n", "\n", "<html><body><h1>hello! HTTP "+cnt+"</h1></body></html>" ; for(int i=0; i<line.length; i++) out.write(line[i]); out.flush(); catch (IOException e) { System.out.println(e); try { out.close(); catch (IOException e) { System.out.println(e); 실행결과 1 서버측실행 ( 포트를 8000으로지정 ) E:\newBook\add\ch02>java MyWebServer My Web Server is Ready 176 자바네트워크프로그래밍
HTTP Req.: Socket[addr=/128.134.168.158,port=1661,localport=8000] 1:GET / HTTP/1.1 2:Accept: */* 3:Accept-Language: ko 4:Accept-Encoding: gzip, deflate 5:User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows 98; YIE6) 6:Host: ikim.hansung.ac.kr:8000 7:Connection: Keep-Alive 2 클라이언트측실행결과 [ 프로그래밍연습 ] 앞의예제를변경하여, 아침에웹서버를접속하면 Hello! Good Morning!" 을출력하고오후에웹서버를접속하면 Hello! Good Afternoon!" 을출력하며, 저녁 6시이후에는 Hello! Good evening!" 을출력하도록한다. 프로그래밍힌트 - java.util.calendar 클래스의메소드 get() 메소드를사용하면현재시각을반환한다. int calendarobj.get(calendar.hour_of_day) - Calendar.getInstance() 를사용하면 Calendar 객체생성 / 반환한다. - http://java.sun.com 사이트를접속한다음, Calendar 를검색창에입력하여검색한다. - 어떠한생성자와메소드가지원되는지반드시살펴보도록한다. ( 자바프로그래머가되기위해서는자주썬사의자바사이트에접속하여원하는클래스와메소드의형식을검색하여볼필요가있다. 3 장네트워크프로그래밍 177
o 컴퓨터시스템의사용중인포트체크다음은 ServerSocket의생성자를사용하여컴퓨터시스템에서사용중인포트번호를출력한다. 생성자 ServerSocket(port_number) 실행할때, 이미 port_number가사용중이라면, IOException이발생한다. 많은포트가사용중이라는것은컴퓨터시스템의많은응용프로그램이외부와통신을하고있다는증거이다. 프로그램소스 import java.io.*; import java.net.*; public class PortCheck { public static void main(string[] args){ ServerSocket ss=null; for(int n=1; n<=65535; n++) try { if(n%5000==0) System.out.println("n="+n); ss=new ServerSocket(n); ss.close(); catch(ioexception e) { System.out.println(n+" 번포트가사용중임 "); 실행결과 80번포트가사용중임 135번포트가사용중임 445번포트가사용중임 1028번포트가사용중임 178 자바네트워크프로그래밍
1050번포트가사용중임 1057번포트가사용중임 1059번포트가사용중임 1060번포트가사용중임 n=5000 8000번포트가사용중임 n=10000 n=15000 n=20000 n=25000 n=30000 n=35000 n=40000 n=45000 n=50000 51103번포트가사용중임 n=55000 n=60000 n=65000 o 1:1 채팅프로그램채팅프로그램은기본적으로멀티스레딩프로그램이다. 기본적으로두개의스레드가필요하다. 하나의스레드는사용자의키보드입력을받아서상대방에게전송하는일을한다. 다른스레드는상대방으로부터전송받은메시지를화면에출력하는일을한다. 1:1 채팅프로그램은다음과같이실행한다. 1 서버와클라이언트가접속하여통신소켓을생성한다. 2 생성된소켓을사용하여데이터입출력스트림을만든다. 3 스레드를생성하고, 상대방으로부터받은메시지를화면에출력시킨다. 4 메인스레드는텍스트필드에메시지를입력받아이벤트처리 ( 상대방에전송 ) 한다. 3 장네트워크프로그래밍 179
채팅서버프로그램 import java.io.*; import java.net.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.border.*; class ServerTest22 extends JFrame implements Runnable, ActionListener { JTextField tf = new JTextField(); JTextArea ta = new JTextArea(); private static final long serialversionuid = 11113L; ServerSocket ss; Socket s; DataOutputStream w; DataInputStream r; int len; public ServerTest22() { super("server Socket Chatter"); Container c = getcontentpane(); c.setlayout(new BorderLayout()); tf.setborder(new TitledBorder("Enter Message")); tf.addactionlistener(this); c.add(tf, BorderLayout.SOUTH); c.add(new JScrollPane(ta), BorderLayout.CENTER); setlocation(200, 200); setsize(400, 250); setvisible(true); 180 자바네트워크프로그래밍
try { ss = new ServerSocket(8888); s = ss.accept(); ta.append("a client connented\n"); w = new DataOutputStream(s.getOutputStream()); r = new DataInputStream(s.getInputStream()); Thread t = new Thread(this); t.start(); catch(exception e) { public void run() { while(true) { try { String tmp = r.readutf(); ta.append("msg: "+ tmp +"\n"); len = ta.gettext().length(); ta.setcaretposition(len); catch(exception ex) { public void actionperformed(actionevent e) { try { w.writeutf(tf.gettext()); ta.append("i: "+tf.gettext()+"\n"); w.flush(); catch(exception ex) { tf.settext(""); public static void main( String args[] ) { ServerTest22 fm = new ServerTest22(); 3 장네트워크프로그래밍 181
fm.addwindowlistener( new WindowAdapter() { public void windowclosing( WindowEvent e ) { System.exit( 0 ); ); 1:1 채팅클라이언트프로그램 import java.io.*; import java.net.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.border.*; class ClientTest22 extends JFrame implements Runnable, ActionListener { JTextField tf = new JTextField(); JTextArea ta = new JTextArea(); private static final long serialversionuid = 11112L; static String ip; Socket s; DataOutputStream w; DataInputStream r; int len; public ClientTest22() { super("client Chatter"); Container c = getcontentpane(); c.setlayout(new BorderLayout()); 182 자바네트워크프로그래밍
tf.setborder(new TitledBorder("Enter Message")); tf.addactionlistener(this); c.add(tf, BorderLayout.SOUTH); c.add(new JScrollPane(ta), BorderLayout.CENTER); setlocation(200, 200); setsize(400, 250); setvisible(true); try { s = new Socket(ip, 8888); ta.append("the Server connented\n"); w = new DataOutputStream(s.getOutputStream()); r = new DataInputStream(s.getInputStream()); Thread t = new Thread(this); t.start(); catch(exception e) { public void run() { while(true) { try { String tmp = r.readutf(); ta.append("msg: "+ tmp +"\n"); len = ta.gettext().length(); ta.setcaretposition(len); catch(exception ex) { public void actionperformed(actionevent e) { try { w.writeutf(tf.gettext()); 3 장네트워크프로그래밍 183
ta.append("i: "+tf.gettext()+"\n"); w.flush(); catch(exception ex) { tf.settext(""); public static void main( String args[] ) { if(args.length==0) ip="127.0.0.1"; else ip=args[0]; ClientTest22 fm = new ClientTest22(); fm.addwindowlistener( new WindowAdapter() { public void windowclosing( WindowEvent e ) { System.exit( 0 ); ); 실행결과 o Vect or 사용멀티캐스팅예제일반적인다자간채팅프로그램은하나의멀티캐스팅서버와다수의채팅클라이언트가필요하다. 멀티캐스팅서버는클라이언트에게서받은메시지를모든클라이언트에게전송하는일을한다. 한사용자가입력한메시지를등록된모든클라이언트에게전송하는역할을 184 자바네트워크프로그래밍
수행하는것이바로멀티캐스팅서버이다. 메시지를멀티캐스팅하기위해서는모든사용자에대한접속정보 ( 접속에사용된소켓정보 ) 를저장하고있어야한다. 모든클라이언트의정보를저장하는데사용하는것이바로 java.util.vector 이다. 다음예제프로그램은 Vect or 를사용하여멀티캐스팅하는것을보여준다. (Vector를사용하여등록된모든사용자에게 Date 정보를전송 ) 다음예제프로그램의역할을정리하면다음과같다. 1 서버소켓과벡터를생성한다. 2 자식스레드는 1초마다벡터의모든클라이언트에게 Date객체전송한다. 3 메인스레드는클라이언트의접속정보를벡터에저장하는무한루프를실행한다. DateServer 프로그램 import java.io.*; import java.net.*; import java.util.*; public class DateServer extends Thread { Vector vec = null; public DateServer(Vector vec){ this.vec = vec; public void run(){ while(true) { Date d=new Date(); Enumeration en = vec.elements(); ObjectOutputStream oos=null; try { while(en.hasmoreelements()){ oos =(ObjectOutputStream) en.nextelement(); oos.writeobject(d); 3 장네트워크프로그래밍 185
oos.flush(); Thread.sleep(1000); catch(exception e){ // System.out.println(e); // (1) // vec.removeelement(oos); // (2) public static void main(string[] args) throws Exception { int port =7788; ServerSocket ss = new ServerSocket(port); Vector<ObjectOutputStream> vec = new Vector<ObjectOutputStream>(5); new DateServer(vec).start(); System.out.println("Server Ready"); while(true){ Socket s = ss.accept(); System.out.println("Accepted from "+s); vec.addelement( new ObjectOutputStream(s.getOutputStream())); DateClient 프로그램 import java.io.*; import java.net.*; import java.util.*; public class DateClient { public static void main(string[] args)throws Exception { int port = 7788; Socket s=null; if(args.length>0) s = new Socket(args[0], port); 186 자바네트워크프로그래밍
else s = new Socket("127.0.0.1",port); ObjectInputStream ois = new ObjectInputStream(s.getInputStream()); while(true){ Date d=(date) ois.readobject(); System.out.println(d.toString()); 실행결과 1 서버실행결과 Server Ready Accepted from Socket[addr=/127.0.0.1,port=1870,localport=7788] 2 클라이언트실행결과 Sat Sep 22 00:14:58 KST 2007 Sat Sep 22 00:14:59 KST 2007 Sat Sep 22 00:15:00 KST 2007 Sat Sep 22 00:15:01 KST 2007... 여러사용자가네트워크로연결되어메시지를수신받는경우, 통신장애로인한프로그램실행에문제가발생할수있다. 다시말하면백터에저장된사용자의컴퓨터또는통신선로에장애가언제든지발생할수있다. 이러한네트워크장애를고려하지않고프로그램을하는경우, 프로그램실행오류가발생할수있다. 다음실습을실행하여보자. [ 프로그램실습 ] 1 위의실행결과가나오는중간에, 클라이언트프로그램을강제로중단시킨다. 그리고클라이언트를다시실행시키면, 실행결과가어떻게되겠는가? 잘동작하지않는다. 3 장네트워크프로그래밍 187
2 서버를중단시키고주석처리된 (1) 라인의주석표시를삭제하고, 1번을다시실행하여보시오. 잘동작하지않는이유를설명할수있는가? 3 서버를중단시키고주석처리된 (1), (2) 라인의주석표시를삭제하고, 1번을다시실행하여보시오. 중단된클라이언트의정보를벡터에서삭제하므로더이상 Exception 이발생하지않으므로, 정상동작한다. 3.6 채팅예제프로그램 채팅서버 (ChatS2) 를먼저실행시키고, 채팅클라이언트 (ChatC2) 에서버의 IP와대화명을입력하면서버와접속된다. 채팅서버는클라이언트가접속할때마다, 스레드객체를생성하여실행시킨다. 접속할때마다생성되는스레드객체 (Chatter 객체 ) 는통신소켓에대한정보를담고있다. 스레드객체는모두벡터에저장되어, 채팅메시지를 broadcasting하는데사용된다. 서버 : ChatS2 Chat Client Chat Client... 클라이언트가서버에접속하면, 가장먼저채팅에사용할이름을전송한다. 이는채팅 ID로사용되므로다른사람과는반드시구분되어야한다. 1 채팅시작 -채팅서버는 "/p"+ 이름을모든클라이언트에게전송하여새로운참가자를등록하도록한다. 서버는관련스레드객체를 Vector 에저장해서, 메시지를 broadcasting 하도록한다. 188 자바네트워크프로그래밍
2 채팅 - 채팅서버는클라이언트의메시지를 [ 이름 ]+ 메시지로변경하여모든클라이언트에게 broadcasting 한다. 클라이언트는이메시지를받아서, 텍스트영역에그대로출력한다. 3 채팅종료 - 종료버튼을누르면, 클라이언트는서버에게 /q" 를전송한다. 채팅서버는이를 "/q"+ 이름으로변경하여모든클라이언트에게전송한다. 클라이언트채팅참가자가 떠났음을알리는메시지를출력한다. 채팅서버는 Vector( 변수명 : vc) 에서, 관련스 레드객체를삭제한다. 클라이언트 이름전송 ( 시작 ) 메시지 /q ( 종료 ) 서버 /p + 이름 ( 등록 ) [ 이름 ]+ 메시지 /q + 이름 ( 삭제 ) 모든클라이언트 채팅서버 : ChatS2.java import java.io.*; import java.net.*; import java.util.*; public class ChatS2 { private ServerSocket ss; private Socket soc; private Vector<Chatter> vc = new Vector<Chatter>(); public ChatS2(){ try{ ss = new ServerSocket(12345); System.out.println("Server Ready..."); 3 장네트워크프로그래밍 189
catch(ioexception ee){ System.err.println(" 포트가이미사용중 "); System.exit(1); public void execute(){ while(true){ try{ soc = ss.accept(); Chatter chat = new Chatter(soc); chat.start(); System.out.println(" 접속자 :" + chat.getnickname() + " : " + soc); Chatter user=null; for(int i = 0; i < vc.size(); i++){ user = vc.elementat(i); user.getout().write(("/p" + chat.getnickname() + "\n")); String message = "*** " + chat.getnickname() + " 님께서입장하셨습니다. ***"; user.getout().write((message + "\n")); user.getout().flush(); vc.add(chat); for(int i = 0; i < vc.size(); i++){ user = vc.elementat(i); chat.getout().write(("/p" + user.getnickname() + "\n")); chat.getout().flush(); catch(ioexception ee){ System.err.println("accept에러 : " + ee); 190 자바네트워크프로그래밍
class Chatter extends Thread{ private Socket socket; private OutputStreamWriter out; private BufferedReader in; private String nickname; public Chatter(Socket s){ socket = s; try{ out = new OutputStreamWriter(socket.getOutputStream()); in = new BufferedReader(new InputStreamReader( socket.getinputstream())); nickname = in.readline(); catch(ioexception ee){ public void run(){ String str = null; while(true){ try{ str = in.readline(); catch(ioexception ee){ if(str == null){ continue; if(str.charat(0) == '/'){ if(str.charat(1) == 'q'){ for(int i = 0; i < vc.size(); i++){ Chatter user = vc.elementat(i); if(user.getnickname().equals(nickname)){ vc.removeelementat(i); break; for(int i = 0; i < vc.size(); i++){ Chatter user = vc.elementat(i); 3 장네트워크프로그래밍 191
String sss = "/q" + nickname; try{ user.getout().write((sss + "\n")); user.getout().flush(); catch(ioexception ee){ break; else{ for(int i = 0; i < vc.size(); i++){ Chatter user = vc.elementat(i); String message = "["+nickname + "] " + str; try{ // System.out.println(i+":"+vc.size()+"mesg:"+message); user.getout().write((message + "\n")); user.getout().flush(); catch(ioexception ee){ // insert some code for fault-tolerance 1 // public String getnickname(){ return nickname; public OutputStreamWriter getout(){ return out; public static void main(string[] ar){ ChatS2 cs =new ChatS2(); cs.execute(); 192 자바네트워크프로그래밍
채팅클라이언트 : ChatC2.java import java.io.*; import java.net.*; import java.awt.*; import java.awt.event.*; class ChatC2 extends Frame implements ActionListener, Runnable{ final static long serialversionuid=12348938; private Label iplb = new Label(" 서버 IP :", Label.RIGHT); private TextField iptf = new TextField("211.48.44.xx"); private Label namelb = new Label(" 대화명 : ", Label.RIGHT); private TextField nametf = new TextField(); private TextArea viewta = new TextArea(); private Label talklb = new Label(" 대화 : ", Label.RIGHT); private TextField talktf = new TextField(); private Button disconnbt = new Button(" 끝내기 "); private Socket soc; private OutputStream out; private BufferedReader in; private Thread currentth; public ChatC2(String str){ super(str); this.init(); this.start(); this.pack(); this.setvisible(true); public void init(){ this.setlayout(new BorderLayout()); Panel p = new Panel(new BorderLayout()); // 중앙부 Panel p1 = new Panel(new BorderLayout());// 우측부 3 장네트워크프로그래밍 193
this.add("center", p); this.add("east", p1); Panel p_1 = new Panel(new GridLayout(1,4)); p_1.add(iplb); p_1.add(iptf); p_1.add(namelb); p_1.add( nametf); p.add("north", p_1); viewta.setfont(new Font("Serif",Font.BOLD,14)); p.add("center", viewta); viewta.seteditable(false); Panel p_2 = new Panel(new BorderLayout()); p_2.add("west", talklb); p_2.add("center", talktf); p_2.add("east", disconnbt); p.add("south", p_2); public void start(){ nametf.addactionlistener(this); talktf.addactionlistener(this); disconnbt.addactionlistener(this); public void actionperformed(actionevent e){ if(e.getsource() == nametf ) { String str = nametf.gettext().trim(); if(str == null str.length() == 0){ nametf.settext(""); nametf.requestfocus(); viewta.settext(" 이름입력하세요."); return; try{ InetAddress ia = InetAddress.getByName(iptf.getText()); 194 자바네트워크프로그래밍
soc = new Socket(ia, 12345); out = soc.getoutputstream(); in = new BufferedReader(new InputStreamReader(soc.getInputStream())); out.write((str + "\n").getbytes()); currentth = new Thread(this); currentth.start(); catch(ioexception ee){ viewta.settext(" 서버와통신불가능 "); return; if(e.getsource() == talktf ) { String message = talktf.gettext().trim(); if(message == null message.length() == 0){ talktf.settext(""); talktf.requestfocus(); return; try{ out.write((message + "\n").getbytes()); catch(ioexception ee){ talktf.settext(""); talktf.requestfocus(); if(e.getsource() == disconnbt){ currentth.interrupt(); try{ out.write(("/q" + "\n").getbytes()); catch(ioexception ee){ System.exit(0); public void run(){ nametf.setenabled(false); 3 장네트워크프로그래밍 195
talktf.requestfocus(); viewta.settext("*** 반갑습니다. ***\n"); String str = null; while(true){ try{ str = in.readline(); catch(ioexception ee){ if(str == null) continue; if(str.charat(0) == '/'){ if(str.charat(1) == 'q'){ String name = str.substring(2); viewta.append("# " + name + " 님퇴장하셨습니다. \n"); else viewta.append(str + "\n"); public static void main(string[] ar){ new ChatC2(" 채팅클라이언트 "); 실행결과 1 서버의도스창 196 자바네트워크프로그래밍
2 채팅클라이언트 o 네트워크장애에견디는코드추가물리적으로떨어진사람들간에채팅이이루어지면, 네트워크연결이불안해지는수가있다. 특히채팅참가자가늘어나면소켓연결이불안해지고채팅서버가제대로동작하지못할가능성이높아진다. 이러한네트워크장애에대비하기위해서다음기능을채팅서버에추가한다. 소켓연결이나빠진채팅참가자를 Vector 에서삭제하고, /q+ 이름 을 broadcasting 한다. 그리고연결이나빠진채팅참가자를담당했던스레드를중단시키는코드를추가한다. [ 프로그래밍실습 ] 1 앞의채팅예제에서, 채팅클라이언트를강제로종료시킨다. 2 그이후채팅이정상적으로이루어지는지살펴본다. 3 프로그램을중단시키고, 다음코드를서버코드에추가시킨다.(1 부분 ) 4 채팅예제를다시실행시키고, 채팅클라이언트를강제로종료시키고장애가발생하는지알아본다. 5 추가된소스코드를분석해본다. ChatS2.java 의 1 부분에추가할코드 // added for fault tolerance System.out.println("Error in "+user.getnickname()); 3 장네트워크프로그래밍 197
vc.remove(user); System.out.println(user.getNickName()+" removed" +"vc.size() ="+vc.size()); String del = "/q"+user.getnickname()+"\n"; System.out.print(del); try { for(int n=0; n<vc.size(); n++) { Chatter tmp = vc.elementat(n); System.out.println("Nick="+tmp.getNickName()); tmp.getout().write(del); tmp.getout().flush(); catch(ioexception exp){ System.out.println("another exp "+exp); System.out.println(nickname+" : "+user.getnickname()); if(nickname.equals(user.getnickname())) this.suspend(); // 또는 return(); // end code for fault tolerance o 채팅자명단나열, 밀담전송기능추가 채팅클라이언트에채팅자명단과채팅인원을표시하고, 전송하는밀담기능을기능을추가하였다. 원하는사람에게만메시지를 1 채팅자명단과채팅인원표시기능추가 - 채팅서버에추가된기능은없다. 기존과같이채팅자가새로들어오면 /p+ 이름 을채팅자가나가면 q+ 이름 을모든클라이언트에게전송한다. - 채팅클라이언트에서새로들어온사람의이름을 List에추가하고, 인원수를표시한다. 대화를떠난사람을 List에서삭제하고, 표시인원수를감소시킨다. 2 밀담전송기능 - 밀담전송을나타내는 Radio 버튼이표시되었다면, "/w" + 이름 + message 를채팅서버에전송한다. - 채팅서버는 [ 이름 ( 밀담 )] + message를 1명에게만전송한다. 198 자바네트워크프로그래밍
3 통신불량 - 통신불량상태인사용자가있다면, 서버는 /x + 이름을모든클라이언트에게전송한다. 통신불량상태인사용자의정보를 Vector 에서삭제하고, 이사용자 ( 통신불량상태 ) 의메시지를 broadcasting 하는스레드의실행을중단시킨다. 이름전송 ( 시작 ) /p + 이름 ( 등록 ) 클라이언트 /w 이름 : 메시지 ( 밀담전송 ) ( 통신불량 ) 서버 [ 밀담 ] 메시지 (1 명만 ) /x + 이름 ( 삭제 ) 모든클라이언트 채팅서버 : ChatS3.java import java.io.*; import java.net.*; import java.util.*; public class ChatS3 { private ServerSocket ss; private Socket soc; private Vector<Chatter> vc = new Vector<Chatter>(); public ChatS3(){ try{ ss = new ServerSocket(15001); System.out.println("Server Ready..."); catch(ioexception ee){ System.err.println(" 포트가이미사용중 "); System.exit(1); 3 장네트워크프로그래밍 199
public void execute(){ while(true){ try{ soc = ss.accept(); Chatter chat = new Chatter(soc); chat.start(); System.out.println(" 접속자 :" + chat.getnickname() + " : " + soc); Chatter user=null; for(int i = 0; i < vc.size(); i++){ user = vc.elementat(i); user.getout().write(("/p" + chat.getnickname() + "\n")); String message = "*** " + chat.getnickname() + " 님께서입장하셨습니다. ***"; user.getout().write((message + "\n")); user.getout().flush(); vc.add(chat); for(int i = 0; i < vc.size(); i++){ user = vc.elementat(i); chat.getout().write(("/p" + user.getnickname() + "\n")); chat.getout().flush(); catch(ioexception ee){ System.err.println("accept에러 : " + ee); class Chatter extends Thread{ private Socket socket; private OutputStreamWriter out; 200 자바네트워크프로그래밍
private BufferedReader in; private String nickname; public Chatter(Socket s){ socket = s; try{ out = new OutputStreamWriter(socket.getOutputStream()); in = new BufferedReader(new InputStreamReader( socket.getinputstream())); nickname = in.readline(); catch(ioexception ee){ public void run(){ String str = null; while(true){ try{ str = in.readline(); catch(ioexception ee){ if(str == null){ continue; if(str.charat(0) == '/'){ if(str.charat(1) == 'w'){ String name = str.substring(2, str.indexof(":")); String msg = str.substring(str.indexof(":") + 1); for(int i = 0; i < vc.size(); i++){ Chatter user = vc.elementat(i); if(user.getnickname().equals(name)){ String secret = "["+nickname+ "( 밀담 )] " + msg; try{ user.getout().write((secret +"\n")); user.getout().flush(); out.write((secret +"\n")); out.flush(); 3 장네트워크프로그래밍 201
catch(ioexception ee){ else if(str.charat(1) == 'q'){ for(int i = 0; i < vc.size(); i++){ Chatter user = vc.elementat(i); if(user.getnickname().equals(nickname)){ vc.removeelementat(i); break; for(int i = 0; i < vc.size(); i++){ Chatter user = vc.elementat(i); String sss = "/q" + nickname; try{ user.getout().write((sss + "\n")); user.getout().flush(); catch(ioexception ee){ break; else{ for(int i = 0; i < vc.size(); i++){ Chatter user = vc.elementat(i); String message = "["+nickname + "] " + str; try{ System.out.println(i+":"+vc.size()+"mesg:"+message); user.getout().write((message + "\n")); user.getout().flush(); catch(ioexception ee){ // added for fault tolerance 202 자바네트워크프로그래밍
System.out.println("Error in "+user.getnickname()); vc.remove(user); System.out.println(user.getNickName()+" removed" +"vc.size() ="+vc.size()); String del = "/x"+user.getnickname()+"\n"; System.out.print(del); try { for(int n=0; n<vc.size(); n++) { Chatter tmp = vc.elementat(n); System.out.println("Nick="+tmp.getNickName()); tmp.getout().write(del); tmp.getout().flush(); catch(ioexception exp){ System.out.println("another exp "+exp); System.out.println(nickname+" : "+user.getnickname()); if(nickname.equals(user.getnickname())) return; // end code for fault tolerance public String getnickname(){ return nickname; public OutputStreamWriter getout(){ return out; public static void main(string[] ar){ ChatS3 cs =new ChatS3(); cs.execute(); 3 장네트워크프로그래밍 203
채팅클라이언트 : ChatC3.java import java.io.*; import java.net.*; import java.awt.*; import java.awt.event.*; class ChatC3 extends Frame implements ActionListener, Runnable{ final static long serialversionuid=12348938; private Label iplb = new Label(" 서버 IP :", Label.RIGHT); private TextField iptf = new TextField("127.0.0.1"); private Label namelb = new Label(" 대화명 : ", Label.RIGHT); private TextField nametf = new TextField(); private TextArea viewta = new TextArea(); private Label talklb = new Label(" 대화 : ", Label.RIGHT); private TextField talktf = new TextField(); private Label inwonlb = new Label(" 인원 : ", Label.RIGHT); private Label inwonlb1 = new Label("0", Label.CENTER); private Label inwonlb2 = new Label(" 명 ", Label.LEFT); private List inwonli = new List(5, false); private CheckboxGroup cg = new CheckboxGroup(); private Checkbox hidecb = new Checkbox(" 밀담전송 ", cg, false); private Checkbox voidcb = new Checkbox(" 밀담해제 ", cg, true); private Button disconnbt = new Button(" 끝내기 "); private Socket soc; private OutputStream out; private BufferedReader in; 204 자바네트워크프로그래밍
private Thread currentth; public ChatC3(String str){ super(str); set_screen(); nametf.addactionlistener(this); talktf.addactionlistener(this); disconnbt.addactionlistener(this); setsize(500,250); setvisible(true); public void set_screen(){ this.setlayout(new BorderLayout()); Panel p = new Panel(new BorderLayout()); Panel p1 = new Panel(new BorderLayout()); this.add("center", p); this.add("east", p1); Panel p_1 = new Panel(new GridLayout(1,4)); p_1.add(iplb); p_1.add(iptf); p_1.add(namelb); p_1.add( nametf); p.add("north", p_1); viewta.setfont(new Font("Serif",Font.BOLD,14)); p.add("center", viewta); viewta.seteditable(false); Panel p_2 = new Panel(new BorderLayout()); p_2.add("west", talklb); p_2.add("center", talktf); 3 장네트워크프로그래밍 205
p_2.add("east", disconnbt); p.add("south", p_2); Panel p1_1 = new Panel(new FlowLayout()); p1_1.add(inwonlb); p1_1.add(inwonlb1); p1_1.add(inwonlb2); p1.add("north", p1_1); p1.add("center", inwonli); Panel p1_2 = new Panel(new BorderLayout()); Panel p1_2_1 = new Panel(new GridLayout(2, 1)); p1_2_1.add(hidecb); p1_2_1.add(voidcb); p1_2.add("center", p1_2_1); Panel p1_2_2 = new Panel(new FlowLayout(FlowLayout.RIGHT)); p1_2_2.add(disconnbt); p1_2.add("south", p1_2_2); p1.add("south", p1_2); public void actionperformed(actionevent e){ if(e.getsource() == nametf){ String str = nametf.gettext().trim(); if(str == null str.length() == 0){ nametf.settext(""); nametf.requestfocus(); viewta.settext(" 이름을입력하세요."); return; try{ InetAddress ia = InetAddress.getByName(iptf.getText()); soc = new Socket(ia, 15001); out = soc.getoutputstream(); 206 자바네트워크프로그래밍
in = new BufferedReader(new InputStreamReader (soc.getinputstream())); out.write((str + "\n").getbytes()); currentth = new Thread(this); currentth.start(); catch(ioexception ee){ viewta.settext(" 서버와통신불가능 "); return; if(e.getsource() == talktf ){ String message = talktf.gettext().trim(); if(message == null message.length() == 0){ talktf.settext(""); talktf.requestfocus(); return; if(hidecb.getstate() == true){ String user = (inwonli.getselecteditem()).trim(); String mymessage = "/w" + user + ":" + message; try{ out.write((mymessage + "\n").getbytes()); catch(ioexception ee){ else{ try{ out.write((message + "\n").getbytes()); catch(ioexception ee){ talktf.settext(""); talktf.requestfocus(); 3 장네트워크프로그래밍 207
else if(e.getsource() == disconnbt){ currentth.interrupt(); try{ out.write(("/q" + "\n").getbytes()); catch(ioexception ee){ System.exit(0); public void run(){ nametf.setenabled(false); talktf.requestfocus(); viewta.settext("*** 반갑습니다. ***\n"); String str = null; while(true){ try{ str = in.readline(); catch(ioexception ee){ if(str == null){ continue; if(str.charat(0) == '/'){ if(str.charat(1) == 'p'){ String name = str.substring(2); name = name.trim(); inwonli.add(name); int xx = Integer.parseInt(inwonlb1.getText().trim()); xx++; inwonlb1.settext(string.valueof(xx)); else if(str.charat(1) == 'q' str.charat(1)=='x'){ 208 자바네트워크프로그래밍
String name = str.substring(2); for(int i = 0; i < inwonli.getitemcount(); i++){ String tmp1 = inwonli.getitem(i); tmp1 = tmp1.trim(); if(tmp1.equals(name)){ inwonli.remove(i); break; int xx = Integer.parseInt(inwonlb1.getText().trim()); xx--; inwonlb1.settext(string.valueof(xx)); if(str.charat(1)=='q') viewta.append("# " + name + " 님퇴장하셨습니다. \n"); else viewta.append("# " + name + " 님통신불통입니다. \n"); else{ viewta.append(str + "\n"); public static void main(string[] ar){ new ChatC3(" 채팅클라이언트 "); 3 장네트워크프로그래밍 209
실행결과 [Home Work] - 대화명입력시에기존의채팅참가자와동일한이름이있을경우, 적절한초치를취하는기능을추가하시오. - 밀담전송기능선택하였을때, 적절한상대방이선택되었는지체크하는기능을추가하시오. - 기타필요하다고기능을추가하시오. ( 파일송수신기능, 통신장애기능보완등 ) 210 자바네트워크프로그래밍
o Fi l t ered St ream 소개입출력스트림을사용하여데이터를파일에저장하거나소켓으로전송할수있다. 이러한일반데이터스트림은내용이암호화되어있지않거나데이터체크기능이없다. Filtered Stream 을사용하면, 기존의데이터스트림을암호화하거나데이터체크기능을추가할수있다. 데이터목적지 일반 OutputStream FilteredOutputStream ( 스트림변형, 기능추가 ) 데이터소스 일반 InputStream FilteredInputStream ( 스트림원형복구 ) 예를들어, 데이터를파일에저장할때시저방식으로암호화 ( 시저방식암호화에대한설명은인터넷검색사이트참조 ) 하여저장하고자한다면 FilteredOutputStream을사용하여저장할문자에변형을가한다음에 FileOutputStream 을사용하여저장한다. 이렇게저장된데이터파일을읽을때에는 FileInputStreamd 으로읽어온데이터를 FilteredInputStream을사용하여복구한다. o Fi l t ered St ream을사용하는코딩절차 1 FilterInputStream과 FilterOutputStream 을상속받은 (extends) 클래스를정의한다. 2 생성자에서 super() 메소드를사용하여기존의 InputStream 또는 OutputStream과연결한다. 3 read(), write() 메소드에서원하는기능을추가하여새롭게정의 (override) 한다. 4 기타필요한메소드를새로정의 (override) 한다. 시저암호화 -로마시대의시저 ( 일명카이사르 ) 가개발하였다고전해짐. -알파벳을주어진키 ( 정수 ) 만큼이동하여, 암호화시키는방법. ( 예 ) Hello라는단어를암호화 ( 키값은 3) 하면 Khoor이됨. 3 장네트워크프로그래밍 211
다음은 Filtered 스트림생성클래스이다. Stream 을사용하여일반입출력스트림을시저암호화시키는입출력 SecInStream, SecOutStream 클래스의소스코드 import java.io.*; class SecInStream extends FilterInputStream { private byte key; public SecInStream(InputStream in, byte key) { super(in); this.key = key; public int read() throws IOException { int b = in.read(); b = (byte)(b-key); return b; public int read(byte b[], int off, int len) throws IOException { int numread = in.read(b, off, len); if (numread <= 0) return 0; for(int i = 0; i < numread; i++) b[off+i]=(byte)(b[off+i]-key); return numread; class SecOutStream extends FilterOutputStream { private byte key; public SecOutStream(OutputStream out, byte key) { super(out); this.key = key; public void write(int b) throws IOException { b=(byte)(b+key); out.write(b); 212 자바네트워크프로그래밍
public void write(string s) throws IOException { byte[] data = s.getbytes(); for(int cnt=0; cnt<data.length; cnt++) write(data[cnt]); 앞에서정의한 SecOutStream, 같다. SecInStream 을테스트하는프로그램을작성하면다음과 FilterStreamTest.java 의소스코드 import java.io.*; public class FilterStreamTest { public static void main(string[] args) throws IOException { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); System.out.print("key in:"); String input=in.readline(); FileOutputStream fo = new FileOutputStream("secret.txt"); SecOutStream so = new SecOutStream(fo,(byte)1); so.write(input); so.close(); File f = new File("secret.txt"); long size = f.length(); byte[] data = new byte[(int)size]; FileInputStream is = new FileInputStream(f); SecInStream si = new SecInStream(is, (byte)1); si.read(data, 0, data.length); System.out.println("recovered data: "+new String(data)); 3 장네트워크프로그래밍 213
실행결과 key in: hello <-- hello를입력하였음. recovered data: Hello secret.txt에는 ifmmp가저장되었음을확인할수있다. 벳의다음알파벳으로변환되었음을알수있다. 즉키가 1 이므로입력된알파 o Fi l eredst ream을사용한소켓보안채팅소켓을통한채팅은통신내용이통신라인을통해전달되므로, 도청의위험이없지않다. 시저암호화를구현한 FileredStream 을사용하는소켓을사용한다면도청의위험을다소줄일수있다. 이를위한새로운 Socket, ServerSocket 클래스를정의하면다음과같다. SecSocket.java import java.io.*; import java.net.*; class SecSocket extends Socket { private byte key; private InputStream in = null; private OutputStream out = null; public SecSocket(byte key) throws IOException { super(); this.key = key; public SecSocket(String host, int port, byte key) throws IOException { super(host, port); this.key = key; 214 자바네트워크프로그래밍
public InputStream getinputstream() throws IOException { in = new SecInStream(super.getInputStream(), key); return in; public OutputStream getoutputstream() throws IOException { out = new SecOutStream(super.getOutputStream(), key); return out; SecServerSocket.java import java.io.*; import java.net.*; class SecServerSocket extends ServerSocket { private final byte key; public SecServerSocket(int port, byte key) throws IOException { super(port); this.key = key; public SecSocket accept() throws IOException { SecSocket s = new SecSocket(key); implaccept(s); // implaccept() 는 ServerSocket을상속한클래스에서만사용가능 // accept() 로생성된소켓이클라이언트와연동하도록만들어준다. return s; [ 프로그래밍과제 ] 위의안전소켓, 안전서버소켓을사용하여앞에서작성한 1:1 채팅프로그램을다시작성하여보시오. 3 장네트워크프로그래밍 215
3.7 UDP(User Datagram Protocol) 자바의 Socket, ServerSocket 클래스는 TCP 전송방식을지원하며, DatagramSocket 은 UDP 전송방식을지원한다. TCP 소켓은상호연결된상태를유지하며통신을하므로높은신뢰성을유지한다. 그러나적은데이터의전송에는오버헤드 (overhead) 가너무많다. UDP는 TCP에비해신뢰성은다소떨어지지만연결을설정하지않으며, 네트워크트래픽에따라각패킷이다른경로를사용할수있으므로네트워크사용효율이높다고하겠다. 비록 UDP가신뢰성이나전달순서를보장하지는않지만일반네트워크환경에서데이터가손실되거나전달순서가잘못될확률은매우적다. o Dat agr ampacket 자바에서 UDP를사용하기위해서는 DatagramPacket, DatagramSocket 클래스를사용하여야한다. UDP에서는데이터를패킷단위로전송하므로사용자는먼저데이터를패킷단위로나누어담아야한다. 자바의 DatagramPacket 클래스가이러한작업을가능하도록해준다. 즉자바프로그래머는 DatagramPacket 클래스를사용하여데이터를패킷으로만든후에 DatagramSocket 을통해전송하게된다. DatagramPacket 클래스의생성자는다음과같다. o 생성자 1 DatagramPacket(byte[] buffer, int size) - 주어진크기의버퍼를가진수신용 DatagramPacket 을생성. 2 DatagramPacket(byte[] buffer, int size, InetAddress ia, int port) - 주어진크기의버퍼, IP주소, 포트번호를가진송신용 DatagramPacket 을생성. 3 DatagramPacket(byte[] buffer, int offset, int size, InetAddress ia, int port) - 주어진시작위치및크기의버퍼, IP 주소, 포트번호를가진송신용패킷생성. DatagramPacket 클래스에서지원되는메소드를정리하면다음과같다. 216 자바네트워크프로그래밍
[ 표 3-6] DatagramPacket 의메소드 메소드 설명 InetAddress getaddress() 데이터를전송할컴퓨터주소반환 byte[] getdata() 패킷데이터를바이트배열로반환 int getlength() 패킷의바이트수를반환 int getport() 포트번호를반환 int getoffset() 데이터의 offset 반환 void setaddress(inetaddress ia) 주어진인터넷주소로설정 void setdata(byte buf[]) buf의내용을패킷의데이터로설정 void setlength(int size) 패킷의크기를바이트단위 size로설정 void setport(int port) 패킷의포트번호를설정 o 데이터그램패킷정보출력예제 DatagramPacketShow.java import java.net.*; public class DatagramPacketShow { public static void printinfo(datagrampacket dp) { System.out.println("InetAddress = " + dp.getaddress()); System.out.println("soc-address = " + dp.getsocketaddress()); System.out.println("data = " + new String(dp.getData())); System.out.println("data length = " + dp.getlength()); System.out.println("data offset = " + dp.getoffset()); System.out.println("port = " + dp.getport());dp.getsocketaddress(); public static void main(string[] ar){ byte[] data = " 데이터그램패킷, 반가와요.".getBytes(); DatagramPacket dp = null; InetAddress ia = null; InetSocketAddress isa = null; try{ 3 장네트워크프로그래밍 217
ia = InetAddress.getByName("www.hansung.ac.kr"); isa = new InetSocketAddress(ia,7788); catch(unknownhostexception ee){ try{ // 다음 2줄중에한줄을선택하여 ( 주석변경필요 ) 실행 // dp = new DatagramPacket(data, data.length, isa); dp = new DatagramPacket(data, 7, data.length-7, isa) catch(socketexception ee){ printinfo(dp); dp.setdata("hello! Datagram Packet.".getBytes()); dp.setport(9909); try { dp.setaddress(inetaddress.getbyname("www.naver.com")); catch(unknownhostexception ee){ printinfo(dp); 실행결과 InetAddress = www.hansung.ac.kr/128.134.165.1 soc-address = www.hansung.ac.kr/128.134.165.1:7788 data = 데이터그램패킷, 반가와요. data length = 20 data offset = 7 port = 7788 InetAddress = www.naver.com/222.122.84.250 soc-address = www.naver.com/222.122.84.250:9909 data = Hello! Datagram Packet. data length = 23 data offset = 0 port = 9909 218 자바네트워크프로그래밍
o Dat agr amsocket DatagramSocket 클래스는데이터그램패킷을전송하거나수신하기위한소켓기능을제공하는클래스이다. DatagramSocket 클래스는다음과같은생성자를가진다. o 생성자 1 DatagramSocket() - 사용가능한포트중하나를사용하는데이터그램소켓을생성한다. 2 DatagramSocket(int port) - 주어진포트를사용하는데이터그램소켓을생성한다. 3 DatagramSocket(int port, InetAddress ia) - 주어진포트와 IP 주소를사용하는데이터그램소켓을생성한다. 다음은 DatagramSocket 클래스에서지원되는주요메소드를정리한것이다. [ 표 3-7] DatagramSocket 클래스의주요메소드 메소드 설명 void receive(datagrampacket o) 데이터그램패킷을전송받는다. voic bind(socketaddress addr) throws SocketException 특정주소와포트에바인딩한다. void connect(inetaddress addr, int port) IllegalArgumentExeption, SecurityException 특정주소와포트에연결한다. void connect(socketaddress addr) throws SocketException 특정주소와포트에연결한다. void disconnect() 연결종료 void setsotimeout(int timeout) 최대대기시간 (timeout) 밀리초단위설정 void setsendbuffersize(int size) 전송버퍼의크기설정 void send(datagrampacket p) 데이터그램패킷을전송한다. int getlocalport() 소켓의지역포트를반환한다. int getport() 소켓의원격포트를반환한다. InetAddress getinetaddress() 소켓이연결된 IP 주소를반환한다. InetAddress getlocaladdress() 소켓의지역 IP 주소를반환한다. voic close() 데이터그램소켓을닫는다. 3 장네트워크프로그래밍 219
o 데이터그램소켓정보출력예제 DatagramSocketShow.java import java.net.*; public class DatagramSocketShow { public static void printinfo(datagramsocket ds){ try { System.out.println("broadcast = " + ds.getbroadcast()); //broadcast 지원여부 System.out.println("buffer Size = " + ds.getreceivebuffersize()); System.out.println("send buffer = " + ds.getsendbuffersize()); System.out.println("timeout = " + ds.getsotimeout()); System.out.println("traffic = " + ds.gettrafficclass()); catch(socketexception e) { public static void main(string[] ar){ InetSocketAddress isa = null; try{ isa = new InetSocketAddress( InetAddress.getByName("www.hansung.ac.kr"),7788); catch(unknownhostexception ee){ DatagramSocket ds=null; try{ ds = new DatagramSocket(); ds.bind(isa); catch(socketexception ee){ printinfo(ds); try{ // 다음줄을주석처리하고실행해보기바람. ds.setsotimeout(2000); ds.setsendbuffersize(10240); catch(socketexception ee){ 220 자바네트워크프로그래밍
byte[] bb = new byte[1024]; DatagramPacket dp = new DatagramPacket(bb, bb.length); try{ ds.receive(dp); catch(java.io.ioexception ee){ System.out.println("time out expired."); byte[] bb1 = "Sample Data".getBytes(); DatagramPacket dpdp = null; try{ dpdp = new DatagramPacket(bb1, bb1.length, InetAddress.getByName("www.hansung.ac.kr"),80); catch(unknownhostexception ee){ try{ ds.send(dpdp); catch(java.io.ioexception ee){ System.out.println("send error: "+ee); printinfo(ds); 실행결과 broadcast = true buffer Size = 8192 send buffer = 8192 timeout = 0 traffic = 0 time out expired. broadcast = true buffer Size = 8192 3 장네트워크프로그래밍 221
send buffer = 10240 timeout = 2000 traffic = 0 다음은데이터를 UDP 방식으로송수신하는자바프로그램이다. UDPSender 프로그램은사용자로부터목적지 IP나도메인이름을입력받고, 메시지를목적지호스트에전송한다. 사용자가 end" 라는문자열을입력하면종료된다. UDPReceiver 프로그램은수신된메시지를출력한다 ( 문자열 ServerEnd 이수신되면종료 ). 예제프로그램 1) 패킷수신프로그램 import java.net.*; public class UDPReceiver { public static void main(string[] args){ String str=null; DatagramSocket ds=null; DatagramPacket dp=null; byte[] buf=null; try { ds = new DatagramSocket(4488); catch(socketexception ee) {System.out.println(ee); while(true){ buf=new byte[1024]; dp=new DatagramPacket(buf, buf.length); try { ds.receive(dp); catch(exception e) {System.out.println(e); str = new String(dp.getData()); // 다음줄을주석처리하고실행해보자. str = str.trim(); 222 자바네트워크프로그래밍
System.out.println(str); if(str.equals("serverend")) { System.out.println("Sever is dead"); break; 2) 패킷전송프로그램 import java.net.*; import java.io.*; class UDPSender { public static void main(string[] args) throws Exception { String input = ""; int port = 4488; BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Enter IP or Domain Name:"); String ip = in.readline(); DatagramSocket ds = new DatagramSocket(); InetAddress ia = InetAddress.getByName(ip); while(true) { System.out.print("Enter Data: "); input = in.readline(); if(input.equals("end")) break; byte buf[] = input.getbytes(); // 문자열을바이트배열로변환 DatagramPacket dp = new DatagramPacket(buf, buf.length, ia, port); ds.send(dp); 3 장네트워크프로그래밍 223
실행결과 1) 송신측실행결과 Enter IP or Domain Name:127.0.0.1 Enter Data: Hello Enter Data: How are you doing these days? Enter Data: ServerEnd Enter Data: end 2) 수신측실행결과 Hello How are you doing these days? ServerEnd Sever is dead o 데이터그램패킷을사용한 1:1 채팅예제다음은데이터그램을사용한간단한 1:1 채팅예제프로그램이다. 채팅클라이언트를실행할때, 명령행인자로채팅서버의 IP나도메인이름을명시하여야한다. 그렇지않을경우, 서버 IP를 127.0.0.1로간주한다. 프로그램의기능을간단히설명하면다음과같다. 1 멀티스레드를사용한다. 생성된스레드는상대방의메시지를수신하여 Text Area 에출력하는역할을수행한다. 2 채팅서버및클라이언트가메시지를송신하는포트와수신하는포트를각기달리하였다. 동일한컴퓨터에서채팅서버와클라이언트가실행되는경우, 하나의포트를사용하면메시지가상대방에게전송되지않는현상이발생한다. 3 패킷을수신하는경우, 기존의바이트배열을재사용하면전에수신한메시지가남아있음에유의한다. 특히새로받은메시지가전에받은메시지에비해짧으면발생한다. 4 수신된패킷의바이트배열로부터생성한문자열은반드시 trim() 을적용한다. trim() 은전송된메시지뒤의공란을삭제한다. 224 자바네트워크프로그래밍
UDPChatServer.java 소스코드 import java.net.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; class UDPChatServer extends JFrame implements Runnable, ActionListener { public static final long serialversionuid=11223344; JTextArea ta = new JTextArea(); JTextField tf = new JTextField(); int SIZE = 40; InetAddress ia = null; int port1 = 7788, port2=7799; DatagramSocket ds1, ds2; DatagramPacket dp1, dp2; byte[] msg1, msg2; Thread th; public UDPChatServer() { super("udp Chat Server"); Container c = getcontentpane(); c.setlayout(new BorderLayout()); ta.seteditable(false); c.add(new JScrollPane(ta), BorderLayout.CENTER); c.add(tf, BorderLayout.SOUTH); tf.addactionlistener(this); 3 장네트워크프로그래밍 225
setlocation(1, 200); setsize(400, 300); setvisible(true); th= new Thread(this); th.start(); public void run() { try { String str; ds1 = new DatagramSocket(port1); while(true) { msg1 = new byte[size]; dp1 = new DatagramPacket(msg1, SIZE); ds1.receive(dp1); ia = dp1.getaddress(); str = new String(dp1.getData()); ta.append("mesg: "+str.trim()+"\n"); catch(exception e) { e.printstacktrace(); public void actionperformed(actionevent e) { try { if (ia==null) return; String tmp = tf.gettext(); ta.append("send:"+tmp+"\n"); msg2 = tmp.getbytes(); ds2=new DatagramSocket(); dp2=new DatagramPacket(msg2, msg2.length, ia, port2); ds2.send(dp2); tf.settext(""); catch(exception exp) { exp.printstacktrace(); 226 자바네트워크프로그래밍
public static void main(string args[]) { UDPChatServer fm = new UDPChatServer(); fm.setdefaultcloseoperation(exit_on_close); UDPChatClient.java 소스코드 import java.net.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; class UDPChatClient extends JFrame implements Runnable, ActionListener { public static final long serialversionuid=10203041; JTextArea ta = new JTextArea(); JTextField tf = new JTextField(); int SIZE = 40; static InetAddress ia = null; int port1 = 7788, port2=7799; DatagramSocket ds1, ds2; DatagramPacket dp1, dp2; byte[] msg1, msg2; Thread th; public UDPChatClient() { super("udp Chat Client"); Container c = getcontentpane(); c.setlayout(new BorderLayout()); 3 장네트워크프로그래밍 227
ta.seteditable(false); c.add(new JScrollPane(ta), BorderLayout.CENTER); c.add(tf, BorderLayout.SOUTH); tf.addactionlistener(this); setlocation(400, 300); setsize(400, 300); setvisible(true); th= new Thread(this); th.start(); public void run() { try { String str; ds2 = new DatagramSocket(port2); while(true) { msg2 = new byte[size]; dp2 = new DatagramPacket(msg2, SIZE); ds2.receive(dp2); str = new String(dp2.getData()); ta.append("mesg: "+str.trim()+"\n"); catch(exception e) { e.printstacktrace(); public void actionperformed(actionevent e) { try { if (ia==null) return; String tmp = tf.gettext(); ta.append("send:"+tmp+"\n"); msg1 = tmp.getbytes(); ds1=new DatagramSocket(); 228 자바네트워크프로그래밍
dp1=new DatagramPacket(msg1, msg1.length, ia, port1); ds1.send(dp1); tf.settext(""); catch(exception exp) { exp.printstacktrace(); public static void main(string args[]) throws Exception { UDPChatClient fm = new UDPChatClient(); if (args.length>0) ia = InetAddress.getByName(args[0]); else ia=inetaddress.getbyname("127.0.0.1"); fm.setdefaultcloseoperation(exit_on_close); 실행결과 1) 채팅서버 2) 채팅클라이언트 E:\>java UDPChatClient 211.48.43.90 3 장네트워크프로그래밍 229
[ 프로그래밍연습 ] 데이터그램패킷을사용한 Banki ng 프로그램작성 - 서버가 bank account에 deposit, withdraw, balance( 잔액조회 ) 기능을수행하도록작성하시오. ( 예금자는 1명이며, 처음예금액은 0이다 ) - 클라이언트가전송할명령어형식은 ( 실행후에현재잔액을 print 한다.) deposit 1000 withdraw 100 balance - 서버는클라이언트가보낸패킷을분석하여명령어를수행한다음, 항상잔액정보를클라이언트에게전송한다. deposit 패킷 클라이언트 서버 balance 패킷 [ 힌트 ] 패킷전송예제 UDPSender.java 는그대로사용한다. 다음 StringTokenizer 사용예제를참조한다. ( 다음예제는전송받은문자열을분리하여출력한다.) 230 자바네트워크프로그래밍
import java.net.*; import java.util.stringtokenizer; public class UDPReceiver2 { public static void main(string[] args){ String str=null; DatagramSocket ds=null; DatagramPacket dp=null; byte[] buf=null; try { ds = new DatagramSocket(4488); catch(socketexception ee) {System.out.println(ee); while(true){ buf=new byte[1024]; dp=new DatagramPacket(buf, buf.length); try { ds.receive(dp); catch(exception e) {System.out.println(e); str = new String(dp.getData()); // 다음줄을주석처리하고실행해보자. str = str.trim(); StringTokenizer stoken = new StringTokenizer(str); String s1 = stoken.nexttoken(); String s2 = stoken.nexttoken(); System.out.println(s1+" : "+s2); if(str.equals("serverend")) { System.out.println("Sever is dead"); break; [Home Work] 다자간채팅프로그램작성데이터그램패킷을사용한다자간채팅프로그램을설계하고구현해보시오. 앞에서와같이, 채팅참석자를표시하도록한다. 다만오랫동안메시지를보내지않은채팅참석자는삭제한다. 3 장네트워크프로그래밍 231
o UDP 사용문자파일전송예제다음은 UDP를사용하여 100바이트단위로패킷을만들어문자파일을전송하고수신하는예제프로그램이다. UDP는신뢰성이낮은프로그램이므로, 장거리전송의경우패킷이분실되거나순서가잘못될수도있다. ( 물론가능성이높지는않다.) 이예제는이러한발생가능한오류를수정하는기능을포함하고있지는않다. 다만패킷순서가바뀌는것을다소예방하기위해서다음패킷을바로전송하지않고 100ms의휴식시간을가진후에전송한다. 1 UDPFileSender: 명령행인자의파일을 100바이트단위패킷으로만들어서버측에전송한다. 2 UDPFileReceiver: 전송받은패키지를문자열로복원하여텍스트영역에출력한다. 파일전송프로그램보다이프로그램을먼저실행하여야한다. UDPFileReceiver.java 소스코드 import java.net.*; import java.awt.*; import javax.swing.*; class UDPFileReceiver extends JFrame implements Runnable { public static final long serialversionuid=12341231; JLabel label = new JLabel("Received Data"); JTextArea ta = new JTextArea(); final int BUFSIZE = 100; int port = 4499; DatagramSocket ds; Thread th; public UDPFileReceiver() { 232 자바네트워크프로그래밍
super("udp Example"); Container c = getcontentpane(); c.setlayout(new BorderLayout()); c.add(label,borderlayout.north); c.add(new JScrollPane(ta), BorderLayout.CENTER); setsize(480, 280); setvisible(true); th = new Thread(this); th.start(); public void run() { try { String str; ds = new DatagramSocket(port); while(true) { byte buf[] = new byte[bufsize]; DatagramPacket dp = new DatagramPacket(buf, buf.length); ds.receive(dp); str = new String(dp.getData()); ta.append(str); catch(exception e) { e.printstacktrace(); public static void main(string args[]) { UDPFileReceiver fm = new UDPFileReceiver(); fm.setdefaultcloseoperation(exit_on_close); 3 장네트워크프로그래밍 233
UDPFileSender.java 소스코드 import java.net.*; import java.io.*; public class UDPFileSender { public static void main(string[] args) throws Exception { int port=4499; int length = 100; DatagramSocket ds = new DatagramSocket(); InetAddress ia = InetAddress.getByName(args[0]); File file= new File(args[1]); FileInputStream from = new FileInputStream(file); int i=0; int filelength = (int) file.length(); for(i=100; i<filelength; i+=100) { byte[] packet = new byte[length]; from.read(packet, 0, 100); DatagramPacket dp = new DatagramPacket(packet, length, ia, port); ds.send(dp); Thread.sleep(100); int last = filelength-i+100; byte[] packet = new byte[last]; from.read(packet, 0, last); DatagramPacket dp = new DatagramPacket(packet, last, ia, port); ds.send(dp); 234 자바네트워크프로그래밍
from.close(); ds.close(); 실행결과 1) UDPFileReceiver 실행결과 2) UDPFileSender 실행 ( 수신측 IP와전송할파일을명시 ) E:\>java UDPFileSender 127.0.0.1 UDPFileReceiver.java [Home Work] 이프로그램을변형하여, 수신된파일을프레임에출력하지말고파일에저장하도록변경하시오. 문자파일뿐아니라이미지파일도전송이가능한지확인하시오. ( 파일이름과파일크기정보를먼저전송하고, 파일내용을 500 바이트단위로전송한다.) 3 장네트워크프로그래밍 235
3.8 Multicast 소켓 java.net.multicastsocket 클래스는 DatagramSocket 의서브클래스이며, 서버가전송하는패킷을여러클라이언트가동시에수신하기위하여사용한다. MulticastSocket 에대해서정리하면다음과같다. 1 서버가전송하는패킷을여러클라이언트가동시에수신하기위해서사용 2 일종의 DatagramSocket 이며, 데이터그램소켓의포트를사용한다. 자바언어에서 TCP 포트와 UDP 포트가각각 2의 16개씩사용가능하다. 3 멀티캐스팅서버는패킷의목적지 IP를멀티캐스트그룹의 IP 사용. 4 멀티캐스트그룹안에있는모든호스트 ( 물론적절한 hop 이내 ) 에게전송한다. 5 멀티캐스팅을위해서는 D 클래스 IP를사용해야한다. 6 D 클래스 IP의범위는 224.0.0.1 239.225.225.255 이다. o Mul t i cast Socket 생성자 1 MulticastSocket() throws IOException 2 MulicastSocket(int port) throws IOException 3 MulticastSocket(SocketAddress sa)throws IOException - 주어진포트를사용하여멀티캐스트소켓을생성한다. o Mul t i cast Socket 에서지원되는메소드 메소드 설명 int gettimetolive() 전송멀티캐스트패킷의수명 (TimeToLive) 을반환 void joingroup(inetaddress a) 멀티캐스트그룹에합류함 void leavegroup(inetaddress a) 멀티캐스트그룹을떠남 void settimetolive(int ttl) 멀티캐스트패킷의수명을설정 InetAddress getinterface() 멀티캐스트패킷에사용된네트워크인터페이스의주소를반환 void setinterface(inetaddress a) 멀티캐스트네트워크인터페이스설정 ( 복수 NIC 가설치된경우에선택 ) boolean getloopbackmode() 루프백모드지원여부반환 (127.0.0.1 사용가능여부 ) void setloopbackmode(boolean b) 루프백모드설정 void setttl(byte ttl) deprecated 메소드, settimetolive() 사용권장 byte getttl() deprecated 메소드, gettimetolive() 사용권장 236 자바네트워크프로그래밍
MulticastSocket 은 DatagramSocket 의하위클래스이므로, close(), connect(), get- InetAddress(), getlocaladdress(), getlocalport(), getport(), getreceviebuffer- Size(), getsendbuffersize(), send(), receive() 사용가능하다. o 멀티캐스트소켓프로그래밍과정. 1) 패킷송신측 - InetAddress 클래스의정적메소드 getbyname() 를사용하여, D 클래스에속한 IP의 (224.0.0.1 239.255.255.255 중에서택일 ) InetAddress 객체를생성한다. - 생성된 InetAddress 객체와약속된포트를사용하여, 패킷을만든다. - 일반데이터그램소켓을사용하여패킷을전송한다. 2) 클라이언트측 - 약속된포트번호를사용하여 MulticastSocket 객체생성한다. - 약속된 D클래스의 IP를사용하여, InetAddress 객체를생성한다. - MulticastSocket 객체의 joingroup() 메소드를사용하여멀티캐스트그룹에연결한다. - MulticastSocket 객체를사용하여패킷을수신한다. o 멀티캐스트예제프로그램다음은 MulticastSocket 을사용하여서버가매4초마다시간을읽어서다수의클라이언트에게전송하는예제프로그램이다. 전송프로그램 : BCastServer.java import java.net.*; import java.util.*; public class BCastServer extends Thread { static int port =7788; static DatagramSocket socket;; public void run() { while (true) { 3 장네트워크프로그래밍 237
try { byte[] buf = new byte[50]; String date = new Date().toString(); buf = date.getbytes(); InetAddress group = InetAddress.getByName("224.0.0.1"); DatagramPacket packet = new DatagramPacket(buf, buf.length, group, port); socket.send(packet); sleep(4000); catch (Exception e) { e.printstacktrace(); socket.close(); public static void main(string[] args) throws Exception { if (args.length>0) port=integer.parseint(args[0]); socket = new DatagramSocket(port); new BCastServer().start(); 수신프로그램 : BCastClient.java import java.net.*; public class BCastClient { public static void main(string[] args) throws Exception { int port =7788; if (args.length>0) port=integer.parseint(args[0]); MulticastSocket socket = new MulticastSocket(port); InetAddress address = InetAddress.getByName("224.0.0.1"); 238 자바네트워크프로그래밍
socket.joingroup(address); DatagramPacket packet; for(int x=0; x<10; x++) { byte[] buf = new byte[50]; packet = new DatagramPacket(buf, buf.length); socket.receive(packet); String received = new String(packet.getData()).trim(); System.out.println("Server time: " + received); socket.leavegroup(address); socket.close(); 실행결과 Server time: Thu Oct 04 16:16:08 KST 2007 Server time: Thu Oct 04 16:16:12 KST 2007 Server time: Thu Oct 04 16:16:16 KST 2007 Server time: Thu Oct 04 16:16:20 KST 2007 Server time: Thu Oct 04 16:16:24 KST 2007 Server time: Thu Oct 04 16:16:28 KST 2007... o Broadcast 송수신 IP는특정네트워크안에서 broadcasting을위한 IP가지정되어있다. 이러한 IP를사용하여패킷을전송하면, 특정네트워크안의모든호스트에게패킷을전송할수있다. Broadcast 는브로드캐스팅을지원하는 IP주소에게전송하면, broadcasting 된다. 그러면해당네트워크그룹의모든 IP 주소에게전달된다. 3 장네트워크프로그래밍 239
다음예제프로그램에서사용된 IP 211.48.44.255는 211.48.44.0 ~ 211.48.44.254의모든 IP에게 broadcasting 하기위한것이다. 멀티캐스팅소켓을사용하지않고, 일반데이터그 램소켓을사용하여패킷을수신하면된다. ( 주의 : 예제에서사용할 IP 주소는실행할 LAN 의 IP에맞추어변경하여야한다.) BroadcastSend.java 소스 import java.net.*; import java.io.*; public class BroadcastSend { public static void main(string[] args) throws IOException { DatagramSocket ds = new DatagramSocket(); BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); while (true) { System.out.print("Message = "); String msg = in.readline(); InetAddress ia = InetAddress.getByName("211.48.44.255"); //.255 는 Broadcast를위한 IP DatagramPacket data = new DatagramPacket(msg.getBytes(), msg.getbytes().length, ia, 7789); ds.send(data); if (msg.startswith("end")) break; ds.close(); BroadcastReceive.java 소스 import java.net.*; import java.io.*; 240 자바네트워크프로그래밍
public class BroadcastReceive { public static void main(string[] args) throws IOException { DatagramSocket ds = new DatagramSocket(7789); byte[] by; while (true) { by = new byte[1024]; DatagramPacket data = new DatagramPacket(by, by.length); ds.receive(data); InetAddress ia = data.getaddress(); String str = new String(data.getData()).trim(); System.out.println(ia.getHostAddress() + ": " + str); if(str.startswith("end"))break; ds.close(); 실행결과 [ 메시지송신측 ] Message = Hello!! Message = THIS is for testing Broadcasting. Message = end [ 메시지수신측 ] 211.48.44.82: Hello!! 211.48.44.82: THIS is for testing Broadcasting. 211.48.44.82: end o Mul t i cast Socket을사용한파일전송다음은 MulticastSocket 을사용하여, 동시에여러컴퓨터에파일을송신하도록하는예제프로그램이다. 파일의이름과크기를먼저전송하고, 100 바이트단위로나누어전송한다. 파일을전송할때, 명령행인자로전송할파일이름을명시하여야한다. 3 장네트워크프로그래밍 241
McastFileSender.java import java.net.*; import java.io.*; public class McastFileSender { public static void main(string[] args) throws Exception { int port=4497; int length = 100; byte[] packet; DatagramPacket dp; DatagramSocket ds = new DatagramSocket(); InetAddress ia = InetAddress.getByName("224.1.2.3"); File file= new File(args[0]); int filelength = (int) file.length(); FileInputStream from = new FileInputStream(file); String namesize = args[0]+":"+filelength; packet = namesize.getbytes(); dp = new DatagramPacket(packet, packet.length, ia,port); ds.send(dp); int i=0; for(i=100; i<filelength; i+=100) { packet = new byte[length]; from.read(packet, 0, 100); dp = new DatagramPacket(packet, length, ia, port); ds.send(dp); Thread.sleep(100); // sleep for not missing packet int last = filelength-i+100; packet = new byte[last]; 242 자바네트워크프로그래밍
from.read(packet, 0, last); dp = new DatagramPacket(packet, last, ia, port); ds.send(dp); from.close(); ds.close(); McastFileReceiver.java import java.io.*; import java.net.*; class McastFileReceiver { public static void main(string args[]) throws Exception { final int BUFSIZE = 100; int port = 4497; byte[] buf = new byte[bufsize]; DatagramPacket packet; MulticastSocket ds =new MulticastSocket(port); InetAddress ia = InetAddress.getByName("224.1.2.3"); ds.joingroup(ia); packet = new DatagramPacket(buf, BUFSIZE); ds.receive(packet); // what if trim() is missed in the next line????? String str = new String(packet.getData()).trim(); String name = str.substring(0, str.indexof(':')); String fs = str.substring(str.indexof(":")+1); 3 장네트워크프로그래밍 243
System.out.println("name & size "+name+" "+fs); // int filelength = Integer.parseInt(fs); FileOutputStream fo = new FileOutputStream("copy-"+name); int i=0; for(i=100; i<filelength; i+=100) { buf = new byte[bufsize]; packet = new DatagramPacket(buf,buf.length); ds.receive(packet); fo.write(buf, 0, BUFSIZE); int last = filelength-i+100; buf = new byte[last]; packet = new DatagramPacket(buf,buf.length); ds.receive(packet); fo.write(buf, 0, last); fo.close(); ds.close(); [ 프로그램연습 ] 100byte 단위로나누어송수신하는것은아무래도너무적은단위이다. 이를 1Kbyte 단위로전송하도록변경하시오. [Home Work] 다음예제는멀티캐스트소켓을사용하여, 채팅과파일송수신을구현한프로그램이다. - 프로그램을분석하시오. - 프로그램을실행하면, 자신이전송한파일이자신에게도수신된다. 자신이보낸파일을자신이수신하는것은불필요하므로이를수정하시오. 244 자바네트워크프로그래밍
- 프로그램을단순화하고부족한부분을수정하시오. ( 소스와실행결과를보일것 ) - 새로운기능을추가하시오. ( 소스와실행결과를보일것 ) - 채팅만을구현한프로그램을작성하시오. ( 소스와실행결과를보일것 ) 예제프로그램 import java.awt.*; import java.awt.event.*; import java.net.*; import java.io.*; class MCastMessanger extends Frame implements Runnable, ActionListener{ public static final long serialversionuid=1203021; private TextArea ta = new TextArea(); private Label lb = new Label(" 대화 : ", Label.RIGHT); private TextField tf = new TextField(); private Button bt = new Button(" 전송 "); private Button bt1 = new Button(" 파일전송 "); private Button bt2 = new Button(" 끝내기 "); private Thread current; private DatagramPacket dp;// 받는거 private MulticastSocket ms;// 받는거 private DatagramPacket dp1;// 보내는거 private MulticastSocket ms1;// 보내는거 private Dialog dlg = new Dialog(this, " 파일전송 ", true); private Button dlgbt = new Button(" 예 "); private Button dlgbt1 = new Button(" 아니오 "); private String dir, file;// 보내는거 3 장네트워크프로그래밍 245
private String dir1, file1;// 받는거 public void initdlg(){ dlg.setlayout(new BorderLayout()); dlg.add("center", new Label(" 파일전송을받으시겠습니까?", Label.CENTER)); Panel pp = new Panel(new GridLayout(1, 2)); pp.add(dlgbt); pp.add(dlgbt1); dlg.add("south", pp); dlg.setsize(250, 120); public MCastMessanger(String str){ super(str); this.initdlg(); this.init(); this.start(); this.setsize(300, 400); this.setvisible(true); public void init(){ this.setlayout(new BorderLayout(5, 5)); Panel p1 = new Panel(new BorderLayout()); p1.add("center", ta); Panel p2 = new Panel(new BorderLayout()); p2.add("west", lb); p2.add("center", tf); p2.add("east", bt); p1.add("south", p2); this.add("center", p1); Panel p = new Panel(new GridLayout(1, 2)); p.add(bt1); p.add(bt2); this.add("south", p); 246 자바네트워크프로그래밍
ta.seteditable(false); tf.requestfocus(); public void start(){ current = new Thread(this); current.start(); tf.addactionlistener(this); bt.addactionlistener(this); bt1.addactionlistener(this); dlgbt.addactionlistener(this); dlgbt1.addactionlistener(this); public void run(){ ta.settext("*** 대화방에입장하셨습니다. ***\n\n"); try{ ms = new MulticastSocket(33333); ms.joingroup(inetaddress.getbyname("239.2.3.4")); catch(ioexception ee){ while(true){ dp = new DatagramPacket(new byte[65508], 65508); try{ ms.receive(dp); catch(ioexception ee){ String datadata = new String(dp.getData()).trim(); if(datadata.charat(0) == '/' && datadata.charat(1) == 'f'){ dlg.setvisible(true); else if(datadata.charat(0) == '/' && datadata.charat(1) == 's'){ try{ File ff = new File(dir1, file1); FileOutputStream fos = new FileOutputStream(ff); BufferedOutputStream bos = new BufferedOutputStream(fos); 3 장네트워크프로그래밍 247
DataOutputStream dos = new DataOutputStream(bos); while(true){ String sss = new String(dp.getData()).trim(); if(sss == null sss.length() == 0 (sss.charat(0) == '/' && sss.charat(0) == 'x')){ break; dos.write(sss.getbytes(), 0, sss.getbytes().length); dp = new DatagramPacket(new byte[65508], 65508); ms.receive(dp); dos.close(); catch(ioexception ee){ else if(datadata.charat(0) == '/' && datadata.charat(1) == 'y'){ try{ dp1 = new DatagramPacket("/s".getBytes(), "/s".getbytes().length, InetAddress.getByName("239.2.3.4"), 33333); ms1 = new MulticastSocket(); ms1.settimetolive((byte)1); ms1.send(dp1); ms1.close(); File ff = new File(dir, file); FileInputStream fis = new FileInputStream(ff); BufferedInputStream bis = new BufferedInputStream(fis); DataInputStream dis = new DataInputStream(bis); byte[] bbb = new byte[65508]; while(true){ int xx = dis.read(bbb); if(xx == -1){ 248 자바네트워크프로그래밍
break; String strstr = new String(bbb).trim(); dp1 = new DatagramPacket(strstr.getBytes(), strstr.getbytes().length, InetAddress.getByName("239.2.3.4"), 33333); ms1 = new MulticastSocket(); ms1.settimetolive((byte)1); ms1.send(dp1); ms1.close(); dp1 = new DatagramPacket("/x".getBytes(), "/x".getbytes().length, InetAddress.getByName("239.2.3.4"), 33333); ms1 = new MulticastSocket(); ms1.settimetolive((byte)1); ms1.send(dp1); ms1.close(); catch(ioexception ee){ else{ ta.append(dp.getaddress() + " >> " + new String(dp.getData()).trim() + "\n"); public void actionperformed(actionevent e){ if(e.getsource() == tf e.getsource() == bt){ String str = tf.gettext().trim(); if(str == null str.length() == 0){ tf.settext(""); tf.requestfocus(); 3 장네트워크프로그래밍 249
return; try{ dp1 = new DatagramPacket(str.getBytes(), str.getbytes().length, InetAddress.getByName("239.2.3.4"), 33333); catch(unknownhostexception ee){ try{ ms1 = new MulticastSocket(); ms1.settimetolive((byte)1); ms1.send(dp1); ms1.close(); catch(ioexception ee){ tf.settext(""); tf.requestfocus(); else if(e.getsource() == bt1){ FileDialog fd = new FileDialog(this, " 파일전송 ", FileDialog.LOAD); fd.setvisible(true); dir = fd.getdirectory(); file = fd.getfile(); ta.append(dir + file + " 이라는파일을전송합니다.\n"); try{ dp1 = new DatagramPacket("/f".getBytes(), "/f".getbytes().length, InetAddress.getByName("239.2.3.4"), 33333); catch(unknownhostexception ee){ try{ ms1 = new MulticastSocket(); ms1.settimetolive((byte)1); ms1.send(dp1); ms1.close(); catch(ioexception ee){ 250 자바네트워크프로그래밍
else if(e.getsource() == dlgbt){ FileDialog fd = new FileDialog(this, " 파일전송받기 ", FileDialog.SAVE); fd.setvisible(true); dir1 = fd.getdirectory(); file1 = fd.getfile(); try{ dp1 = new DatagramPacket("/y".getBytes(), "/y".getbytes().length, InetAddress.getByName("239.2.3.4"), 33333); catch(unknownhostexception ee){ try{ ms1 = new MulticastSocket(); ms1.settimetolive((byte)1); ms1.send(dp1); ms1.close(); catch(ioexception ee){ else if(e.getsource() == dlgbt1){ dlg.setvisible(false); String strstr = " 파일전송이취소되었습니다."; try{ dp1 = new DatagramPacket(strstr.getBytes(), strstr.getbytes().length, InetAddress.getByName("239.2.3.4"), 33333); catch(unknownhostexception ee){ try{ ms1 = new MulticastSocket(); ms1.settimetolive((byte)1); ms1.send(dp1); ms1.close(); catch(ioexception ee){ 3 장네트워크프로그래밍 251
public static void main(string[] ar){ MCastMessanger mc =new MCastMessanger("Messanger"); mc.addwindowlistener( new WindowAdapter(){ public void windowclosing(windowevent e){ System.exit(0); ); 실행결과 252 자바네트워크프로그래밍
[ 영문해석 ] 다음은 UDP에대한설명이다. 이를해석하시오. User Datagram Protocol (UDP) is one of the core protocols of the Internet protocol suite. Using UDP, programs on networked computers can send short messages sometimes known as datagrams (using Datagram Sockets) to one another. UDP is sometimes called the Universal Datagram Protocol. The protocol was designed by David P. Reed in 1980. UDP does not guarantee reliability or ordering in the way that TCP does. Datagrams may arrive out of order, appear duplicated, or go missing without notice. Avoiding the overhead of checking whether every packet actually arrived makes UDP faster and more efficient, for applications that do not need guaranteed delivery. Time-sensitive applications often use UDP because dropped packets are preferable to delayed packets. UDP's stateless nature is also useful for servers that answer small queries from huge numbers of clients. Unlike TCP, UDP is compatible with packet broadcast (sending to all on local network) and multicasting (send to all subscribers). Common network applications that use UDP include: the Domain Name System (DNS), streaming media applications such as IPTV, Voice over IP (VoIP), Trivial File Transfer Protocol (TFTP) and onli ne games. 3 장네트워크프로그래밍 253
4 장 Remote Method Invocation(RMI) 4.1 RMI 의소개 RMI는원격메소드호출 (Remote Method Invocation) 의약어이다. 원격컴퓨터에서실행되고있는객체를마치로컬컴퓨터의객체처럼호출하는것을의미한다. 자바프로그래머는원격객체의위치 (IP 또는도메인이름 ), 객체이름, 메소드이름, 인자, 반환값에대한정보만알고있으면원격메소드를호출할수있다. RMI는소켓을사용한분산프로그램와비교하여, 프로그래머의부담이많이줄어든다. RMI가개발되기전에원격프로시저호출 (RPC) 라는기술이먼저개발되었다. RPC에대한특성을정리하면다음과같다. RPC (Remote Procedure call) - Developed before RMI tech. developed. - Not object oriented languages (for example, C) - Sockets are considered low-level. (transmit pure data, no service) - RPC offers Higher level services of Communication - Client makes procedure calls to "remote server" using ordinary procedure call mechanism. RMI 는 RPC 에객체지향개념을추가한것이다. RMI 의실행과정을그림으로그리면 다음과같다. 클라이언트는원격서버를논리적으로직접호출한다. 클라이언트와서버가 물리적으로떨어져있다는사실은프로그래머에게감추어진다. RMI 프로그래머는약간의 4 장 Remote Method Invocation(RMI) 257
설정과정후에원격객체를로컬객체와동일한방법으로호출할수있다. 그러나이러한논리적인직접호출은 stub를통해소켓메시지로변경되어서버측의 skeleton에전달된다 ( 네트워크통신 ). skeleton은전송받은메시지를원래호출정보로복원하여적절한서버객체를호출하여실행한다. Remote 인터페이스 구현 UnicastRemoteObject 상속 Client 객체 b() 호출 논리적인호출 Server 객체 b(){... stub 메시지통신 skeleton [ 그림 4-1] 자바 RMI 의실행 o 데이터 marshalling과 unmarshalling Stub가클라이언트의호출정보를통신메시지로만드는과정을데이터 marshalling 이라고한다. Skeleton이전송받은통신메시지를원래데이터로복원하는과정을데이터 unmarshalling이라고한다. 실행결과또한메시지로변환되어클라이언트에게전달된다. 즉실행결과가서버측의 skeleton에의하여메시지로변환 (marshalling) 되어클라이언트측의 sub에게전달된다. Stub는전달받은메시지를실행결과로복원 (unmarshalling) 하여, 호출한프로세스에게전달한다. Stub와 skeleton을프로그래머가직접작성하는것은매우어렵다. 하지만자바 RMI에서는 stub와 skeleton을자동생성시키는 rmic 라는툴을제공한다. o Naming 서비스일종의디렉토리서비스이다. 객체의이름을사용하여, 객체의위치정보를검색할수있도록한다. 디텍토리서비스는예로서전화번호부를들수있다. 전화번호부는같이검색을원하는전화번호를사람이름을키로사용하여검색할수있다. RMI 프로그래머는원격객체 258 자바네트워크프로그래밍
의이름과메소드정보는알수있지만, 그객체의정확한위치를파악하기어렵다. Naming 서비스는원격서비스객체를적절한이름으로등록한후, 검색할수있도록해준다. 자바 RMI에서는 Naming 클래스의정적메소드 rebind() 를사용하여객체를등록할수있으며, Naming 클래스의정적메소드 lookup() 을사용하여객체를검색할수있다. 4.2 RMI 예제 4.2.1 예제프로그램설명 RMI 프로그래밍과정을정리하면다음과같다. 여기서작성하는예제는양의정수 n 을 인자로받아서, 1 부터 n 까지의모든정수의합을계산하는 RMI 프로그램이다. Client process sum(10) return(55) Server process [ 그림 4-2] 메소드호출과결과값반환 1 RMI 프로그래밍에필요한패키지의수입 import java.rmi.*; // Remote와 RemoteException import java.rmi.server.*; // UnicastRemoteObject 2 원격인터페이스파일의정의 (Summing.java) 원격클라이언트가호출가능한서버의메소드의원형을정의한다. 원격인터페이스정의시 java.rmi.remote 를상속하여야한다. 원격메소드원형은메소드이름, 반환값, 인자의유형및개수를포함하여야하며, RemoteException 예외를 throws 해야한다. import java.rmi.*; public interface Summing extends Remote { int sum(int max) throws RemoteException; 4 장 Remote Method Invocation(RMI) 259
3 원격인터페이스의구현클래스의정의 (SummingImpl.java) 원격인터페이스구현클래스의이름은원격인터페이스 (Summing)+Impl을붙힌다. UnicastRemoteObject를상속받고, 원격인터페이스 Summing을구현하여정의한다. 생성자는반드시명시하여야하며, RemoteException을 throws 해야한다. SummingImpl 객체를생성하여, Naming.rebind( 쉬운이름, 서버객체 ) 을사용하여네임서버에등록한다. import java.rmi.*; import java.rmi.server.*; public class SummingImpl extends UnicastRemoteObject implements Summing { public SummingImpl() throws RemoteException { // 이생성자필수 public int sum(int max) throws RemoteException { if(max <= 0) return 0; else return (max + sum(max - 1)); public static void main(string args[]) { try { SummingImpl s = new SummingImpl(); Naming.rebind("SumServer", s); // 서버이름 : SumServer 등록 catch(exception e) { System.out.println("Exception: "+e); 4 stub와 skeleton의생성 stub와 skeleton은원격호출을소켓통신으로변화시키는모듈이다. java에서는 rmic 명령어를사용하여 stub와 skeleton을자동생성할수있다. 자바 1.2에서는 stub와 skeleton을구분하지않고, stub로통일하였다. C:\>rmic SummingImpl 5 클라이언트프로그램의작성 (SummingClient.java) Naming.lookup() 메소드를사용하여원격객체를검색한다. 검색된원격객체의메소드를사용한다. import java.rmi.*; public class SummingClient { 260 자바네트워크프로그래밍
public static void main(string args[]) { if (args.length < 2) { System.out.println("Specify IP & number"); System.exit(1); try { String serverurl = "rmi://" + args[0] + "/SumServer"; Summing s = (Summing)Naming.lookup(serverURL); System.out.println("Your Input = " + args[1]); int max = Integer.parseInt(args[1]); System.out.println("The sum 1 to " + max + " is "+ s.sum(max)); catch(exception e) { System.out.println("Exception: " + e); 4.2.2 예제프로그램실행 앞에서작성한 RMI 예제의실행과정은다음과같다. 1 원격인터페이스파일 Summing.java 를컴파일한다. C:\> javac Summing.java 2 서비스구현파일 SummingImpl.java 를컴파일한다. C:\> javac SummingImpl.java 3 스터브와스켈레톤파일을생성한다. ( 자바 1.2 이후에는스터브로통일되었음 ) C:\> rmic -v1.2 SummingImpl 4 장 Remote Method Invocation(RMI) 261
4 서버측에필요한파일을선택한다. - Summing.class - SummingImpl.class - SummingImpl_Stub.class 5 클라이언트측에필요한파일을선택한다. - Summing.class - SummingClient.class - SummingImpl_Stub.class 6 서버측에서네이밍서버 (rmiregistry) 를실행한다. C:\>start rmiregistry 7 서버측에서 RMI 서버를실행한다. C:\>java SummingImpl 8 클라이언트측에서클라이언트프로그램을실행한다. C:\>java SummingClient 127.0.0.1 100 Your Input = 100 The sum 1 to 100 is 5050 [ 프로그래밍연습 ] 앞의 RMI 예제프로그램을수정하시오. 두개의정수를인자로받아서, 그합을계산하는원격객체를작성하시오. 4.2.3 소켓사용하여다시작성한예제프로그램 소켓을사용하는분산프로그래밍기법과 RMI 기법을비교하기위하여, 앞에서작성한 RMI 프로그램과동일한기능을수행하는 UDP 소켓프로그램을작성한다. 물론소켓을사용하여도앞의프로그램의기능을수행하는프로그램을작성할수있다. 그러나소켓은데이터를단순히송수신하기에는매우편리하지만, 단순데이터전송이아닌서비스를제공하기에는다소복잡한절차가필요하다는것을보여주기위함이다. 262 자바네트워크프로그래밍
클라이언트는정수를바이트배열로변경한다음, 데이터패킷을만들어서버에전송한다. 서버는패킷에서데이터를복원한다음, 1부터 n까지의정수합을계산한다. 계산된결과를다시메시지로만들어클라이언트에게전송한다. 클라이언트는메시지에서계산된결과를복원한다. 이러한과정을그림으로표현하면다음과같다. < 클라이언트 > 1 데이터 패킷 요구패킷 < 서버 > 2 패킷 데이터 4 패킷 결과 결과패킷 3 결과 패킷 [ 그림 4-3] 예제프로그램의실행과정 소켓예제에서사용된메소드의기능을정리하면다음과같다. [ 표 4-1] SumServer/SumClient 에서사용된메소드 메소드이름 기능 makebytes 정수를바이트배열 [4] 로변환하여반환 restoreint 바이트배열 [4] 을정수로복원 예제소스코드 : SumServer.java import java.net.*; class SumServer { public static void main(string[] args) { InetAddress ia; int port = 7788; 4 장 Remote Method Invocation(RMI) 263
int port2 = 7799; int num1, sum; byte[] buf = new byte[4]; byte[] result = new byte[4]; try { DatagramPacket dp = new DatagramPacket(buf, buf.length); DatagramSocket ds = new DatagramSocket(port); DatagramSocket ds2= new DatagramSocket(); while(true) { ds.receive(dp); // Packet receive buf = dp.getdata(); ia = dp.getaddress(); num1 = restoreint(buf); // 정수복원 System.out.println(ia+" sent "+num1); sum = 0; // 계산실행 for(int x=1; x<=num1; x++) sum+=x; result = makebytes(sum); // 바이트배열로변환 DatagramPacket p = // 패킷생성 new DatagramPacket(result, result.length, ia, port2); ds2.send(p); // 패킷전송 catch(exception e) { e.printstacktrace(); public static byte[] makebytes(int m) { byte[] val = new byte[4]; for(int k=0; k<4; k++) { val[k] = (byte)(m % 128); m = (int) m / 128; return val; // 정수를 byte 배열 [4] 로변환후반환 264 자바네트워크프로그래밍
public static int restoreint(byte[] val) { int m = 0; for(int k=3; k>=0; k--) m = m * 128 + val[k]; return m; // byte 배열 [4] 을정수로복원후반환 예제소스코드 : SumClient.java import java.net.*; class SumClient { public static void main(string[] args) { if(args.length<2) { System.out.println("Specify Server IP & Int num"); System.exit(1); try { int port = 7788; int port2 = 7799; DatagramSocket ds = new DatagramSocket(); InetAddress ia = InetAddress.getByName(args[0]); int n1 = Integer.parseInt(args[1]); byte[] b1 = makebytes(n1); System.out.println("Input="+restoreInt(b1)); DatagramPacket dp = new DatagramPacket(b1,b1.length, ia, port); ds.send(dp); byte[] b3 = new byte[4]; DatagramSocket ds2 = new DatagramSocket(port2); DatagramPacket p = new DatagramPacket(b3, b3.length); ds2.receive(p); byte[] tmp = p.getdata(); int result = restoreint(tmp); System.out.println("the Result from Server: "+result); catch(exception e) { e.printstacktrace(); 4 장 Remote Method Invocation(RMI) 265
public static byte[] makebytes(int m) { byte[] val = new byte[4]; for(int k=0; k<4; k++) { val[k] = (byte)(m % 128); m = (int) m / 128; return val; public static int restoreint(byte[] val) { int m = 0; for(int k=3; k>=0; k--) m = m * 128 + val[k]; return m; 서버측실행결과 C:\Chap04>java SumServer /211.48.44.82 sent 1000 /211.48.44.82 sent 100 클라이언트측실행결과 C:\chap04>java SumClient 211.48.44.82 100 Input=100 the Result from Server: 5050 예제프로그램을살펴보면, 1부터 n까지합을계산하는코드보다메시지 marshalling 과 unmarshalling 처리코드가복잡하다는것을알수있다. RMI에서는분산프로그래밍에서이러한메시지처리의부담이없다. 또한 RMI에서는로컬객체와동일한방법으로원격객체를호출할수있다. 266 자바네트워크프로그래밍
4.2.4 RMI 를실행하기위해필요한파일 1) 인터페이스파일의작성 - java.rmi.remote 인터페이스를확장하여정의한다. - 원격객체가제공할메소드의기본정보를제공한다. ( 인터페이스이름, 메소드이름, 반환값, 인자의수와유형 ) - 메소드를구현하지는않는다. ( 메소드의몸체는없다 ) - 모든메소드는 java.rmi.remoteexception을 throws 해야한다. - 모든메소드는 public 접근허용되어야한다. - 인터페이스정의형식은다음과같다. interface name extends Remote { public type method1(args_type, args_name) throws RemoteException; public type method2(args_type, args_name) throws RemoteException;... 예 ) Hello.java import java.rmi.*; public interface Hello extends Remote { public String greeting(string name) throws RemoteException; 2) 인터페이스를구현한서버클래스의작성 - 서버클래스는원격인터페이스를구현한다고명시하여야한다. - 서버클래스의이름관행적으로 인터페이스이름 + Impl 으로한다. - java.rmi.server.unicastremoteobject 클래스를확장하여정의한다. - 인터페이스의모든메소드를구현하여야하며, 구현한메소드의접근자는 public이어야한다. - 기본생성자를반드시정의해야하며, RemoteException 을 throws 해야한다. 상속받은 UnicastRemoteObject의생성자가 RemoteException 예외를발생시키기때문이다. 4 장 Remote Method Invocation(RMI) 267
예 ) HelloImpl.java import java.rmi.*; import java.rmi.server.unicastremoteobject; public class HelloImpl extends UnicastRemoteObject implements Hello { public HelloImpl() throws RemoteException { public String greeting(string name) throws RemoteException { return "Hello! "+name; 3) 서버객체의네이밍서버등록하는클래스 - 서버객체를생성한다. - 생성된서버객체를네이밍서버에적절한이름으로등록한다. 서버클래스는 classpath 의디렉토리에위치하여야한다. ( 예를들어, classpath=.;c:\lib 이라면서버클래스가있는디렉토리에서서버등록클래스를실행하거나, 서버클래스를 C:\lib 에위치시켜야한다.) 예 ) HelloServer.java import java.net.*; import java.rmi.*; public class HelloServer { public static void main(string[] args) throws RemoteException, MalformedURLException { HelloImpl h=new HelloImpl(); Naming.rebind("hello", h); System.out.println("Hello Server Ready"); 4) 클라이언트의작성 - Naming.lookup() 메소드를사용하여객체를검색한다. ( 검색시에사용하는이름은네임서버에등록할때사용한문자열을그대로사용하여야한다.) - 검색된객체를사용하여메소드를호출한다. 268 자바네트워크프로그래밍
예 ) HelloClient.java import java.net.*; import java.rmi.*; public class HelloClient { public static void main(string[] args) throws RemoteException, MalformedURLException, NotBoundException { Hello h = (Hello) Naming.lookup("rmi://127.0.0.1/hello"); String msg = h.greeting("rmi"); System.out.println(msg); 5) stub 의파일의생성 c:> rmic HelloImpl 6) 서버측에필요한파일과실행명령어 - Hello.class - HelloImpl.class - HelloImpl_Stub.class - HelloServer.class 1 C:> start rmiregistry 2 C:> java HelloServer Hello Server Ready start 명령어는윈도우 DOS 창에서 back-ground 실행명령어이다.(UNIX 의 & 와동일 ) 즉 start rmiregistry 를입력하면, 새로운창이만들어져 rmiregistry 명령어를실행한다. 7) 클라이언트측에필요한파일과실행명령어 - Hello.class - HelloImpl_Stub.class - HelloClient.class 1 C:> java HelloClient Hello! RMI 4 장 Remote Method Invocation(RMI) 269
LocateRegistry 사용네이밍서버실행도스창에서 rmiregistry를실행한후에, Naming.rebind() 와 Naming.lookup() 메소드를사용할수있다. LocateRegistry 클래스는서버프로그램안에서네이밍서버를실행하는것을가능하게한다. ( 실행방법은간단함 ) 다음예제 (HelloServer2.java) 는 LocateRegistry 클래스를사용하여네이밍서버에등록하는것을보여준다. LocateRegistry.createRegistry(1099) 를사용하면, 도스창에서 rmir -egistry를구동할필요가없다. LocateRegistry.getRegistry(1099) 를사용하면, 도스창에서실행시킨 rmiregistry의참조를얻어올수있다. - 다음예제 HelloServer2.java 를실행시키기전에, rmiregistry를실행시킬필요가없다. 다음 HelloServer2.java 를사용하여, 앞의 Hello RMI 를실행하시오. 소스코드 : HelloServer2.java import java.net.*; import java.rmi.*; import java.rmi.registry.*; public class HelloServer2 { public static void main(string[] args) throws RemoteException, MalformedURLException { Registry rg = LocateRegistry.createRegistry(1099); // Registry rg = LocateRegistry.getRegistry(1099); HelloImpl h=new HelloImpl(); rg.rebind("hello", h); // Naming.rebind("hello", h); System.out.println("Hello Server Ready"); 270 자바네트워크프로그래밍
o 객체등록과검색 : Naming.rebind() & Naming.lookup() 클라이언트가원격객체에대한참조를가지고있어야만, 원격객체의메소드를호출할수있다. 자바 RMI에서는원격객체에대한등록, 검색기능을위하여 rmiregistry를제공한다. 먼저 RMI에사용될객체를 rmiregistry에등록하기위해서는 Naming.rebind() 메소드를사용한다. 원격서버객체를검색하기위해서는 Naming.lookup() 메소드를사용한다. Naming 클래스에서지원되는정적메소드를간단히정리하면다음과같다. 1 static void bind(string name, Remote obj) - 주어진이름을원격객체에바인드한다. 2 static String[] list(string name) - 레지스트리에바인드된이름의배열을반환한다. 3 static Remote lookup(string name) - 주어진이름과연관된원격객체의참조를반환한다. 4 static void rebind(string name, Remote obj) - 주어진이름을원격객체에다시바인드한다. 5 static void unbind(string name) - 주어진객체의바인드를해제한다. 원격객체 1 service1 Naming.bind() 원격객체 2 service2 RmiRegistry 1) service1: 원격객체 1 2) service2: 원격객체 2... [ 그림 11-4] RmiRegistry 에원격객체등록 4 장 Remote Method Invocation(RMI) 271
Client Naming.lookup ("rmi://server/name") 인터넷 원격객체 1 RmiRegistry ------------ 1 name : 원격객체 1 2 name2: 원격객체 2... [ 그림 4-12] Naming.lookup() 을통한원격객체검색 다음은 Naming 클래스의 list() 메소드를사용하여로컬호스트의 rmiregistry 에등록된 모든객체를나열하는예제프로그램이다. ShowAll.java import java.rmi.*; public class ShowAll { public static void main(string[] args) { try { String[] names = Naming.list(""); for (int i = 0; i < names.length; i++) System.out.println(names[i]); catch(exception e) { System.out.println(e); 272 자바네트워크프로그래밍
실행결과 //:1099/hello 4.2.5 객체를인자로사용하는 RMI Serializable 을구현한클래스는원격메소드호출 (RMI) 에사용되는인자와반환값으로 사용될수있다. 이예제에서는 Record 클래스를정의하고, RMI 에서 Record 배열을인자 와반환값으로사용한다. Record.java import java.io.*; public class Record implements Serializable { public static final long serialversionuid=12131288l; String name; int math, eng, total; Record(String n, int m, int e, int t) { name = n; math = m; eng = e; total = t; public String tostring() { return (name+": "+math+" "+eng+" "+total); 4 장 Remote Method Invocation(RMI) 273
Score.java import java.rmi.*; public interface Score extends Remote { public Record[] compute(record[] r) throws RemoteException; ScoreImpl.java import java.rmi.*; import java.rmi.server.*; import java.rmi.registry.*; public class ScoreImpl extends UnicastRemoteObject implements Score { static final long serialversionuid=11229841l; public ScoreImpl() throws RemoteException { // 생성자정의필요 public Record[] compute(record[] r) throws RemoteException { for(int i=0; i<r.length; i++) r[i].total = r[i].math + r[i].eng; return r; public static void main(string[] args) { try { LocateRegistry.createRegistry(1099); ScoreImpl s = new ScoreImpl(); Naming.rebind("ScoreServer2",s); System.out.println("Server is turned on"); catch(exception e) { System.out.println("Exception: "+e); 274 자바네트워크프로그래밍
ScoreClient.java import java.rmi.*; public class ScoreClient { public static void main(string[] args) { String url = "rmi://"+args[0]+"/scoreserver2"; Record[] r2 = { new Record("Kim ",88, 92, 0), new Record("Lee ",87,46, 0), new Record("Jung", 77, 68, 0) ; try { Score s = (Score) Naming.lookup(url); Record[] r1= s.compute(r2); for(int i=0; i<r1.length; i++) System.out.println(i+": "+r1[i]); catch(exception e) { System.out.println("Exception: "+e); 예제실행과정 1 서버측에필요한파일 - Record.class - Score.class - ScoreImpl_Stub.class - ScoreImpl.class 4 장 Remote Method Invocation(RMI) 275
2 서버측실행결과 C:\> java ScoreImpl Server is turned on 3 클라이언트측에필요한파일 - Record.class - Score.class - ScoreImpl_Stub.class - ScoreClient.class 4 클라이언트측실행결과 C:\> java ScoreClient 127.0.0.1 0: Kim : 88 92 180 1: Lee : 87 46 133 2: Jung: 77 68 145 [ 프로그래밍연습 ] 클라이언트는이름과성적의배열을전송하고, 서버는 1등한학생 (1 명 ) 을반환하는프로그램을작성하시오. 1등은모든과목의합이가장큰값을가지는학생이다. 사용할원격인터페이스와클라이언트프로그램은다음과같다. Score2.java import java.rmi.*; public interface Score2 extends Remote { public Record compute(record[] r) throws RemoteException; Score2Client.java import java.rmi.*; public class Score2Client { 276 자바네트워크프로그래밍
public static void main(string[] args) { String url = "rmi://"+args[0]+"/scoreserver3"; Record[] r2 = { new Record("Cho ", 79, 80, 0), new Record("Kim ",88, 92, 0), new Record("Lee ",87,46, 0), new Record("Jung", 77, 68, 0) ; try { Score2 s = (Score2) Naming.lookup(url); Record r1= s.compute(r2); System.out.println("Rank 1: "+r1); catch(exception e) { System.out.println("Exception: "+e); 4.2.6 RMI 예제프로그램 1) 주소록프로그램다음은 RMI를사용한간단한전화번호부예제프로그램이다. 이름과전화번호를저장하 기위해서 Properties 클래스를사용하였다. Telephone.java import java.io.*; public class Telephone implements Serializable { final static long serialversionuid=1213459023l; String name, tel; Telephone(String n, String t) { 4 장 Remote Method Invocation(RMI) 277
name = n; tel = t; public String tostring() { return(name+ "'s phone: " + tel); String getname() { return name; String gettel() { return tel; 원격인터페이스 : Tel_Book.java import java.rmi.*; public interface Tel_Book extends Remote { public void store() throws RemoteException; public void save(telephone t) throws RemoteException; public Telephone find(string name) throws RemoteException; 서비스구현파일 : Tel_BookImpl.java import java.io.*; import java.rmi.*; import java.rmi.server.*; import java.util.*; public class Tel_BookImpl extends UnicastRemoteObject implements Tel_Book { final static long serialversionuid = 58483L; Properties prop; public Tel_BookImpl() throws RemoteException { prop = new Properties(); try { 278 자바네트워크프로그래밍
prop.load(new FileInputStream("tel.dat")); catch(ioexception exp) { System.out.println(exp); public void store() throws RemoteException { try { prop.store(new FileOutputStream("tel.dat"),"tel-book"); catch(ioexception exp) { System.out.println(exp); public void save(telephone t) throws RemoteException { prop.setproperty(t.getname(), t.gettel()); public Telephone find(string name) throws RemoteException { String tel = prop.getproperty(name); return new Telephone(name, tel); public static void main(string args[]) { try { Tel_BookImpl ts= new Tel_BookImpl(); Naming.rebind("TelServer", ts); System.out.println("Server ready"); catch(exception e) {System.out.println(e); 클라이언트 Tel_BookClient.java import java.awt.*; import java.awt.event.*; import java.rmi.*; 4 장 Remote Method Invocation(RMI) 279
public class Tel_BookClient extends Frame implements ActionListener { static final long serialversionuid = 12312354L; private TextField namefield, telfield; private Button find, enter; private static Tel_Book tp; public Tel_BookClient() { super("telephone Client"); setsize(240, 120); setlayout( new GridLayout( 3, 2, 4, 2 ) ); // create the components of the Frame add( new Label( " Name: " ) ); namefield = new TextField( 12 ); add( namefield ); add( new Label( "Tel. Number" ) ); telfield = new TextField( 12 ); add( telfield ); find = new Button( "Find" ); find.addactionlistener( this ); add( find ); enter = new Button( "Enter" ); enter.addactionlistener( this ); add( enter ); setvisible( true ); addwindowlistener( new WindowAdapter() { public void windowclosing( WindowEvent e ) { try { 280 자바네트워크프로그래밍
tp.store(); System.exit( 0 ); catch(remoteexception exp) { System.out.println(exp); ); public void actionperformed(actionevent e) { try { if (e.getsource() == enter) { String m = namefield.gettext().trim(); String n = telfield.gettext().trim(); Telephone t = new Telephone(m,n); tp.save(t); namefield.settext(""); telfield.settext(""); else { Telephone t = tp.find(namefield.gettext().trim()); String n = t.gettel(); telfield.settext(n); catch(remoteexception exp) { public static void main(string args[]) { String ip; if (args.length <1) ip = "127.0.0.1"; else ip = args[0]; try { String url = "rmi://" + ip + "/TelServer"; tp = (Tel_Book)Naming.lookup(url); catch(exception e) { System.out.println("No Server"); System.exit(0); new Tel_BookClient(); 4 장 Remote Method Invocation(RMI) 281
실행순서및실행결과 1 인터페이스, 서버, 클라이언트코드컴파일 C:\rmi\ex4>javac Tel_Book.java Tel_BookImpl.java C:\rmi\ex4>javac Tel_BookClient.java 2 스터브클래스생성 C:\rmi\ex4>rmic -v1.2 Tel_BookImpl 3 rmiregistry 실행 C:\rmi\ex4>start rmiregistry 4 서버측필요파일 5 클라이언트측필요파일 - Tel_BookImpl.class - Tel_Book.class - Tel_BookImpl_Stub.class - Telephone.class - Telephone.class - Tel_Book.class - Tel_BookImpl_Stub.class - Tel_BookClient.class - Tel_BookClient$1.class 6 서버의실행 ( 처음엔 tel.dat 파일이없으므로예외발생 ) C:\rmi\ex4>java Tel_BookImpl java.io.filenotfoundexception: tel.dat ( 파일을찾을수없습니다 ) Server ready 7 클라이언트의실행 C:\rmi\ex4\client>java Tel_BookClient ikim.hansung.ac.kr - 이름과전화번호를입력하고 [Enter] 버튼을클릭하면저장된다. - 이름만입력하고 [Find] 버튼을클릭하면, 전화번호가검색된다. 282 자바네트워크프로그래밍
[ 프로그램연습 ] [Delete] 와 [End] 버튼을추가하시오. Delete 버튼을클릭하면, 현재 Name 필드에입력된이름을전화번호부에서삭제한다. End 버튼을클릭하면프로그램실행을종료한다. Delete 버튼을구현하기위해서는 Properties 의상위클래스인 java.util.hashtable 에서지원되는 remove() 메소드를사용한다. 2) File 송수신프로그램 다음은클라이언트와서버간에파일을송수신할수있는프로그램이다. 원격인터페이스 파일에파일의나열, 파일 upload(), 파일 download() 에관한메소드를제공한다. 인터페이스파일 : FileTrans.java import java.rmi.*; public interface FileTrans extends Remote { public String[] list(string dir) throws RemoteException; public byte[] download(string file) throws RemoteException; public int upload(string file, byte[] con) throws RemoteException; 다음 FileImpl.java 는원격메소드를구현한클래스이다. ( 파일의나열, 파일 upload, 파일 download 의기능 ) 사용될메소드의반환값, 인자들은반드시 java.io.serializable 인터페이스를구현한것이어야한다. 파일서버파일 : FileImpl.java import java.io.*; import java.rmi.*; import java.rmi.server.unicastremoteobject; public class FileImpl extends UnicastRemoteObject implements FileTrans { static final long serialversionuid=19010284l; 4 장 Remote Method Invocation(RMI) 283
public FileImpl() throws RemoteException{ public String[] list(string dir) { try { File file = new File(dir); return file.list(); catch(exception e) { System.out.println("error: "+e); return null; public byte[] download(string name){ try { File file = new File(name); byte buffer[] = new byte[(int)file.length()]; FileInputStream inf = new FileInputStream(name); inf.read(buffer,0,buffer.length); inf.close(); System.out.println(name+" is downloaded"); return(buffer); catch(exception e){ System.out.println("FileImpl: "+e); return(null); public int upload(string name, byte[] con) { try { File file = new File(name); String m = new String(con); FileOutputStream out = new FileOutputStream(name); out.write(con, 0, con.length); out.flush(); out.close(); System.out.println(file+" is uploaded"); 284 자바네트워크프로그래밍
return(0); catch(exception e) { System.out.println("error: "+e); return(-1); public static void main(string argv[]) { try { FileImpl fs = new FileImpl(); Naming.rebind("FileServer", fs); System.out.println("Server Ready"); catch(exception e) { System.out.println("FileServer: "+e); 다음은파일을나열하는 RMI 클라이언트프로그램이다. 파일 List 클라이언트 : FileList.java import java.rmi.*; public class FileList{ public static void main(string argv[]) { if(argv.length!= 2) System.out.println("Specify IP & Directory"); else try { String name = "rmi://" + argv[0] + "/FileServer"; FileTrans fi = (FileTrans) Naming.lookup(name); String[] dirs = fi.list(argv[1]); System.out.println("File Server's File List"); for(int x=0; x<dirs.length; x +=2) { 4 장 Remote Method Invocation(RMI) 285
System.out.print(dirs[x]+"\t"); if(x<dirs.length-1) System.out.println(dirs[x+1]); else System.out.println(); catch(exception e) { System.out.println(e); 다음은파일을다운로드하는 RMI 클라이언트프로그램이다. 파일다운로드클라이언트 : FileDown.java import java.io.*; import java.rmi.*; public class FileDown{ public static void main(string argv[]) { if(argv.length!= 2) System.out.println("Specify IP & FileName"); else try { String name = "rmi://" + argv[0] + "/FileServer"; FileTrans fi = (FileTrans) Naming.lookup(name); byte[] fdata = fi.download(argv[1]); BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(argv[1])); out.write(fdata, 0, fdata.length); out.flush(); out.close(); System.out.println("File '"+argv[1]+"' down success"); catch(exception e) { 286 자바네트워크프로그래밍
System.out.println(e); 다음은파일을업로드하는 RMI 클라이언트프로그램이다. 파일업로드클라이언트 : FileUp.java import java.io.*; import java.rmi.*; public class FileUp{ public static void main(string argv[]) { if(argv.length!= 2) System.out.println("Specify IP & FileName"); else try { String name = "rmi://" + argv[0] + "/FileServer"; FileTrans fs = (FileTrans) Naming.lookup(name); String fname = argv[1]; File file=new File(fname); byte[] buf = new byte[(int)file.length()]; BufferedInputStream input = new BufferedInputStream(new FileInputStream(fname)); input.read(buf, 0, buf.length); int status= fs.upload(fname, buf); if(status==0) System.out.println("Upload Success"); input.close(); catch(exception e) { System.out.println(e); 4 장 Remote Method Invocation(RMI) 287
실행과정및실행결과 1) 서버측에필요한실행파일 - FileImpl_Stub.class - FileTrans.class - FileImpl.clas 2) 서버실행과정 E:\>start rmiregistry E:\file\server>java FileImpl Server Ready FileImpl.class is downloaded FileDown.class is uploaded 3) 클라이언트측에필요한실행파일 - FileImpl_Stub.class - FileTrans.class - FileList.class - FileUp.class - FileDown.class 4) 클라이언트측에필요한실행파일 E:\file\client>java FileList ikim.hansung.ac.kr Specify IP & Directory E:\file\client>java FileList ikim.hansung.ac.kr. File Server's File List FileImpl.class E:\file\client>java FileList ikim.hansung.ac.kr.. File Server's File List 288 자바네트워크프로그래밍
FileTrans.java FileImpl.java FileList.java FileDown.java FileUp.java server client E:\file\client>java FileDown ikim.hansung.ac.kr FileImpl.class File 'FileImpl.class' down success E:\file\client>java FileUp ikim.hansung.ac.kr FileDown.class Upload Success rmiregistry를실행할때주의할점은 CLASSPATH에명시된디렉토리에인터페이스파일과스터브파일등실행에필요한파일들을위치시켜야한다는것이다. 만일 CLASSPATH=.;C:\mine 으로설정되었다면, rmiregistry가실행된디렉토리또는 C:\mine 에실행에필요한파일들을위치시켜야한다. [Home Work] - 파일을전송하는 RMI 예제프로그램을변경하시오. - FileList 를실행하였을때, 파일이디렉토리인경우에파일이름뒤에 / 를붙이시오. ( 예, sub/ ) - 서버가현제디렉토리의값을내부변수에기억하도록하시오. ( 서버가실행된디렉토리가초기값임 ) - 클라이언트 pwd 를실행하면 (print working directory), 서버의현재디렉토리값을출력하도록하시오. - 클라이언트 cd 를실행하면 (change directory), 서버의현재디렉토리를변경하시오. 물론 cd 실행시에, 변경하고자하는디렉토리이름도명시하여야한다. [ 프로그래밍연습 ] 다음은서버가발생하는임의의정수 (0-2000 사이 ) 를추측맞히는 RMI 프로그램이다. 추측하는수를전송하면, 서버는발생된수와비교하여문자열을반환한다. 교수자의 PC 에는서버를실행하고, 학생들은원격인터페이스와서비스구현파일을참조하여클라이언트프로그램을작성하여서버와 RMI로통신한다. 프로그램이발생한난수를맞힐때까지동일한학생에게만접속을허용한다. 학생들은 GuessClient.java 의 4줄을완성한후, RMI를실행할수있다. 4 장 Remote Method Invocation(RMI) 289
GuessNum.java import java.rmi.*; public interface GuessNum extends Remote { String guess(string name, int num) throws RemoteException; GuessNumImpl.java import java.rmi.*; import java.rmi.server.*; import java.util.random; public class GuessNumImpl extends UnicastRemoteObject implements GuessNum { static final long serialversionuid=8549120l; int mynum, max=2000,cnt=0; Random rd=null; String msg, name=null; public GuessNumImpl() throws RemoteException { rd = new Random(); mynum=rd.nextint(max); // System.out.println("num="+myNum); public String guess(string n, int num) throws RemoteException { if(name==null) name=n; else if(!name.equals(n)) { System.out.println("Sorry This is not your turn:"+n); return "Not this time"; cnt++; if (num==mynum) { 290 자바네트워크프로그래밍
msg = "Congraturations!!!"; mynum=rd.nextint(max); System.out.println(cnt+": "+name+": "+msg); System.out.println("The number is "+num); System.out.println("We start again"); name = null; cnt=0; else { if(num > mynum) msg="bigger than Mine"; else msg="smaller than Mine"; System.out.println(cnt+": "+name+": "+num+" is "+msg); return msg; public static void main(string[] args)throws Exception { GuessNumImpl gn = new GuessNumImpl(); Naming.rebind("GuessNumber", gn); System.out.println("Guess Server Ready"); GuessClient.java import java.io.*; import java.rmi.*; public class GuessClient { public static void main(string[] args) throws Exception { String serverip, name; BufferedReader in= new BufferedReader (new InputStreamReader(System.in)); System.out.print("type Server IP: "); // (IP 를읽어서변수 IP에저장 ) 작성하시오. System.out.print("type Your name: "); // ( 이름을읽어서변수 name에저장 ) 작성하시오. 4 장 Remote Method Invocation(RMI) 291
int num; String answer; // ( 네이밍서비스 look up 부분 ) 작성하시오. while(true){ try { System.out.print("Guess a num:"); num=integer.parseint(in.readline()); if (num <= 0) break; // ( 원격객체호출부분 ) 작성하시오. System.out.println(answer); catch(numberformatexception ee){ System.out.println("Number format error: try again"); System.out.println("Execution End"); 실행결과 type Server IP: 211.48.44.82 type Your name: 김정수 Guess a num:1200 Bigger than Mine Guess a num:600 Smaller than Mine Guess a num:900 Bigger than Mine Guess a num:750 Bigger than Mine Guess a num:675 Smaller than Mine Guess a num:720 Smaller than Mine 292 자바네트워크프로그래밍
Guess a num:735 Smaller than Mine Guess a num:745 Bigger than Mine Guess a num:740 Bigger than Mine Guess a num:738 Congraturations!!! 서버측실행결과 We start again 1: 김정수 : 1200 is Bigger than Mine 2: 김정수 : 600 is Smaller than Mine 3: 김정수 : 900 is Bigger than Mine 4: 김정수 : 750 is Bigger than Mine 5: 김정수 : 675 is Smaller than Mine 6: 김정수 : 720 is Smaller than Mine 7: 김정수 : 735 is Smaller than Mine 8: 김정수 : 745 is Bigger than Mine 9: 김정수 : 740 is Bigger than Mine 10: 김정수 : Congraturations!!! The number is 738 We start again 4 장 Remote Method Invocation(RMI) 293
4.2.7 RMI CallBack 지금까지작성한 RMI 프로그램은클라이언트가서버의메소드를호출하고, 서버는클라이언트의요구에따라적절한메소드의실행결과를반환하는것이었다. 일부분산응용프로그램의경우, 서버가중요한정보를급하게전송할필요가있다. 예를들어, 증권응용프로그램의경우중요한변동사항이발생하였을때, 급하게서버가클라이언트에게이러한정보를알려줄필요가있다. 이러한문제를해결하기위해서제안된것이바로 Call-Back 이다. RMI callback은클라이언트가서버의메소드를호출하는것이아니라, 서버가클라이언트의메소드호출을허용하는것을의미한다. 일반 RMI call Client RMI Server Call Back [ 그림 4-6] RMI Call-Back o RMI CallBack 프로그램의특징 1 클라이언트메소드의원격호출을위한인터페이스파일이필요하다. 2 클라이언트객체 (obj) 를생성한후, UnicastRemoteObject.exportObject(obj) 인자로사용하여서버의접근이가능하도록한다. 3 클라이언트를서버에등록해야한다. 4 클라이언트의실행이종료되지않는다. 다음은서버가 4 초마다클라이언트에게 Date 객체를전송하는 RMI 예제프로그램이다. 실행과정은다음과같다. 1 클라이언트가서버에게자신을등록한다. 서버는등록된클라이언트참조를저장한다. 2 서버는저장된클라이언트객체의참조를이용하여클라이언트메소드를호출할수있다. 3 서버는주기적으로 Date객체를생성하여, 클라이언트 ( 메소드를호출하여 ) 에게전송한다. 294 자바네트워크프로그래밍
서버인터페이스파일 : DateServer.java - 원격호출가능한서버메소드의원형을정의한다. import java.rmi.*; public interface DateServer extends Remote { public void connect(dateclient c) throws RemoteException; public void disconnect() throws RemoteException; 클라이언트인터페이스파일 : DateClient.java - 원격호출가능한클라이언트메소드의원형을정의한다. import java.rmi.*; import java.util.date; public interface DateClient extends Remote { public void senddate(date d) throws RemoteException; 서버구현파일 : DateServerImpl.java import java.rmi.*; import java.rmi.server.*; import java.util.date; public class DateServerImpl extends UnicastRemoteObject implements DateServer { static final long serialversionuid=8495390l; DateClient client = null; public DateServerImpl() throws RemoteException // 생성자메소드는반드시명시해야함. { public void connect(dateclient c) throws RemoteException // 클라이언트등록 { client = c; 4 장 Remote Method Invocation(RMI) 295
System.out.println("A Client connected"); public void disconnect() throws RemoteException { client = null; System.out.println("Client disconnected"); public static void main(string[] args) throws Exception { DateServerImpl ds = new DateServerImpl(); Naming.rebind("rmi://localhost/DateServer", ds); System.out.println("Date Server Ready"); Date date; while(true) { if(ds.client!= null) { date = new Date(); ds.client.senddate(date); Thread.sleep(4000); 클라이언트구현파일 : DateClientImpl.java import java.rmi.*; import java.rmi.server.*; import java.util.date; import java.awt.*; import java.awt.event.*; public class DateClientImpl extends Frame implements DateClient { final static long serialversionuid=8761020l; TextArea ta = new TextArea(); 296 자바네트워크프로그래밍
public void senddate(date d) { ta.append("\ndate from Server:"+d); public DateClientImpl() { super("rmi Client Window"); setsize(380,280); add(ta, BorderLayout.CENTER); setvisible(true); public static void main(string args[]) { String ip; DateClientImpl dc = new DateClientImpl(); try { UnicastRemoteObject.exportObject(dc);// 중요한차이점 if(args.length>0) ip = "rmi://"+args[0]; else ip="rmi://localhost"; final DateServer ds = (DateServer) Naming.lookup(ip+"/DateServer"); ds.connect(dc); dc.addwindowlistener( new WindowAdapter() { public void windowclosing(windowevent e) { try { ds.disconnect(); catch(exception ee) { System.exit(0); ); catch(exception ex) { System.out.println(ex); 4 장 Remote Method Invocation(RMI) 297
실행과정및실행결과 1 모든자바코드컴파일 C:\> javac *.java 2 rmic를사용하여서버의스터브파일생성 C:\> rmic DateServerImpl 3 rmic를사용하여클라이언트의스터브파일생성 C:\> rmic DateClientImpl 4 서버측에서 rmiregistry와 DateServerImpl을실행 C:\> start rmiregistry C:\> java DateServerImpl Date Server Ready A Client connected Client disconnected 5 클라이언트측에서 DateClientImpl을실행한다. C:\> java DateClientImpl 211.48.46.122 [ 프로그래밍연습 ] 앞의 RMI 예제프로그램은하나의클라이언트에게만 Date 객체를전송한다. 다음은 Vector 를사용하여다수의클라이언트를등록하고, 등록된모든클라이언트에게 Date 객체를 broadcast 하도록변경한것이다. 코드의전후관계를살펴보고, 일부미완성된코드를완성하고, 실행하시오. 298 자바네트워크프로그래밍
DateServer2.java import java.rmi.*; public interface DateServer2 extends Remote { public void connect(dateclient2 c) throws RemoteException; public void disconnect(dateclient2 c) throws RemoteException; DateClient2.java import java.rmi.*; import java.util.date; public interface DateClient2 extends Remote { public void senddate(date d) throws RemoteException; DateServer2Impl.java import java.rmi.*; import java.rmi.server.*; import java.util.*; public class DateServer2Impl extends UnicastRemoteObject implements DateServer2 { static final long serialversionuid=84953915l; Vector vc = new Vector(); DateClient2 client = null; public DateServer2Impl() throws RemoteException // 생성자는반드시명시 { public void connect(dateclient2 c) throws RemoteException // 클라이언트등록 { vc.addelement(c); System.out.println("New Client connected"); 4 장 Remote Method Invocation(RMI) 299
public void disconnect(dateclient2 c) throws RemoteException { vc.removeelement(c); System.out.println("A Client disconnected"); public void execute() throws Exception { Date date=null; while(true) { date = new Date(); for(int i=0;i< vc.size();i++){ client=(dateclient2)vc.elementat(i); try { client.senddate(date); catch(remoteexception e){ vc.remove(client); System.out.println("remove err:"+e); Thread.sleep(4000); public static void main(string[] args) throws Exception { DateServer2Impl ds = new DateServer2Impl(); Naming.rebind("rmi://localhost/DateServer2", ds); System.out.println("Date Server 2 Ready"); ds.execute(); DateClient2Impl.java import java.rmi.*; import java.rmi.server.*; 300 자바네트워크프로그래밍
import java.util.date; import java.awt.*; import java.awt.event.*; public class DateClient2Impl extends Frame implements DateClient2 { final static long serialversionuid=8761020l; TextArea ta = new TextArea(); public void senddate(date d) { ta.append("\ndate from Server:"+d); public DateClient2Impl() { super("rmi Client Window"); setsize(380,280); add(ta, BorderLayout.CENTER); setvisible(true); public static void main(string args[]) { String ip; final DateClient2Impl dc = new DateClient2Impl(); try { UnicastRemoteObject.exportObject(dc);// 중요한차이점 if(args.length>0) ip = "rmi://"+args[0]; else ip="rmi://localhost"; final DateServer2 ds = (DateServer2) Naming.lookup(ip+"/DateServer2"); ds.connect(dc); dc.addwindowlistener( new WindowAdapter() { public void windowclosing(windowevent e) { try { ds.disconnect(dc); catch(exception ee) { System.exit(0); ); 4 장 Remote Method Invocation(RMI) 301
catch(exception ex) { System.out.println(ex); o RMI 채팅프로그램다음은 RMI 콜백을사용한채팅예제프로그램이다. 1 클라이언트실행시에명령행인자로서채팅서버의 IP를명시하여야한다. 2 클라이언트를실행하면, 클라이언트는서버에자신을등록하여콜백이가능하도록하였다. 3 인터페이스파일은서버와클라이언트양쪽모두필요하다. 4 채팅서버는백터를사용하여, 전송받은메시지를 broad-casting 하였다. 5 클라이언트가종료되면메시지전송시에예외가발생하고, 서버측의백터에서삭제한다. 서버측원격인터페이스 : ChatServer.java import java.rmi.*; public interface ChatServer extends Remote { public void sendmsg(string m) throws RemoteException; public void connect(chatclient c)throws RemoteException; 클라이언트측원격인터페이스 : ChatClient.java import java.rmi.*; public interface ChatClient extends Remote { public void send_msg(string m)throws RemoteException; 채팅서버구현파일 : ChatServerImpl.java import java.rmi.*; 302 자바네트워크프로그래밍
import java.rmi.server.*; import java.util.*; public class ChatServerImpl extends UnicastRemoteObject implements ChatServer { static final long serialversionuid=198023l; Vector vc = new Vector(); String m=null; ChatClient client=null; public ChatServerImpl() throws RemoteException { public void connect(chatclient c)throws RemoteException { vc.addelement(c); public void sendmsg(string msg)throws RemoteException { for(int i=0; i<vc.size();i++){ ChatClient cc=(chatclient) vc.elementat(i); try { cc.send_msg(msg); catch(remoteexception e){ System.out.println("Error in"+cc); vc.removeelement(cc); public static void main(string[] args)throws Exception{ ChatServerImpl sv=new ChatServerImpl(); Naming.rebind("rmi://127.0.0.1/chat",sv); System.out.println("RMI Chat Server Ready"); 4 장 Remote Method Invocation(RMI) 303
채팅클라이언트구현파일 : ChatClientImpl.java import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.border.*; import java.rmi.*; import java.rmi.server.*; public class ChatClientImpl extends JFrame implements ChatClient, ActionListener { static final long serialversionuid = 929395L; JTextField tf = new JTextField("Your name:"); JTextArea ta = new JTextArea(); String name=null; ChatServer cs=null; public ChatClientImpl(ChatServer cs) { super("rmi Chatting Client"); this.cs = cs; Container c = getcontentpane(); c.setlayout(new BorderLayout()); tf.setborder(new TitledBorder("Enter Message")); tf.addactionlistener(this); c.add(tf, BorderLayout.SOUTH); c.add(new JScrollPane(ta), BorderLayout.CENTER); setdefaultcloseoperation(exit_on_close); setlocation(200,200); setsize(450,280); setvisible(true); public void actionperformed(actionevent e){ try{ String m=tf.gettext(); 304 자바네트워크프로그래밍
if(name!=null) { cs.sendmsg(name+m+"\n"); else { cs.sendmsg(m+" 님이입장하셨습니다.\n"); name =m+": "; tf.settext(""); catch(exception ee){ System.out.println("error:"+ee); public void send_msg(string m)throws RemoteException { ta.append(m); ta.setcaretposition(ta.gettext().length()); public static void main(string[] args) throws Exception { String ip=null; if(args.length > 0) ip="rmi://"+args[0]; else ip="rmi://127.0.0.1"; ChatServer cs = (ChatServer) Naming.lookup(ip+"/chat"); ChatClientImpl cc = new ChatClientImpl(cs); UnicastRemoteObject.exportObject(cc); cs.connect(cc); o RMI 채팅실행과정 1 모든인터페이스파일과자바응용프로그램을컴파일 C:\> javac *.java 2 rmic 를사용하여서버의스터브파일생성 C:\> rmic -v1.2 ChatServerImpl 4 장 Remote Method Invocation(RMI) 305
3 rmic 를사용하여클라이언트의스터브파일생성 C:\> rmic -v1.2 ChatClientImpl 4 서버실행에필요한클래스파일들 - ChatServerImpl.class - ChatServer.class - ChatClient.class - ChatClientImpl_Stub.class - ChatServerImpl_Stub.class 5 클라이언트시행에필요한클래스파일들 - ChatClientImpl.class - ChatServer.class - ChatClient.class - ChatClientImpl_Stub.class - ChatServerImpl_Stub.class 6 서버측에서 rmiregistry와채팅서버를실행 C:\rmi\ex5\server>start rmiregistry C:\rmi\ex5\server>java ChatServerImpl 7 클라이언트측에서채팅클라이언트를실행 C:\rmi\ex5\client>java ChatClientImpl 127.0.0.1 8 다음과같은채팅창이나타나면, "Your name:" 을삭제하고채팅이름을입력한다. 306 자바네트워크프로그래밍
o 일반 RMI 응용과 RMI 콜백프로그램의차이점 1 클라이언트를위한원격인터페이스파일및스터브파일이필요하다. 2 서버에서클라이언트객체참조필요하다. 서버클래스에서클라이언트객체를등록하는메소드가필요하다. 3 클라이언트객체의 export 필요하다. - UnicastRemoteObject.exportObject(Remote obj) - UnicastRemoteObject.exportObject(Remote obj, int port) [ 연습문제 ] 앞의 RMI 채팅예제프로그램에서채팅에참가한클라이언트하나가종료되면, 어떠한일이발생하는가? ( 채팅서버는메시지를 broadcasting 하기위해서백터에서클라이언트객체를하나씩빼내어메시지전송메소드를호출한다.) [Home Work] 앞의 RMI 채팅프로그램을수정하시오. 채팅참가자명단나열, 밀담기능, 강퇴 ( 강제로퇴장시킴 ) 기능, 파일송수신등필요한기능을추가하시오. 4 장 Remote Method Invocation(RMI) 307
[ 영문해석 ] 다음은자바 RMI에대한설명이다. 이를해석하시오. The Java Remote Method Invocation API, or Java RMI, is a Java application programming interface for performing the object equivalent of remote procedure calls. There are two common implementations of the API. The original implementation depends on Java Virtual Machine (JVM) class representation mechanisms and it thus only supports making calls from one JVM to another. The protocol underlying this Java-only implementation is known as Java Remote Method Protocol (JRMP). In order to support code running in a non-jvm context, a CORBA version was later developed. Usage of the term RMI may denote solely the programming interface or may signify both the API and JRMP, whereas the term RMI-IIOP, read RMI over IIOP, denotes the RMI interface delegating most of the functionality to the supporting CORBA implementation. The original RMI API was generalized somewhat to support different implementations, such as an HTTP transport. Additionally, work was done to CORBA, adding a pass by value capability, to support the RMI interface. Still, the RMI-IIOP and JRMP implementations are not fully identical in their interfaces. The package name is java.rmi Note that with Java versions up to 5.0 it was necessary to compile RMI stubs in a separate compilation step using rmic. Version 5.0 of Java and beyond no longer require this step. 308 자바네트워크프로그래밍
5 장코바 (Corba) 5.1 코바 (Corba) 의소개 자바의 RMI 가개발전에도분산프로그래밍에대한표준화요구가대두되었다. 다양한 프로그래밍언어에대한분산객체프로그래밍표준을 CORBA 라고한다. 1) 분산컴퓨팅환경 - 컴퓨터네트워크와인터넷기술을널리보급됨 - 분산응용프로그램이다양한플렛폼에서실행 - 당시효율적이며, 신뢰성, 확장성이있는분산응용의개발이어려웠음. o OMG - 1989년 4월 3COM, HP, Sun사포함하는 11개회사및단체가모여결성 - OMG(Object Management Group), CORBA 관련정의를책임지는단체 - 분산객체응용프로그램을개발을위한표준가이드라인과객체관리규약정립 - 현재 800여개이상의회원을가지고있는비영리컨소시엄 - 표준안과가이드라인을제정. - 직접 CORBA제품을구현, 판매하지는않음. - OMG 홈페이지 (http://www.omg.org) 5 장코바 (Corba) 311
o CORBA - OMG는 1990년 OMA(Object Management Architecture) 가이드를발표 - OMA에 CORBA 포함. - 분산응용프로그램의개발을단순화하고, 융통성있는고급서비스를제공하는목적 - 분산객체에필요한분야별클래스라이브러리들로구성 - 객체의생성, 소멸, 저장, 트랜잭션기능까지분산객체환경에서필요한모든서비스를제공. [ 그림 5-1] OMA 참조모델 312 자바네트워크프로그래밍
o OMA 모델의구성요소 1) ORB(Object Request Broker) - OMA 모델의가장중요한부분임. - 원격객체서비스요구수신, 원격객체의위치파악, 원격객체와통신, 실행결과 client에반환 - 분산객체간의요구와응답을전송하는통신메커니즘제공 - 실제구현제품의 ORB는클라이언트와서버프로그램에링크되는라이브러리형태로구현. - Client/Server 에서사용하는프로그래밍언어와독립적 (language independent) - ORB는 Cleint( 또는서버 ) 에서사용된언어를표준인터페이스기술언어 (IDL) 로매핑시킴. Client Server ORB 2) 객체서비스 (Object Service) -ORB에구현된기본기능에추가된서비스 -Object life cycle, Naming, Event, Concurrency Control, Query, Persistency, Transaction, Security 5 장코바 (Corba) 313
Service obj. life cycle Naming Events Relationships Externalization Transactions Concurrency Control Property Trader Query Description Defines how CORBA objects are created, removed, moved, and copied Defines how CORBA objects can have friendly symbolic names De-couples the communication between distributed objects Provides arbitrary typed n-ary relationships between CORBA objects Coordinates the transformation of CORBA objects to and from external media Coordinates atomic access to CORBA objects Provides a locking service for CORBA objects in order to ensure serializable access Supports the association of name-value pairs with CORBA objects Supports the finding of CORBA objects based on properties describing the service offered by the object Supports queries on objects 3) 공통기능 (Common Facilities) - 어플리케이션의개발생산성을높이기위해정의된클래스라이브러리 - 객체서비스보다고급수준의서비스 - 사용자인터페이스, 프린트기능, 데이터베이스기능, 시스템관리기능 4) 도메인인터페이스 (Domain Interface) - 금융, 제조, 의료등으로구분, 이에따라필요한서비스를제공. 5) 응용프로그램인터페이스 (Application Interfaces) - 응용클래스는시스템에서제공하는클래스를상속하고, 기능을추가하여작성. 314 자바네트워크프로그래밍
5.2 ORB 구조 : ORB 는통신을담당하는일종의소프트웨어버스또는미들웨어 (middle ware) [ 그림 5-2] CORBA ORB 의구조 1 DII(Dynamic Invocation Interface) - a client call can a remote procedure even if its signature or its name are unknown until runtime. 2 DSI(Dynamic Skeleton Interface) - it allows servers to serve a servant object without prior knowledge of the object's interface. 3 Object Adapter connects a request using object reference with the proper code to service that request. 4 GIOP : General Inter-ORB protocol 5 IIOP : Internet Inter-ORB protocol 5 장코바 (Corba) 315
(1) IDL(Interface Definition Language) 인터페이스 - C++ 와매우유사 - 독자적으로실행파일을생성하지않는인터페이스기술언어 - 원격호출이가능한객체의메소드를정의 - 메소드의이름, 입출력매개변수, 메소드수행에서발생할수있는예외를규정 - C, C++, Smalltalk, Java, Cobol, Ada 언어에대하여 IDL로작성된인터페이스를매핑방법규정 (2) 호출방식 1) 정적호출방식 - IDL 인터페이스로부터생성된 stub를통해서전달되는방식 - 컴파일시간에클라이언트와서버간의통신기능이확정 2) 동적호출방식 - 클라이언트실행시에 ORB 함수를이용하여요청을만들어상대방 ORB에전달하여호출하는방식 - 동적호출방식은보다융통성이높으나, 코드의크기가늘어남. 3) IIOP - CORBA는표준시스템기술 - 수많은소프트웨어업체에서생산하는 CORBA 제품은각기다른구현방법을사용. - 서로다른 CORBA 제품간의연동및기존인터넷과 CORBA간에연동필요 - CORBA ORB간에는통신규약인 GIOP(General Inter-ORB Protocol) 이제정 [ 그림 5-3] IIOP 사용 ORB 연동 316 자바네트워크프로그래밍
4) 객체어댑터 (Object Adapter) o 객체어댑터의역할 1 클라이언트의 CORBA 객체에대한요청을적절한서번트에전달 2 필요에따라 CORBA 객체를활성화 3 서번트에게요청된연산과매개변수를전달 4 등록된 CORBA 객체에대한객체참조자를생성. 5 초기의객체어댑터는 BOA(Basic Object Adapter) 6 1997년 POA(Portable Object Adapter) 규약이제정 o POA를사용한프로그래밍순서 1 ORB_init() 메소드를사용하여 ORB 객체를초기화한다. 2 Root POA에대한객체참조자를획득한다. 3 POA에서사용할서번트 (servant, 서비스구현객체 ) 를생성한다. 4 서번트를활성화된 POA에등록한다. 5 네이밍서비스객체의참조자를획득한다. 6 서번트의이름을네이밍서비스에등록한다. 7 ORB 객체를실행시켜, 원격서비스를시작한다. o POA의정책 1 LifeSpanPolicy - PERSISTENT와 TRANSIENT 로나뉜다. 2 ThreadPolicy - ORB_CTRL_MODEL 또는 SINGLE_THREAD_MODEL 값 3 ObjectIDUniquessPolicy - 가능한값은 UNIQUE_ID 와 MULTIPLE_ID 가있으며, 기본값은 UNIQUE_ID 이다. 4 ServantRetentionPolicy - 가능한값은 RETAIN과 NON_RETAIN 이있으며, 기본값은 RETAIN이다. 5 RequestProcessingPolicy - 가능한값은 USE_ACTIVE_OBJECT_MAP_ONLY, USE_DEFAULT_SERVANT, USE_SERVANT_MANAGER 이며, 기본값은 USE_ACTIVE_OBJECT_MAP_ONLY이다. 5 장코바 (Corba) 317
6 ImplictActivationPolicy - 가능한값은 IMPLICIT_ACTIVATION( 기본값 ), NO_IMPLICT_ACTIVATION 이다. 7 IdAssignmentPolicy - 가능한값은 USER_ID와 SYSTEM_ID( 기본값 ) 이다. 5) CORBA 프로그래밍작성순서 1 서비스객체가제공할서비스를 IDL로기술. 2 IDL로정의된객체정의를사용클라이언트용스터브코드와서버용스켈레톤코드를생성. 3 서버프로그램을적절한프로그램언어 (C, C++, Smalltalk, Java, Ada 등 ) 를사용하여작성 4 IDL로기술한서비스를사용하는클라이언트프로그램을적절한프로그램언어를사용하여작성. 5 서버프로그램과스켈레톤프로그램을컴파일, 링크하여실행코드를생성. 6 클라이언트프로그램과스터브프로그램을컴파일, 링크하여실행코드를생성. 이책에서는 JDK 1.4 이후버전에서지원되는 CORBA 기능을사용하므로, 클라이 언트프로그래밍과서버프로그래밍언어로써모두자바를사용하기로한다. 5.3 간단한 CORBA 예제의실행 o CORBA 예제의작성 1) IDL로서비스를기술한다. [IDL 파일 : Hello.idl] module HelloApp { interface Hello { string sayhello(); long sum(in long x); 318 자바네트워크프로그래밍
; ; 2) IDL 파일을원하는목적언어로번역한다. ( 여기서는자바 ) C:\corba\ex1\>idlj -fall Hello.idl - 생성되는파일들 1 HelloPOA.java 2 _HelloStub.java 3 Hello.java 4 HelloHelper.java 5 HelloHolder.java 6 HelloOperations.java 3) idlj 가생성한파일의컴파일한다. C:\corba\ex1\server>javac HelloApp\*.java 4) 서버파일의작성한다. [ 서버파일 : HelloServer.java] import HelloApp.*; import org.omg.cosnaming.*; import org.omg.cosnaming.namingcontextpackage.*; import org.omg.corba.*; import org.omg.portableserver.*; import org.omg.portableserver.poa; class HelloImpl extends HelloPOA { public String sayhello() { return "Hello World!\n"; public int sum(int x) { if (x>0) return(x+sum(x-1)); else return 0; 5 장코바 (Corba) 319
public class HelloServer { public static void main(string args[]) { try { ORB orb=orb.init(args, null); POA rootpoa = (POA)orb.resolve_initial_references("RootPOA"); rootpoa.the_poamanager().activate(); HelloImpl hello = new HelloImpl(); org.omg.corba.object ref = rootpoa.servant_to_reference(hello); Hello href = HelloHelper.narrow(ref); org.omg.corba.object oref = orb.resolve_initial_references("nameservice"); NamingContextExt ncref = NamingContextExtHelper.narrow(oRef); String name = "Hello"; NameComponent path[] = ncref.to_name(name); ncref.rebind(path, href); System.out.println("Hello Server Ready"); orb.run(); catch(exception e) { e.printstacktrace(); System.out.println("Hello Server Exiting"); 5) 클라이언트의작성한다. [ 클라이언트파일 : HelloClient.java] import HelloApp.*; import org.omg.cosnaming.*; import org.omg.cosnaming.namingcontextpackage.*; import org.omg.corba.*; public class HelloClient { static Hello hello; public static void main(string args[]) 320 자바네트워크프로그래밍
{ try{ ORB orb = ORB.init(args, null); org.omg.corba.object objref = orb.resolve_initial_references("nameservice"); NamingContextExt ncref = NamingContextExtHelper.narrow(objRef); hello = HelloHelper.narrow(ncRef.resolve_str("Hello")); System.out.println(hello.sayHello()); System.out.println("The sum 1 to 100 is "+hello.sum(100)); System.out.println("The sum 1 to 1000 is "+hello.sum(1000)); catch (Exception e) { e.printstacktrace(); 6) 서버와클라이언트의컴파일한다. C:\corba\ex1\>javac HelloServer.java C:\corba\ex1\>javac HelloClient.java 7) 자바에서제공되는 ORB 데몬의실행한다. C:\corba\ex1\>start orbd -ORBInitialPort 1099 8) Hello 서버의실행한다. C:\corba\ex1\>java HelloServer -ORBInitialPort 1099 -ORBInitialHost localhost Hello Server Ready 9) Hello Client 의실행및실행결과는다음과같다. C:\corba\ex1\>java HelloClient -ORBInitialPort 1099 -ORBInitialHost localhost Hello World! The sum 1 to 100 is 5050 The sum 1 to 1000 is 500500 5 장코바 (Corba) 321
[ 프로그래밍연습 ] - 앞의예제는양의정수 n을받아들여, 1부터 n까지의합을반환한다. 이를변경하여, 서버가두정수를합하여반환하도록변경하시오. - 클라이언트는 server.add(int a, int b) 를호출한다. - 서버는 adding이라는이름으로네이밍서버에등록한다. - 서버는클라이언트로부터두정수를받아들여, 그합을반환한다. (2) HelloServer.java 프로그램의이해 1 필요패키지수입단계 import HelloApp.*; // 스터브파일을포함한패키지수입 import org.omg.cosnaming.*; // Naming 서비스에필요한패키지수입 import org.omg.cosnaming.namingcontextpackage.*; // Naming 서비스에서발생하는예외처리패키지수입 import org.omg.corba.*; // 모든 CORBA 응용프로그램에필수적인패지키수입 import org.omg.portableserver.*; import org.omg.portableserver.poa; // Portable 서버에필요한패키지의수입 2 서번트정의단계 class HelloImpl extends HelloPOA { // Hello 인터페이스구현객체는 HelloPOA를확장하여정의 public String sayhello() { // sayhello() 메소드의구현 return "Hello World!\n"; public int sum(int x) { // sum() 메소드의구현 if(x>0) return(x+sum(x-1)); else return 0; 322 자바네트워크프로그래밍
3 서버클래스정의 public class HelloServer { public static void main(string args[]) { try { ORB orb=orb.init(args, null); // 명령행인자의값을읽어 ORB를초기화 POA rootpoa = (POA)orb.resolve_initial_references("RootPOA"); rootpoa.the_poamanager().activate(); // 루트POA를구하여활성화시킴 HelloImpl hello = new HelloImpl(); // Hello 서비스구현객체 ( 서번트 ) 의생성 org.omg.corba.object ref = rootpoa.servant_to_reference(hello); Hello href = HelloHelper.narrow(ref); // 객체참조를서번트와연관시키고 // narrow() 메소드를사용하여 Hello 클래스로형변환시킴 org.omg.corba.object oref = orb.resolve_initial_references("nameservice"); // Name 서비스객체의초기참조를구함 NamingContextExt ncref = NamingContextExtHelper.narrow(oRef); // 네임서버에객체등록이가능하도록네임서비스객체를 // narrow() 메소드로 NamingContextExt 클래스로형변환. String name = "Hello"; NameComponent path[] = ncref.to_name(name); ncref.rebind(path, href); // 네임서버에 Hello라는이름으로서번트를등록 System.out.println("Hello Server Ready"); orb.run(); 5 장코바 (Corba) 323
// ORB 를실행시켜, 원격호출서비스개시 catch(exception e) { e.printstacktrace(); System.out.println("Hello Server Exiting"); 5.4 HelloClient.java 프로그램의이해 클라이언트소스파일 : HelloClient.java // 필요한패키지의수입 import HelloApp.*; import org.omg.cosnaming.*; import org.omg.cosnaming.namingcontextpackage.*; import org.omg.corba.*; public class HelloClient { static Hello hello; // Hello 인터페이스참조객체의선언 public static void main(string args[]) { try{ ORB orb = ORB.init(args, null); // 명령행인자사용 ORB 객체의초기화 org.omg.corba.object objref = orb.resolve_initial_references("nameservice"); // 네임서비스객체의초기참조를구함 NamingContextExt ncref = NamingContextExtHelper.narrow(objRef); // 네임서비스에등록, 검색기능을수행하기위해, narrow() 를 // 사용하여네임서비스객체의형변환 hello = HelloHelper.narrow(ncRef.resolve_str("Hello")); 324 자바네트워크프로그래밍
// 네임서비스와 HelloHelper.narrow() 메소드를사용하여 // Hello 서번트객체의참조를구함 System.out.println(hello.sayHello()); // Hello 서번트객체의 sayhello() 메소드실행 System.out.println("The sum 1 to 100 is "+hello.sum(100)); System.out.println("The sum 1 to 1000 is "+hello.sum(1000)); // Hello 서번트객체의 sum() 메소드실행 catch (Exception e) { e.printstacktrace(); org.omg.portableserver.poa interface POA 객체는구현된서비스객체를관리한다. POA는 Object ID로구분되는객체네임공간 (name space) 을지원한다. POA는다른 POA를위한 name space도지원한다. POA객체는 root POA가가장먼저생성되고, 각 POA는자식 POA를생성할수있다. (POA hierarchy를형성할수있다 ) 다른프로세스에게 POA객체를보이기위해서는다음의형식 ("ORB::object_to_string") 을사용한다. POA 구현객체의메소드 o POA create_poa(string adapter_name, POAManager amanager, Policy[] policies) - this method creates a new POA as a child of the target POA o LifespanPolicy create_lifespan_policy(lifespanpolicy value) - These operations each return a reference to a policy object with the specified policy type. o POAManager the_poamanger() - 현 POA 객체와연관된 POA manager 를반환한다. o Servant get_servant() - This operation returns the default servant associated with the POA. o byte[] id() - This returns the unique id of the POA in the process in which it is created. 5 장코바 (Corba) 325
o Object servant_to_reference(servant p_servant) - This operation requires the RETAIN policy and either the UNIQUE_ID or IMPLICIT_ACTiVATION policy if invoked outside the context of an operation dispatched by this POA. org.omg.corba.orb 클래스 이클래스는 CORBA Object Request Broker 기능을위한 API를제공한다. ORB는 CORBA 객체들이상호통신하는것을가능하게한다. ORB를사용하여프로그래밍하는절차는다음과같다. 1. initializes the ORB implementation by supplying values for predefined properties and environmental parameters 2. obtains initial object references to services such as the NameService using the method resolve_initial_references 3. converts object references to strings and back 4. connects the ORB to a servant (an instance of a CORBA object implementation) and disconnects the ORB from a servant 5. run ORB object with run() method ORB 클래스의메소드 o void connect(object obj) - Connect the given servant object to the ORB o static ORB init() - ORB 객체를반환한다. o static ORB init(applet app, Properties props) - 주어진애플릿을위한 ORB 객체를생성한다. o static ORB init(string[] args, Properties props) - standalone 응용을위한새로운 ORB 객체를반환한다. o abstract String[] list_initial_service() - Returns a list of the initially available CORBA object references, such as "NameService" and "InterfaceRepository". 326 자바네트워크프로그래밍
o abstract String object_to_string(object obj) - 주어진 CORBA객체를문자열로변환한다. o abstract Object resolve_initial_reference(string object_name) - Resolves a specific object reference from the set of available initial service names. o void run() - This operation blocks the current thread until the ORB has completed the shutdown process, initiated when some thread calls shutdown. o abstract Object set_parameters(applet app, Properties props) - Allows the ORB implementation to be initialized with the given applet and parameters. o set_parameters(string[] args, Properties props) - Allows the ORB implementation to be initialized with the given parameters and properties. o shutdown(boolean wait_for_completion) - Instructs the ORB to shut down, which causes all object adapters to shut down, in preparation for destruction. If the wait_for_completion parameter is true, this operation blocks until all ORB processing (including processing of currently executing requests, object deactivation, and other object adapter operations) has completed. o string_to_object(string str) - Converts a string produced by the method object_to_string back to a CORBA object reference. org.omg.cosnaming.namingcontextext NamingContextExt는 NamingContext 의확장클래스이며, 중복허용되지않는이름과연관된객체를저장한다. 하나의객체에여러이름을바인딩 (binding) 할수도있다. NamingContextExt를사용하면, URL 기반이름을바인딩하거나검색할수있다. 지원되는메소드 o Object resolve_str(string stringfied_name) throws NotFound, CannotProceed, 5 장코바 (Corba) 327
InvalidName - 주어진이름을사용하여, 검색된객체를반환한다. o NameComponent[] to_name(string stringfied_name) throws InvalidName - 주어진이름을동등한 Name Component 배열로변환한다. o String to_string(namecomponent[] n) throws InvalidName - This operation creates a stringified name from the array of Name components. o String to_url(string addr, String sn) throws InvalidAddress, InvalidName - This operation creates a URL based "iiopname://" format name from the Stringified Name of the object. 인자 addr은네임서비스실행중인호스트의주소를의미한다. o void list(int how_many, bindinglistholder bl, BindingIteratorHolder bi) - naming context의바인딩들을나열하는것이가능하다. o void rebind(namecomponent[] n, Object obj) throws NotFound, CannotProceed, Invalidname - 이름과 naming Context 의객체를다시바인딩한다. o unbind(namecomponent[] n, Object obj) throws NotFound, CannotProceed, Invalidname - 이름과 naming Context 의객체를바인딩을해제한다. org.omg.cosnaming.namingcontextexthelper NamingContextExtHelper 클래스는 NamingContextExt의보조클래스이며, 다음과같은정적메소드를지원한다. 지원되는메소드 o static NamingContextExt extract(any a) o static void insert(any a, NamingContextExt nameobj) o static NamingContextExt narrow(object obj) - 인자로받은객체를 NamingContextExt 객체로형변환시킨다. org.omg.cosnaming.namecomponent - Many of the operations defined on a naming context take names as parameters. 328 자바네트워크프로그래밍
- Names have structure. A name is an ordered sequence of components. - A name with a single component is called a simple name; a name with multiple components is called a compound name. - Each component except the last is used to name a context; the last component denotes the bound object. - A name component consists of two attributes: the identifier attribute(id) and the kind attribute(kind). Both the identifier attribute and the kind attribute are represented as IDL strings. The kind attribute adds descriptive power to names in a syntax-independent way. - Examples of the value of the kind attribute include c_source, object_code, executable, postscript, or " ". 5.5 애플릿으로작성한 CORBA 클라이언트 1 명령행인자를줄수없으므로, HTML 문서에서 param 의이름과값을설정 2 orb 를초기화할때, ORB.init() 메소드의인자로 this 객체를사용 ORB orb=orb.init(this. null); 자바소스 : HelloClientApplet.java import HelloApp.*; import java.awt.*; import java.applet.applet; import org.omg.cosnaming.*; import org.omg.cosnaming.namingcontextpackage.*; import org.omg.corba.*; public class HelloClientApplet extends Applet 5 장코바 (Corba) 329
{ private Hello hello; private String msg1="", msg2=""; public void init() { try{ ORB orb = ORB.init(this, null); org.omg.corba.object objref = orb.resolve_initial_references("nameservice"); NamingContextExt ncref = NamingContextExtHelper.narrow(objRef); hello = HelloHelper.narrow(ncRef.resolve_str("Hello")); msg1 = hello.sayhello().trim(); msg2 = "the sum 1 to 100 = " + hello.sum(100); catch (Exception e) { e.printstacktrace(); public void paint(graphics g) { g.setfont(new Font("Serf", Font.BOLD, 18)); g.drawstring(msg1, 25, 40); g.drawstring(msg2, 25, 80); [ 사용할 HTML 파일 ] <html> <head><title>hello Client</title></head> <body> <center> <applet code=helloclientapplet.class width=280 height=120> <param name="org.omg.corba.orbinitialhost" value="localhost"> <param name="org.omg.corba.orbinitialport" value="1099"> </applet> 330 자바네트워크프로그래밍
</center> </body></html> 실행결과 [ 코딩연습 ] - 세개의실수 (float 형 ) 를받아들여, 가장큰수를반환하는서비스를 CORBA 로구현하시오. ( 실행예 ) float f1 = corbaobj.biggest(4.1, 5.8, 3.2) f1 에는 5.8이저장된다. [Home Work] - 문자열을받아들여, 그것을문자열을거꾸로만들어반환하는메소드를 CORBA 로구현하시오. ( 실행예 ) String s1 = corbaobj.reverse("em ssik"); System.out.println(s1); Kiss me 를출력한다. 5 장코바 (Corba) 331
5.6 orbd(object Request Broker Daemon) [ 실행형식 ] C:\> start orbd -ORBInitialPort port [ options ] 1) 필수옵션 - ORBInitalPort port 2) 기타옵션 - port port - defaultdb directory - serverpollingtime milliseconds - serverstartupdelay milliseconds 3) 실행예 C:\> start orbd -ORBInitialPort 1088 4) IDL과 Java o 주석 (comments) - //, /* 와 */ 을사용하여주석표시 o 식별자 (identifier) - 대소문자를구분하지않으며, 예약어는식별자로사용할수없음. o 예약어 (reserved word) abstract double long readonly unsigned any enum module sequence union attribute exception native short ValueBase boolean factory Object string valuetype case FALSE octet struct void char fixed oneway supports wchar const float out switch wstring context in private TRUE custom inout public truncatable default interface raises typedef 332 자바네트워크프로그래밍
o 특이한예약어 1 in : 매개변수의전달방향을나타낸다. ( 클라이언트 서버 ) 2 out: 매개변수가서버에서클라이언트로전송됨 3 inout: 매개변수가양방향으로전송됨. 4 oneway: 비동기적으로전송하거나실행. 5 any: 모든유형의데이터를나타냄 (C의 void* 와같음 ) o IDL 기본데이터유형 o IDL 의구조체유형 IDL 타입 Java 타입 module package boolean boolean char, wchar char octet byte string, wstring java.lang.string short, unsigned short short long, unsigned long int long long long float float double double fixed java.math.bigdecimal enum, struct, union class sequence, array array Any org.omg.corba.any typedef helper classes readonly attribute accessor method readwrite attribute accessor and modifier method operation method 5 장코바 (Corba) 333
1 모듈 (module) // IDL moudle Example {... // 대응자바코드 package Example;... 2 인터페이스 (interface) // IDL module Example { interface Comp {... // Java package Example; public interface Comp extends org.omg.corba.object {... 3 오퍼레이션 (operation) // IDL module Company { interface Employee { string getname(in long id) ;; // Java package Company; public interface Employee extends org.omg.corba.object { public String getname(int id); o 코바예제프로그램의실행 - 은행계좌에관련된간단한예제프로그램 334 자바네트워크프로그래밍
1) IDL 파일의작성 - open() 메소드는이름과예금액을가지고은행구좌를개설하는메소드 - find() 는이름을가지고현재잔액을찾는메소드 [ 파일 : Bank.idl] module Bank { interface AccountMgr { void open(in string name, in long bal); long find(in string name); ; ; 2) idlj 명령어의사용 Bank.idl을클라이언트 / 서버디렉토리에모두복사한다음, 클라이언트디렉토리에서다음명령어실행 C:\> idlj Bank.idl 서버디렉토리에서는다음의명령어를실행한다. C:\> idlj -fall Bank.idl 3) Bank 서버의작성및컴파일 파일 : BankServer.java import Bank.*; import java.util.hashmap; import org.omg.cosnaming.*; import org.omg.cosnaming.namingcontextpackage.*; import org.omg.corba.*; import org.omg.portableserver.*; import org.omg.portableserver.poa; 5 장코바 (Corba) 335
class AccountImpl { private int bal; public AccountImpl(int init_val) { bal = init_val; public int balance() { return bal; class AccountMgrImpl extends AccountMgrPOA { HashMap book = new HashMap(); public AccountMgrImpl() { public synchronized void open(string name, int val) { AccountImpl act = new AccountImpl(val); book.put(name, act); System.out.println("A new account opened: "+name); public int find(string name) { AccountImpl act = (AccountImpl) book.get(name); if (act==null) return 0; else return act.balance(); public class BankServer { public static void main(string args[]) { try { ORB orb=orb.init(args, null); POA rootpoa = (POA)orb.resolve_initial_references("RootPOA"); rootpoa.the_poamanager().activate(); AccountMgrImpl mgr = new AccountMgrImpl(); org.omg.corba.object ref = rootpoa.servant_to_reference(mgr); AccountMgr href = AccountMgrHelper.narrow(ref); org.omg.corba.object oref = orb.resolve_initial_references("nameservice"); 336 자바네트워크프로그래밍
NamingContextExt ncref = NamingContextExtHelper.narrow(oRef); String name = "Bank"; NameComponent path[] = ncref.to_name(name); ncref.rebind(path, href); System.out.println("Bank Server Ready"); orb.run(); catch(exception e) { e.printstacktrace(); System.out.println("Bank Server Exiting"); 4) 클라이언트의컴파일 파일 : BankClient.java import Bank.*; import org.omg.cosnaming.*; import org.omg.cosnaming.namingcontextpackage.*; import org.omg.corba.*; import java.util.properties; public class BankClient { public static void main(string args[]) { try{ Properties prof = System.getProperties(); prof.put("org.omg.corba.orbinitialhost","localhost"); prof.put("org.omg.corba.orbinitialport", "1099"); ORB orb = ORB.init(args, prof); org.omg.corba.object oref = 5 장코바 (Corba) 337
orb.resolve_initial_references("nameservice"); NamingContextExt ncref = NamingContextExtHelper.narrow(oRef); AccountMgr m = AccountMgrHelper.narrow(ncRef.resolve_str("Bank")); if (args.length==0) { System.out.println("specify name or name & balance"); System.exit(1); String name = args[0]; if (args.length>1) { int bal= Integer.parseInt(args[1]); m.open(name, bal); System.out.println("Your account is opened"); else { System.out.println("Your balance = "+m.find(args[0])); catch (Exception e) { System.out.println("Error:"+e); e.printstacktrace(); 5) 동일호스트에서코바응용실행하기 서버측명령어 [ 서버측명령어 ] C:\corba\ex2\server>start orbd -ORBInitialPort 1099 C:\corba\ex2\server>java BankServer -ORBInitialPort 1099 Bank Server Ready A new account opened: Kim A new account opened: Lee 338 자바네트워크프로그래밍
클라이언트측명령어 C:\corba\ex2\client>java BankClient Kim 1200 Your account is opened C:\corba\ex2\client>java BankClient Lee 880 Your account is opened C:\corba\ex2\client>java BankClient Kim Your balance = 1200 C:\corba\ex2\client>java BankClient Lee Your balance = 880 C:\corba\ex2\client> 6) 컴퓨터 3 대를사용하여코바응용실행하기 1 먼저컴퓨터 A 에서 ORB 데몬을실행한다. C:\> start orbd -ORBInitialPort 1099 2 컴퓨터 B 에서 Bank 서버를실행시킨다. 여기서 -ORBInitialHost 는 ORB 데몬을실 행시킨컴퓨터시스템의 IP 나도메인이름을명시한다. C:\>java BankServer -ORBInitialPort 1099 -ORBInitialHost 128.134.168.176 3 컴퓨터 C에서 Bank 클라이언트를실행시킨다. 먼저 BankClient.java에서 Properties 객체에설정값을 localhost 대신에 ORB 데몬의 IP나도메인이름으로변경한다음컴파일하여야한다. 여기서는 localhost 대신에 128.134.168.176 으로변경하였다. 소스코드를변경하였으면다음과같이컴파일하고, 클라이언트를실행시킨다. C:\> javac BankClient.java C:\> java BankClient Kim 1200 Your account is opened C:\> java BankCleint Kim Your balance = 1200 5 장코바 (Corba) 339
[ 프로그래밍연습 ] 앞의 CORBA 예제를수정하여, 입출금기능을추가하시오. 실행예 ) Kim 의계좌에 100 원을입금한다. ( 명령어라인 + 추가 ) C:\corba> java BankClient Kim 100 + Your balance = 2400 실행예 ) Kim 의계좌에 100 원을출금한다. ( 명령어라인 - 추가 ) C:\corba> java BankClient Kim 100 - Your balance = 2300 o 파일송수신을위한 CORBA 프로그램 1) 인터페이스파일의정의 IDL 파일 : FileTrans.idl interface FileTrans { typedef sequence<octet> Data; Data download(in string filename); long upload(in string filename, in Data fdata); ; 2) 서비스구현클래스와서버클래스의작성 서버파일 : FileServer.java import java.io.*; import java.util.properties; import org.omg.cosnaming.*; import org.omg.cosnaming.namingcontextpackage.*; import org.omg.corba.*; import org.omg.portableserver.*; 340 자바네트워크프로그래밍
class FileTransImpl extends FileTransPOA { public byte[] download(string name){ try { File file = new File(name); byte buffer[] = new byte[(int)file.length()]; FileInputStream inf = new FileInputStream(name); inf.read(buffer,0,buffer.length); inf.close(); System.out.println(name+" is downloaded"); return(buffer); catch(exception e){ System.out.println("FileImpl: "+e); return(null); public int upload(string name, byte[] con) { try { File file = new File(name); String m = new String(con); FileOutputStream out = new FileOutputStream(name); out.write(con, 0, con.length); out.flush(); out.close(); System.out.println(file+" is uploaded"); return(0); catch(exception e) { System.out.println("error: "+e); return(-1); public class FileServer { 5 장코바 (Corba) 341
public static void main(string args[]) { try{ Properties prop = new Properties(); prop.put("org.omg.corba.orbinitialport", "1099"); ORB orb = ORB.init(args, prop); POA root = (POA) orb.resolve_initial_references("rootpoa"); root.the_poamanager().activate(); FileTransImpl file = new FileTransImpl(); org.omg.corba.object ref=root.servant_to_reference(file); FileTrans ft = FileTransHelper.narrow(ref); org.omg.corba.object obj = orb.resolve_initial_references("nameservice"); NamingContextExt ncref = NamingContextExtHelper.narrow(obj); NameComponent path[] = ncref.to_name("filetrans");; ncref.rebind(path, ft); System.out.println("Server is Ready"); orb.run(); catch(exception e) { System.err.println("ERROR: " + e.getmessage()); 342 자바네트워크프로그래밍
3) 클라이언트프로그램의작성 파일업로드클라이언트 : FileUp.java import java.io.*; import java.util.*; import org.omg.cosnaming.*; import org.omg.cosnaming.namingcontextpackage.*; import org.omg.corba.*; public class FileUp { public static void main(string argv[]) { try { Properties prop = new Properties(); prop.put("org.omg.corba.orbinitialport", "1099"); ORB orb = ORB.init(argv, prop); // get the root naming context org.omg.corba.object objref = orb.resolve_initial_references("nameservice"); NamingContextExt ncref = NamingContextExtHelper.narrow(objRef); FileTrans fileref = FileTransHelper.narrow(ncRef.resolve_str("FileTrans")); if(argv.length < 1) { System.out.println("specify FileName"); System.exit(1); String fname = argv[0]; File file=new File(fname); byte[] buf = new byte[(int)file.length()]; 5 장코바 (Corba) 343
BufferedInputStream input = new BufferedInputStream(new FileInputStream(fname)); input.read(buf, 0, buf.length); int status= fileref.upload(fname, buf); if(status==0)system.out.println("upload Success"); input.close(); catch(exception e) { System.out.println("Error: " + e.getmessage()); 파일다운로드클라이언트 : FileDown.java import java.io.*; import java.util.*; import org.omg.cosnaming.*; import org.omg.cosnaming.namingcontextpackage.*; import org.omg.corba.*; public class FileDown { public static void main(string argv[]) { try { Properties prop = new Properties(); prop.put("org.omg.corba.orbinitialport", "1099"); ORB orb = ORB.init(argv, prop); // get the root naming context org.omg.corba.object objref = orb.resolve_initial_references("nameservice"); NamingContextExt ncref = NamingContextExtHelper.narrow(objRef); 344 자바네트워크프로그래밍
FileTrans fileref = FileTransHelper.narrow(ncRef.resolve_str("FileTrans")); if(argv.length < 1) { System.out.println("specify FileName"); System.exit(1); // save the file File file = new File(argv[0]); byte data[] = fileref.download(argv[0]); BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(argv[0])); output.write(data, 0, data.length); output.flush(); output.close(); catch(exception e) { System.out.println(" Error: " + e.getmessage()); 4) 서버의실행방법 1 서버측에필요한소스파일 - FileTrans.idl - FileServer.java 2 idlj를사용한인터페이스의번역 C:\file\server> idlj -fall FileTrans.idl 3 모든자바소스코드의컴파일 C:\file\server> javac *.java 4 orb 데몬의실행 C:\file\server> start orbd -ORBInitialPort 1099 5 장코바 (Corba) 345
5 서버의실행 C:\file\server> java FileServer Server is Ready sample.txt is downloaded sample.txt is uploaded 5) 클라이언트의실행방법 1 클라이언트측에필요한소스파일 - FileTrans.idl - FileUp.java - FileDown.java 2 idlj를사용한인터페이스의번역 C:\file\client> idlj FileTrans.idl 3 모든자바소스의컴파일 C:\file\client> javac *.java 4 파일업로드의실행 C:\file\client> java FileUp sample.txt -ORBInitialHost ikim.hs.ac.kr Upload Success 5 파일다운로드의실행 C:\file\client> java FileDown sample.txt -ORBInitialHost ikim.hs.ac.kr ( 만일서버와클라이언트가동일한호스트에서실행된다면, -ORBInitialHost은생략할수있다.) [Home Work] 앞의예제프로그램에서버디렉토리의내용을볼수있는 list, 현재디렉토리값을출력하는 pwd, 디렉토리를변경하는 cwd 기능을추가하시오. 346 자바네트워크프로그래밍
o CORBA Call Back program 클라이언트가서버의메소드를호출하는것이일반적이지만, 그와는반대로서버가클라이언트의메소드를호출하는것이필요한경우가있다. 이렇게서버가클라이언트의메소드를호출하는것을 CALLBACK이라고한다. 예를들어, 주식시장의경우급격한가격변화를보일때, 이러한변동을클라이언트에게적절하게알려주기위해서는 CALL BACK 기능을사용하는것이바람직하다. 다음은 Callback 을사용하여서버가클라이언트에게 Date정보를주기적으로전달해주는예제프로그램이다. CORBA에서도 RMI에서콜백하는과정과거의비슷하게동작한다. 먼저클라이언트는서버에게 CALLBACK 객체를등록해야한다. (IDL파일에서정의된 register() 메소드를사용함 ) IDL 파일 interface Listener { void message(in string msg); ; interface DateServer { void register(in Listener lt); ; [DateServerImpl.java] 파일 import java.util.*; public class DateServerImpl extends DateServerPOA { private Vector clients = new Vector(); private ReadThread rt = null; public DateServerImpl() { rt = new ReadThread(this); public void register(listener lt) { 5 장코바 (Corba) 347
clients.add(lt); public void startthread() { rt.start(); public void message(string msg) { Iterator it = clients.iterator(); while (it.hasnext()) { Listener lt = (Listener) it.next(); lt.message(msg); class ReadThread extends Thread { DateServerImpl msimpl = null; public ReadThread(DateServerImpl msimpl) { this.msimpl = msimpl; public void run() { try { String msg=null; for (;;) {msg=new Date().toString(); msimpl.message(msg); Thread.sleep(1800); catch (Exception e) { Server.java 파일 import org.omg.corba.orb; import org.omg.portableserver.poa; import org.omg.portableserver.poahelper; 348 자바네트워크프로그래밍
import org.omg.cosnaming.namecomponent; import org.omg.cosnaming.namingcontext; import org.omg.cosnaming.namingcontexthelper; import java.util.properties; public class Server { public static void main(string[] args) throws Exception { //create and initialize the ORB Properties props = System.getProperties(); props.put("org.omg.corba.orbinitialport", "1050"); //args[0] specify the host on which running the server props.put("org.omg.corba.orbinitialhost", args[0]); ORB orb = ORB.init(args, props); System.out.println("Initialized ORB"); //Instantiate Servant and create reference POA poa=poahelper.narrow(orb.resolve_initial_references ("RootPOA")); DateServerImpl msimpl = new DateServerImpl(); poa.activate_object(msimpl); DateServer msref = DateServerHelper.narrow( poa.servant_to_reference(msimpl)); //Bind reference with NameService NamingContext namingcontext = NamingContextHelper.narrow( orb.resolve_initial_references("nameservice")); System.out.println("Resolved NameService"); NameComponent[] nc={new NameComponent("DateServer",""); namingcontext.rebind(nc, msref); //Activate rootpoa poa.the_poamanager().activate(); System.out.println("Server ready and running..."); //start servant's thread msimpl.startthread(); orb.run(); 5 장코바 (Corba) 349
ListenerImpl.java 파일 public class ListenerImpl extends ListenerPOA { public void message(string msg) { System.out.println("Message from server : " + msg); Client.java 파일 import java.util.properties; import org.omg.corba.orb; import org.omg.portableserver.poa; import org.omg.portableserver.poahelper; import org.omg.cosnaming.namecomponent; import org.omg.cosnaming.namingcontext; import org.omg.cosnaming.namingcontexthelper; public class Client { public static void main(string[] args) throws Exception { //initialize orb Properties props = System.getProperties(); props.put("org.omg.corba.orbinitialport", "1050"); //args[0] specify the host on which running the server props.put("org.omg.corba.orbinitialhost", args[0]); ORB orb = ORB.init(args, props); System.out.println("Initialized ORB"); //Instantiate Servant and create reference POA rootpoa = POAHelper.narrow( 350 자바네트워크프로그래밍
orb.resolve_initial_references("rootpoa")); ListenerImpl listener = new ListenerImpl(); rootpoa.activate_object(listener); Listener ref = ListenerHelper.narrow( rootpoa.servant_to_reference(listener)); //find DateServer from ORB DateServer msgserver = DateServerHelper.narrow( orb.string_to_object("corbaname:iiop:"+ "1.2@"+args[0]+":1050#DateServer")); //Register listener reference of the callback obj. msgserver.register(ref); System.out.println("Listener registered"); //Activate rootpoa rootpoa.the_poamanager().activate(); System.out.println("Wait for incoming messages"); orb.run(); // callback client requires this line 실행방법 1) 이예제를처음실행하는것이아니라면, 기존에생성된파일을삭제한다. C:\dist\callback> del.\orb.db 2) IDL 파일을사용하여, stub, skeleton및기타보조파일을생성한다. ( 이예제에서는 classes라는서브디렉토리를만들어서, 그곳에파일을생성한다.) C:\dist\callback> mkdir classes C:\dist\callback> idlj -fall -td classes callback.idl 3) 모든자바프로그램을컴파일한다. C:\dist\callback> javac -classpath.\classes -d classes *.java 5 장코바 (Corba) 351
( 여기서일부경고메시지가출력되지만, 실행에는지장이없다.) 4) 네이밍서비스 orbd를실행시킨다. ( 여기서는 JVM이설치된컴퓨터 123.234.111.222에서 orbd를실행한다고가정한다.) Host:123.234.111.222> start orbd -ORBInitialPort 1050 5) 서버를다음과같이실행시킨다. ( 다수의클라이언트에 Date정보를전송할수있음 ) C:\dist\callback> java -classpath.\classes Server 123.234.111.222 6) 클라이언트를다음과같이실행시킨다. C:\dist\callback> java -classpath.\classes Client 123.234.111.222 실행결과 C:\dist\corba\callback>java -classpath.\classes Client 211.48.44.82 Initialized ORB Listener registered Wait for incoming messages Message from server : Sat Apr 30 17:52:04 KST 2005 Message from server : Sat Apr 30 17:52:06 KST 2005 Message from server : Sat Apr 30 17:52:08 KST 2005 Message from server : Sat Apr 30 17:52:10 KST 2005 Message from server : Sat Apr 30 17:52:12 KST 2005... [CallBack 프로그램에대한질문 ] 1 Listener 클래스는무엇이며, 어디에서사용되는가? 2 서버는어떠한과정을거쳐서, Date 정보를클라이언트에게전송하는가? 3 Client.java 의마지막부분에 orb.run() 메소드는클라이언트의종료를막아준다. 클라이언트가종료되면어떠한문제점이있는가? 4 CALLBACK 프로그램이기존의 CORBA 응용과비교해서다른점들은? [ 프로그래밍연습 ] 앞의 CallBack 프로그램을수정하여, 채팅프로그램을작성하시오. 서버는한클라이언트로부터전송받은문자열을모든클라이언트에 broadcasting 한다. 352 자바네트워크프로그래밍
[ 영문해석 ] 다음은코바에대한설명이다. 이를해석하시오. (wikipedia에서발췌 ) CORBA is a mechanism in software for normalizing the method-call semantics between application objects that reside either in the same address space (application) or remote address space (same host, or remote host on a network). CORBA uses an interface definition language (IDL) to specify the interfaces that objects will present to the outside world. CORBA then specifies a mapping from IDL to a specific implementation language like C++ or Java. Standard mappings exist for Ada, C, C++, Lisp, Smalltalk, Java, COBOL, PL/I and Python. There are also non-standard mappings for Perl, Visual Basic, Ruby, Erlang, and Tcl implemented by object request brokers (ORBs) written for those languages. The CORBA specification dictates that there shall be an ORB through which the application interacts with other objects. In practice, the application simply initializes the ORB, and accesses an internal Object Adapter which maintains such issues as reference counting, object (& reference) instantiation policies, object lifetime policies, etc. The Object Adapter is used to register instances of the generated code classes. Generated Code Classes are the result of compiling the user IDL code which translates the high-level interface definition into an OS- and language-specific class base for use by the user application. This step is necessary in order to enforce the CORBA semantics and provide a clean user processes for interfacing with the CORBA infrastructure. Some IDL language mappings are more hostile than others. For example, due to the very nature of Java, the IDL-Java Mapping is rather trivial and makes usage of CORBA very simple in a Java application. The C++ mapping is not trivial but accounts for all the features of CORBA, e.g. exception handling. The C-mapping is even more strange (since it's not an OO language) but it does make sense and handles the RPC semantics just fine. (Red Hat Linux delivers with the GNOME UI system, which has its IPC built on CORBA.) 5 장코바 (Corba) 353
A "language mapping" requires that the developer ("user" in this case) create some IDL code representing the interfaces to his objects. Typically a CORBA implementation comes with a tool called an IDL compiler. This compiler will convert the user's IDL code into some language-specific generated code. The generated code is then compiled using a traditional compiler to create the linkable-object files required by the application. This diagram illustrates how the generated code is used within the CORBA infrastructure: This figure illustrates the high-level paradigm for remote interprocess communications using CORBA. Issues not addressed here, but that are accounted-for in the CORBA specification include: data typing, exceptions, network protocol, communication timeouts, etc. For example: Normally the server side has the Portable Object Adapter (POA) that redirects calls either to the local servants or (to balance the load) to the other servers. Also, both server and client parts often have interceptors that are described below. Issues CORBA (and thus this figure) does not address, but that all distributed systems must address: object lifetimes, 354 자바네트워크프로그래밍