김상욱의 awt 멀티채팅강좌

 

※ 강좌에 대한 질문은 '문의게시판'에서 해주세요.

 


[채팅룸 예시Design]

 

네, 안녕하십니까 김상욱입니다. 이 글의 목적은 제가 회사와서 처음 제작한 자바프로그램인 멀티채팅애플릿에 쓰여진 각종 기술들을 공개함으로써 제가 불과 몇개월전까지 걷고 있던 길을 걷고 계시는 초보분들에게 조금이라도 보탬이 되고자 하는 데에 있습니다. 아무쪼록 도움이 되시길 바랍니다. 물론 제가 쓴 방법 말고도 수만가지 방법이 있으니(^^) 생각에 여지를 두고 이 글을 읽어주시길 바랍니다. 제가 정확하게 아는 부분이 아니면 언급하지 않는 것을 전제로 하겠습니다.

이 글은 이렇게 구성되어 있습니다.(제목을 클릭하시면 보다 빠르게 이동합니다.)

1. 서론 (잡담)

2. Socket의 개념과 채팅에서의 사용법

3. Socket부분 소스

4. Thread 이용법

5. Thread부분 소스

6. 프로토콜 정의와 StringTokenizer클래스의 이용법

7. Canvas를 이용한 Image다루기

...

 

1. 서론

웹프로그래밍에서 게시판 짜는 것이 기본이듯이(여기서 기본이라는 것은 이모저모 다 알아야만 할 수 있는 프로그램이다라는 뜻입니다.) 네트웍프로그래밍에서 채팅을 짜는 것은 기본입니다. 자바가 처음에 인기를 끈 이유가 인터넷의 보급과 브라우저에서 볼 수 있는 프로그램(애플릿-작은 애플리케이션)이었기 때문에 네트웍 프로그래밍으로 자바를 시작하신다면 현명한 선택을 하신 것이 됩니다. 눈에 보여지는 것이 많기 때문에 배우는 입장에서도 무척 흥미로운 내용입니다. 제법 잘 짜여진 채팅애플릿을 만들기 위해서 어떤 것을 알아야 할까요?

이를 알기 위해서는 채팅이 어떤 구조로 되어있는 지 대략적으로 알아야 합니다. 가장 간단한 채팅은 대부분의 네트웍프로그램이 가지는 서버/클라이언트의 양단을 애플리케이션과 애플릿이 차지하는 것입니다. 클라이언트(손님)의 요청을 서버가 받아서 처리한 후 클라이언트(들)에게 전송해주는 것이 주요 골자죠. 따라서 클라이언트가 전송할 애플릿 프로그램과 서버에 해당하는 애플리케이션 두 가지를 제작해야 하겠습니다. 네트웍 상에서 서버/클라이언트 요청을 처리하려면 아무래도 서버를 돌리기 위해 웹서버 역할을 하는 컴퓨터가 따로 있어야 합니다.(자신의 컴퓨터-로컬컴퓨터 를 쓰셔도 되구요.) 웹서버 역할을 할 수 있다는 것은 NT거나, 2000 server에 IIS(ms에서 제공하는 서버), 리눅스/유닉스에 아파치 등을 깔았다거나, 아니면 우리가 보통 쓰는 윈도(95, 98, Me 등)에 퍼스널웹서버를 설치해야한다는 것이죠. 에구 뭐가 이리 어려워? /^_^/

어쨌든 서버역할을 하는 컴이 있어서 거기에다 서버용 프로그램을 설치하고 돌려야겠구나 하고 감 잡으시면 되겠습니다.

채팅애플릿을 만드면서 공부하게 될 것은 우선 가장 중요한 통신도구 소켓(socket)통신에 대한 것입니다. 소켓에는 서버가 쓰는 서버소켓과 일반소켓이 있는데 그리 어렵지 않습니다. 그 소켓을 통해 주고받는 메시지들을 처리하기 위해서 몇가지 클래스들을 보너스로 배우겠습니다.(StringTokenizer 등)

어, 언제 손님이 접속하고, 메세지가 올 지 모르니 무한 루프를 도는 쓰레드(Thread)에 대해서도 알아야겠네요.

대충 초기설정이 끝나면 이제 액션을 받을 수 있어야겠죠. 액션이벤트 마우스이벤트 등에 대해 배워봅니다.

이것만 알면 가장 간단한 채팅은 구현할 수 있겠습니다만, 디자인 이쁘고, 기능 멋있는 건 무리겠죠... 다양한 방을 관리할 수 있도록 해주는 몇 가지(Vector, HashTable 등)유틸 클래스와 하는 김에 내부클래스의 개념에 대해서 알아보고요, 내친 김에 마우스 이벤트를 받을 수 있는 이미지영역 Canvas 클래스에 대해 알아보겠습니다.

여기에 친구와 음악을 같이 듣는다거나 친구에게 이미지를 보내준다거나 하면 재미있겠죠? 구현해봅니다 (^^)

서론 끝! 이 글에서는 여러분이 궁금해하시는 부분부분에 대해 각개격파식으로 통달해나가도록 하겠습니다. 부디 도움이 되시길....

참, 이 글을 읽을 때에는 항상 API의 index문서를 준비해두세요... 같이 찾아봅니당../^_^/

※ 강좌에 대한 질문은 '문의게시판'에서 해주세요.

 

2. Socket의 개념과 채팅에서의 사용법

소켓(Socket)에 앞서 스트림(Stream)에 대해 알아야 합니다. 스트림의 개념은 간단합니다. 한 프로그램에서 다른 프로그램으로 어떤 값들을 넘겨주어야 할 때 스트림을 사용합니다. 스트림(흐름)은 그야말로 데이타들의 흐름이죠. 자바에서만 지원하는 것이 아니기 때문에 양단이 반드시 자바프로그램이어야할 필요는 없습니다.

Socket은 TCP을 사용해 통신을 하는데 쓰입니다. Socket을 이용해 통신하기 위해서는 상대 프로그램이 깔린 컴퓨터의 ip와 사용할 port를 정해야 합니다. port는 1024이상 6만5천번까지 사용할 수 있습니다. 통신이란 서버/클라이언트 구조로 되어있으므로 통신을 하기 위해서는 먼저 서버에서 소켓을 준비해야겠지요. 이를 서버소켓이라고 합니다. 생성은 간단합니다. 서.버.소.켓 이라고 쓰면 되지요.

ServerSocket 서버소켓객체이름 = new ServerSocket(사용할 포트);

됐습니다. 이렇게 서버에서 사용할 포트로 서버소켓을 열어놓으면 이제 통신할 준비가 된 것입니다. 이 서버소켓은 클라이언트의 소켓요청이 들어오면 새로이 소켓을 하나 생성하고 이를 클라이언트와 접속시켜주는 역할을 합니다. 요청을 기다려 소켓을 생성하는 코드입니다.

Socket 소켓객체이름 = 서버소켓객체이름.accept();

accept()함수는 block함수로 요청이 들어올 때까지 진행을 멈추고 기다립니다. 요청이 들어오면? Socket을 소켓객체이름으로 생성하죠. 여기에 스크림을 연결하려면 소켓에서 스트림을 얻기만 하면 됩니다.

InputStream 스트림객체이름 = 소켓객체이름.getInputStream();

스트림의 종류는 InputStream말고도 셀 수 없이 다양합니다. ^^; 기본적인 입출력스트림과 파일 입출력 스트림 외에 연결, 메모리, 필터 입출력 스트림 등이 그것입니다. 각자 유용한 메소드들로 무장되어 있습니다.

클라이언트에서 이 컴퓨터와 통신하고 싶으면 포트 외에 한 가지 더 ip를 알아야겠지요? 소켓을 생성합니다.

Socket 객체이름 = new Socket(상대 ip, 포트);

마찬가지로 여기에도 스트림을 연결하면 됩니다. 끝! 참 쉽게 말하죠? ^^; 살은 하나도 붙이지 않고 두리뭉실하게 뼈만 만들었습니다. /^_^/

일방통신(클라이언트의 요청을 서버가 받아 처리해서 다시 클라이언트에게 응답)소켓프로그램으로 에.. 통신계산기를 가정해볼까요? 두리뭉실~ 계산기 서버는 서버소켓을 생성하고 클라이언트를 기다립니다. 클라이언트는 소켓을 생성하고 여기에 스트림을 붙인 다음 서버에 ip와 port로 접속합니다. 서버에서 서버소켓이 클라이언트의 소켓과 통신할 소켓을 생성해주겠지요? 클라이언트가 소켓으로부터 얻은 스트림에 계산식을 쓰면 계산식이 스트림을 통해 소켓을 타고 서버로 전달되어 서버가 계산하도록 하고 서버는 이를 계산해 클라이언트에게 응답합니다.

끝! 너무 심심하죠? 계산 한 번 하고 끝내니까 그렇죠. 음 그러면 서버에서 계산식을 받는(read) 부분을 스레드로 구성해 무한루프 돌리면 되겠군요. 그러면 계산식이 오면 스레드가 받아서 열심히 계산하고 있고, 그 중간에라도 클라이언트로부터 또 요청이 오면 새로운 스레드가 받아서 열심히 받아서 보내(write)해주면 되겠네요.

끝! 에 또 섭섭한가요? 에써 서버를 구축했는데 클라이언트를 하나만 받자니 좀 그렇네요. 클라이언트의 요청을 받아 소켓을 생성하는 부분 즉,

Socket 소켓객체이름 = 서버소켓객체이름.accept();

이 부분도 무한루프에 스레드 처리해서 클라이언트가 접속하는 대로 새로 소켓을 생성하면 되겠군요. 스레드 처리했으니까 이전의 클라이언트의 요청과 상관없이 새로운 클라이언트도 계산을 할 수 잇겠습니다. 진짜 끝! 지금까지 다 채팅을 위해서 필요한 내용이었습니다. 스레드(멀티스레드)는 자바의 주요한 특징중의 하나입니다. c로 하면 무쟈게 늘어질 코딩을 단숨에 줄여주죠. 그만큼 부하도 많이 걸립니다만...

자, 지금까지 모르는 내용 있으셨나요? 걱정마세요. 100% 200% 뒤에서 다 설명할 겁니다. 일단 읽어둔다 생각하고 머리속에 넣어두세요. /^_^/

 

채팅에서는 이 소켓을 어떤 구조로 이용해야 할까요?

눈 크게 뜨세요. 뼈만 세우는 것이어도 조금 복잡합니다.

채팅은 클라이언트와 서버가 하는 것이 아닙니다. 클라이언트끼리 하는 것이죠. 2명이 채팅을 한다고 합시다. 둘 다 클라이언트입니다. 먼저 사람이 서버에 통신계산기처럼 소켓접속을 해서 기다리고 있고, 두 번째 사람도 소켓접속을 합니다. 한 사람이 말을 합니다. 이 글씨가 두 사람 모두에게 보여야겠지요? 그럴려면 그 사람의 말이 자신의 화면에 그냥 보여져서는 안됩니다. 우선 서버에 자신이 말을 한 것을 알려야 되고요, 서버는 이를 받아서 처리(할 게 있나?) 자신에게 접속한 클라이언트 모두에게 뿌려줍니다. 말을 친 사람도 그제서야 자신의 말이 보입니다. 물론 상대도 보입니다. 끝!

어렵지 않지요? 그럼 여기에 스레드는 어디어디에 들어가야 할까요? 우선 계산기에서 말했듯이 클라이언트가 언제든 접속할 수 있도록 서버소켓에 소켓을 열어주는 부분에 걸어서 각 클라이언트(소켓)이 서로 상관없이 동작할 수 있도록 해주어야겠지요. 그리고? 네 열려진 소켓에 언제 메세지가 갈 지 모르니 메세지를 받아 읽는 부분에다 스레드를 걸어주어야겠군요.

끝! 이렇게 떼어서 이야기를 하니 쉬워보이지만, 아아 스레드가 두부분이나 들어가다니 /^_^/ 깊게 생각할 수록 머리가 복잡해집니다. 위의 이야기를 그림으로 보이겠습니다.

짠~ /^_^/ 네, 요새 s/w단속떠서 포토샵 라이센스를 디자이너만 줘가지구 그림을 못그립니다. 어흑~ 만능엔터테이너가 되려는 나에게 포토샵도 안주다니..

어쩔 수 없이 눈 아프시게 다시 한 번 말로 떠듭니다. 채팅서버에서 서버소켓을 생성합니다. 클라이언트의 연결을 무한루프로 기다립니다. 소켓연결되는 족족 클라이언트 스레드를 생성해주고 이 쓰레드를 서버의 리스트에 추가합니다. 서버가 이들을 관리하기 위해서죠. 주로 Vector클래스를 사용합니다. (앗! 벡터가 나왔군요! ) 클라이언트에서는 우선, 서버와 소켓 연결을 합니다. 스트림도 붙여야 겠지요. 그리고 언제라도 서버로부터 메시지를 받을 수 있도록 스레드를 생성해 구동합니다. 이 스레드가 메시지를 받으면, 화면에 표시해줍니다. 이와 별도로 사용자가 어떤 입력을 하면 메시지를 서버로 전송합니다. 메시지가 서버에 전송되면 서버가 처리해서 자신의 스레드 리스트 모두(혹은 일부)에게 메시지를 보내줍니다. 다른 클라이언트 스레드들은 기다렸다는 듯이 이를 받아 화면에 표시하겠죠. 끝!

아아 눈아프게 해드려서 죄송합니다. 이로써 채팅에서의 소켓구조에 대해 알아보았습니다. 그리 어렵지 않으셨기를~ 앗 다 아시는 내용이라구요~ /^_^/ 후다닥~

3. Socket부분 소스

서버부분:

//자 메인함수에서 포트를 정할 수 있도록 했습니다. 실행을 할 때 java chat 20001 이런식으로 실행합니다.

//만약 리눅스에서 백그라운드로 항상 실행되어 돌아가도록 하려면,

//nohup java chat 20001 & <-이렇게 실행합니다.

public static void main(String arg[])throws IOException
{
//chat이 서버부분의 클래스입니다.

chat server = new chat();

//서버용 소켓이 따로 있다고 말씀드렸죠?
ServerSocket serverSocket = null;

//20001을 받아옵니다.
int port = Integer.parseInt(arg[0]);

boolean listening = true;
try
{

//서버용 소켓을 port로 엽니다. 서버용 소켓이므로 ip는 필요없겠죠?
serverSocket = new ServerSocket(port);
}
catch(IOException e)
{
System.out.println("연결 실패입니다. \n");
System.exit(-1);
}
System.out.println("서버" + serverSocket +","+ port+ "에서 연결을 기다립니다.\n");
//말씀하신 대롭니다^^;

}

클라이언트 부분:

public void connect()
{
try
{

//chaturl:ip와 port로 소켓을 엽니다.
chatSocket = new Socket(applet.chaturl,applet.port);

//각각 스트림을 연결합니다.

input = new DataInputStream(new BufferedInputStream(chatSocket.getInputStream()));
output = new DataOutputStream(new BufferedOutputStream(chatSocket.getOutputStream()));

//입력스트림에 읽을 준비를 합니다. readUTF는 block method로 읽을 것이 올 때까지 기다립니다.

String line=input.readUTF();

//서버에서 소켓이 연결되면 Terminal이란 메세지를 보내기로 했습니다.
if(line.equals("Terminal"))
{
//사용자정보를 보냅니다. |로 연결된 스트링을 보내는데, 나중에 StringTokenizer로 잘라써야겠지요.

output.writeUTF("chogi|"+subtitle+"|"+sex+"|"+age+"|"+nick.trim()+"|"+loca+"|"+email+"|"+cpic+"|"+ccolor);

//쓸 때는 꼭 flush를 해주어야 버퍼에 쌓여있지 않고 바로 전송됩니다.

output.flush();
}
}
catch( UnknownHostException e)
{
System.exit(1);
}
catch(IOException e)
{
System.exit(1);
}
}

 

4. Thread 이용법

음 프로세스와 Thread의 차이점은 아시겠지요?

일반적으로 프로그램을 실행시키면, 하나의 프로세스로서 동작하게 됩니다. 다시 말해서, 우리가 실행시키는 하나의 프로그램은 하나의 프로세스로서 나타나게 됩니다. 자바에서의 프로세스는 자바 런타임 환경과 밀접한 관계를 갖고 있습니다. 왜냐하면, 자바 런타임 환경은 프로세스가 실행될 수 있는 기반 환경을 제공해 주기 때문입니다. 프로세스는 다른 프로세스를 생성할 수 있는데, 이 때 생성된 프로세스를 자식 프로세스라하고 기존에 있던 프로세스를 부모 프로세스라 합니다. 이러한 부모/자식 프로세스 개념은 하나의 자바 프로그램에서 다른 프로그램을 실행시키고자 할 때, 주로 사용됩니다. 다시 말해서, 플랫폼 독립적인 자바 프로그램이 플랫폼과 밀접한 관련이 있는 작업을 해야 할 경우, 해당 작업을 수행할 프로그램을 다른 언어로 해당 플랫폼에 맞도록 작성하고, 이 프로그램을 자바 프로그램에서 실행시켜 주는 것입니다. - 박용우님 강좌에서 발췌 -

자바에서 프로세스를 관리해주는 클래스로 Runtime 클래스와 Process클래스가 있습니다. 메모장같은 외부프로그램을 실행하고 관리할 수 있도록 해주지요.

하나의 프로그램을 프로세스라고 볼 때, 스레드는 하나의 프로그램 내에서의 실행 단위라고 할 수 있습니다. 자바에서는 각 작업(타스크)을 스레드로 표현하도록 하고, 이러한 스레드를 여러 개 둘 수 있도록 함으로써 멀티타스킹을 가능하게 해 줍니다. 다시 말해서, 자바에서는 멀티태스킹을 여러 개의 스레드를 동시에 수행하는 멀티스레딩을 이용하여 해결하고 있습니다. 따라서, 자바 가상머신은 하나의 애플리케이션이 동시에 수행되는 여러 개의 스레드를 가질 수 있도록 하고 있습니다. 물론, 일의 우선순위가 존재하듯이 모든 스레드는 그 우선순위를 가지게 됩니다. - 역시 박용우님 강좌에서 발췌 -

멀티쓰레드는 자바에 장점 중에 하나입니다. c에서 쓰레드를 구현하려면 무척 힘들지만(가령, 0.05초마다 번갈아가며 함수를 수행한다든지), 자바에서는 다만 Thread클래스를 상속 혹은 Runnable 인터페이스를 구현하기만 하면 됩니다.

자 그러면 제가 만든 채팅에서는 쓰레드가 어디서 어떻게 쓰이고 있는 지 한 번 알아볼까요? (위에서도 언급한 내용이긴 합니다.)

우선 서버가 사용해야 합니다. 일단 서버용 소켓을 열어놓고, 클라이언트들이 언제 접속할 지 모르니 클라이언트들의 소켓접속 시도가 오면 이를 소켓 하나를 열어 연결시켜주는 작업을 스레드로 처리합니다. 스레드가 아니라면 접속요청이 하나 왔을 때, 소켓열고 하는 작업 하는 동안 다른 클라이언트는 접속을 할 수 없겠지요?

또한, 메시지를 받을 때도 필요합니다. 메시지가 언제 올 지 모르니 메시지를 받는 함수를 무한루프를 통해 돌고 있어야 겠지요. 이를 스레드로 처리하지 않으면 메시지 받기 위해서 다른 아무 일도 할 수 없겠지요? ^^; 메시지 받아서 처리해서 돌려보내는 동안 다른 메시지가 와도 처리 못하는 것은 물론이고요.

클라이언트 입장에서는 메시지 받을 때 필요하겠군요. 어떤 넘이 말할 지 모르니 음....또 언제 필요 있나? 갸우뚱 /?^_^?/ 말이 필요없다. 소스를 보시겠습니다. ^^

5.Thread부분 소스

서버 클라이언트 접속 받아 스레드로 생성해 넘기는 부분:

try
{
while(listening)
{
//ChatThread 는 각 클라이언트를 다루는 Thread를 상속한 클래스입니다.

ChatThread thread;
thread = new ChatThread(server,serverSocket.accept());
// server.addClient(thread);
thread.start();
}
serverSocket.close();
}
catch(IOException e) {}

이 부분이 서버측에서 위에서 보신 소스(main 메소드) 뒤 부분에 추가되어야하는 부분입니다.

listening = true니까 무한루프겠지요.

thread 생성시, chat 클래스(server)를 인자로 넘겨줍니다. 그쪽에서 쓸 필요가 있거든요.

그러면 ChatThread를 보시겠습니다. 필요없거나 지나치게 긴 부분은 생략된 부분입니다.

//클래스 자체가 스레드입니다.

class ChatThread extends Thread //그치요?
{
chat xServer;
Socket xSocket;

//클라이언트 속성
String bangje;
String szUserName;
String sex = null;
String age = null;
String loca = "없음";
String email = "없음";
String host;

//스트림 선언

public DataInputStream streamIn;
public DataOutputStream streamOut;
int nState=0;
boolean bangjang = false;//방장인가?
String time=null;
Vector temp;
String cpic="0",ccolor="0";//캐릭터채팅과 글자색상 저장을 위한 변수

public ChatThread(chat server,Socket socket)
{

//생성자 처리

xServer = server;
xSocket = socket;
streamOut =null;
streamIn = null;
}

public void run()
{
try
{

//스트림 열고
streamIn = new DataInputStream(new BufferedInputStream(xSocket.getInputStream()));
streamOut = new DataOutputStream(new BufferedOutputStream(xSocket.getOutputStream()));

String inputLine, outputLine;

//아까 접속되면 Terminal이라고 써서 보내주기로 했지요.

streamOut.writeUTF("Terminal");
streamOut.flush();

//이건 제가 함 해본건데, 클라이언트의 ip를 추적한 것이지요.

String localhost = xSocket.getInetAddress().toString();
host=localhost.substring(0,localhost.indexOf("/"));

//메시지받는 무한루프입니다.

while ((inputLine = streamIn.readUTF()) != null)
{

//StringTokenizer 사용법은 나중에.


StringTokenizer str = new StringTokenizer(inputLine,"|");
inputLine =(String) str.nextToken();

//이런저런 메시지 처리하도록 if문 분기

if(inputLine.equals("admin"))
{
xServer.adminchogi(str,this);
}
else if(inputLine.equals("adminout"))
{
xServer.adminout(this);
}
else if(inputLine.equals("newbang"))
{
xServer.newbang(str,this);
}

}
//종료처리

streamOut.close();
streamIn.close();
xSocket.close();
xServer.removes(this);
}

//Exception발생시 종료처리
catch(Exception e)
{

//갑자기 접속끊기거나 해서 나갔다면, 그 넘을 방에서 빼지 않으면 계속 남아 있을 지도..(리스트에서도 삭제)
temp = (Vector)xServer.groups.get(bangje);
temp.removeElement(this);
xServer.groups.put(bangje,temp);
}
}
}

헉헉.. 됐군요. 네 다 알려드리고 있습니다. 속 후련하시죠?

클라이언트 부분:

이 부분은 간단합니다. 메시지만 무한루프 돌면서 받아주면 되지요.

public void run()//Thread시건.. Runnable을 구현하셨던.. 어쨌든 run()메소드
{
try
{
while(true)
{
//메시지 종류에 따라 처리해주는 메소드
Messagekind(new StringTokenizer(input.readUTF(),"|"));
try
{

//스레드 잠깐 쉬어줍니다. 스레드가 너무 바쁘게 돌아가면 자칫 해야할 일을 못합니다.
wait(100);
}
catch(Exception e)
{
}
}
}
catch(IOException e)
{
disconnect();//스트림이 끊기면 접속이 끊겼단 뜻이므로 disconnet()를 호출(물론 사용자 정의 메소드)
}
}

 

6. 프로토콜 정의와 StringTokenizer 클래스의 이용법

먼저, 통신규약(프로토콜)을 먼저 정의해봅시다. 프로토콜은 보내는 측과 받는 측에서 미리 약속한 일정한 형식 같은 것을 의미합니다. 아무튼 서버와 클라이언트가 어떠한 형식으로 서로를 구분할 것인지를 약속해야 합니다. 프로토콜을 잘 정의하는 것은 소켓 통신의 기본입니다. 프로토콜을 얼마나 잘 정의하느냐에 따라서 프로그래밍이 대단히 어려울 수도 있고, 쉬워질 수도 있습니다. 프로토콜을 잘못 정의하면 프로그램 중에 프로토콜을 바꾸어야 하는 경우도 생길 수 있습니다. 이런 경우에는 데이터를 전송하거나 수신하는 부분을 다시 다 바꾸어주어야 하므로 보통 힘든 일이 아니겠죠.

정의는 크게 두 가지가 있습니다. 쓸 때 일정 바이트 수로 척척척 써서, 받을 때 그 바이트 수대로 잘라서 사용하는 법과, 각 토큰(우리가 사용할 메시지들)과 토큰 사이에 분리자를 넣어서 받을 때 분리자를 기준으로 메시지를 잘라서 사용하는 법이 그것입니다.토큰의 크기가 일정한 크기를 갖거나, 특정 최대값을 갖고 있다면 모르지만, 채팅에서는 일반적으로 그렇지 않기 때문에, (이 사람이 몇 글자를 칠 지 어떻게 알겠습니까..) 두 번째 방법을 많이 씁니다.

가령, 대화는 "dewha:할말" 귓속말은 "ear:id:할말" 이런 식으로 만들어서 서버로 전송하는 것이지요. 이해가 되시나요? 저것을 API에 나와있는 String의 무지 많은 메소드들을 잘 활용해서 잘라서 쓰면 되는 것입니다. 어려우시다고요? 음....그러면.. 이를 편하게 해주는 StringTokenizer클래스에 대해 알아볼까요? 간단합니다. 당장 치겠습니다. ^^;

StringTokenizer stn = new StringTokenizer("메시지", "구분자들");

String str1 = stn.nextToken();

String str2 = stn.nextToken();

...

이런 식으로 사용합니다. 구분자들은? 예를 들어 "@!"이렇게 넣으면 @혹은 !을 기준으로 토큰들을 구분해냅니다.

StringTokenizer str = new StringTokenizer(inputLine,"|");
s = str.nextToken();

s2 = str.nextToken();

s3 = str.nextToken();

이렇게 되어 있습니다. inputLine이라는 String을 "|"을 기준으로 잘라내는 것이지요.

inputLine은 뭐.. "ear | id | 할 말 " 이렇게 되어 있다면 s에 ear가 s2에 id가 s3에 할말이 들어가겠네요.

ear이므로 귓속말에 해당하는 메소드를 호출하시면 되겠고.. id이므로 자신의 방에 들어있는 각 클라이언트 스레드들을 검색해서 그 대화명이랑 여기 id랑 같은 스레드에게만 "할 말"을 보내주면 됩니다.

음하하. 오늘은 여기까지..... 일 안하고 강좌만 두 시간을 써댔네요. 문의는 문의게시판으로 해주세요. 다 도움되셨죠?

7. Canvas를 이용한 Image다루기

자, 이번엔 Canvas클래스에 대해 알아보도록 하겠습니다. Canvas는 마우스 이벤트를 받을 수 있는 그림 영역으로, Applet처럼 public void paint(Graphics g)를 오버라이드해서 사용합니다. Applet을 사용해보셨던 (이 채팅도 애플릿으로 되어있긴 하지만..) 분이라면 어렵지 않게 익히실 수 있으실 겁니다. Graphics클래스도 아셔야겠지요? Graphics는 Image를 그리는 도화지라고 표현을 하더군요. 하지만, 제가 보기엔 도화지보다는 펜이라는 생각이 듭니다. 어쨌든 이 안에는 drawString이라든지.. drawImage라든지.. 무궁무진한 메소드들이 있지요. API 한 번 찾아서 꼼꼼히 읽어보세요.

제가 자주 사용하는 Image 크기에 맞춰 그 크기 그대로 그리는 Canvas를 하나 보시겠습니다.

class Imagespr extends Canvas
{

Image xImg = null;

public void paint(Graphics g)
{
if(xImg == null) return ;
g.drawImage(xImg,0,0,this);
}

public void loadImage(Image img)
{
int status;
xImg = img;
setSize(xImg.getWidth(this),xImg.getHeight(this));
repaint();
}
}

네, 내부클래스로 많이 사용을 하지요. 사용은 Imagespr의 객체를 생성한 다음에 이 안의 loadImage(Image)메소드를 호출하여 표시합니다. Image는 applet에서 getDocumentBase(), getCodeBase() 등으로 받아와야겠지요? 그 부분을 보시겠습니다.

iloading = getImage(getCodeBase(),"chart-img/loading.gif");

로딩이미지를 받아오는 부분입니다. iloading은 Image의 객체겠지요. 음 화면에 바로 보여야하는 이미지라면 이렇게만 하면 간혹 큰 이미지의 경우 전체가 다 로딩되지 않은 채 애플릿이 뜰 때도 있습니다. 이미지를 로딩하는 것은 자바에서 내부적으로 별도의 스레드를 돌려서 로딩하기 때문이지요.

이럴 때는

MediaTracker tracker;
tracker = new MediaTracker(this);

iloading = getImage(getCodeBase(),"chart-img/loading.gif");
tracker.addImage(iloading,0);
iarrow = getImage(getCodeBase(),"chart-img/arrow.gif");
tracker.addImage(iarrow,0);

try
{
tracker.waitForAll(0);
}
catch(InterruptedException e)
{
return;
}

 

이런 식으로 MediaTracker를 이용합니다. MediaTracker생성하고, tracker에 Image와 인덱스(0)를 붙이고, 그 인덱스(0) 모두를 기다립니다. 써먹어보세요. ^^; 또한 이에 대한 API도 찾아보시기 바랍니다. 다른 메소드들도 대동소이합니다.

이제, 저 위에서 보인 Imagespr의 객체를 생성해 봅시다.

Imagespr isbt = new Imagespr[12];//기왕이면 어렵게 배열로 선언해봅시다. 배열 개수 선언하고,
for(int i = 0 ; i < 12 ; i++)
{
isbt[i] = new Imagespr();//각자 따로 생성해야 하는 거 아시죠?
if(i == 0)
isbt[i].loadImage(applet.ibt_p[i]);//loadImage를 사용합니다.

//이때, 상위클래스 객체로 받아온 Image를 인자로 받습니다.

else
isbt[i].loadImage(applet.ibt[i]);
isbt[i].addMouseListener(new MyMouseListener());//마우스리스너도 붙이네요? 필요하다면..
if (i < 10)
if (i == 4 || i == 9)
addComp(isbt[i], i, 0, 1, 1, 2);//이는 제가 만든 메소드로 GridBackLayout으로 어딘가에 붙이는 과정.
else
addComp(isbt[i], i, 0, 1, 1, 0);

else
addComp(isbt[i], i+4, 0, 1, 1, 1);
}

 

코딩을 보니까 단박에 이해되신다고요? 흠흠~ 좋습니다.

자 Canvas클래스의 자세한 이용(더블버퍼링, 백업이미지다루기, Clip이용, 스크롤바와의 연계 등)은 웹차트 강의때 하기로 하고...오늘은 일단 유용한 내부클래스 하나를 배워보는 것으로 ...여기까지!! ..........하고 끝내려고 했으나, 내부클래스에 대해 궁금해하시는 분이 계실까봐.. ㅜ.ㅜ 마저 더 써봅니다.

자 클래스 안에 또 클래스가 들어오는 형식이 4가지가 있습니다.(자세한 건 제 강좌 아래에 박용우님의 강좌 참고) 그 중 하나가 내부클래스인데, 그냥 멤버변수 선언하는 부분에 떡하니 클래스가 들어오는 것입니다. 여기서는 static변수를 사용할 수 없으며, 내부클래스안에서는 외부클래스의 필드와 메소드를 아무런 제한 없이 그대로 사용하실 수 있습니다. 허걱 좋지요.. 그러나 기우에서 말씀드리는데 혹시 this라는 명령어를 쓰고 싶으시면, 이 내부클래스 안에서 this를 사용하시면, 내부클래스 자체를 가리키겠지요...(당연한가?) 그럼 외부클래스의 메소드를 사용하는데 그 외부클래스 자체를 나타내고 싶다.. 하면. 그 외부클래스명.this 이렇게 쓰셔야 합니다. 쉽지요? 자 오늘은 여기까지. /^.^/

 

보다 나은 버전의 채팅 등 3가지의 프로젝트를 보다 충실하고 친절하게 설명한

"자바 실무 테크닉 비법전수"가 발간되었습니다.

구경하기 -> [http://www.50001.com/books/ ]