이번 강좌에는 Java Network Programming, Second Edition에 있는 채팅 프로그램을 만들어 보도록 하겠습니다. 이전 예제인 MuktiThreadEchoServer를 이해 하셨다면 그리 어렵지는 않으리라 생각 됩니다.
[먼저 실행 결과를 본후 소스 코드를 보도록 하겠습니다.]
서버의 실행
클라이언트1의 실행
클라이언트2의 실행
아래에는 그 소스 입니다.
[ChatServer.java]
import java.io.*;
import java.net.*;
import java.util.*;
import java.net.*;
import java.util.*;
public class ChatServer {
public static void main (String args[]) throws IOException {
if (args.length != 1)
throw new IllegalArgumentException ("Syntax: ChatServer <port>");</port>
public static void main (String args[]) throws IOException {
if (args.length != 1)
throw new IllegalArgumentException ("Syntax: ChatServer <port>");</port>
int port = Integer.parseInt (args[0]);
ServerSocket server = new ServerSocket (port); //지정한 포트에 서버 소켓을 생성하고 연결을 기다립니다.
ServerSocket server = new ServerSocket (port); //지정한 포트에 서버 소켓을 생성하고 연결을 기다립니다.
while (true) {
Socket client = server.accept (); //클라이언트가 연결을 했다면 큐에 쌓이는데 그 큐에서 연결 하나를 가지고 옵니다.
System.out.println ("Accepted from " + client.getInetAddress ());
ChatHandler handler = new ChatHandler (client); //ChatHandler는 Runnable 인터페이스를 구현했으므로 스레드 입니다.
//즉 클라이언트 하나를 상대하기 위해 스레드를 만들어 분기하는 거죠...
handler.start (); //스레드를 시작 합니다.
}
}
}
Socket client = server.accept (); //클라이언트가 연결을 했다면 큐에 쌓이는데 그 큐에서 연결 하나를 가지고 옵니다.
System.out.println ("Accepted from " + client.getInetAddress ());
ChatHandler handler = new ChatHandler (client); //ChatHandler는 Runnable 인터페이스를 구현했으므로 스레드 입니다.
//즉 클라이언트 하나를 상대하기 위해 스레드를 만들어 분기하는 거죠...
handler.start (); //스레드를 시작 합니다.
}
}
}
[ChatHandler.java]
import java.io.*;
import java.net.*;
import java.util.*;
import java.net.*;
import java.util.*;
public class ChatHandler implements Runnable {
protected Socket socket;
public ChatHandler (Socket socket) {
this.socket = socket;
}
protected Socket socket;
public ChatHandler (Socket socket) {
this.socket = socket;
}
protected DataInputStream dataIn;
protected DataOutputStream dataOut;
protected Thread listener;
protected DataOutputStream dataOut;
protected Thread listener;
//ChatServer에서 클라이언트의 연결을 accept한 후 부르는 start 메소드 입니다.
//
public synchronized void start () {
if (listener == null) {
try {
dataIn = new DataInputStream
(new BufferedInputStream (socket.getInputStream ()));
dataOut = new DataOutputStream
(new BufferedOutputStream (socket.getOutputStream ()));
listener = new Thread (this); //스레드를 만듭니다.
listener.start (); //스레드를 시작 하면 run 메소드가 호출되는 것은 아시죠?
} catch (IOException ignored) {
}
}
}
//
public synchronized void start () {
if (listener == null) {
try {
dataIn = new DataInputStream
(new BufferedInputStream (socket.getInputStream ()));
dataOut = new DataOutputStream
(new BufferedOutputStream (socket.getOutputStream ()));
listener = new Thread (this); //스레드를 만듭니다.
listener.start (); //스레드를 시작 하면 run 메소드가 호출되는 것은 아시죠?
} catch (IOException ignored) {
}
}
}
public synchronized void stop () {
if (listener != null) {
try {
if (listener != Thread.currentThread ())
listener.interrupt ();
listener = null;
dataOut.close ();
} catch (IOException ignored) {
}
}
}
if (listener != null) {
try {
if (listener != Thread.currentThread ())
listener.interrupt ();
listener = null;
dataOut.close ();
} catch (IOException ignored) {
}
}
}
protected static Vector handlers = new Vector ();
//run 메소드 안에 해당 쓰레드가 수행할 내용들이 있습니다.
public void run () {
try {
handlers.addElement (this); //클라이언트 하나의 연결을 벡터에 담습니다. 요즘은 ArrayList를 주로 사용 합니다.
while (!Thread.interrupted ()) {
String message = dataIn.readUTF ();
broadcast (message); //메소드를 호출 합니다. (클라이언트로 메시지를 보냄)
}
} catch (EOFException ignored) {
} catch (IOException ex) {
if (listener == Thread.currentThread ())
ex.printStackTrace ();
} finally {
handlers.removeElement (this);
}
stop ();
}
//run 메소드 안에 해당 쓰레드가 수행할 내용들이 있습니다.
public void run () {
try {
handlers.addElement (this); //클라이언트 하나의 연결을 벡터에 담습니다. 요즘은 ArrayList를 주로 사용 합니다.
while (!Thread.interrupted ()) {
String message = dataIn.readUTF ();
broadcast (message); //메소드를 호출 합니다. (클라이언트로 메시지를 보냄)
}
} catch (EOFException ignored) {
} catch (IOException ex) {
if (listener == Thread.currentThread ())
ex.printStackTrace ();
} finally {
handlers.removeElement (this);
}
stop ();
}
//아래에서 벡터에 있는 클라이언트의 연결 만큼 루프 돌면서 메시지를 보내고 있습니다. unicast현태로 broadcast를 흉내 내는거죠...
protected void broadcast (String message) {
synchronized (handlers) {
Enumeration enum = handlers.elements ();
while (enum.hasMoreElements ()) {
ChatHandler handler = (ChatHandler) enum.nextElement ();
protected void broadcast (String message) {
synchronized (handlers) {
Enumeration enum = handlers.elements ();
while (enum.hasMoreElements ()) {
ChatHandler handler = (ChatHandler) enum.nextElement ();
try {
handler.dataOut.writeUTF (message);
handler.dataOut.flush ();
} catch (IOException ex) {
handler.stop ();
}
}
}
}
}
handler.dataOut.writeUTF (message);
handler.dataOut.flush ();
} catch (IOException ex) {
handler.stop ();
}
}
}
}
}
[ChatClient.java]
import java.io.*;
import java.net.*;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.awt.*;
import java.awt.event.*;
public class ChatClient implements Runnable, WindowListener, ActionListener {
protected String host;
protected int port;
protected Frame frame;
protected TextArea output;
protected TextField input;
//생성자에서 UI 만듭니다. Main에서 new 할때 호출 됩니다.
public ChatClient (String host, int port) {
this.host = host;
this.port = port;
frame = new Frame ("ChatClient [" + host + ':' + port + "]");
frame.addWindowListener (this); //프레임(윈도우)이벤트 처리를 위한 리스너를 붙임, 윈도우가 Open된다든지, Close 될때 무언가를 하기 위해
output = new TextArea ();
output.setEditable (false);
input = new TextField ();
input.addActionListener (this); //텍스트 필드에서 엔터 쳤을때 이벤트를 받기 위해
frame.add ("Center", output);
frame.add ("South", input);
frame.pack ();
}
protected String host;
protected int port;
protected Frame frame;
protected TextArea output;
protected TextField input;
//생성자에서 UI 만듭니다. Main에서 new 할때 호출 됩니다.
public ChatClient (String host, int port) {
this.host = host;
this.port = port;
frame = new Frame ("ChatClient [" + host + ':' + port + "]");
frame.addWindowListener (this); //프레임(윈도우)이벤트 처리를 위한 리스너를 붙임, 윈도우가 Open된다든지, Close 될때 무언가를 하기 위해
output = new TextArea ();
output.setEditable (false);
input = new TextField ();
input.addActionListener (this); //텍스트 필드에서 엔터 쳤을때 이벤트를 받기 위해
frame.add ("Center", output);
frame.add ("South", input);
frame.pack ();
}
protected DataInputStream dataIn;
protected DataOutputStream dataOut;
protected Thread listener;
//메인에서 new 한 후 start 메소드를 호출 합니다.
public synchronized void start () throws IOException {
if (listener == null) {
Socket socket = new Socket (host, port);
try {
dataIn = new DataInputStream
(new BufferedInputStream (socket.getInputStream ()));
dataOut = new DataOutputStream
(new BufferedOutputStream (socket.getOutputStream ()));
}
catch (IOException ex) {
socket.close ();
throw ex;
}
protected DataOutputStream dataOut;
protected Thread listener;
//메인에서 new 한 후 start 메소드를 호출 합니다.
public synchronized void start () throws IOException {
if (listener == null) {
Socket socket = new Socket (host, port);
try {
dataIn = new DataInputStream
(new BufferedInputStream (socket.getInputStream ()));
dataOut = new DataOutputStream
(new BufferedOutputStream (socket.getOutputStream ()));
}
catch (IOException ex) {
socket.close ();
throw ex;
}
//스레드를 만들어 시작 합니다. 서버와의 지속적인 통신을 시작 합니다. (스레드가 어떤일을 하는지는 run 메소드를 보면 됩니다.)
listener = new Thread (this);
listener.start ();
frame.setVisible (true);
}
}
listener = new Thread (this);
listener.start ();
frame.setVisible (true);
}
}
public synchronized void stop () throws IOException {
frame.setVisible (false);
if (listener != null) {
listener.interrupt ();
listener = null;
dataOut.close ();
}
}
frame.setVisible (false);
if (listener != null) {
listener.interrupt ();
listener = null;
dataOut.close ();
}
}
//스레드에서 실행 될 메소드, 서버가 보내는 데이터를 읽어 화면에 뿌립니다.
public void run () {
try {
while (!Thread.interrupted ()) {
String line = dataIn.readUTF ();
output.append (line + "\n");
}
} catch (IOException ex) {
handleIOException (ex);
}
}
public void run () {
try {
while (!Thread.interrupted ()) {
String line = dataIn.readUTF ();
output.append (line + "\n");
}
} catch (IOException ex) {
handleIOException (ex);
}
}
protected synchronized void handleIOException (IOException ex) {
if (listener != null) {
output.append (ex + "\n");
input.setVisible (false);
frame.validate ();
if (listener != Thread.currentThread ())
listener.interrupt ();
listener = null;
try {
dataOut.close ();
} catch (IOException ignored) {
}
}
}
if (listener != null) {
output.append (ex + "\n");
input.setVisible (false);
frame.validate ();
if (listener != Thread.currentThread ())
listener.interrupt ();
listener = null;
try {
dataOut.close ();
} catch (IOException ignored) {
}
}
}
//처음 화면이 열렸을때
public void windowOpened (WindowEvent event) {
input.requestFocus ();
}
public void windowOpened (WindowEvent event) {
input.requestFocus ();
}
//사용자가 윈도우를 종료 했을때
public void windowClosing (WindowEvent event) {
try {
dataOut.writeUTF ("전 이만 나갑니다... Bye...");
stop ();
} catch (IOException ex) {
ex.printStackTrace ();
}
}
public void windowClosing (WindowEvent event) {
try {
dataOut.writeUTF ("전 이만 나갑니다... Bye...");
stop ();
} catch (IOException ex) {
ex.printStackTrace ();
}
}
public void windowClosed (WindowEvent event) {}
public void windowIconified (WindowEvent event) {}
public void windowDeiconified (WindowEvent event) {}
public void windowActivated (WindowEvent event) {}
public void windowDeactivated (WindowEvent event) {}
public void windowIconified (WindowEvent event) {}
public void windowDeiconified (WindowEvent event) {}
public void windowActivated (WindowEvent event) {}
public void windowDeactivated (WindowEvent event) {}
//텍스트 필드에서 엔터 쳤을때 호출 됩니다. 즉 서버로 메시지를 보내게 되겠죠....
public void actionPerformed (ActionEvent event) {
try {
input.selectAll ();
dataOut.writeUTF (event.getActionCommand ());
dataOut.flush ();
} catch (IOException ex) {
handleIOException (ex);
}
}
public void actionPerformed (ActionEvent event) {
try {
input.selectAll ();
dataOut.writeUTF (event.getActionCommand ());
dataOut.flush ();
} catch (IOException ex) {
handleIOException (ex);
}
}
//아래는 메인 함수 입니다. 실행 후 객체 만들어 start 메소드를 호출 합니다.
public static void main (String[] args) throws IOException {
if ((args.length != 1) || (args[0].indexOf (':') < 0))>
throw new IllegalArgumentException ("Syntax: ChatClient <host>:<port>");
int idx = args[0].indexOf (':');
String host = args[0].substring (0, idx);
int port = Integer.parseInt (args[0].substring (idx + 1));
ChatClient client = new ChatClient (host, port);
client.start ();
}
}</port></host>
public static void main (String[] args) throws IOException {
if ((args.length != 1) || (args[0].indexOf (':') < 0))>
throw new IllegalArgumentException ("Syntax: ChatClient <host>:<port>");
int idx = args[0].indexOf (':');
String host = args[0].substring (0, idx);
int port = Integer.parseInt (args[0].substring (idx + 1));
ChatClient client = new ChatClient (host, port);
client.start ();
}
}</port></host>
이번 강좌에서는 자바 AWT기반의 채팅 프로그램을 만들어 보았습니다. 혹시 의문 사항이나 질문 있으시면 Q/A에 올려 주세요~
감사합니다.
댓글 없음:
댓글 쓰기