본문 바로가기

JAVA

소켓 기반 채팅 프로그램 귓속말 기능 구현

package ex03;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;



public class MultiChatServer {

	private ServerSocket serverSocket = null;
	private Socket socket = null;
	
	ArrayList <ChatThread> chatlist = new ArrayList <ChatThread>();
	HashMap<String, ChatThread> hash= new HashMap<String, ChatThread>();
	
	
	public void start() {
		try {
			
			serverSocket = new ServerSocket(8888);
			System.out.println("server start");
			
		
			while(true) {
				socket = serverSocket.accept();				
				
				ChatThread chat = new ChatThread();
				
				chatlist.add(chat);
				
				chat.start();
			}
		} catch(Exception e) {
		
			System.out.println("[MultiChatServer]start() Exception 발생!!");
		}   
	} 
	
	public static void main(String[] args) {
		MultiChatServer server = new MultiChatServer();
		server.start();
	}
	

	void broadCast(String msg) { //채팅방 인원 전체출력
		for(ChatThread ct : chatlist) {
			ct.outMsg.println(msg);
		}
	}
	void wisper(ChatThread from, ChatThread to,String msg) { //송신그레드,수신스레드,대화내용 매개변수)
		from.outMsg.println(msg); //송신스레드 채팅창에 출력
		to.outMsg.println(msg); // 수신스레드 채팅창에 출력
	}
	
	class ChatThread extends Thread {
		

		String msg;
		String[] rmsg;
		
		
		
		private BufferedReader inMsg = null;
		private PrintWriter outMsg = null;

		public void run() {
		
			boolean status = true;
			System.out.println("##ChatThread start...");
			try {
				
				inMsg = new BufferedReader(new InputStreamReader(socket.getInputStream()));
				outMsg = new PrintWriter(socket.getOutputStream(), true);
				
				
				

				while(status) {
					
					msg = inMsg.readLine();
					
					rmsg = msg.split("/");
					
					
					
					
					
					if(rmsg[1].equals("logout")) {
						hash.remove(rmsg[0]); //hashmap에서 제거
						chatlist.remove(this); //chatlist에서 제거 
						
						 broadCast("SERVER/" + rmsg[0] + "님이 종료했습니다.");
						
						status = false;
					}
					
					else if(rmsg[1].equals("login")) {
						if(hash.containsKey(rmsg[0])) { //id로 hashmap에서 중복검사
							this.outMsg.println("SERVER/ 로그인불가>ID 중복"); 
                            //로그인 한 상대방 채팅창에 로그인 불가 안내메시지 출력
							socket.close(); //소켓 제거
							chatlist.remove(this); //스레드리스트에서 제거 
							status = false; // 상태변경으로 while문 탈출
						}
						
						else{
							hash.put(rmsg[0], this);
                            //중복이 아니면 해당 아이디를 key/ 스레드를 value로 추가
							broadCast("SERVER/"+rmsg[0]+"님이 로그인했습니다."); 
                            //채팅창에 로그인 메세지 출력
						}
						
					}
		
					else if(hash.containsKey(rmsg[1])) { 
                    // 귓속말을 보낼경우 rmsg[0]: 송신id / rmsg[1] 수신id / rmsg[3] 내용 이렇게 분할
					// rmsg[1]이 hashmap에 있는 지 검사 있으면 귓속말의 경우로 판단
					// 수정필요 다른 기능 구현할 때 문제가 될 가능성 
						ChatThread from = hash.get(rmsg[0]); 
                        // rmsg[0] 송신 id를 key값으로 value스레드 찾기
						ChatThread to =  hash.get(rmsg[1]); 
                        // rmsg[1] 수신 id를 key값으로 value스레드 찾기
						wisper(from,to, msg); 
                        //찾은 송신 스레드 , 수신 스레드, 내용을 매개변수로 wisper메소드 호출
					}
					else {
						 broadCast(msg);
					}
				} 
				
				this.interrupt();
				System.out.println("##"+this.getName()+"stop!!");
			} catch(IOException e) {
				chatlist.remove(this);
				// e.printStackTrace();
				System.out.println("[ChatThread]run() IOException 발생!!");
			}
		}
	}
	
}

클라이언트

package ex03;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;



public class MultiChatClient implements ActionListener, Runnable {
    private String ip;
    private String id;
    private String contents;
    private Socket socket;
    private BufferedReader inMsg = null;
    private PrintWriter outMsg = null;

    private JPanel loginPanel;
    private JButton loginButton;
    private JLabel label1;
    private JTextField idInput;

    private JPanel logoutPanel;
    private JLabel label2;
    private JButton logoutButton;

    private JPanel msgPanel;
    private JTextField msgInput;
    private JButton exitButton;

    private JFrame jframe;
    private JTextArea msgOut;

    private Container tab;
    private CardLayout clayout;
    private Thread thread;

    boolean status;

    public MultiChatClient(String ip) {
        this.ip = ip;

        loginPanel = new JPanel();
        loginPanel.setLayout(new BorderLayout());
        idInput = new JTextField(15);
        loginButton = new JButton("로그인");
        loginButton.addActionListener(this);
        label1 = new JLabel("대화명");

        loginPanel.add(label1, BorderLayout.WEST);
        loginPanel.add(idInput, BorderLayout.CENTER);
        loginPanel.add(loginButton, BorderLayout.EAST);

  
        logoutPanel = new JPanel();

        logoutPanel.setLayout(new BorderLayout());
        label2 = new JLabel();
        logoutButton = new JButton("로그아웃");

        logoutButton.addActionListener(this);
 
        logoutPanel.add(label2, BorderLayout.CENTER);
        logoutPanel.add(logoutButton, BorderLayout.EAST);

 
        msgPanel = new JPanel();
 
        msgPanel.setLayout(new BorderLayout());
        msgInput = new JTextField(30);
 
        msgInput.addActionListener(this);
        exitButton = new JButton("종료");
        exitButton.addActionListener(this);
  
        msgPanel.add(msgInput, BorderLayout.CENTER);
        msgPanel.add(exitButton, BorderLayout.EAST);

        tab = new JPanel();
        clayout = new CardLayout();
        tab.setLayout(clayout);
        tab.add(loginPanel, "login");
        tab.add(logoutPanel, "logout");


        jframe = new JFrame("::멀티챗::");
        msgOut = new JTextArea("", 10, 30);
      
        msgOut.setEditable(false);
        
        JScrollPane jsp = new JScrollPane
        (msgOut, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, 
        JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        jframe.add(tab, BorderLayout.NORTH);
        jframe.add(jsp, BorderLayout.CENTER);
        jframe.add(msgPanel, BorderLayout.SOUTH);
       
        clayout.show(tab, "login");
        
        jframe.pack();
        
        jframe.setResizable(false);
        
        jframe.setVisible(true);

    }

    public void connectServer() {
        try {
           
            socket = new Socket(ip, 8888);
            System.out.println("[Client]Server 연결 성공!!");

    
            inMsg = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            outMsg = new PrintWriter(socket.getOutputStream(), true);

    
            outMsg.println(id+"/"+"login");

 
            thread = new Thread(this);
            thread.start();
        } catch(Exception e) {
            // e.printStackTrace();
            System.out.println("[MultiChatClient]connectServer() Exception 발생!!");
        }
    }


    public void actionPerformed(ActionEvent arg0) {
        Object obj = arg0.getSource();

 
        if(obj == exitButton) {
            System.exit(0);
            
        } else if(obj == loginButton) {
            id = idInput.getText();
            label2.setText("대화명 : " + id);
            clayout.show(tab, "logout");
            connectServer();
        } else if(obj == logoutButton) {
   
            outMsg.println(id + "/" + "logout");
        
            msgOut.setText("");
   
            clayout.show(tab, "login");
            outMsg.close();
            try {
                inMsg.close();
                socket.close();
            } catch(IOException e) {
                e.printStackTrace();
            }

            status = false;
            
            
        } else if(obj == msgInput) {
        		contents = msgInput.getText(); //입력창의 내용 contents에 대입
        		
        		if(contents.indexOf("to")!=-1) { 
                // contents에 to가 있다면 (to가 없으면 -1을 반환하는데,-1이 아니라면 )
        			int begin = contents.indexOf(" ") + 1;   
                    //  to 1111 안녕하세요 일 경우 처음 빈칸 다음자리부터
        			int end = contents.indexOf(" ", begin); 
                    //끝자리 포함x(+1 안함)  // 다음 빈칸까지(마지막 자리는 포함 안됨)
        			String toid = contents.substring(begin, end); 
                    //contents에서 해당 부분을 찾아 toid에 대입
        			
        			String wisper = contents.substring(end+1); 
                    //두번째 빈칸 다음자리부터 끝까지를 뽑아서 wisper에 저장(내용)
        			outMsg.println(id + "/"+ toid+ "/" + wisper); 
                    // 각 내용을 /로 구분해서 출력
        		}
        		else {outMsg.println(id + "/" + contents);
        		}
            msgInput.setText("");
        }
    }

    public void run() {

        String msg;
        String[] rmsg;
        int size;

        status = true;

        while(status) {
            try {
           
                msg = inMsg.readLine();
                rmsg = msg.split("/");
                size = rmsg.length;

                
               if(size>=3) {
               // 입력창에 "to 귓속말상대방아이디 귓속말 내용 "  이렇게 입력할 경우 
               //size가 자동으로 3
            	 // 수정해야 함 추후에 다른 기능 집어 넣을 때 구별이 안되는 문제 발생
            	   msgOut.append(rmsg[0] + ">>"+rmsg[1] + "\n" + rmsg[2] +"\n");
               }
               else {
                msgOut.append(rmsg[0] + ">"+rmsg[1] + "\n");
               }
      
                msgOut.setCaretPosition(msgOut.getDocument().getLength());
            } catch(IOException e) {
                // e.printStackTrace();
                status = false;
            }
        }

        System.out.println("[MultiChatClient]" + thread.getName() + "종료됨");
    }

    public static void main(String[] args) {
        MultiChatClient mcc = new MultiChatClient("127.0.0.1");
    }
}

귓속말 관련 코드에만 주석 넣음

일단 구현을 목적으로 한거라 수정해야 하는 요소 있음

나중에 방장의 강퇴 기능같은 걸 넣는다면  van 강퇴아이디 이렇게 구현할 생각인데 그러면 위 코드로는 구분이 안됨

chatlist / hash 두개나 필요한가 싶음

chatlist를 빼려면 broadcast 코드를 바꿔야 하는데 지금 당장 생각이 안 떠오른다. 

 

 

2222 채팅창에는 출력안됨.

동기화를 깜박했네... 멀티 스레드에서 가장 중요한 일인데...

아직 갈길이 멀다...

스레드 풀도 써야하고 

각 기능별로 분리도 시켜야 하는데... 멀티스레드에 대한 이해가 덜 되서 자꾸 데드락이 발생한다.

 

안드로이드 앱까지 완성하는게 목표인데 과연 할 수 있을 지....

 

 

 

현재 구상중인 기능  

도배방지

채팅내용 입력시간 같이 출력

방 참가자 리스트 구현 (gui로 따로 패널을 만들어야 할 것 같은데 gui 공부하기 싫은데,,,,)

방장 강퇴권한 부여 

chatlist[0]인 사람한테 권한을 주려고 함 

0번 사람이 나가면 다시 0번이 된 사람이 권한을 부여받고 안내메시지 출력