JAVA/HIGH JAVA

[JAVA] 멀티 채팅

아잠만_ 2024. 5. 10. 11:33

멀티 채팅을 위해선

서버에서는 대화하는 사람의 이름을 저장하고

모두에게 메세지를 전송하는 작업을 해야된다

 

그래서 이름은 map을 통해 socket과 함께 이름을 저장하도록하여 중복을 피하도록하며

(이 때 같은 타이밍에 같은 이름을 작성할 수도 있으므로 동기화처리를 필수로 한다)

 

메세지 수신용 스레드를 따로 만들어서 클라이언트에서 받은 메세지를 서버에서 모두에게 메세지를 출력하도록 한다

 

Server

  1. 서버 시작 메서드
  2. 클라이언트에게 메서지를 전송하는 ' 메서드 '
    (전체에게 전송해야하므로 clientMap에 저장된 데이터 개수만큼 반복 처리하여 DateoutputStream을 처리)
  3. 클라이언트에게 받은 메세지를 전체에게 보내는 스레드 클래스
    (처음 시작할 때 이름 데이터 중복검사를 한 후 ' 메서드 '를 이용하여 클라이언트에게 메세지를 전송)
package kr.or.ddit.basic.tcp;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class TcpMultiChatServer {
	// 접속할 클라이언트 정보를 저장할 Map객체변수 선언
	//		==> key값 : 접속한 사람 이름, value값 : 접속한 Client의 Socket객체
	private Map<String, Socket> clientMap;
	
	// 생성자
	public TcpMultiChatServer() {
		// cilentMap을 동기화 처리가 가능하게 생성한다.
		clientMap = Collections.synchronizedMap(new HashMap<String, Socket>());
	}

	public static void main(String[] args) {
		new TcpMultiChatServer().chatStart();
	}

	private void chatStart() {
		ServerSocket server = null;
		Socket socket = null;
		
		try {
			server = new ServerSocket(7777);
			System.out.println("서버가 시작되었습니다");

			while(true) {
			
			socket = server.accept(); // 클라이언트의 접속을 기다린다
			
			// 접속한 클라이언트 정보 출력해보기
			System.out.println("["+socket.getInetAddress()+" : "+socket.getLocalPort()+"]에서 접속했습니다..");
			
			// 스레드 처리
			ServerReceiver th = new ServerReceiver(socket);
			th.start();
			}
		} catch (Exception e) {
			// TODO: handle exception
		}
	}

	// clientMap에 저장된 전체 사용자에게 메세지를 보내는 메서드
	private void sendToAll(String msg) {
		// clinetMap에 저장된 데이터 개수만큼 반복 처리한다
		for(String name : clientMap.keySet()) {
			try {
				// key값과 한 짝으로 저장된 Socket객체를 이용하여 출력용 스트림 객체를 생성한다
				DataOutputStream dout = new DataOutputStream(clientMap.get(name).getOutputStream());
				dout.writeUTF(msg);
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
	}
	
	// 서버에서 클라이언트로 메세지를 전송하는 Thread를 작성한다
	class ServerReceiver extends Thread{
		private Socket socket;
		private DataOutputStream dout;
		private DataInputStream din;
		
		
		// 생성자
		public ServerReceiver(Socket socket) {
			this.socket = socket;
			try {
				// 소켓을 이용하여 스트림 객체생성
				dout = new DataOutputStream(this.socket.getOutputStream());
				din = new DataInputStream(this.socket.getInputStream());
			}catch (Exception e) {
				// TODO: handle exception
			}
		}
		
		// 한 클라이언트가 접속이 완료되면 이 클라이언트는 처음에 '대화명'을 입력받아서 전송한다
		// 서버는 '대화명' 수신하고 이 '대화명'이 중복되는 지 여부를 검사하여 검사 결과를
		// 클라이언트에게 보내준다 (대화명이 중복되지 않을 때 까지 반복한다)
		// 대화명이 중복되지 않으면 해당 대화명과 Socket정보를 Map에 추가한다
		@Override
		public void run() {
			String name = "";
			try {
				// 대화명이 중복되지 않을 때 까지 반복..
				while(true) {
					name= din.readUTF();	// 대화명 수신하기
					
					// 대화명 중복 검사
					if(clientMap.containsKey(name)) {
						// 중복될 때
						dout.writeUTF("대화명중복");
					} else {
						// 중복 X
						dout.writeUTF("사용가능");
						break;	// 반복문 탈출
					}
				}
				
				// 지금 접속한 사람을 기존에 접속되어있는 사람들에게 알려준다.
				sendToAll("["+name+"] 님이 대화방에 입장했습니다");
				
				// 대화명과 클라이언트의 Socket객체를 Map에 추가
				clientMap.put(name, socket);
				
				System.out.println("현재 접속 자 수 : "+ clientMap.size() + "명");
				
				// 한 클라이언트가 보내온 메세지를 받아서 다른 모든 접속자에게 전송한다.
				while(din!=null) {
					sendToAll(din.readUTF());
				}
				
			} catch (Exception e) {
				// TODO: handle exception
			} finally {
				// 이 finally 영역이 실행된다는 것은 클라이언트의 접속이 종료되었다는 것을 의미한다
				sendToAll("["+name+"] 님이 접속을 종료했습니다");
				
				// Map에서 해당 자료를 삭제한다.
				clientMap.remove(name);
				
				System.out.println("["+socket.getInetAddress()+" : "+socket.getLocalPort()+"] 에서 접속종료했습니다..");
				System.out.println();
				System.out.println("현재 접속 자 수 : "+ clientMap.size() + "명");
				System.out.println();
			}
		}
		
	}
}

Client

  1. 클라이언트 시작 메서드
  2. 메세지 전송용 스레드 클래스
    (전송 시 이름이 필요하므로 생성자에서 이름 중복확인) 
  3. 메세지 출력용 스레드 클래스
package kr.or.ddit.basic.tcp;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.Scanner;

public class TcpMultiChatClient {
	public static void main(String[] args) {
		new TcpMultiChatClient().clientChatStart();
	}
	
	// 시작 메서드
	private void clientChatStart() {
		Socket socket = null;
		
		try {
			socket = new Socket("localhost", 7777);
			System.out.println("서버에 접속되었습니다");
			
			// 스레드처리
			ClientSender s = new ClientSender(socket);
			ClientReceiver r = new ClientReceiver(socket);
			
			s.start();
			r.start();
		} catch (Exception e) {
			// TODO: handle exception
		}
	}
	
	// 메세지 전송용 스레드
	class ClientSender extends Thread{
		private Socket socket;
		private DataOutputStream dout;
		private DataInputStream din;
		
		private String name;
		private Scanner scan;
		
		// 생성자
		public ClientSender(Socket socket) {
			this.socket = socket;
			scan = new Scanner(System.in);
			
			try {
				din = new DataInputStream(this.socket.getInputStream());
				dout = new DataOutputStream(this.socket.getOutputStream());
				
				if(din!=null) {
					// 대화명을 입력해서 서버로 전송하고 서버에서
					// 대화명 중복 여부를 검사한 결과를 받아오는 작업을
					// 대화명이 중복되지 않을 때 까지 반복한다.
					while(true) {
						System.out.print("대화명 입력 >> ");
						String name = scan.nextLine();
						dout.writeUTF(name);
						
						// 대화명의 중복 여부 검사 결과를 받는다
						String feedback = din.readUTF();
						
						// 중복 여부 확인하기
						if(feedback.equals("대화명중복")) {
							// 대화명이 중복될 때
							System.out.println(name + "은(는) 중복되는 대화명입니다.");
							System.out.println("다른 대화명을 입력하세요..");
						} else {
							// 대화명이 중복되지 않을 때
							this.name = name;
							System.out.println(name+" 대화명으로 대화방에 입장했습니다...");
							break;
						} 
					}
				}
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
		
		@Override
		public void run() {
			try {
				while(dout!=null) {
					// 키보드로 입력한 메세지를 서버로 전송한다.
					dout.writeUTF("["+name+"] : "+ scan.nextLine());
				}
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
	}

	// 메시지 수신용 스레드
	class ClientReceiver extends Thread{
		private Socket socket;
		private DataInputStream din;
		
		// 생성자
		public ClientReceiver(Socket socket) {
			this.socket = socket;
			try {
				din = new DataInputStream(this.socket.getInputStream());
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
		
		@Override
		public void run() {
			while(din!=null) {
				try {
					// 서버로 부터 받은 메세지를 화면에 출력한다
					System.out.println(din.readUTF());
				} catch (Exception e) {
					// TODO: handle exception
				}
			}
		}
	}
}