출처 : http://jmonster.springnote.com/pages/1083522
서비스 인터페이스 WorkLoad
여기서는 Concurrent 서버를 재활용할 수 있는 간명하고도 유쾌한 방법을 소개합니다.
자바로 프로그램을 하다가 재활용할 수 있는 것을 발견하는 날이면 참 유쾌해집니다.
TCP네트워크 서버가 하는 일은 무지 다양하겠지만 가장 기본적인 것은 네트워크로부터
읽고,쓰는것일 것입니다.
클라이언트 입장에서보면 서버가 제공하는 서비스라는 것은 결국 네트워크로부터 읽고,쓰는
것이 전부입니다. 서버로 정보를 보냈을 때 얼마나 가치있는 정보를 클라이언트가 받아볼수
있느냐에 따라 서버의 가치가 달라지겠지만, 간명하게는 네트워크로부터 읽고,쓰는 것임을
부정할 수 없습니다.
이런 관점에서 살펴봤을 때 Concurrent TCP 네트워크 서버가 수행해야할 일을 설계해
보았습니다. 클라이언트 입장에서는 서버로부터 제공받은 서비스인 셈이고,
서버입장에서는 클라이언트에게 제공해야할 서비스인 셈입니다.
WorkLoad인터페이스입니다.
package tcp;
import java.net.*;
import java.io.*;
/**
* TCP네트워크 서버가 제공하는 서비스의 겉모양입니다.
* TCP네트워크 서버가 제공하는 서비스 객체는 WorkLoad의 겉모양을 갖고 있어야합니다.
*서비스 객체는 WorkLoad를 반드시 구현해야합니다.
*/
public interface WorkLoad {
/**
* TCP네트워크 서버는 Socket으로 읽고,쓰기를 수행함으로써 서비스를 제공합니다.
*/
public void work(Socket s);
}
예제 11 - 11 WorkLoad.java
WorkLoad인터페이스는 TCP 네트워크 서버에 의해서 사용될 것이므로 tcp 패키지에
속하게 했습니다.
WorkLoad에는 단 하나의 메소드가 선언되어 있는데 public void work(Socket s) 입니다.
네트워크 서버가 갖고 있는 서비스객체는 반드시 WorkLoad이어야 하는데, WorkLoad가
Socket객체를 메소드의 인자로 갖고 있으므로 서비스 객체는 Socket객체로부터
InputStream과 OuptStream을 가질 수 있고, 이를 이용하면 클라이언트와 읽고,쓰기를
할 수 있습니다.
WorkLoad를 구현한 객체가 바로 서버가 제공하는 서비스를 의미합니다.
서비스 객체 TimeLoad
현재 시간을 서비스하는 TCP 네트워크 서버는 현재시간을 클라이언트에게 알려주는
서비스 객체가 필요합니다. 이 서비스 객체가 WorkLoad 인터페이스를 구현한 TimeLoad
입니다.
package tcp;
import java.io.*;
import java.util.*;
import java.net.*;
/**
* TCP네트워크 서버가 제공하는 WorkLoad를 구현한 객체입니다.
*현재의 시각을 클라이언트에게 알려줍니다.
*/
public class TimeLoad implements WorkLoad {
/**
* Socket객체를 통해서 클라이언트와 읽고,쓰기를 할 수 있습니다.
*클라이언트로부터 입력을 받는 것없이 현재시간을 클라이언트에게 알려줍니다.
*/
public void work(Socket s) {
PrintWriter pw = null;
try {
/* Socket객체로 부터 OutputStream을 얻고 OutputStream으로부터
* PrintWriter을 얻습니다. PrintWriter의 public void println(String )를
*이용해 클라이언트에게 현재 시각과 newline을 스트링으로 씁니다. */
pw = new PrintWriter(new OutputStreamWriter(s.getOutputStream()));
String time = new Date().toString(); //현재시각의 스트링표현
pw.println(time);
pw.flush();
}
catch(IOException io) {
io.printStackTrace();
}
finally {
/*네트워크 시스템 자원을 해제합니다. */
try {
if (pw != null)
pw.close();
if (s != null)
s.close();
}catch(Exception ignore) { }
}
}
}
예제 11 - 12 TimeLoad.java
TimeLoad클래스는 WorkLoad 인터페이스를 구현했으며, 내용은 Socket객체로부터
OutputStream을 구한후 PrintWriter를 이용해서 서버의 현재 시각을 네트워크로
전송하는것 입니다. 간명한 내용입니다.
서비스 객체 StarLoad
WorkLoad인터페이스를 구현한 또 다른 클래스 StarLoad를 만들어 보겠습니다.
StarLoad는 스타의 인사말 서비스를 제공하는 서비스 객체입니다.
package tcp;
import java.io.*;
import java.util.*;
import java.net.*;
/**
* Socket의 InputStream으로 스타의 이름을 받으면, Socket의 OutputStream으로
*스타의 인사말을 보내줍니다.
*이미 만들어져 있는 Star객체를 이용합니다.
*/
public class StarLoad implements WorkLoad {
/*스타인사말 서비스 객체의 서비스를 제공합니다.*/
public void work(Socket s) {
Star star = new Star(s);
star.hello();
}
}
예제 11 - 13 StarLoad.java
StarLoad클래스 역시 WorkLoad를 구현했습니다.내용은 스타인사말 서비스를 제공하는
건데, 이미 만들어져 있는 Star객체를 이용했습니다.
자바에서는 만들어져 있는 객체를 재활용하게 되는 경우 참 기분이 좋지요.
여기서도 마찮가지입니다.
서비스 객체 CopyLoad
그럼 마지막 서비스객체로 CopyLoad를 살펴보겠습니다.
package tcp;
import java.io.*;
import java.util.*;
import java.net.*;
/**
* TCP네트워크 서버가 제공하는 WorkLoad를 구현한 객체입니다.
*클라이언트로부터 전송된 데이타를 스트림을 이용해 파일로 복사합니다.
*/
public class CopyLoad implements WorkLoad {
public CopyLoad() {
}
/**
* Socket으로부터 InputStream로부터 , 파일 "output.txt"의 OutputStream으로
*데이타를 Copy합니다.
*/
public void work(Socket s) {
InputStream is = null;
OutputStream os = null;
try {
is = s.getInputStream();
os = new FileOutputStream("output.txt");
int c = -1;
while((c = is.read()) != -1) {
os.write(c);
}
os.flush();
}
catch(IOException io) {
io.printStackTrace();
}
finally {
/*네트워크 시스템 자원을 해제합니다. */
try {
if (is != null)
is.close();
if (os != null)
os.close();
if (s != null)
s.close();
}
catch(Exception ignore) { }
}
}
}
예제 11 - 14 CopyLoad.java
CopyLoad클래스 역시 WorkLoad를 구현했으며,구현한 내용은 Socket으로부터
InputStream을 얻은 후에 InputStream으로부터 읽은 정보를 “output.txt”라는 파일로 옮겨
쓰는 일을 하고 있습니다.
TimeLoad, StarLoad, CopyLoad 3개의 WorkLoad는 TCP 네트워크를 기반으로 서비스를
제공하고자할때 사용될 수 있을 법한 서비스객체들입니다.
Concurrent TCP네트워크 서버 Worker II
3가지 형태의 서비스를 제공하기 위해서는 일반적으로는 3개의 네트워크 서버를 만들어야
합니다. 여기서는 하나의 네트워크 서버만으로 3개의 서비스를 제공할 수 있도록 간단하게
구성해 보도록 하겠습니다.
Worker는 TCP 네트워크 서버로 준비된 클래스입니다. 대부분의 네트워크 서버는 여러 개
의 클라이언트를 동시에 서비스를 해야하는 구조를 가져야하기 때문에 반드시 Thread와
연관되어야하고 서비스를 제공하는 Runnable 서비스 객체를 가지게 마련입니다.
package tcp;
import java.io.*;
import java.net.*;
/**
* TCP네트워크서버의 서비스 객체입니다.
*서비스 객체가 Runnable개체이므로 Thread와 함께 사용되어
*클라이언트마다 Thread를 할당하여 일대일로 서비스를 제공하도록 구성합니다.
*서비스객체는 클라이언트로부터 정보를 읽고,쓸수 있는 Socket객체와
*구제적 서비스를 의미하는 WorkLoad를 인자로 가집니다.
*/
public class Worker implements Runnable {
/**
*서비스객체는 클라이언트로부터 정보를 읽고,쓰기위한 Socket이 필요합니다.
*/
private Socket s;
/**
*서비스객체는 제공할 서비스를 의미하는 WorkLoad객체가 필요합니다.
*/
private WorkLoad load;
/**
* Socket객체와 WorkLoad 객체로 Worker 서비스객체를 만듭니다.
*/
public Worker(Socket s, WorkLoad load) {
this.s = s;
this.load = load;
}
/**
*클라이언트와 일대일로 생성되는 Thread가 시작하면 WorkLoad 객체의 서비스를
*제공합니다.
*/
public void run() {
try {
load.work(s);
}
catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
if (args.length != 1) {
System.out.println("java -classpath CLASSPATH tcp.Worker
workload-class-name");
System.exit(1);
}
/* WorkLoad이름 */
String name = args[0];
/*제공할 서비스인 WorkLoad를 로드합니다.*/
WorkLoad load = (WorkLoad)Class.forName(name).newInstance();
/* TCP네트워크 Concurrent 서버를 설계하는 가장 간단한 방법입니다.
8989 TCP포트로 Listening ServerSocket을 Main Thread로 만든후
반복문안에서 클라이언트의 네트워크 연결을 기다린후, TCP 연결이 되면
Runnable서비스객체를 만들고 별도의 Thread를 만들어 서비스합니다. */
ServerSocket server = new ServerSocket(8989);
while(true) {
Socket s = server.accept();
new Thread(new Worker(s,load)).start();
}
}
catch(Exception e) {
e.printStackTrace();
}
}
}
예제 11 - 15 Worker.java
Worker클래스는 Concurrent TCP 서버의 기본적인 규칙에 충실합니다.
Worker자체가 TCP 서버의 Runnable 서비스 객체입니다.
Worker의 public void run()을 살펴보면 아주 재미있는 것이 있습니다.
public void run()에서 실행하는 것은 load.work(s) 즉, WorkLoad 인터페이스의
public void work(Socket )메소드를 실행하는데,재미있는것은 WorkLoad가 인터페이스이기
때문에 실제로 어떤 구체적 객체가 전달될지 현재는 알수 없고(컴파일할때는 알수 없고)
실행시에 비로소 알게 됩니다.
Worker입장에서 보면 WorkLoad 인자에 어떤 구체적 WorkLoad 객체가 전달 되느냐에
따라 TimeLoad의 public void work(Socket)을 수행할 수도 있고, StarLoad의
public void work(Socket)을 수행할 수 도 있고, CopyLoad의 public void work(Socket)를
수행할 수 도 있습니다. 하지만 분명한 것은 어떤 WorkLoad 객체가 전달 되더라도
폴리모피즘 덕분에 Worker는 훌륭히 WorkLoad의 public void work(Socket)을 잘 수행해
냅니다. 바로 이 폴리모피즘 덕분에 Worker는 3가지 형태의 서비스를 할 수 있게 됩니다.
앞으로 많은 서비스 객체가 생겨나더라도 WorkLoad 인터페이스를 구현하기만 한다면
그 서비스 객체역시 Worker가 서비스 할 수 있습니다.
public static void main(String[])를 보면 Worker서버는 명령행 인자에서 WorkLoad의
구체적 이름을 받아서 WorkLoad를 구현한 실제 객체를 만듭니다.
그리고 대표적인 Concurrent TCP 서버의 구조를 가집니다.
Worker서버의 경우는 명령행 인자에서 받은 인자로부터 생성해내는 WorkLoad 객체가 어떤
객체냐에 따라 (즉 TimeLoad냐 StarLoad냐 또는 CopyLoad냐에 따라) 각각의 서비스를
제공할 수 있습니다.
TimeLoad를 서비스하다가 StarLoad 서비스를 하기 위해서 Worker서버를 수정하거나 새로
컴파일해야할 필요가 전혀 없습니다.
그림 11-11 은 Woker서버가 StarLoad를 서비스하는 경우의 예입니다.
그림 11 -12와 같이 클라이언트로는 Fan을 이용했습니다.