|
SSISO Community검색 |
|
SSISO Community메뉴 |
|
SSISO Community카페 |
|
블로그 카테고리 |
|
|
Synchronized로 검색한 결과 |
|
등록일:2008-03-06 13:06:58 작성자: 제목:클라이언트 상호 통신 |
|
좀더 진보하여 클라이언트끼리 정보를 주고받을 수 있는 서버/클라이언트 프로그램을 생각해 보자. 이때 서버는 클라이언트를 서로 이어주는 중계자 역할을 한다.
다중 클라이언트와 서버가 있고, 클라이언트끼리 메시지를 주고받는 채팅 프로그램을 예로 들어보자. 채팅에는 공개방, 비밀방, 귓말기능 등이 필요하다. 하지만 초보자에게는 어려운 것이고, 또 내용이 방대할 것 같다. 그래서 모든 클라이언트가 하나의 공간에서 메시지를 주고받는 아주 단순한 경우를 생각하자. 아래는 채팅 프로그램에서 클라이언트와 서버의 통신을 표현한 그림이다. 그림에는 클라이언트가 둘밖에 없지만 더 많이 있을 수 있다.
[그림 21-12] 다중 클라이언트 ↔ 채팅 서버
서버에는 메시지 방송자라는 객체가 있다. 이 객체는 모든 클라이언트에게 주어진 메시지를 전송하는 기능을 가졌기 때문에 그렇게 이름지었다. 모든 클라이언트에게 메시지를 전달하려면 방송자는 클라이언트와 연결된 모든 소켓을 참조해야 한다. 따라서 방송자는 소켓의 리스트를 가진 Vector를 포함해야할 것이다. 먼저 어떤 클라이언트가 연결된 소켓으로 메시지를 보내면(ⓐ), 소켓은 방송자에게 그 메시지를 보낸다(ⓑ). 그러면 방송자는 자신이 참조하고 있는 모든 소켓의 출력 스트림으로 메시지를 전달한다(ⓒ). 따라서 모든 클라이언트에게 메시지가 전송된다(ⓓ).
그런데, 생각해 볼 문제가 있다. 클라이언트들이 거의 동시에 ⓐ를 수행했을 때 ⓑ와 ⓒ부분에서 데이터가 엉킬 위험이 있다. 따라서 소켓의 출력 스트림으로 메시지를 전달하는 메소드는 동기화되어야 할 것이다. 또, 많은 클라이언트들이 거의 동시에 접속했을 때나 나갔을 때 소켓 관리자의 add나 remove 메소드도 동기화되어야 할 것이다. 하지만 Vector 객체는 내부에서 동기화를 수행하므로 크게 신경 쓰지 않아도 된다.
채팅 서버
채팅 서버에서 유심히 살펴볼 부분은 방송자를 구현하는 부분이다. 메시지 방송자 클래스의 이름은 BManager이며 Vector를 상속한다.
class BManager extends Vector{
BManager(){}
void add(Socket sock){
// 소켓을 추가한다.
}
void remove(Socket sock){
// 소켓을 제거한다.
}
Synchronized void sendToAll(String msg){
// 모든 소켓의 출력 스트림으로 msg를 출력한다.
}
Synchronized void sendClientInfo(){
// 모든 소켓의 출력 스트림으로 현재 채팅 인원의 수를 출력한다.
}
}
Server5.java
import java.net.*;
import java.io.*;
import java.util.*;
public class Server5{
private ServerSocket server;
private BManager bMan=new BManager(); // 메시지 방송자
public Server5(){}
void startServer(){
try{
server=new ServerSocket(7777);
System.out.println("서버소켓이 생성되었습니다.");
while(true){
Socket socket=server.accept();
// 클라이언트와 통신하는 스레드를 생성하고 실행시킨다.
new Chat_Thread(socket).start();
// 방송자의 리스트에 socket을 추가한다.
bMan.add(socket);
// 방송자는 모든 클라이언트에게 현재 접속 인원의 수를 전송한다.
bMan.sendClientInfo();
}
}catch(Exception e){
System.out.println(e);
}
}
public static void main(String[] args){
Server5 server=new Server5();
server.startServer();
}
// 클라이언트와 통신하는 스레드 클래스
class Chat_Thread extends Thread{
Socket socket;
private BufferedReader reader;
private PrintWriter writer;
Chat_Thread(Socket socket){
this.socket=socket;
}
public void run(){
try{
reader=new BufferedReader(
new InputStreamReader(socket.getInputStream()));
writer=new PrintWriter(socket.getOutputStream(), true)
String msg;
// 입력 스트림으로부터 메시지를 얻는다.
while((msg=reader.readLine())!=null){
System.out.println(msg);
// 모든 클라이언트에게 메시지를 전송한다.
bMan.sendToAll(msg);
}
}catch(Exception e){
}finally{
try{
// 방송자의 리스트에서 socket을 제거한다.
bMan.remove(socket);
if(reader!=null) reader.close();
if(writer!=null) writer.close();
if(socket!=null) socket.close();
reader=null; writer=null; socket=null;
System.out.println("클라이언트가 나갔습니다.");
// 모든 클라이언트에게 현재 접속 인원의 수를 전송한다.
bMan.sendClientInfo();
}catch(Exception e){}
}
}
}
// 메시지 방송자 클래스, Vector를 상속한다.
class BManager extends Vector{
BManager(){}
void add(Socket sock){ // 소켓을 추가한다.
super.add(sock);
}
void remove(Socket sock){ // 소켓을 제거한다.
super.remove(sock);
}
// 모든 클라이언트에게 msg를 전송한다. 동기화 메소드
Synchronized void sendToAll(String msg){
PrintWriter writer=null; // 출력 스트림
Socket sock; // 소켓
for(int i=0; i<size(); I++){ // 소켓의 개수만큼 반복 실행
sock=(Socket)elementAt(i); // i번째 소켓을 얻는다.
try{
// i번째 소켓의 출력 스트림을 얻는다.
writer=new PrintWriter(sock.getOutputStream(), true);
}catch(IOException ie){}
// i번째 소켓의 출력 스트림으로 msg를 출력한다.
if(writer!=null)writer.println(msg);
}
}
// 모든 클라이언트에게 현재 채팅 인원의 수를 전송한다.
Synchronized void sendClientInfo(){
String info="현재 채팅 인원: "+size();
System.out.println(info);
sendToAll(info);
}
}
}
채팅 클라이언트
채팅 클라이언트 소스는 앞에서 작성한 다른 클라이언트의 그것과 비슷하다. 출력 스트림으로 데이터를 보내고, 입력 스트림으로부터 데이터를 읽어서 화면에 보여주면 된다. 다음은 3명이 접속하여 대화를 나누는 화면이다.
[그림 21-13] 채팅 서버/클라이언트 실행 화면
다음은 채팅 클라이언트의 소스이다.
Client5.java
import java.awt.*;
import java.net.*;
import java.io.*;
import java.awt.event.*;
public class Client5 extends Frame implements Runnable{
private TextField nameBox=new TextField("<이름>"); // 사용자 이름을 나타낸다.
private TextArea msgView=new TextArea();
private TextField sendBox=new TextField();
private BufferedReader reader;
private PrintWriter writer;
private Socket socket;
public Client5(String title){
super(title);
msgView.setEditable(false);
// 컨트롤들을 배치한다.
add(nameBox,"North");
add(msgView,"Center");
add(sendBox,"South");
// sendBox 액션 이벤트 처리
sendBox.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent ae){
try{
// 사용자의 이름과 메시지 내용을 전송한다.
writer.println(nameBox.getText()+" : "+ sendBox.getText());
sendBox.setText(""); // sendBox의 내용을 지운다.
}catch(Exception ie){}
}
});
pack();
}
public void run(){
while(true){
try{
// 입력 스트림으로부터 데이터를 읽어서 msgView에 추가한다.
msgView.append(reader.readLine()+"\n");
}catch(IOException ie){}
}
}
private void connect(){
try{
msgView.append("서버소켓과의 연결을 시도합니다.\n");
socket=new Socket("127.0.0.1", 7777);
msgView.append("채팅 준비가 완료되었습니다.\n");
reader=new BufferedReader(
new InputStreamReader(socket.getInputStream()));
writer=new PrintWriter(socket.getOutputStream(), true);
new Thread(this).start();
}catch(Exception e){
msgView.append("연결 실패..");
}
}
public static void main(String[] args){
Client5 client=new Client5("채팅");
client.setVisible(true);
client.connect();
}
}
출처 : http://java.pukyung.co.kr/Lecture/Chapter21.php [:2008년 03월 06일 14:47:03 수정되었습니다.:] |
|
|
|
|
|