JAVA

자바 63강. 프로토콜 통신

JJJAEOoni 2022. 2. 16. 17:24
반응형

통신을 할 때

아무나 서버에 접근하여 세션이 만들어지면

보안에 취약해진다.

 

접근을 했을 때 인증 과정을 거쳐야 한다.

 

처음 메시지로 아이디, 비밀번호를 입력하면

인증이 되어

세션을 만들어주게 약속을 하는 것이다.

 

이 약속이 프로토콜이다.

1. 구분자 ':'
2. 최초 메시지는 username으로 체킹
3. ALL : 메시지
4. CHAT : 받는 사람 아이디 : 메시지

 

 

package site.metacoding.chatapp.server;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.Vector;

/**
 * ProtocolCheck 프로토콜
 * 
 * 1. 최초 메시지는 username으로 체킹
 * 2. 구분자 :
 * 3. 전체채팅 ALL:메시지
 * 4. 귓속말 CHAT:아이디:메시지
 */

public class ChatServer {

    // 리스너(연결받기) -> 메인 스레드
    ServerSocket serverSocket;
    List<ClientThread> clientList;

    // 서버는 메시지 여러명에게 받아서 보내기(순차적)
    // 새로운 스레드, 클라이언트 수마다
    // 채팅서버는 메시지(요청)를 받을 때만 동작! -> pull 서버

    public ChatServer() {

        try {
            serverSocket = new ServerSocket(2000);
            clientList = new Vector<>(); // 동기화가 처리된 ArrayList

            // while 돌리기
            // 여러사람이 요청할 때마다 소켓이 새로 생성되어야하기 때문에 전역변수로 생성 X
            while (true) {
                Socket socket = serverSocket.accept(); // 대기 -> main 스레드가 하는 일
                System.out.println("클라이언트 연결됨");

                // while의 stack이 종료되면 t의 값이 가비지컬렉션 되기 때문에
                // 고객 socket을 기억하기 위해 전역 ArrayList에 보관하기
                ClientThread t = new ClientThread(socket);
                clientList.add(t); // 클라이언트 각각의 socket을 가지고있음
                System.out.println("고객리스트 크기 : " + clientList.size());

                new Thread(t).start();
            }
        } catch (Exception e) {
            System.out.println("오류내용 : " + e.getMessage());
        }

    }

    // 내부 클래스
    class ClientThread implements Runnable {

        // 소켓 보관 컬렉션
        Socket socket;
        String username;
        BufferedReader reader;
        BufferedWriter writer;
        boolean isLogin;

        public ClientThread(Socket socket) {
            this.socket = socket;

            // new될 때 양끝단에 버퍼달림
            try {
                reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        // 전체채팅 ALL:메시지
        public void chatPublic(String msg) {
            try {
                // System.out.println("전체채팅");

                // 메시지 받았으니까 List<고객전담스레드> 고객리스트 <== 여기에 담긴
                // 모든 클라이언트에게 메시지 전송(컬렉션 크기만큼 for문 돌려서!!)
                // 컬렉션의 1번째 데이터를 t에 넣는것
                // 자신이 보낸 메세지는 자신에게 돌아오지 않기 if문
                for (ClientThread t : clientList) { // 컬렉션타입 : 컬렉션
                    if (t != this) {
                        t.writer.write("[전체채팅]" + username + " : " + msg + "\n");
                        t.writer.flush();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

        // 귓속말 CHAT:아이디:메시지
        public void chatPrivate(String reciever, String msg) {
            // System.out.println("귓속말");

            try {
                for (ClientThread t : clientList) { // 컬렉션타입 : 컬렉션
                    if (t.username.equals(reciever)) {
                        t.writer.write("[귓속말]" + username + " : " + msg + "\n");
                        t.writer.flush();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

        // 프로토콜 검사기
        // ALL:안녕
        // CHAT:재원:안녕
        public void protocolCheck(String inputData) {
            // 1. 프로토콜 분리
            String[] token = inputData.split(":"); // 0번지 : 프로토콜, 배열의 크기가 2 or 3 z
            // 크기로 구분하지 말기 !! ALL:안녕:ㅋㅋㅋ 이것도 3이니까 !!
            String protocol = token[0];
            if (protocol.equals("ALL")) {
                String msg = token[1];
                chatPublic(msg);
            } else if (protocol.equals("CHAT")) {
                String reciever = token[1];
                String msg = token[2];
                chatPrivate(reciever, msg);
            } else { // 프로토콜 통과 못함
                System.out.println("프로토콜 없음");
            }
        }

        @Override
        public void run() {

            // 최초 메시지는 username
            try {
                username = reader.readLine();
                isLogin = true; // 세션 생성
            } catch (Exception e) {
                isLogin = false; // 세션 생성 X
                System.out.println("username을 받지 못했습니다.");
            }

            while (isLogin) {
                String inputData;
                try {
                    inputData = reader.readLine();
                    // System.out.println("From 클라이언트 : " + inputData);

                    // 프로토콜 검사기로 던지기
                    protocolCheck(inputData);

                } catch (Exception e) {
                    try {
                        // 클라이언트로부터 메세지를 읽는데, 클라이언트가 연결을 해지하면
                        // readline에서 대기하다가 Stream이 끊겨서 catch로 넘어오고
                        // while은 catch만 계속 반복해서 출력된다.
                        System.out.println("오류내용 : " + e.getMessage());
                        isLogin = false;
                        
                        // 리스트에서 참조중이라서 사라지지 않으니까 heap에 떠있는 자신을 날림
                        clientList.remove(this);
                        reader.close();
                        writer.close();
                        socket.close();
                    } catch (Exception e1) {
                        System.out.println("연결해제 프로세스 실패 " + e1.getMessage());
                    }
                }

            }
        }
    }

    public static void main(String[] args) {
        new ChatServer();
    }
}
package site.metacoding.char_v3;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Scanner;

public class MyClientSocket {

    String username;
    Socket socket;

    // write 스레드
    Scanner sc;
    BufferedWriter writer;

    // read 스레드
    BufferedReader reader;

    public MyClientSocket() {
        try {
            // localhost = 127.0.0.1 루프백 주소
            // 쌤 IP = 192.168.0.132
            socket = new Socket("localhost", 2000); // 연결

            sc = new Scanner(System.in);
            writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

            // 새로운 스레드(읽기 전용)
            new Thread(new 읽기전담스레드()).start();

            // 최초 username 전송 프로토콜
            System.out.println("아이디를 입력하세요.");
            username = sc.nextLine();
            writer.write(username + "\n"); // 내 버퍼에 담기
            writer.flush(); // 버퍼에 담긴 데이터 Stream으로 흘려보내기

            System.out.println("username : " + username + " 이 서버로 전송되었습니다.");

            // 메인 스레드(쓰기 전용)
            while (true) {
                String keyboardInputData = sc.nextLine();

                // 중계자(서버 소켓)에게만 write하면 됨
                // 끝에 "\n" 필수 -> 이거 안쓰려면 PrintWrite 쓰면 됨
                writer.write(keyboardInputData + "\n"); // 내 버퍼에 담기
                writer.flush(); // 버퍼에 담긴 데이터 Stream으로 흘려보내기
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 내부 클래스로 만들면 좋은점
    // MyClientSocket의 전역변수를 모두 new 없이 사용가능
    class 읽기전담스레드 implements Runnable {

        @Override
        public void run() {
            try {
                while (true) {
                    String inputData = reader.readLine();
                    System.out.println(inputData);
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    public static void main(String[] args) {
        new MyClientSocket();
    }
}

 

 

 

[출처]

https://cafe.naver.com/metacoding

 

메타코딩 : 네이버 카페

코린이들의 궁금증

cafe.naver.com


메타 코딩 유튜브
https://www.youtube.com/c/%EB%A9%94%ED%83%80%EC%BD%94%EB%94%A9

 

메타코딩

문의사항 : getinthere@naver.com 인스타그램 : https://www.instagram.com/meta4pm 깃헙 : https://github.com/codingspecialist 유료강좌 : https://www.easyupclass.com

www.youtube.com

 

반응형