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

TCP 프로그래밍 - 윈도우용 채팅 클라이언트(AWT를 이용)

경진 2008. 7. 19. 14:31
윈도우용 채팅 클라이언트

채팅 서버를 사용하는 윈도우용 클라이언트를 작성한다.

윈도우 채팅 클라이언트 작성 개요

윈도우용 클라이언트의 작성 방법을 배우려면 먼저 윈도우용 클라이언트의 동작 원리에 대해서 이해해야 한다.

명령 창에서 실행되는 채팅 클라이언트의 경우, 문장을 전송하기 위해서 한 줄의 끝을 의미하는 엔터 키를 입력했다 하지만 윈도우 용 클라이언트는 텍스트 입력 창에서 엔터키를 눌렀다는 이벤트를 처리하는 부분에서 문장을 전송해야 한다.

또한 명령 창에서 실행되는 채팅 클라이언트 서버로부터 전송 받을 문자열을 단순히 화면에 출력했지만, 윈도우용 클라이언트는 서버로 전송 받은 문자열을 TextArea 등에 보여지게 해야한다.

import java.awt.*;       
import java.awt.event.*;        
       
import java.net.*;       
import java.io.*;       

public class WinChatClient extends Frame implements ActionListener{       
    private TextField idTF = null;   
    private TextField input = null;   
    private TextArea display = null;   
    private CardLayout cardLayout = null;    

    private BufferedReader br = null;   
    private PrintWriter pw = null;   
    private Socket sock = null;   
    public WinChatClient(String ip){   
        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{   
            sock = new Socket(ip, 10001);
            pw = new PrintWriter(new OutputStreamWriter(sock.getOutputStream()));
            br = new BufferedReader(new InputStreamReader(sock.getInputStream()));
        }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){
                pw.println("/quit");   
                pw.flush();   
                try{   
                    sock.close();
                }catch(Exception ex){}   
                System.out.println("종료합니다.");   
                System.exit(0);   
            }       
        });           
        setVisible(true);           
    }                
                   
                   
    public static void main(String[] args) {               
        if(args.length != 1){           
            System.out.println("사용법 : java WinChatClient ip");       
            System.exit(1);   
        }       
        new WinChatClient(args[0]);       
    }            
               
    public void actionPerformed(ActionEvent e) {           
        if(e.getSource() == idTF){       
            String id = idTF.getText();   
            if(id == null || id.trim().equals("")){   
                System.out.println("아이디를 다시 입력하여 주세요.");
                return;
            }   
            pw.println(id.trim());   
            pw.flush();   
            WinInputThread wit = new WinInputThread(sock, br);   
            wit.start();   
            cardLayout.show(this, "main");       
            input.requestFocus();       
        }else if(e.getSource() == input){           
            String msg = input.getText();       
            pw.println(msg);       
            pw.flush();       
            if(msg.equals("/quit")){       
                try{   
                    sock.close();
                }catch(Exception ex){}   
                System.out.println("종료합니다.");   
                System.exit(1);   
            }       
            input.setText("");       
            input.requestFocus();       
        }           
    } // actionPerformed                
                   
                   
    class WinInputThread extends Thread{               
        private Socket sock = null;           
        private BufferedReader br = null;           
        public WinInputThread(Socket sock, BufferedReader br){           
            this.sock = sock;       
            this.br = br;       
        }           
        public void run(){           
            try{       
                String line = null;   
                while((line = br.readLine()) != null){   
                    display.append(line + "\n");
                }   
            }catch(Exception ex){           
            }finally{           
                try{       
                    if(br != null)   
                        br.close();
                }catch(Exception ex){}       
                try{       
                    if(sock != null)   
                        sock.close();
                }catch(Exception ex){}       
            }           
        } // InputThread               
    } // WinInputThread end                   
} // WinChat Client class                       

윈도우용 채팅 클라이언트는 윈도우 컴포넌트 여러 개로 구성된다. 그렇지만 윈도우(창)은 하나만 존재한다 이런 이유로 Frame을 상속 받았으며, 여러 종류의 윈도우 컴포넌트를 필드로 선언했다. 또한 ActionListener를 구현한 이유는 이벤트(텍스트 입력 창에서 엔터 키를 입력했거나, 버튼을 눌렀을 경우 등)을 처리하기 위해서다.

public class WinChatClient extends Frame implements ActionListener{       
    private TextField idTF = null;   
    private TextField input = null;   
    private TextArea display = null;   
    private CardLayout cardLayout = null;    

    private BufferedReader br = null;   
    private PrintWriter pw = null;   
    private Socket sock = null;   

WinChatClient 생성자에서는 인자로 접속할 IP를 전달받는다. 그리고 나서, 아이디 입력 TextField와 문자열 TextField에 ActionEvent를 처리하기 위한 ActionListener를 추가한다. 생략된 부분은 윈도우 컴포넌트를 생성하는 부분과 배치하는 부분이다.

    public WinChatClient(String ip){   
        super("채팅 클라이언트");
……
        idTF = new TextField(20);
        idTF.addActionListener(this);
……
        input = new TextField();
        input.addActionListener(this);
……

서버에 접속하기 위해서 Socket을 생성한 후, Socket을 이용해서 InputStream과 OutputStream을 얻고 각각 BufferedReader와 PrintWriter로 변환 시킨다.

    public WinChatClient(String ip){
……
        try{   
            sock = new Socket(ip, 10001);
            pw = new PrintWriter(new OutputStreamWriter(sock.getOutputStream()));
            br = new BufferedReader(new InputStreamReader(sock.getInputStream()));
……

윈도우 Frame에 WindowListener를 추가한다. 윈도우의 종료 버튼이 클릭되면 서버에게 "/quit" 메시지를 전송한 후, Socket을 닫고 종료한다.

    public WinChatClient(String ip){
……
        addWindowListener(new WindowAdapter(){   
            public void windowClosing(WindowEvent e){
                pw.println("/quit");   
                pw.flush();   
                try{   
                    sock.close();
                }catch(Exception ex){}   
                System.out.println("종료합니다.");   
                System.exit(0);   
            }       
        });           
        setVisible(true);           
    }

actionPerformed() 메소드는 ActionEvent를 처리하기 위한 메소드다. 아이디 입력 창으로부터 아이디를 입력 받았다면 서버로부터 문자열을 입력받아 TextArea에 추가하는 WinInputThread를 생성한 후 실행한다. 그 후 CardLayout을 변경해서 실제로 채팅할 수 있는 패널로 변경시킨다.

    public void actionPerformed(ActionEvent e) {           
        if(e.getSource() == idTF){       
            String id = idTF.getText();   
            if(id == null || id.trim().equals("")){   
                System.out.println("아이디를 다시 입력하여 주세요.");
                return;
            }   
            pw.println(id.trim());   
            pw.flush();   
            WinInputThread wit = new WinInputThread(sock, br);   
            wit.start();   
            cardLayout.show(this, "main");       
            input.requestFocus();       
        }else if(e.getSource() == input){            
……      
        }           
    } // actionPerformed                

채팅 입력 TextField로부터 이벤트가 발생하면 TextField에 저장된 문자열을 소켓을 통해서 전송한다. 그런 후 TextField의 문자열을 지우고 클라이언트가 다시 입력받을 수 있도록 준비시킨다. 만약, 클라이언가 "/quit" 문자열을 입력했다면, 서버에게 "/quit"를 전송한 후 클라이언트를 종료시킨다.

    public void actionPerformed(ActionEvent e) {           
        if(e.getSource() == idTF){        
……
        }else if(e.getSource() == input){           
            String msg = input.getText();       
            pw.println(msg);       
            pw.flush();       
            if(msg.equals("/quit")){       
                try{   
                    sock.close();
                }catch(Exception ex){}   
                System.out.println("종료합니다.");   
                System.exit(1);   
            }       
            input.setText("");       
            input.requestFocus();       
        }           
    } // actionPerformed                

윈도우용 채팅 클라이언트 실행

채팅 서버를 실행한 후, 채팅 클라이언트에서 아이디를 입력해서 로그인한 후 대화가 잘 되는지 확인한다.

로그인 화면

로그인 화면

윈도우 채팅 프로그램 대화 화면

윈도우 채팅 프로그램 대화 화면

윈도우 채팅 프로그램 대화 화면

윈도우 채팅 프로그램 대화 화면

윈도우용 채팅 클라이언트에서 개선해야 할 부분

윈도우용 채팅 클라이언트 역시 명령 창 채팅 클라이언트와 마찬가지로 방 하나만 가지고 있다. 채팅에 참여하는 인원이 많아지면, 마찬가지로 방 여러 개와 대기실을 가질 수 있게 만들어야 한다.

※ 윈도우 프로그래밍을 하기 전에 내가 만들어야 할 UI(User Interface)가 어떤 형태인지 정확하게 정의를 내리자 내부적으로 동작하는 원리도 중요하지만 윈도우 프로그래밍의 경우 만들어야 할 UI를 정확하게 정의내리고 시작한다면 좀더 쉽게 프로그래밍 할 수 있다.