채팅프로그램 만들기

ho's·2022년 6월 22일
0

🌏 서버 프로그램 만들기

채팅 프로그램
1) java 패키지명.ChatClient 닉네임 [enter]
   - 서버에 접속을 한다.
2) 접속한 모든 사용자에게 "홍길동님이 접속하였습니다."
3) 채팅을 입력한 후 [enter]를 입력하면 모든 사용자(나 포함)에게 내 메세지가 전달되면 좋겠다.
4) /quit을 입력하면 연결이 끊어진다. 연결이 끊어질 때, 모든 사용자에게 "홍길동님이 연결을 끊었습니다."
5) 강제로 연결을 끊었을 때, (ex>프로그램 강제종료) 사용자에게 "홍길동님이 연결을 끊었습니다."

🪐 서버 입장

  • 사용자가 접속할 때 마다 Thread를 만든다. ChatThread를 만든다.
  • ChatThread는 클라이언트와 통신을 하는 용도
    ex) 클라이언트가 10개면 ChatThread도 10개 생성
  • 클라이언트 접속이 끊어지면 하나의 ChatThread도 종료된다.
  • ChatThread는 사용자가 보내준 chat메세지를 현재 접속한 모든 사용자에게 보낸다.
  • ChatThread는 현재 접속한 모든 사용자 연결정보 or 출력객체를 알아야 한다.

🚀 List가 ChatClient를 갖도록 해보자.

  • chatClient가 접속을 했을 때, 클라이언트는 닉네임을 서버에게 보낸다.
    ex) 홍길동[enter]

  • Thread는 2개인데 List는 하나이다. 이것을 공유객체라고 한다.

🚀 위의 그림을 코드로 구현해보자.

1. List형식의 ChatClient 형태를 갖는 list 변수를 선언하자.

public class ChatThread extends Thread{
	List<ChatThread> list;
}

2.ChatThread생성자에서 list를 인자로 갖게 하고, 자기자신(ChatClient)를 넣어준다.

public class ChatThread extends Thread{
	List<ChatThread> list;
 	~~~
}

	public ChatThread(Socket socket, List<ChatThread> list) throws Exception{
    
    this.list = list;
    this.list.add(this);
    
    }

🚀 접속을 하면 닉네임을 서버에 전송하는 코드

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.List;

public class ChatThread extends Thread {
    private String name;
    private BufferedReader br;
    private PrintWriter pw;
    private Socket socket;
    List<ChatThread> list;

    public ChatThread(Socket socket, List<ChatThread> list)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.list = list;
        this.list.add(this);

    }
}

ChatServer 클래스

package chat2;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class ChatServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8888);
        Socket socket = serverSocket.accept();
    }
}

🚀 while문을 이용해 계속 접속을 받자.

package chat2;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

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

        // 동시성 문제를 해결하기 위해서..!
        List<ChatThread> list = Collections.synchronizedList(new ArrayList<>());

        while(true) {
            Socket socket = serverSocket.accept();
            ChatThread chatThread = new ChatThread(socket, list);
            chatThread.start();
        }
    }
}

🚀 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()));

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

🚀 백그라운드로 서버가 보내준 메세지를 읽어들여서 화면에 출력하는 코드

InputThread클래스 만들기

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("....");
        }
    }
	
}

ChatClient클래스에 InputThread 객체를 만들어 시작하기

InputThread inputThread = new InputThread(br);
inputThread.start();

🚀 클라이언트는 키보드를 통해 읽어들인 메세지를 서버에 전송한다.

keyboard로 입력받기

        BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));

/quit을 입력받으면 종료하는 코드

      try{
            String line = null;
            while((line = br.readLine()) != null){
                if("/quit".equals(line))
                    break;
                pw.println(line);
                pw.flush();
            }
        }catch(Exception ex){
            System.out.println("....");
        }

        socket.close();

🚀 ChatThread클래스 코드 작성하기

ChatThread에서 run() 메소드 구현

run() 메소드는 다음과 같은 특징을 갖는다.

  1. broadcast
  2. ChatThread는 사용자가 보낸 메세지를 읽어들여서 접속된 모든 클라이언트에게 메세지를 보낸다.
  3. 나를 제외한 모든 사용자에게 "OO님이 연결되었습니다."
  4. 현재 ChatThread를 제외하고 보낸다.

List<ChatThread> list;
list에 현재 접속한 ChatThread가 들어가 있다.

ChatThread클래스에 sendMessage 클래스를 구현하자.

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

broadcast 메소드 구현

broadcast메소드는 접속된 모든 클라이언트에게 메세지를 보내는 메소드

    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("///");
        }
}

🪐 지금까지 완성된 코드와 실행 결과

🚀 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("...");
        }
    }
}

🚀 ChatServer 클래스

package chat2;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

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

        // 동시성 문제를 해결하기 위해서..!
        List<ChatThread> list = Collections.synchronizedList(new ArrayList<>());

        while(true) {
            Socket socket = serverSocket.accept();
            ChatThread chatThread = new ChatThread(socket, list);
            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.ArrayList;
import java.util.List;

public class ChatThread extends Thread {

    private String name;
    private BufferedReader br;
    private PrintWriter pw;
    private Socket socket;
    List<ChatThread> list;

    public ChatThread(Socket socket, List<ChatThread> list)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.list = list;
        this.list.add(this);

    }

    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){
                broadcast(name + " : " + line,true);
            }
        }catch(Exception ex){
            // ChatThread가 연결이 끊어졌다는 것.
            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("///");
        }
        }
    }

🚀 결과

🚀 한계점

quit을 입력할시에, ~~가 접속을 종료했습니다. 메세지가 뜨지 않는다. 무엇이 잘못 되었을까?

ChatThread 부분에 run()메소드에서 문제가 있었다.

   @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();
        }
        finally{
            broadcast(name + "님이 연결이 끊어졌습니다.", false);
            this.list.remove(this);
            try{
                br.close();
            }catch(Exception ex){
            }

            try{
                pw.close();
            }catch(Exception ex){
            }

            try{
                socket.close();
            }catch(Exception ex){
            }
        }
    }

클라이언트가 /quit 를 입력했을 때, while문을 빠져나가서 예외를 발생하지 않아 catch부분에 걸리지 않는다.
따라서 연결이 끊어졌다는 코드는 try부분이 끝나고 실행되는 finally 부분에 코드를 넣어야 한다.

 finally{
            broadcast(name + "님이 연결이 끊어졌습니다.", false);
            this.list.remove(this);
            try{
                br.close();
            }catch(Exception ex){
            }

            try{
                pw.close();
            }catch(Exception ex){
            }

            try{
                socket.close();
            }catch(Exception ex){
            }
        }
}

🪐 완성된 코드를 보자

ChatThread -(1)

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;
    List<ChatThread> list;

    public ChatThread(Socket socket, List<ChatThread> list)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.list = list;
        this.list.add(this);

    }

    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("///");
        }
    }
}

ChatThread(2)

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;
    List<ChatThread> list;

    public ChatThread(Socket socket, List<ChatThread> list)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.list = list;
        this.list.add(this);

    }

    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();
        }
        finally{
            broadcast(name + "님이 연결이 끊어졌습니다.", false);
            this.list.remove(this);
            try{
                br.close();
            }catch(Exception ex){
            }

            try{
                pw.close();
            }catch(Exception ex){
            }

            try{
                socket.close();
            }catch(Exception ex){
            }
        }
    }


    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("///");
        }
    }
}

ChatServer

package chat2;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

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

        // 동시성 문제를 해결하기 위해서..!
        List<ChatThread> list = Collections.synchronizedList(new ArrayList<>());

        while(true) {
            Socket socket = serverSocket.accept();
            ChatThread chatThread = new ChatThread(socket, list);
            chatThread.start();
        }
    }
}

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("...");
        }
    }
}

결과


profile
그래야만 한다

0개의 댓글