채팅프로그램 만들기(2)

ho's·2022년 6월 26일
0

위 글은 김성박선생님의 자바 강의를 바탕으로 쓰여졌습니다.
더 자세한 내용은 아래 링크를 통해 알아보실 수 있습니다.

부부개발단 유투브


📺 채팅프로그램(2)

우리는 다음과 같은 기능을 원한다.

클라이언트에서
/ 로 시작하는 것은 명령어로 취급한다.
/quit : 접속종료
/create 방제목: 방생성
/list : 방 목록보기
/join 방 번호: 방에 입장
/exit : 방에 빠져 나간다.

💻 기능을 추가해보자.

🔊 ChatRoomService

ChatRoom을 관리하는 클래스를 만들자.

🔊 ChatRoom 클래스

package chat2;

import java.util.ArrayList;
import java.util.List;

public class ChatRoom {
    private int id;
    private String title;
    private List<ChatThread> chatThreadList;

    public ChatRoom(){
        chatThreadList = new ArrayList<>();
    }

    public void addChatThread(ChatThread chatThread){
        chatThreadList.add(chatThread);
        chatThread.setChatRoom(this);
        
    }

    public void removeChatThread(ChatThread chatThread){
        chatThreadList.remove(chatThread);
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    @Override
    public String toString() {
        return "ChatRoomService{" +
                "id=" + id +
                ", title='" + title + '\'' +
                '}';
    }
}
  • 변수로 id, title, ChatThread형식의 chatThreadList를 갖는다.
  • 위의 변수들을 private로 한 후 getter, setter를 통해 접근할 수 있도록 하자.
  • addChatThread, removeChatThread 메소드를 만들어 ChatThreadList에 추가/삭제하는 기능을 만들자.
  • ChatRoom클래스는 chatThread를 참조하고 있어야 하므로, chatThread클래스에 setChatRoom()메소드를 만들어 참조하게 만들자.

ChatThread 클래스에 추가되는 코드

  • ChatRoom을 필드로 선언하기
private ChatRoom chatRoom;
  • setChatRoom 메소드 만들기
public void setChatRoom(ChatRoom chatRoom){
	this.chatRoom = chatRoom;
}

🔊 위의 기능이 추가된 ChatRoom 코드

package chat2;

import java.util.ArrayList;
import java.util.List;

public class ChatRoom {
    private int id;
    private String title;
    private List<ChatThread> chatThreadList;

    public ChatRoom(){
        chatThreadList = new ArrayList<>();
    }

    public void addChatThread(ChatThread chatThread){
        chatThreadList.add(chatThread);
        chatThread.setChatRoom(this);

    }

    public void removeChatThread(ChatThread chatThread){
        chatThreadList.remove(chatThread);
        chatThread.setChatRoom(null);
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    @Override
    public String toString() {
        return "ChatRoomService{" +
                "id=" + id +
                ", title='" + title + '\'' +
                '}';
    }
}

💻 우리는 대화방에 있는 사람들에게 메세지를 보내고 싶다. 어떻게 해야 할까

🔊 broadcast 메소드를 ChatRoom클래스에 넣자.

public void broadcast(String msg){
	for(int i=0;i<chatThreadList.size();i++){
    	ChatThread chatThread = chatThreadList.get(i);
        chatThread.sendMessage(msg);
    }
}

🔊 ChatRoom의 생성자를 설정하자

public ChatRoom(int id, String title){
	this.id = id;
    this.title = title;
    chatThreadList = new ArrayList<>();
}
  • id와 title은 유니크한 값이다.
  • getId()메소드는 있지만, setId()메소드는 없애자.

여기까지 ChatRoom 코드

	package chat2;

import java.util.ArrayList;
import java.util.List;

public class ChatRoom {
    private int id;
    private String title;
    private List<ChatThread> chatThreadList;

    public ChatRoom(int id, String title){
        this.id = id;
        this.title = title;
        chatThreadList = new ArrayList<>();
    }

    public void broadcast(String msg){
        for(int i=0;i<chatThreadList.size();i++){
            ChatThread chatThread = chatThreadList.get(i);
            chatThread.sendMessage(msg);
        }
    }

    public void addChatThread(ChatThread chatThread){
        chatThreadList.add(chatThread);
        chatThread.setChatRoom(this);

    }

    public void removeChatThread(ChatThread chatThread){
        chatThreadList.remove(chatThread);
        chatThread.setChatRoom(null);
    }

    public int getId() {
        return id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    @Override
    public String toString() {
        return "ChatRoomService{" +
                "id=" + id +
                ", title='" + title + '\'' +
                '}';
    }
}

🔊 ChatRoomService 클래스 코드 작성하기

ChatRoomService는 ChatRoom을 관리하는 클래스이다.
ChatRoomService에서 추가하고 싶은 기능은 아래와 같다.

  • ChatRoom을 생성하는 메소드
	public ChatRoom createChatRoom(String title){
    	ChatRoom chatRoom = new ChatRoom(GEN_ID, title);
        GEN_ID++;
        return chatRoom;
    }
  • ChatRoom의 목록을 출력하는 메소드
public Iterator<ChatRoom> getChatRoomIterator(){
	return chatRoomList.iterator();
}
ChatRoomService의 전체 코드
package chat2;

import java.util.Iterator;
import java.util.List;

public class ChatRoomService {

    private static int GEN_ID = 1;
    private List<ChatRoom> chatRoomList;

    public ChatRoom createChatRoom(String title){
        ChatRoom chatRoom = new ChatRoom(GEN_ID, title);
        GEN_ID++;
        return chatRoom;
    }

    public Iterator<ChatRoom> getChatRoomIterator(){
        return chatRoomList.iterator();
    }
}

🔊 채팅방에 속하는 사람에게만 메세지를 보내고 싶다. 어떻게 구현할까

위의 부분을 지우고, ChatRoomService객체를 공유객체로 만들자.

ChatThread 클래스에서 확인해 보자.

위의 코드에서 List<ChatThread> list 부분을 설정하자.

🔊 ChatThread클래스의 run()메소드 부분을 작성해보자.

ChatThread의 기존 코드

package chat2;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class ChatThread extends Thread {

    private String name;
    private BufferedReader br;
    private PrintWriter pw;
    private Socket socket;
    ChatRoomService chatRoomService;
    private ChatRoom chatRoom;

    public ChatThread(Socket socket, ChatRoomService chatRoomService)throws Exception {
        this.socket =socket;
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        PrintWriter pw =  new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
        this.br = br;
        this.pw = pw;
        this.name = br.readLine();
        this.chatRoomService = chatRoomService;
    }

    public void sendMessage(String msg){
        pw.println(msg);
        pw.flush();
    }

    @Override
    public void run() {

        // ChatThread는 사용자가 보낸 메세지를 읽어들여서,
        // 접속된 모든 클라이언트에게 메세지를 보낸다.
        // 나를 제외한 모든 사용자에게 "00님이 연결되었습니다"..
        try{
            broadcast(name + "님이 연결되었습니다.", false);
            String line = null;
            while((line = br.readLine()) != null){
                if("/quit".equals(line)){
                    break;
                }
                broadcast(name + " : " + line,true);
            }
        }catch(Exception ex){
            // ChatThread가 연결이 끊어졌다는 것.
            //ex.printStackTrace();
            broadcast(name + "님이 연결이 끊어졌습니다.", false);
            this.list.remove(this);
        }
    }


    private void broadcast(String msg, boolean includeMe){
        List<ChatThread> chatThreads = new ArrayList<>();
        for(int i=0;i<this.list.size();i++){
            chatThreads.add(list.get(i));
        }

        try{
            for(int i=0;i<chatThreads.size();i++){
                ChatThread ct = chatThreads.get(i);
                if(!includeMe){ // 나를 포함하고 있지 말아라.
                    if(ct == this){
                        continue;
                    }
                }
                ct.sendMessage(msg);
            }
        }catch(Exception ex) {
            System.out.println("///");
        }
    }

    public void setChatRoom(ChatRoom chatRoom) {
        this.chatRoom = chatRoom;
    }
}

위의 코드에서 broadcast의 기능은 모두에게 보내는 것이 아닌 방에 속한 사람들에게만 보내고 싶으니깐 코드를 제거해주도록 하자.

package chat2;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class ChatThread extends Thread {

    private String name;
    private BufferedReader br;
    private PrintWriter pw;
    private Socket socket;
    ChatRoomService chatRoomService;
    private ChatRoom chatRoom;

    public ChatThread(Socket socket, ChatRoomService chatRoomService)throws Exception {
        this.socket =socket;
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        PrintWriter pw =  new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
        this.br = br;
        this.pw = pw;
        this.name = br.readLine();
        this.chatRoomService = chatRoomService;
    }

    public void sendMessage(String msg){
        pw.println(msg);
        pw.flush();
    }

    @Override
    public void run() {

        // ChatThread는 사용자가 보낸 메세지를 읽어들여서,
        // 접속된 모든 클라이언트에게 메세지를 보낸다.
        // 나를 제외한 모든 사용자에게 "00님이 연결되었습니다"..
        try{
            String line = null;
            while((line = br.readLine()) != null){
                if("/quit".equals(line)){
                    break;
                }
            }
        }catch(Exception ex){
            // ChatThread가 연결이 끊어졌다는 것.
            //ex.printStackTrace();
        }
    }

    public void setChatRoom(ChatRoom chatRoom) {
        this.chatRoom = chatRoom;
    }


}

필요한 기능을 하나씩 추가해 보자.

  • 방 생성요청
if(line.length() >= 9){
	String title = line.substring(8);
    ChatRoom chatRoom = chatRoomService.createChatRoom(title);
    this.chatRoom = chatRoom;
}

this.chatRoom = chatRoom 은 ChatThread 자체도 ChatRoom을 가르키게끔 하는 코드이다.

  • 내가 방에 있을 경우 해당 방에 있는 사용자에게 메세지 보내기
else if(this.chatRoom != null){
	chatRoom.broadcast(name + ":" + line);
}

🔊 ChatThread클래스의 run()메소드 큰 틀


    @Override
    public void run() {

        // ChatThread는 사용자가 보낸 메세지를 읽어들여서,
        // 접속된 모든 클라이언트에게 메세지를 보낸다.
        // 나를 제외한 모든 사용자에게 "00님이 연결되었습니다"..
        System.out.println("ChatThread" + Thread.currentThread().getName());
        try{
            String line = null;

            while((line = br.readLine()) != null){
                System.out.println("line -" + line);
                if("/quit".equals(line)){
                    break;
                }
                else if(line.indexOf("/create") == 0){ // 방 생성 요청

                    if(line.length() >= 9) {
                        String title = line.substring(8);
                        ChatRoom chatRoom = chatRoomService.createChatRoom(title);
                        this.chatRoom = chatRoom;
                        this.chatRoom.addChatThread(this);
                    }else{
                        System.out.println("방 제목을 입력하세요.");
                    }
                }
                else if(line.indexOf("/join") == 0){ // 방 입장

                }
                else if(line.indexOf("/exit") == 0){ // 방에서 빠져나가기

                }

                else if(line.indexOf("/list") == 0){ // 방에서 빠져나가기

                }
                else if(this.chatRoom != null){
                    System.out.println("속한 방에 브로드캐스트 합니다."+ line);
                    chatRoom.broadcast(line);

                }else{
                    System.out.println("방에 들어와있지 않아요. " + line);
                }
                // 내가 방에 있을 경우, 해당 방에 있는 사용자에게 메세지 전송하기.
            }
        }catch(Exception ex){
            // ChatThread가 연결이 끊어졌다는 것.
            //ex.printStackTrace();
        }
    }

명령어 /create 코드

 else if(line.indexOf("/create") == 0){ // 방 생성 요청

                    if(line.length() >= 9) {
                        String title = line.substring(8);
                        ChatRoom chatRoom = chatRoomService.createChatRoom(title);
                        this.chatRoom = chatRoom;
                        this.chatRoom.addChatThread(this);
                    }else{
                        System.out.println("방 제목을 입력하세요.");
                    }
  • 명령어의 길이가 9이상일때 실행되도록 조건문을 처리했다.
  • substring을 이용해 8글자 부터 읽어온 글자를 title로 저장한다.
  • title을 chatRoomService의 createChatRoom메소드를 이용해 방을 만들고 ChatRoom 객체로 저장한다.
  • 저장한 객체를 필드에 저장한다.
  • 저장한 객체를 ChatThread도 참조하도록 this.chatRoom.addChatThread(this)를 작성한다.

명령어 /exit 코드

  else if(line.indexOf("/exit") == 0){ // 방에서 빠져나가기
                    this.chatRoom.removeChatThread(this);
                }

ChatRoom클래스의 removeChatThread메소드도 수정하자.

 public void removeChatThread(ChatThread chatThread){
        chatThreadList.remove(chatThread);
        chatThread.setChatRoom(null);
        broadcast(chatThread.getName() + "님이 퇴장하셨습니다.");
    }

마지막 줄에 broadcast() 메소드를 통해, 퇴장한것을 다른 사람에게 메세지 보낼 수 있다.

명령어 /list 코드

  else if(line.indexOf("/list") == 0){ // 방에서 빠져나가기
                    Iterator<ChatRoom> chatRoomIterator= chatRoomService.getChatRoomIterator();
                    while(chatRoomIterator.hasNext()){
                        ChatRoom cr = chatRoomIterator.next();
                        pw.println(cr.getId() + " - " + cr.getTitle());
                        pw.flush();
                    }
                }

명령어 /join 코드

   else if(line.indexOf("/join") == 0){ // 방 입장
                    try {
                        chatRoomService.join(Integer.parseInt(line.substring(6)), this);
                    } catch(Exception ex){
                        pw.println("방 번호가 잘못 되었습니다.");
                        pw.flush();
                    }
          }

ChatRoomService 클래스에서 join메소드 만들기

public void join(int id, ChatThread chatThread) {
        for(int i=0;i<chatRoomList.size();i++){
            ChatRoom chatRoom = chatRoomList.get(i);
 }

💻 전체코드

ChatClient

package chat2;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
public class ChatClient {
    public static void main(String[] args) throws Exception {
        if(args.length != 1){
            System.out.println("사용법 : java com.example.chat2.ChatClient 닉네임");
            return;
        }

        String name = args[0];
        Socket socket = new Socket("127.0.0.1", 8888);
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
        BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));

        // 닉네임 전송
        pw.println(name);
        pw.flush();

        // 백그라운드로 서버가 보내준 메세지를 읽여들여서 화면에 출력한다.
        InputThread inputThread = new InputThread(br);
        inputThread.start();

        // 클라이언트는 읽어들인 메세지를 서버에 전송한다.
        try{
            String line = null;
            while((line = keyboard.readLine()) != null) {
                if("/quit".equals(line)) {
                    pw.println("/quit");
                    pw.flush();
                    break;
                }
                pw.println(line);
                pw.flush();
            }
        }catch(Exception ex){
            System.out.println("...");
        }

        try{
            br.close();
        }catch(Exception ex){
            System.out.println("111");
        }

        try{
            pw.close();
        }catch(Exception ex){
            System.out.println("222");
        }

        try{
            System.out.println("socket close!!");
            socket.close();
        }catch(Exception ex){
            System.out.println("333");
        }

    }
}


class InputThread extends Thread {
    BufferedReader br;
    public InputThread(BufferedReader br){
        this.br = br;
    }

    @Override
    public void run() {
        try{
            String line = null;
            while((line = br.readLine()) != null){
                System.out.println(line);
            }
        }catch(Exception ex){
            System.out.println("...");
        }
    }
}

ChatRoom 클래스

package chat2;

import java.util.ArrayList;
import java.util.List;

public class ChatRoom {
    private int id;
    private String title;
    private List<ChatThread> chatThreadList;

    public ChatRoom(int id, String title){
        this.id = id;
        this.title = title;

        chatThreadList = new ArrayList<>();

    }

    public void broadcast(String msg){
        System.out.println("ChatRoom에서 메세지 브로드캐스트" + msg);
        for(int i=0;i<chatThreadList.size();i++){
            ChatThread chatThread = chatThreadList.get(i);
            chatThread.sendMessage(msg);
        }
    }

    // 방에 입장했을때,
    public void addChatThread(ChatThread chatThread){
        chatThreadList.add(chatThread);
        chatThread.setChatRoom(this); //
    }

    public void removeChatThread(ChatThread chatThread){
        chatThreadList.remove(chatThread);
        chatThread.setChatRoom(null);
        broadcast(chatThread.getName() + "님이 퇴장하셨습니다.");
    }



    @Override
    public String toString() {
        return "ChatRoom{" +
                "id=" + id +
                ", title='" + title + '\'' +
                '}';
    }

    public int getId() {
        return id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}

ChatRoomService 클래스

package chat2;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

// ChatRoom을 관리하는 클래스..
// 싱글턴으로 만들어주는 것이 좋다..! - 메모리에 하나씩 만들어 준다.

public class ChatRoomService {

    private static int GEN_ID = 1;
    // 메모리에 오직 1개 생긴다.
    private List<ChatRoom> chatRoomList;

    // 생성자
    public ChatRoomService(){
        this.chatRoomList = new ArrayList<>();
    }


    public ChatRoom createChatRoom(String title){
        System.out.println("방 생성을 합니다. : " + title);
        ChatRoom chatRoom = new ChatRoom(GEN_ID, title);
        GEN_ID++;
        chatRoomList.add(chatRoom);
        return chatRoom;
    }

    public Iterator<ChatRoom> getChatRoomIterator(){
        return chatRoomList.iterator();
    }

    public void join(int id, ChatThread chatThread) {
        for(int i=0;i<chatRoomList.size();i++){
            ChatRoom chatRoom = chatRoomList.get(i);
            if(chatRoom.getId() == id ){
                chatRoom.addChatThread(chatThread);
                break;
            }
        }
    }
}

ChatServer 클래스

package chat2;

import java.net.ServerSocket;
import java.net.Socket;

public class ChatServer {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(8888);

        // 동시성 문제를 해결하기 위해서..!
        ChatRoomService chatRoomService = new ChatRoomService();

        while(true) {
            Socket socket = serverSocket.accept();
            System.out.println("접속 : " + socket);
            ChatThread chatThread = new ChatThread(socket, chatRoomService);
            chatThread.start();
        }
    }
}

ChatThread 클래스

package chat2;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Iterator;
public class ChatThread extends Thread {

    private String name;
    private BufferedReader br;
    private PrintWriter pw;
    private Socket socket;
    ChatRoomService chatRoomService;
    private ChatRoom chatRoom;

    public ChatThread(Socket socket, ChatRoomService chatRoomService)throws Exception {
        this.socket =socket;
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        PrintWriter pw =  new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
        this.br = br;
        this.pw = pw;
        this.name = br.readLine();
        this.chatRoomService = chatRoomService;
    }

    public void sendMessage(String msg){
        System.out.println("sendMessage : " + msg);
        pw.println(msg);
        pw.flush();
    }

    @Override
    public void run() {

        // ChatThread는 사용자가 보낸 메세지를 읽어들여서,
        // 접속된 모든 클라이언트에게 메세지를 보낸다.
        // 나를 제외한 모든 사용자에게 "00님이 연결되었습니다"..
        System.out.println("ChatThread" + Thread.currentThread().getName());
        try{
            String line = null;

            while((line = br.readLine()) != null){
                System.out.println("line -" + line);
                if("/quit".equals(line)){
                    break;
                }
                else if(line.indexOf("/create") == 0){ // 방 생성 요청

                    if(line.length() >= 9) {
                        String title = line.substring(8);
                        ChatRoom chatRoom = chatRoomService.createChatRoom(title);
                        this.chatRoom = chatRoom;
                        this.chatRoom.addChatThread(this);
                    }else{
                        System.out.println("방 제목을 입력하세요.");
                    }
                }
                else if(line.indexOf("/join") == 0){ // 방 입장
                    try {
                        chatRoomService.join(Integer.parseInt(line.substring(6)), this);
                    } catch(Exception ex){
                        pw.println("방 번호가 잘못 되었습니다.");
                        pw.flush();
                    }
                    }
                else if(line.indexOf("/exit") == 0){ // 방에서 빠져나가기
                    this.chatRoom.removeChatThread(this);
                }

                else if(line.indexOf("/list") == 0){ // 방에서 빠져나가기
                    Iterator<ChatRoom> chatRoomIterator= chatRoomService.getChatRoomIterator();
                    while(chatRoomIterator.hasNext()){
                        ChatRoom cr = chatRoomIterator.next();
                        pw.println(cr.getId() + " - " + cr.getTitle());
                        pw.flush();
                    }
                }
                else if(this.chatRoom != null){
                    System.out.println("속한 방에 브로드캐스트 합니다."+ line);
                    chatRoom.broadcast(line);

                }else{
                    System.out.println("방에 들어와있지 않아요. " + line);
                }
                // 내가 방에 있을 경우, 해당 방에 있는 사용자에게 메세지 전송하기.
            }
        }catch(Exception ex){
            // ChatThread가 연결이 끊어졌다는 것.
            //ex.printStackTrace();
        }
    }

    public void setChatRoom(ChatRoom chatRoom) {
        this.chatRoom = chatRoom;
    }


}
profile
그래야만 한다

0개의 댓글