개인참고자료/자바(네트워크)

멀티캐스팅 - 채팅프로그램작성

경진 2008. 7. 20. 01:59
채팅프로그램작성

TCP 방식을 이용한 채팅 클라이언트를 작성했다. 이번에 만들 채팅 클라이언트는 멀티캐스팅을 이용한 방식이다. TCP 방식의 경우에는 채팅을 하기 위해서 채팅 서버가 필요했다. 하지말 멀티캐스팅을 이용하면 서버가 필요없는 진정한 의미의 P2P 채팅 프로그램을 작성할 수 있다.

TCP 방식의 채팅 프로그램에서 서버가 하는 가장 중요한 역할은 하나의 클라이언트가 문자열을 보내게 되면 접속한 모든 클라이언트에게 문자열을 전송하는 일이있었다. 그렇지만 멀티캐스팅을 이용하게 되면 사용자가 입력한 문자열을 다른 채팅 클라이언트에게 전송할 때에 서버를 거칠 필요 없이 불특정 다수의 클라이언트에게 문자열을 전송할 수 있다. 즉, 멀티캐스팅을 이용한 채팅 프로그램은 서버가 필요 없고 클라이언트만 있으면되는 것이다.

채팅 클라이언트 작성 : MulticastChatClient

멀티캐스팅을 이용한 채팅 클라이언트는 크게 두가지 중요한 부분으로 구성된다. 첫째는 사용자가 입력한 문자열을 멀티캐스팅 그룹으로 패킷을 전송하는 부분이고, 둘째는 다른 사용자가 멀티캐스팅 그룹에 보낸 패킷을 읽어 들여 출력하는 부분이다. 첫째의 경우에는 사용자가 글을 입력했을 때 이벤트를 처리하는 부분에서 패킷을 전송하면 되고, 둘째의 경우에는 앞서 배웠던 채팅 클라이언트처럼 별도의 스레드를 두어서 처리하면된다. 별도로 존재하는 스레드는 계속해서 멀티스레드 그룹에 전달되는 패킷을 검사해서 읽어 들여 화면에 출력하기 때문이다.

import java.awt.*;   
import java.awt.event.*;   
   
import java.net.*;   
import java.io.*;   
public class MulticastChatClient extends Frame implements ActionListener{   
    private TextField idTF = null;
    private TextField input = null;
    private TextArea display = null;
    private CardLayout cardLayout = null;
   
    DatagramSocket socket = null;
    DatagramPacket spacket = null;
    InetAddress schannel =null;
    int sport = 20005;
    String saddress = "239.0.0.1";
    boolean onAir = true;    
    String id = "";   
       
    public MulticastChatClient(){   
        super("채팅 클라이언트");
        cardLayout = new CardLayout();
        setLayout(cardLayout);
        Panel loginPanel = new Panel();
        loginPanel.setLayout(new BorderLayout());
        loginPanel.add("North", new Label("아이디를 입력하여 주신후 엔터를 입력하여 주세요.");
        idTF = new TextField(20);
        idTF.addActionListener(this);
        Panel c = new Panel();
        c.add(idTF);
        loginPanel.add("Center", c);
        add("login", loginPanel);
        Panel main = new Panel();           
        main.setLayout(new BorderLayout());           
        input = new TextField();           
        input.addActionListener(this);           
        display = new TextArea();           
        display.setEditable(false);           
        main.add("Center", display);           
        main.add("South", input);           
        add("main", main);           
        try{           
            socket = new DatagramSocket(sport);        
        }catch(Exception ex){           
            System.out.println("서버와 접속시 오류가 발생하였습니다.");       
            System.out.println(ex);       
            System.exit(1);       
        }           
        setSize(500, 500);           
        cardLayout.show(this, "login");           
        addWindowListener(new WindowAdapter(){           
            public void windowClosing(WindowEvent e){       
                System.out.println("종료합니다.");   
                sendMsg(id + " 님이 종료합니다.");   
                try{   
                    socket.close();
                }catch(Exception ex){}   
                System.exit(0);   
            }       
        });           
        setVisible(true);           
    }               
                   
                   
    public static void main(String[] args) {       
        new MulticastChatClient();   
    }       
    public void sendMsg(String msg){       
        byte[] b = new byte[2000];    
          try {    
            b = msg.getBytes(); // 바이트 배열로 만듦
            schannel = InetAddress.getByName(saddress);
            spacket = new DatagramPacket(b, b.length, schannel, sport);
            socket.send(spacket);
        } catch (IOException e) {    
            e.printStackTrace();
        }     
    }       
           
    public void actionPerformed(ActionEvent e) {       
        if(e.getSource() == idTF){       
            id = idTF.getText();   
            if(id == null || id.trim().equals("")){   
                System.out.println("아이디를 다시 입력하여 주세요.");
                return;
            }   
            sendMsg(id + " 님이 입장하였습니다.\n");   
            WinInputMulticastThread wit = new WinInputMulticastThread();   
            wit.start();   
            cardLayout.show(this, "main");   
            input.requestFocus();   
        }else if(e.getSource() == input){       
            String msg = input.getText();   
            sendMsg(id + ":" + msg + "\n");   
            if(msg.equals("/quit")){   
                try{
                    socket.close();
                }catch(Exception ex){}   
                sendMsg(id + " 님이 종료합니다.");   
                System.out.println("종료합니다.");   
                System.exit(1);   
            }       
            input.setText("");       
            input.requestFocus();       
        }           
    } // actionPerformed               
                   
                   
    class WinInputMulticastThread extends Thread{               
        MulticastSocket receiver = null;            
        DatagramPacket packet = null;           
        InetAddress channel =null;           
        int port = 20005;            
        String address = "239.0.0.1";           
        public WinInputMulticastThread(){           
            try {        
                      
               receiver = new MulticastSocket(port);        
               channel = InetAddress.getByName(address);        
               receiver.joinGroup(channel);        
            } catch (IOException e) {        
               e.printStackTrace();        
            }         
        }           
        public void run(){           
            try{       
                while(true){   
                    byte[] b = new byte[2000];
                    packet = new DatagramPacket(b, b.length);
                       receiver.receive(packet);
                    String msg = new String(packet.getData());   
                    if(msg.equals("/quit"))   
                        break;
                    display.append(msg);   
                }    
                receiver.leaveGroup(channel);    
                receiver.close();    
            }catch(Exception ex){       
                System.out.println(ex);   
            }       
        } // InputThread           
    } // WinInputMulticastThread end               
} // MulticastChat Client class                    

sendMsg() 메소드는 문자열을 인자로 전달받아 내부 멀티캐스팅 그룹 IP에 패킷을 전송한다. 그 결과 모든 해당 멀티캐스트 그룹에 포함된 모든 채팅 클라이언트는 패킷을 읽을 수 있게 된다.

    String saddress = "239.0.0.1";
……
    public void sendMsg(String msg){       
        byte[] b = new byte[2000];    
          try {    
            b = msg.getBytes(); // 바이트 배열로 만듦
            schannel = InetAddress.getByName(saddress);
            spacket = new DatagramPacket(b, b.length, schannel, sport);
            socket.send(spacket);
        } catch (IOException e) {    
            e.printStackTrace();
        }     
    }       

아이디 입력 창에서 아이디를 입력하고 엔터키를 입력하면 ActionEvent가 발생하면서 자동으로 actionPerfoemd() 메소드가 호출된다. 아이디로 입력한 값이 공백이 아닐 경우에는 모든 사용자에게 "XXX님이 입장했습니다." 라는 문자열을 전송하게 된다.

그런 후, 멀티 캐스팅 그룹으로부터 패킷을 읽어 들여 화면에 출력하는 WinInputMulticastThread() 객체를 생성해서 새로운 스레드로 실행시킨다.

    public void actionPerformed(ActionEvent e) {       
        if(e.getSource() == idTF){       
            id = idTF.getText();   
            if(id == null || id.trim().equals("")){   
                System.out.println("아이디를 다시 입력하여 주세요.");
                return;
            }   
            sendMsg(id + " 님이 입장하였습니다.\n");   
            WinInputMulticastThread wit = new WinInputMulticastThread();   
            wit.start();   
            cardLayout.show(this, "main");   
            input.requestFocus();   
……
    } // actionPerformed        

사용자가 채팅 입력 창에서 글을 입력하면, "/quit" 문자열이 아닐 경우, 멀티캐스팅 방법을 이용해서 입력한 문장을 전송한다. 이때 앞서 분석한 sendMsg() 메소드가 사용된다.

    public void actionPerformed(ActionEvent e) {       
        if(e.getSource() == idTF){       
……
        }else if(e.getSource() == input){       
            String msg = input.getText();   
            sendMsg(id + ":" + msg + "\n");   
            if(msg.equals("/quit")){   
                try{
                    socket.close();
                }catch(Exception ex){}   
                sendMsg(id + " 님이 종료합니다.");   
                System.out.println("종료합니다.");   
                System.exit(1);   
            }       
            input.setText("");       
            input.requestFocus();       
        }           
    } // actionPerformed          

WinInputMulicastThread 스레드 객체는 멀티캐스팅 그룹에 전달된 패킷을 읽어 들여 화면에 출력 하기 위한 객체다. 생성자에 멀티캐스팅 그룹에 전달되는 패킷을 읽어오기 위해서 MulticastSocket 객체를 생성하고 멀티캐스팅 그룹에 참여하는 것을 알 수 있다.

        String address = "239.0.0.1";           
        public WinInputMulticastThread(){           
            try {        
                      
               receiver = new MulticastSocket(port);        
               channel = InetAddress.getByName(address);        
               receiver.joinGroup(channel);        
            } catch (IOException e) {        
               e.printStackTrace();        
            }         
        }           

WinInputMulticastThread의 run() 메소드는 실제로 멀티캐스팅 그룹에서 패킷을 읽어와 채팅 윈도우의 TextArea에 읽어온 내용을 추가하게 되어 있다. 접속을 종료할 상황이 되면 멀티캐스팅 그룹에 대한 참여를 해지한 후(leaveGroup() 메소드 사용) MulticastSocket 객체를 닫는다.

        public void run(){           
            try{       
                while(true){   
                    byte[] b = new byte[2000];
                    packet = new DatagramPacket(b, b.length);
                       receiver.receive(packet);
                    String msg = new String(packet.getData());   
                    if(msg.equals("/quit"))   
                        break;
                    display.append(msg);   
                }    
                receiver.leaveGroup(channel);    
                receiver.close();    
            }catch(Exception ex){       
                System.out.println(ex);   
            }       
        } // InputThread