EunJng

[Java] 이것이 자바다 Ch.19 본문

STUDY/JAVA

[Java] 이것이 자바다 Ch.19

Eunjng 2024. 2. 27. 20:30

'이것이 자바다' 교재 및 강의를 참고하여 정리한 내용입니다.

Ch.19 네트워크 입출력

네트워크 기초

  • 네트워크: 여러 컴퓨터들을 통신 회선으로 연결한 것
  • LAN(Local Area Network): 특정 영역에 존재하는 컴퓨터를 연결한 것
  • WAN(Wide Area Network): LAN을 연결한 것. 인터넷

서버와 클라이언트

  • 서버: 서비스를 제공하는 프로그램
  • 클라이언트: 서비스를 요청하는 프로그램

IP 주소

  • 네트워크 어댑터마다 할당
  • 윈도우는 명령 프롬프트에서 ipconfig 명령어로, 맥OS에서는 ifconfing 명령어로 IP 주소 확인
  • DNS(Domain Name System): 도메인 이름. IP 등록하는 저장소

Port 번호

  • 운영체제가 관리하는 서버 프로그램의 연결 번호
  • 0 ~ 1023 : Well Know Port Numbers - 국제인터넷주소관리기구가 특정 애플리케이션용으로 미리 예약한 포트
  • 1024 ~ 49151 : Registered Port Numbers - 회사에서 등록해서 사용할 수 있는 포트
  • 49152 ~ 65545 : Dynamic Or Private Port Numbers - 운영체제가 부여하는 동적 Port 또는 개인적인 목적으로 사용할 수 있는 포트

 

IP 주소 얻기

// 로컬 컴퓨터의 InetAddress 얻기
InetAddress ia = InetAddress.getLocalHost();

// 컴퓨터의 도메인 이름 아는 경우
InetAddress ia = InetAddress.getByName(String domainName);
InetAddress[] isArr = InetAddress.getAllByName(String domainName);

String ip = InetAddress.getHostAddress();

 

TCP 네트워킹

  • 전송용 프로토콜: IP 주소로 프로그램들이 통신할 때 약속된 데이터 전송 규약
  • TCP(Transmission Control Protocol)
    • 연결형 프로토콜
    • 클라이언트가 연결 요청을 하고 서버가 수락하면 통신 회선이 고정되고, 데이터는 고정 회선을 통해 전달
    • 보낸 데이터가 순서대로 전달되며 손실이 발생하지 않음

  • UDP(User Datagram Protocol)

TCP 서버

// ServerSocket 객체 생성
ServerSocket serverSocket = new ServerSocket(포트 번호);

// or
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(포트 번호));

serverSocket.bind(new InetSocketAddress("IP 주소", 포트 번호)); // 특정 IP에서만 서비스


// 연결 요청 수락
Socket socket = serverSocket.accept();
// 서버 종료 - 언바인딩
serverSocket.close();
  • Port가 다른 프로그램에서 사용 중이라면 BindException 발생 -> 다른 Port로 바인딩하거나 사용 중인 프로그램 종료 후 재실행

TCP 클라이언트

// 클라이언트가 서버에 연결 요청
Socket socket = new Socket("IP", 포트번호);
Socket socket = new Socket(new InetAddress.getByName("domainName", 포트번호);	// IP 주소 대신 도메인 이름 사용

// or
socket = new Socket();
socket.connect(new InetSocketAddress("domainName", 포트번호);


// 예외 처리
try {
	Socket socket = new Socket("IP", 50001);
} catch (UnknownHostException e) {
	// IP 표기 방법이 잘못되었을 경우
} catch (IOException e) {
	// IP와 Port로 서버에 연결할 수 없는 경우
}

// 연결 끊기
socket.close();

입출력 스트림으로 데이터 주고 받기

// Socket으로부터 InputStream과 OutputStream 얻기
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();

//// 데이터 보내기
String data = "데이터";
byte[] bytes = data.getBytes("UTF-8");
OutputStream os = socket.getOutputStream();
os.write(bytes);
os.flush();

// 보조 스트림 사용
String data = "데이터";
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeUTF(data);
dos.flush();


//// 데이터 받기
byte[] bytes = new byte[1024];
InputStream is = socket.getInputStream();
int num = is.read(bytes);
String data = new String(bytes, 0, num, "UTF-8");

// DataOutputStream으로 문자 보낼 경우 DataInputStream 사용 가능
DataInputStream dis = new DataInputStream(socket.getInputStream());
String data = dis.readUTF();

 

UDP 네트워킹

  • 발신자가 일방적으로 수신자에게 데이터를 보내는 방식. 데이터 전송 속도가 TCP보다 상대적으로 빠름
  • 여러 회선을 통해 데이터가 전송되어 데이터가 순서대로 전달되지 않거나 손실 발생 가능

UDP 서버

// DatagramSocket 객체 생성
DatagramSocket datagramSocket = new DatagramSocket(포트번호);

// receive(): 데이터를 수신할 때까지 블로킹되고, 데이터가 수신되면 DatagramPacket에 저장
DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024);
datagramSocket.receive(receivePacket);

// 데이터와 바이트 수 얻기
byte[] bytes = receivePacket.getData();
int num = receivePacket.getLength();

// String 생성자로 문자열 얻기 가능
String data = new String(bytes, 0, num, "UTF-8");


// SocketAddress 객체 얻기
SocketAddress socketAddress = receivePacket.getSocketAddress();

String data = "처리 내용";
byte[] bytes = data.getBytes("UTF-8");
DatagramPacket sendPacket = new DatagramPacket(bytes, 0, bytes.length, socketAddress);

datagramSocket.send(sendPacket);	// DatagramPacket을 클라이언트로 보내기

datagramSocket.close();	// UDP 서버 종료

UDP 클라이언트

DatagramSocket datagramSocket = new DatagramSocket();	// Port 번호는 자동 부여

String data = "요청 내용";
byte[] bytes = data.getBytes("UTF-8");
DatagramPacket sendPacket = new DatagramPacket(
	bytes, bytes.length, new InetSocketAddress("localhost", 50001)
);

datagramSocket.send(sendPacket);

datagramSocket.close();

 

서버의 동시 요청 처리

  • accept()와 receive()를 제외한 요청 처리 코드를 별도의 스레드에서 작업하는 것이 좋다.
  • 서버의 과도한 스레드 생성 방지를 위해 스레드풀 사용
    • 스레드풀은 작업 처리 스레드 수를 제한해서 사용. 다만 작업 큐의 대기 작업이 증가되어 클라이언트에서 응답을 늦게 받을 수 있다.

 

JSON 데이터 형식

{
  "id": "winter",
  "name": "한겨울",
  "age": 25,
  "student": true,
  "tel": { "home": "02-123-1234", "mobile": "010-123-1234" },
  "skill": [ "java", "c", "c++" ]
}
  • JSON 표기법 관련 클래스 - JSONObject, JSONArray

 

TCP 채팅 프로그램

package chat;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ChatServer {
    public static void main(String[] args) {
        try {
            ChatServer chatServer = new ChatServer();
            chatServer.start();

            System.out.println("----------------------------");
            System.out.println("서버를 종료하려면 q를 입력하고 Enter");
            System.out.println("----------------------------");

            Scanner scanner = new Scanner(System.in);
            while (true) {
                String key = scanner.nextLine();
                if (key.equals("q")) break;
            }
            scanner.close();
            chatServer.stop();
        } catch (IOException e) {
            System.out.println("[서버] " + e.getMessage());
        }
    }
    // 필드
    ServerSocket serverSocket;
    ExecutorService threadPool = Executors.newFixedThreadPool(100);
    Map<String, SocketClient> chatRoom = Collections.synchronizedMap(new HashMap<>());
    
    // 메소드: 서버 시작
    public void start() throws IOException {
        serverSocket = new ServerSocket(50001);
        System.out.println("[서버] 시작됨");

        Thread thread = new Thread(() -> {
            try {
                while (true) {
                    Socket socket = serverSocket.accept();
                    SocketClient sc = new SocketClient(this, socket);
                }
            } catch (IOException e) {
            }
        });
        thread.start();
    }

    // 메소드: 클라이언트 연결 시 SocketClient 생성 및 추가
    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");
    }

    // 메소드: 클라이언트 연결 종료 시 SocketClient 제거
    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> socketClients = chatRoom.values();
        for (SocketClient sc : socketClients) {
            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("[서버] 종료됨 ");
        } catch (IOException e1) {
        }
    }
}
package chat;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;

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

    // 생성자
    public SocketClient(ChatServer chatServer, Socket socket) {
        try {
            this.chatServer = chatServer;
            this.socket = socket;
            this.dis = new DataInputStream(socket.getInputStream());
            this.dos = new DataOutputStream(socket.getOutputStream());
            InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
            this.clientIp = isa.getHostName();
            receive();
        } catch (IOException e) {
        }
    }

    // 메소드: JSON 받기
    public void receive() {
        chatServer.threadPool.execute(() ->  {
            try {
                while (true) {
                    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":
                            String message = jsonObject.getString("data");
                            chatServer.sendToAll(this, message);
                            break;
                    }
                }
            } catch (IOException e) {
                chatServer.sendToAll(this, "나가셨습니다.");
                chatServer.removeSocketClient(this);
            }
        });
    }

    // 메소드: JSON 보내기
    public void send(String json) {
        try {
            dos.writeUTF(json);
            dos.flush();
        } catch (IOException e) {
        }
    }

    // 메소드: 연결 종료
    public void close() {
        try {
            socket.close();
        } catch (Exception e) {
        }
    }
}
package chat;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;

public class ChatClient {
    public static void main(String[] args) {
        try {
            ChatClient chatClient = new ChatClient();
            chatClient.connect();

            Scanner scanner = new Scanner(System.in);
            System.out.println("대화명 입력: ");
            chatClient.chatName = scanner.nextLine();

            JSONObject jsonObject = new JSONObject();
            jsonObject.put("command", "incoming");
            jsonObject.put("data", chatClient.chatName);
            String json = jsonObject.toString();
            chatClient.send(json);

            chatClient.receive();

            System.out.println("----------------------------");
            System.out.println("보낼 메시지를 입력하고 Enter");
            System.out.println("채팅을 종료하려면 q를 입력하고 Enter");
            System.out.println("----------------------------");
            while (true) {
                String message = scanner.nextLine();
                if (message.toLowerCase().equals("q")) {
                    break;
                } else {
                    jsonObject = new JSONObject();
                    jsonObject.put("command", "message");
                    jsonObject.put("data", message);
                    json = jsonObject.toString();
                    chatClient.send(json);
                }
            }
            scanner.close();
            chatClient.unconnect();
        } catch (IOException e) {
            System.out.println("[클라이언트] 서버 연결 안됨");
        }
    }

    // 필드
    Socket socket;
    DataInputStream dis;
    DataOutputStream dos;
    String chatName;

    // 메소드: 서버 연결
    public void connect() throws IOException {
        socket = new Socket("localhost", 50001);
        dis = new DataInputStream(socket.getInputStream());
        dos = new DataOutputStream(socket.getOutputStream());
        System.out.println("[클라이언트] 서버에 연결됨");
    }

    // 메소드: JSON 받기
    public void receive() {
        Thread thread = new Thread(() -> {
            try {
                while (true) {
                    String json = dis.readUTF();
                    JSONObject root = new JSONObject(json);
                    String clientIp = root.getString("clientIp");
                    String chatName = root.getString("chatName");
                    String message = root.getString("message");
                    System.out.println("<" + chatName + "@" + clientIp + ">" + message);
                }
            } catch (Exception e) {
                System.out.println("[클라이언트] 서버 연결 끊김");
                System.out.println(0);
            }
        });
        thread.start();
    }

    // 메소드: JSON 보내기
    public void send(String json) throws IOException {
        dos.writeUTF(json);
        dos.flush();
    }

    // 메소드: 서버 연결 종료
    public void unconnect() throws IOException {
        socket.close();
    }
}

'STUDY > JAVA' 카테고리의 다른 글

[Java] 이것이 자바다 Ch. 18  (0) 2024.02.25
[Java] 이것이 자바다 Ch.17  (0) 2024.02.22
[Java] 이것이 자바다 Ch.16  (0) 2024.02.20
[Java] 이것이 자바다 Ch.15  (0) 2024.02.19
[Java] 이것이 자바다 Ch.14  (0) 2024.02.16