[자바] TCP 소켓 프로그래밍을 통한 멀티 채팅 구현하기

Heon·2023년 7월 5일
0

자바

목록 보기
3/4

이번 게시글은 TCP 소켓 프로그래밍을 통해 멀티 채팅을 구현을 소개할 예정이다.

TCP 소켓 프로그래밍에 대한 소개는 이전 게시글 TCP 소켓 프로그래밍을 통한 멀티 채팅 구현하기를 참고하고 바로 코드부터 살펴보자

서버 코드

import java.net.*;
import java.io.*;
import java.util.*;

public class TcpIpMultichatServer {
    HashMap clients;

    TcpIpMultichatServer() {
        clients = new HashMap();
        Collections.synchronizedMap(clients);
    }

    public void start() {
        ServerSocket serverSocket = null;
        Socket socket = null;

        try {
            serverSocket = new ServerSocket(7777);
            System.out.println("서버가 시작되었습니다");

            while(true) {
                socket = serverSocket.accept();
                System.out.println("["+socket.getInetAddress()+":"+socket.getPort()+"]"+"에서 접속하였습니다.");
                ServerReceiver thread = new ServerReceiver(socket);
                thread.start();
            }
        } catch(Exception e) {
            e.printStackTrace();
        }
    } // start()

    void sendToAll(String msg) {
        Iterator it = clients.keySet().iterator();

        while(it.hasNext()) {
            try {
                DataOutputStream out = (DataOutputStream)clients.get(it.next());
                out.writeUTF(msg);
            } catch(IOException e){}
        } // while
    } // sendToAll

    public static void main(String args[]) {
        new TcpIpMultichatServer().start();
    }
    class ServerReceiver extends Thread {
        Socket socket;
        DataInputStream in;
        DataOutputStream out;

        ServerReceiver(Socket socket) {
            this.socket = socket;
            try {
                in  = new DataInputStream(socket.getInputStream());
                out = new DataOutputStream(socket.getOutputStream());
            } catch(IOException e) {}
        }

        public void run() {
            String name = "";
            try {
                name = in.readUTF();
                sendToAll("#"+name+"님이 들어오였습니다.");

                clients.put(name, out);
                System.out.println("현재 서버의 접속자는 "+ clients.size()+"입니다.");

                while(in!=null) {
                    sendToAll(in.readUTF());
                }
            } catch(IOException e) {
                // ignore
            } finally {
                sendToAll("#"+name+"님이 나가셨습니다.");
                clients.remove(name);
                System.out.println("["+socket.getInetAddress() +":"+socket.getPort()+"]"+"에서 접속을 종료하였습니다.");
                System.out.println("현재 서버의 접속자 수는 "+ clients.size()+"입니다.");
            } // try
        } // run
    } // ReceiverThread
} // class

서버 코드 설명

serverSocket = new ServerSocket(7777);
class Receiver extends Thread { ... }
class Sender extends Thread { ... }

에 대한 설명은 이전 글을 참고하자.
여기선 새로운 코드에 대한 설명을 진행하도록 하겠다.

HashMap clients;

TcpIpMultichatServer() {
	clients = new HashMap();
	Collections.synchronizedMap(clients);
}

접속한 클라이언트들을 관리하고, client의 메시지를 멀티채팅방에 뿌리기 위해 HashMap으르 선언하고 각기 다른 client가 동시에 접속할 수 있으므로 동기화를 진행해주었다.
clients에는 클라이언트에서 설장한 name과 클라이언트 소켓의 출력스트림이 저장된다.

void sendToAll(String msg) { ... }

sendToAll 함수를 통해 client가 채팅방에 접속 시 접속알림을 보내고, client의 메시지를 각 client에게 전달할 때 사용된다.

class ServerReceiver extends Thread { ... }

위 sendToAll을 호출하는 클래스이자 쓰레드이다.
이 쓰레드는 TcpIpMultichatServer.start() 함수를 통해 클라이언트가 서버소켓에 연결을 요청하면 생성되고 그 클라이어언트를 관리하기 위한 쓰레드라고 생각하면 된다. 또한 여기서 client를 관리하는 HashMap clients에 값을 넣어주는 client.put()이 호출 되고 클라이언트의 이름과 클라이언트 소켓의 출력스트림이 저장된다.

클라이언트 코드

import java.net.*;
import java.io.*;
import java.util.Scanner;

public class TcpIpMultichatClient {
    public static void main(String args[]) {
        if(args.length!=1) {
            System.out.println("USAGE: java TcpIpMultichatClient 대화명");
            System.exit(0);
        }

        try {
            String serverIp = "127.0.0.1";
            // 소켓을 생성하여 연결을 요청한다.
            Socket socket = new Socket(serverIp, 7777);
            System.out.println("서버에 연결되었습니다.");
            Thread sender   = new Thread(new ClientSender(socket, args[0]));
            Thread receiver = new Thread(new ClientReceiver(socket));

            sender.start();
            receiver.start();
        } catch(ConnectException ce) {
            ce.printStackTrace();
        } catch(Exception e) {}
    } // main

    static class ClientSender extends Thread {
        Socket socket;
        DataOutputStream out;
        String name;

        ClientSender(Socket socket, String name) {
            this.socket = socket;
            try {
                out = new DataOutputStream(socket.getOutputStream());
                this.name = name;
            } catch(Exception e) {}
        }

        public void run() {
            Scanner scanner = new Scanner(System.in);
            try {
                if(out!=null) {
                    out.writeUTF(name);
                }

                while(out!=null) {
                    out.writeUTF("["+name+"]"+scanner.nextLine());					}
            } catch(IOException e) {}
        } // run()
    } // ClientSender

    static class ClientReceiver extends Thread {
        Socket socket;
        DataInputStream in;

        ClientReceiver(Socket socket) {
            this.socket = socket;
            try {
                in = new DataInputStream(socket.getInputStream());
            } catch(IOException e) {}
        }

        public void run() {
            while(in!=null) {
                try {
                    System.out.println(in.readUTF());
                } catch(IOException e) {}
            }
        } // run
    } // ClientReceiver
} // class

이전 일대일 채팅 구현 게시글에서 작성한 코드에서 Sender와 Receiver 이름만 수정됐을 뿐 크게 달라진 건 없다. 달라진 점은 실행시 args[]로 name을 입력받는다는 것이다.

실행방법

자 똑같이 여러 client를 실행해야 하므로 다중 실행은 https://balabala.tistory.com/25 을 참고하도록 하고 설정이 끝났다면 args 설정하여 실행해보자
저자는 인텔리제이에서 args를 설정하여 실행하였다.

우선 서버를 시작한다.

다음으로 client를 시작할 건데 args를 설정해야 한다.


Program arguments에 원하는 client의 이름을 넣고 아직 다중실행 설정을 못했다면 해당 창에서 파란 글씨로 적힌 Modify options -> Allow multiple instances 를 클릭 후에 적용시키면 된다. args 설정이 끝났다면 이제 실행시키기만 하면 된다.

실행 후 다시 args를 바꾸어 여러 개의 클라이언트를 실행하여 채팅창을 확인해보자
4개의 클라이언트를 만들어 보았다. 이제 채팅을 해보자
HashMap clients에 저장된 정보를 바탕으로 sendToAll()이 정상적으로 작동하는 것을 볼 수 있다.

profile
코딩일기

0개의 댓글