기초단계/JAVA

2022.12.23 JAVA 복습 네트워크 입출력

춘핑이 2022. 12. 25. 17:30

19. 네트워크 입출력

19.5 서버의 동시 요청 처리

말그대로 서버에서 동시에 요청을 처리하는 것
이전예제들은 하나만 받고 하나만 처리했다.
요청-> 연결수락 및 외장처리 ->연결수락및 요청처리를 위한 반복
서버는 불특정다수가 처리를 요청할 경우 처리를 여러번 해줘야한다.

먼저 연결한 클라이언트의 요청처리 시간이 길어질수록 다음 클라이언트의 요청처리 작업이 지연될 수밖에없다.
따라서 accept()와 receive()를 제외한 요청처리 코드를 별도의 스레드에서 작업하는 것이 좋다.
스레드를 처리할때 주의할점은 클라이언트의 폭주으로 인한 서버의 과도한 스레드 생성을 방지해야한다는 것이다.
그래서 스레드풀을 사용하는 것이 바람직하다.


스레드풀은 작업처리 스레드 수를 제한해서 사용하기 때문에 갑작스런 클라이언트 폭증이 발생해도 크게 문제가 되지않는다.
다만 작업 큐의 대기 작업이 증가되어 클라이언트에서 응답을 늦게 받을 수 있다.
작업하나하나가 Runnable객체 or Callable객체인것

19.5.1 TCP EchoServer 동시요청처리

interface Runnable{ void run()} 으로 되있는데
이 run을 구현하기위해 구현객체를 넣음 () ->{}
동시에 서버 요청을 받고자 한다면 스레드풀을 구현하는 것은 필수불가결하다.
메인스레드에서 서버 실행 및 종료 등 메소드사용
스레드1에서 키보드 입력받아 클라이언트 요청기다리기
스레드풀에서 요청들을 받아 처리하기
이전예제들에 스레드풀 추가
private static ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.execute(() -> {실행내용});
Runnable 객체 run메소드재정의 하는데 이걸 람다식으로 대체
종료시 executorService.shutdownNow(); 다 닫아주기
메인이 종료가 되어도 스레드풀은 계속일함. 메인닫히면 스레드풀도 닫아야함.

executorService.execute(() -> {
try {
//연결된 클라이언트 정 보얻기
InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
//String clientIp = isa.getHostName();
//호스트이름알면 이름으로 나와서 ip만 얻으려면 getHostString();사용하기
String clientIp = isa.getHostString(); 
System.out.println("[서버]"+ clientIp + "연결요청을 수락함.");

//데이터 받기
//InputStream is = socket.getInputStream();
//DataInputStream dis = new DataInputStream(is);
DataInputStream dis = new DataInputStream(socket.getInputStream());
String message = dis.readUTF();

//데이터보내기
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeUTF(message);
dos.flush();
System.out.println("[서버] 받은 데이터를 다시보냄:" + message);

19.6 JSON 데이터형식

네트워크로 전달하는 데이터가 복잡할수록 구조화된 형식이 필요하다.
현재까지는 단순히 문자열 숫자만 보냈다.
예를들어 회원을 보낼때 이름 전화번호 이런걸 하나씩 보내는 것보다는 한번에 보내는 것이 좋다.
JSON 데이터형식은 데이터의 의미까지 포함하여 데이터를 구조화시켜서 이것은 가장 많이 사용되는 형식 중 하나임
네트워크 통신에서 가장많이 사용된다. 매우심플하게 구조화가능하기때문이다.
JavaScript Object Notation으로 자바스크립트에서 처음 사용했지만 장점이 많아 데이터 전달 포맷으로 사용한다.

JSON 데이터형식은 형식을 따르지 않으면 오류가 난다.
NTT=객체, 속성명=자바의 필드명 "속성명" 큰따옴표로 반드시 감싸야함. 속성간의 구분은 ','넣어줘야하고 마지막엔 생략해야함.
배열표기 [항목,항목] 자바의 배열x JSON의 배열임.
회원의 정보를 JSON으로 표기한것

{
    "id": "winter",
    "name": "한겨울",
    "age": 25,
    "student": true,
    "tel": { "home": "02-123-1234", "mobile": "010-123-1234"}, //객체 표기 두개이상의 속성
    "skill": ["java", "c", "c++"] //두개 이상의 값이 있는 경우
}

다음은 JSON표기법과 관련된 클래스들이다.
JSONObject JSON객체 표기를 생성하거나 파싱할때 사용 위 사진 위쪽
JSONArray JSON배열 표기를 생성하거나 파싱할때 사용 위 사진 아래쪽
JSON을 만들기도하고 JSON을 해석해서 데이터를 얻는데도 사용함.

public class CreateJsonExample {
public static void main(String[] args) throws IOException {
    //JSON 객체 생성
    JSONObject root = new JSONObject();

    //속성 추가
    root.put("id", "winter"); //"id": "winter"
    root.put("name", "한겨울"); // "name": "한겨울"
    root.put("age", 25);    //  "age": 25
    root.put("student", true); // "student": true

    //객체 속성 추가 
    //tel을 JSONObject의 형태로 만들기
    //root에 저장하였으니 속성의 값으로 tel객체를 넣는것 "tel":{객체표기법}으로 넣을 수 있음.
    // "tel": { "home": "02-123-1234", "mobile": "010-123-1234"}
    JSONObject tel = new JSONObject();
    tel.put("home", "02-123-1234");
    tel.put("mobile", "010-123-1234");
    root.put("tel", tel);
    //"tel" : {} 중괄호에 put한게 속성 : 값 형식으로 들어감 속성이 하나마다 객체라고생각

    //배열 속성 추가
    JSONArray skill = new JSONArray();
    skill.put("java");
    skill.put("c");
    skill.put("c++");
    root.put("skill", skill); 

    //만들고나서 실습
    //JSON 얻기
    String json = root.toString();

    //콘솔에 출력
    System.out.println(json);

    //파일로 저장
    // json은 보통 네트워크에서 사용하기때문에 이걸 사용하는 경우는 설정을 저장할경우에 파일로 만든다.
    //writer이니 charset으로 넣기 가능
    Writer writer = new FileWriter("C:/Temp/member.json", Charset.forName("UTF-8"));
    writer.write(json);
    writer.flush();
    writer.close();
}}

추가한순서와 출력된 순서는 다를 수 있다. 꼭 저장된 순서대로 들어가지는 않는다.
또한 줄바꿈처리가 되지않는데 오히려 네트워크 전송량을 줄어줄기 때문에 좋다.

19.6.1 JSON 파싱

member.json파일을 읽고 JSON을 파싱하기
파싱 = 읽어들인다.
기호가 {}면 getJSONObject객체얻는 메소드 사용
기호가 []면 getJSONArray 배열얻는 메소드 사용
구분해서 사용하기

public class ParseJsonExample {
public static void main(String[] args) throws IOException {
    //파일로부터 JSON 읽기
    //한행을 읽을수 있는 메소드 제공하니 편리하게 사용하자.
    BufferedReader br = new BufferedReader(
        new FileReader("C:/Temp/member.json", Charset.forName("UTF-8"))
    );
    String json  = br.readLine();
    br.close();

    //JSON 파싱
    //JSONObject을만들고 해성하기 위해 해석해야할 것을 생성자에 넣어줌.
    //한줄씩 읽은것을 생성자 매개변수로 넣어줌
    JSONObject root = new JSONObject(json);

    //속성 정보 읽기 getString속성이름을 주면 속성을 뱉어냄.
    System.out.println("id: " + root.getString("id"));
    System.out.println("name: " + root.getString("id"));
    System.out.println("age: " + root.getInt("age"));
    System.out.println("student: " + root.getBoolean("student"));

    //객체 속성 정보 읽기
    //getJSONObject객체얻는 메소드 사용
    //객체를 얻엇으니 JSONObject 변수 = 참조변수.getJSONObject(); 메소드사용
    JSONObject tel = root.getJSONObject("tel");
    System.out.println("home: " + tel.getString("home"));
    System.out.println("mobile: " + tel.getString("mobile"));

    //배열 속성 정보 읽기
    //getJSONArray 배열얻는 메소드 사용
    JSONArray skill = root.getJSONArray("skill");
    System.out.print("skill: ");
    for(int i=0; i<skill.length(); i++) {
        System.out.print(skill.get(i) + ", ");
    }}}

19.7 TCP 채팅프로그램

1.서버가 다수의 클라이언트지원
2.받은걸 나도보고 상대한테도 보내줘야함.
3.상대방이 연결끊엇을때 알기
ChatClient 챗하느것
ChatServer
연결하면 Socket이 만들어지는데 이걸 별도로 사용하는 SocketClient만들기
ChatClient 가 만들어질때마다 SocketClient가 생성됨.

기능별로 하나씩 추가하기

1.필드선언
서버소켓, 스레드풀
Map<String, SocketClient> chatRoom = Collections.synchronizedMap(new HashMap<>());
chatRoom이라는 맵에 클라이언트 식별 문자를 키로 SocketClient 클라이언트와 통신하는역할 객체가 들어가있음.
내부의 소켓으로 클라이언트와 통신
클라이언트별로 통신하는 소켓을 맵에서 관리하겠다.
<클라이언트 식별 문자, 클라이언트와 통신하는역할>

2.서버 시작 메소드 작성
포트 바인딩
스레드만드는데 runnable객체 구현해서 thread클래스로부터 직접만들기
생성자 매개값으로 runnable객체를 람다식으로 제공함.
SocketClient가 ChatServer에서 만들어진 class를 사용하기 위해서
생성자 만듬
SocketClient내부에서 ChatServer객체 Socket객체를 사용하기때문임
SocketClient 필드에 저장해놓고 쓰겟다.
socket을 바로사용하는게 아니라 SocketClient라는 객체를 만들어서 이 내부사용하겠다.
그래서 SocketClient을 만들때 ChatServer에 대한 참조와 통신객체에 대한 참조를 넘겨 사용하겟다.

3.서버에 인원 추가 제거 메소드 작성
채팅방안에는 많은 사람이 이 있음.
SocketClient가 chatClient를 대변하는 통신객체임. 클라이어트만큼 소켓클라이언트를 만들어줘야함.
얘를 챗서버에서 관리를 해야한다. 이것들을 맵타입인 chatRoom에서 관리를 하겠다.
사람 추가 제거하기 키값을 받고 맵에 추가 키값을 받고 맵에서 제거하기
매개변수는 start()에서 생성한 객체를 넣음
입장시 키 출력, 엔트리수 출력해주기
채팅 SocketClient.chatName + @ + SocketClient.clientIP 다 달라야하기 때문에 IP를 추가해서 키값으로 구분하기
맵이 가진 사이즈수로 채팅방 사람수 표시 = 엔트리 수로 표시
리무브
채팅방에서 나갈때
나갈 SocketClient를 받고 이 키를 삭제하면 나가짐
Map에서 빠져짐
나간사람 키 출력, 남아있는 사람수 = 엔트리 수
생성된 sc를 넣는거임. 이sc의 chatName clientIp를 키로 사용

4.모든 클라이언트에게 메시지 보내는 sendToAll()메소드 작성
1.보낼메시지 만들기 2.보낼사람들 정하고 보내기
매개변수에는 SocketClient에서 보낸사람, 뭐를 message를
내용을 JSON에 담아서 보냄
chatRoom : Map
SocetClient에서 ChatClient에 바바박 보냄
SocetClient의 밸류만 얻어서 컬렉션 타입을 얻어서 보내기
15.4맵컬렉션 메소드 중 Collection values() // 값들만 모아서 Collection타입으로 전환사용
Collection<SocketClient> socketCleints = chatRoom.values();
<SocketClient>인 이유 애초에 value타입이 SocketClient이기때문임. 밸류값만 뽑은거니 SocketClient를 넣어줌
이후 for (SocketClient sc : socketCleints)
socketCleints를 하나씩 받아서 sc에 보내는 것임.
sender에겐 메시지가 안감

5.서버종료
1.소켓닫기 2.스레드풀닫기 3.채팅룸 밸류 얻어서 = SocketClient 각각 닫기
chatRoom.values().stream().forEach(sc -> sc.close());
흘러오는 객체 chatRoom.values = SocketClient

6.메인메소드
메소드 시작 q누르면 종료

7.SocketClient 만들기 서버측과 통신하는 클라이언트
클라이언트와 통신하는 소켓
소켓으로부터 인풋스트림 아웃스트림 얻어서 필드에 넣기
Socket socket = serverSocket.accept();
socket은 accept햇기때문에 클라이언트의 정보를 가지고있음.
이 socket을 생성자에 대입했기때문에 알수잇음.
클라이언트의 IP얻기
ChatServer 에서 SocketClient를 생성자 호출해서 객체를 생성하면서
serverSocket.accept();이 accept한거를 socekt로 넣어서 다시 SocketClien에 넣어줌
통신용 객체 생성하면서 receive()메소드도 같이 시작

8.receive()메소드 주고받고 하는 일을 하는 메소드
SocketClient의 Socket에서 주고받고를 해야하는데 여기서 받는게 receive()메소드임.
클라이언트가 언제 보낼지 모르니 항상 받을 준비를 해야해서 스레드를 이용하는데 스레드풀로 추가될때마다 받기
execute(()->{}); 작업큐에 작업 저장 runnalbe객체 람다식으로 구현
receive()메소드는 클라이언트가 보낸 JSON메시지를 읽는 역할을한다.
dis.readUTF로 JSON을 읽고 파싱해서 command값을 먼저 얻어낸다.
클라이언트가 보내느 데이터 형태가 이런식 일것임. {"command": "xxx", ...}
명령 command가 뭘 하고싶은지를 파악하고 하고싶은 내용을 처리함.
command가 incoming이라면 대화명을 읽고 챗룸에 SocketClinet를 추가
들어오려면 chatName을 제공해야함. 파싱한 JsonObject에 속성은 data인 chatName을 받음
들어왔으니 sendToAll메소드 실행 sendToAll(SocketClient, message)니까 이 소켓클라이언트, 들어왔습니다.
그리고 chatServer.addSocketClient(this);해서 추가해주기
들어올때 json에 chatName을 넣어서 보내주고 socket생성시에 각종 정보를 얻어서 key에 넣고
값에 이 SocketClient를 넣어서 객체가 생성됨
command가 message라면 JSON에서 메시지를 읽고 연결되어 있는 모든 클라이언트에게 보내기
클라이언트가 채팅종료하면 dis.readUTF 가 대기중에 클라이언트의 연결이 끊어져 예외가 발생하기 때문에 예외처리에서 클라이언트를 제거하는 메소드 실행하기
누가 나간지 알게 sendToAll()과 removeSocketClient()메소드 실행해주기

9.send()메소드 작성하기
클라이언트로 보내는 것
send()메소드는 JSOM메시지를 보내는 역할을한다. ChatServer의 sendToAll()메소드에서 호출된다.
sendToAll에서 작성된 json을 담아 보내는거임

10.채팅클라이언트 만들기
채팅 클라이언트는 ChatClient 단일클래스로 1.채팅서버로 연결을 요청하고 연결된 후에는 제일먼저 대화명을 보낸다. 그리고 난다음 서버와 메시지를 주고받는다.
필드 Socket DataInputStream DataOutputStream chatName

11.연결할때 호출되는 connect()메소드 만들기
socket 서버와 연결 서버 ip와 포트번호 제공
채팅서버에 연결 요청을하고 Socket필드에 저장한다.
문자열입출력을 위해 DataInputStream DataOutputStream을 생성해서 필드에 저장한다.

12.데이터 받을 receive()메소드
서버가 언제 데이터를 보낼지 모르니 항상 켜져있어야함 스레드 필요. 단일클래스니 하나의 작업 스레드만 만들기 스레드풀 사용x
서버의 sendToAll의 Json을 받는거니 이것을 파싱하는 것임.
서버가 보낸 JSON메시지를 읽는 역할을 한다. dis.readUTF()로 JSON을 읽고 파싱해서 clientIpm chatName, message 를 얻어낸다.
그리고 콘솔뷰에 <chatName@clientIp> message로 출력한다.
계속반복적으로 읽고 출력 읽고 출력하기
역시 서버와 통신이끊어지면 예외가발생하니 예외처리를 해서 클라이언트도 종료도되록하기
System.exit(0);으로 프로그램 자체 종료해버리기

13.나도 보내야하니 send()메소드 작성하기

14.연결끊는 unconnect()메소드 작성

15.메인메소드
있어야하는 기능
1.q가 입력되엇을때 채팅을 종료하기 2.connect()호출해서 서버와 연결하기
3.연결되면 대화명을 입력하기 + json을 보내는데
command가 incomming이고 data속성으로 chatName을 담아서 보냄

이러고 receive()메소드를 호출해서 데이터 받을 준비하기

1.q가 입력되엇을때 채팅을 종료하기 아니라면 메시지를
command 가 message인 json을 담아서 보내기
1.들어갈때 메시지 + 2.다른참가자에게 메시지 보내기

public class ChatServer {
//필드선언
ServerSocket serverSocket;
ExecutorService threadPool = Executors.newFixedThreadPool(100);
//맵타입의 컬렉션 셍성 간단하게 멀티스레드환경이기때문에 hashtable사용해도됨. <클라이언트 식별 문자, 클라이언트와 통신하는역할>
//synchronizedMap동기화 안된걸 동기화 시킨 맵으로 만들기 사용
Map<String, SocketClient> chatRoom = Collections.synchronizedMap(new HashMap<>());
//=Map<String, SocketClient> chatRoom = new Hashtable<>();

//메소드
public void start() throws IOException{
    serverSocket = new ServerSocket(50001);
    System.out.println("[서버] 시작됨");
    //스레드만드는데 thread클래스로부터 직접만들고 생성자 매개값으로 runnable객체를 람다식으로 제공함.

    Thread thread = new Thread(() -> {
        try {
            while (true) {
                Socket socket = serverSocket.accept();
                //람다식에서 this는 바깥쪽 객체 ChatServer
                //매개변수에 ChatServer객체, ChatServer에서 만든 Socket가 들어감.
                SocketClient sc = new SocketClient(ChatServer.this, socket);
            }

        } catch (Exception e) {}

    });
    thread.start();
}

//매개변수는 위 start()에서 생성한 객체를 넣음 생성된 sc를 넣는거임.
public void addSocketClient(SocketClient socketClient) {
    String key = socketClient.chatName + "@" + socketClient.clientIp;
    chatRoom.put(key, socketClient);
    System.out.println("입장: " + key);
    System.out.println("현재 채팅자 수: " + chatRoom.size() + "\n");
} 

public void removeSocketClient(SocketClient socketClient) {
    String key = socketClient.chatName + "@" + socketClient.clientIp;
    chatRoom.remove(key);
    System.out.println("나감: " + key);
    System.out.println("현재 채팅자 수: " + chatRoom.size() + "\n");
} 

public void sendToAll(SocketClient sender, String message) {
    JSONObject root = new JSONObject();
    root.put("clientIp", sender.clientIp);
    root.put("chatName", sender.chatName);
    root.put("message", message);
    String json = root.toString();
    //------
    Collection<SocketClient> socketCleints = chatRoom.values();
    for(SocketClient sc : socketCleints) {
        if(sc == sender) {continue;} 
        sc.send(json);
    }
}

public void stop() {
    try {
        serverSocket.close();
        threadPool.shutdownNow();
        chatRoom.values().stream().forEach(sc -> sc.close());
        System.out.println("[서버] 종료됨");
        /*
        Collection<SocketClient> socketCleints = chatRoom.values();
        for(SocketClient sc : socketCleints) {
            if(sc == sender) {continue;} 
            sc.close();
        */

    } catch (IOException e) {}
}
public static void main(String[] args)  {
    ChatServer chatServer = new ChatServer();

    System.out.println("-------------------------------------------------------");
    System.out.println("서버를 종료하려면 q 또는 Q를 입력하고 Enter 키를 입력하세요.");
    System.out.println("-------------------------------------------------------");
    try {
    //TCP 서버시작 메소드
    chatServer.start();

    //키보드 입력
    Scanner scanner = new Scanner(System.in);
    while(true) {
        String key = scanner.nextLine();
        if(key.toLowerCase().equals("q")) { //toLowerCase()대소문자 구분x q면 break
            break;
        }
    }
    scanner.close(); //지금까지는 JVM이 종료되서 안닫아도됫다. 서버는 24시간 돌아가기때문에 사용안하면 닫아야함.

    //TCP 서버종료 메소드
    chatServer.stop();

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

  public class SocketClient {
//필드
ChatServer chatServer;
Socket socket;
String clientIp;
String chatName;
DataInputStream dis;
DataOutputStream dos;

public SocketClient(ChatServer chatServer, Socket socket) throws IOException {
    this.chatServer = chatServer;
    this.socket = socket;
    this.dis = new DataInputStream(socket.getInputStream());
    this.dos = new DataOutputStream(socket.getOutputStream());
    //클라이언트의 IP얻기
    InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
    //this.clientIp = isa.getHostName();
    this.clientIp = isa.getHostString();
    receive();
}

public void receive() {
    //JSON받기
    chatServer.threadPool.execute(() -> {
        try {
        while (true) {
                //{"command": "incoming", "data" : "chatName"}
                //{"command": "message", "data" : "내용"}
                String receiveJson = dis.readUTF();
                JSONObject jsonObject = new JSONObject(receiveJson);
                String command = jsonObject.getString("command");

                switch(command) {
                    case "incoming":
                        this.chatName = jsonObject.getString("data");
                        chatServer.sendToAll(this, "들어오셨습니다.");
                        chatServer.addSocketClient(this);
                        break;
                    case "message": 
                        //커맨드가 message일때의 json의 data를 파싱해서 message에 넣기 
                        String message = jsonObject.getString("data");
                        chatServer.sendToAll(this, message);
                        break;
                }

            }    
            } catch (IOException e) {
                //여기 예외 왜 발생? 연결끊겼을때
                chatServer.sendToAll(this, "나가셨습니다");
                chatServer.removeSocketClient(this);
            }
        });}

public void send(String json) {
    try {
        dos.writeUTF(json);
        dos.flush();
    } catch (IOException e) {}

}
public void close() {}}

public class ChatServer {
//필드선언
ServerSocket serverSocket;
ExecutorService threadPool = Executors.newFixedThreadPool(100);
//맵타입의 컬렉션 셍성 간단하게 멀티스레드환경이기때문에 hashtable사용해도됨. <클라이언트 식별 문자, 클라이언트와 통신하는역할>
//synchronizedMap동기화 안된걸 동기화 시킨 맵으로 만들기 사용
Map<String, SocketClient> chatRoom = Collections.synchronizedMap(new HashMap<>());
//=Map<String, SocketClient> chatRoom = new Hashtable<>();

//메소드
public void start() throws IOException{
    serverSocket = new ServerSocket(50001);
    System.out.println("[서버] 시작됨");
    //스레드만드는데 thread클래스로부터 직접만들고 생성자 매개값으로 runnable객체를 람다식으로 제공함.

    Thread thread = new Thread(() -> {
        try {
            while (true) {
                Socket socket = serverSocket.accept();
                //람다식에서 this는 바깥쪽 객체 ChatServer
                //매개변수에 ChatServer객체, ChatServer에서 만든 Socket가 들어감.
                SocketClient sc = new SocketClient(ChatServer.this, socket);
            }

        } catch (Exception e) {}

    });
    thread.start();
}

//매개변수는 위 start()에서 생성한 객체를 넣음 생성된 sc를 넣는거임.
public void addSocketClient(SocketClient socketClient) {
    String key = socketClient.chatName + "@" + socketClient.clientIp;
    chatRoom.put(key, socketClient);
    System.out.println("입장: " + key);
    System.out.println("현재 채팅자 수: " + chatRoom.size() + "\n");
} 

public void removeSocketClient(SocketClient socketClient) {
    String key = socketClient.chatName + "@" + socketClient.clientIp;
    chatRoom.remove(key);
    System.out.println("나감: " + key);
    System.out.println("현재 채팅자 수: " + chatRoom.size() + "\n");
} 

public void sendToAll(SocketClient sender, String message) {
    JSONObject root = new JSONObject();
    root.put("clientIp", sender.clientIp);
    root.put("chatName", sender.chatName);
    root.put("message", message);
    String json = root.toString();
    //------
    Collection<SocketClient> socketCleints = chatRoom.values();
    for(SocketClient sc : socketCleints) {
        if(sc == sender) {continue;} 
        sc.send(json);
    }
}

public void stop() {
    try {
        serverSocket.close();
        threadPool.shutdownNow();
        chatRoom.values().stream().forEach(sc -> sc.close());
        System.out.println("[서버] 종료됨");
        /*
        Collection<SocketClient> socketCleints = chatRoom.values();
        for(SocketClient sc : socketCleints) {
            if(sc == sender) {continue;} 
            sc.close();
        */

    } catch (IOException e) {}



}
public static void main(String[] args)  {
    ChatServer chatServer = new ChatServer();

    System.out.println("-------------------------------------------------------");
    System.out.println("서버를 종료하려면 q 또는 Q를 입력하고 Enter 키를 입력하세요.");
    System.out.println("-------------------------------------------------------");
    try {
    //TCP 서버시작 메소드
    chatServer.start();

    //키보드 입력
    Scanner scanner = new Scanner(System.in);
    while(true) {
        String key = scanner.nextLine();
        if(key.toLowerCase().equals("q")) { //toLowerCase()대소문자 구분x q면 break
            break;
        }
    }
    scanner.close(); //지금까지는 JVM이 종료되서 안닫아도됫다. 서버는 24시간 돌아가기때문에 사용안하면 닫아야함.

    //TCP 서버종료 메소드
    chatServer.stop();

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

2022.12.23 리뷰

바빠서 작성을 잊고 있었다.
네트워크 구성 자체는 이해가 가는데 json에 담아서 보내는 것이 매우 복잡한 부분이 정말 어렵다.
코드를 보면 이해는 가지만 혼자서 순서대로 구성하는게 매우 매우 어렵다.
2회독이지만 어렵다.