컴퓨터 네트워크

Network program

으엉어엉 2024. 12. 24. 15:46
728x90

TCP/IP 통신에서는 통신할 대상 서버를 찾을 때 호스트 이름이 아니라, IP 주소가 필요하다, 예를 들면 google은 다음과 같이 DNS탐색을 할 수 있다.

public class InetAddressMain {
    public static void main(String[] args) throws UnknownHostException {
        InetAddress localhost = InetAddress.getByName("localhost");
        System.out.println(localhost);

        InetAddress google = InetAddress.getByName("google.com");
        System.out.println(google);
    }
}

 여기서 Inet이란 인터넷이다.

 

 

localhost/127.0.0.1
google.com/142.250.197.142 //계속 바뀌긴 한다. 

 

InetAddress.getByName("호스트명") 메서드를 사용해서 해당하는 IP 주소를 조회할 수 있다. 

 

DataInputStream input = new DataInputStream(socket.getInputStream());

DataOutputStream output = new DataOutputStream(socket.getOutputStream());

 

Socket 은 서버와 연결되어 있는 연결점이라고 생각하면 된다. Socket을 통해서 서버와 통신할 수 있다.

public class Server {
    private static final int PORT = 12345;

    public static void main(String[] args) throws IOException {
        log("서버 시작");

        ServerSocket serverSocket = new ServerSocket(PORT);
        log("서버 소켓 시작 - 리스닝 포트: " + PORT);

        Socket socket = serverSocket.accept();
        log("소켓 연결: " + socket);

        DataInputStream input = new DataInputStream(socket.getInputStream());
        DataOutputStream output = new DataOutputStream(socket.getOutputStream());

        //Client로 부터 문자 받기
        String received = input.readUTF();
        log("client -> server: " + received);

        // 클라이언트에게 문자 보내기
        String toSend = received + " World!";
        output.writeUTF(toSend);
        log("client <- server: " + toSend);

        // 자원 정리
        log("연결 종료: " + socket);
        input.close();
        output.close();
        socket.close();
        serverSocket.close();
    }
}
public class Client {
    private static final int PORT = 12345; //이건 서버 PORT

    public static void main(String[] args) throws IOException {
        log("클라이언트 시작");
        Socket socket = new Socket("localhost",PORT); //CLIENT PORT는 자동으로 만들어짐
        DataInputStream input = new DataInputStream(socket.getInputStream()); //외부 데이터 받을때
        DataOutputStream output = new DataOutputStream(socket.getOutputStream()); //외부 데이터 보낼때
        log("소켓 연결: "+ socket);

        //서버에 문자 보내기
        String toSend  = "Hello";
        output.writeUTF(toSend);
        log("client -> server: " + toSend);

        //서버로부터 문자 받기
        String received = input.readUTF();
        log("client <- server: " + received);

        //자원 정리
        log("연결 종료: "+socket);
        input.close();
        output.close();
        socket.close();
    }
}

서버 소켓

서버는 특정 포트를 열어두어야 한다. 그래야 클라이언트가 해당 포트를 지정해서 접속할 수 있다. ServerSocket을 지정한 포트로 만들어야 한다. 

 

 

  • 서버가 12345 포트로 서버 소켓을 열어둔다. 클라이언트는 이제 12345 포트로 서버에 접속할 수 있다.
  • 클라이언트가 12345 포트에 연결을 시도한다.
  • 이때 OS 계층에서 TCP 3 way handshake가 발생하고, TCP 연결이 완료된다.
  • TCP 연결이 완료되면 서버는 OS backlog queue라는 곳에 클라이언트와 서버의 TCP 연결 정보를 보관한다.
    • 이 연결 정보를 보면 클라이언트의 IP, PORT, 서버의 IP, PORT 정보가 모두 들어있다

 

클라이언트와 랜덤 포트: TCP 연결시에는 클라이언트 서버 모두 IP, 포트 정보가 필요하다. 예제에서 사용된 IP 포트는 다음과 같다.

Client:  localhost(127.0.0.1), 50000(포트 랜덤 생성) -> 자신의 포트를 지정한 적이 없다.

Server : localhost(127.0.0.1), 12345

 

 

Socket socket = serverSocket.accept();

서버에 있는 socket 이 필요함. (ServerSocket X -> 클라이언트가 서버에 접속할 수 있고 backlogQueue에서 정보 꺼낼수만 있게함 ) 

 

accept() 를 호출하면 backlog queue에서 TCP 연결 정보를 조회한다. 먄약 TCP 연결 정보가 없다면, 연결 정보가 생성될 때 까지 대기한다. (블로킹) 

꺼낼 것이 있다면 해당 정보를 기반으로 Socket 객체를 생성

사용한 TCP연결정보는 Backlog Queue에서 제거된다.

그리고 TCP로 연결되어 있고 Stream을 통해 메세지를 주고 받을 수 있다.

 

복사는 할 수 있지만 아직 통신이 되지는 않는다.

Server 에 둘 이상의 클라이언트가 작동하지 않는 이유는 accept와 readUTF가 blocking 이기 때문에 두개를 별도의 Thread로 만들어서 해결해야 한다. 

 

 

public class Session implements Runnable {
    private final Socket socket;

    public SessionV3(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        //run 이라 예외 X
        try {
            DataInputStream input = new DataInputStream(socket.getInputStream());
            DataOutputStream output = new DataOutputStream(socket.getOutputStream());

            while (true) {
                // 클라이언트로부터 문자 받기
                String received = input.readUTF(); // 블로킹
                log("client -> server: " + received);
                if (received.equals("exit")) {
                    break;
                }
                // 클라이언트에게 문자 보내기
                String toSend = received + " World!";
                output.writeUTF(toSend);
                log("client <- server: " + toSend);
            }
            // 자원 정리
            log("연결 종료: " + socket);
            input.close();
            output.close();
            socket.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
public class Server {
    private static final int PORT = 12345;
    public static void main(String[] args) throws IOException {
        log("서버 시작");
        ServerSocket serverSocket = new ServerSocket(PORT);
        log("서버 소켓 시작 - 리스닝 포트: " + PORT);
        while (true) {
            Socket socket = serverSocket.accept(); // 블로킹
            log("소켓 연결: " + socket);
            //session을 별도의 thread에서 사용한다.
            SessionV3 session = new SessionV3(socket);
            Thread thread = new Thread(session);

            thread.start();
        }
    }
}

이런식으로 session을 통해 thread를 사용해서 여러개의 client가 통신되게끔 한다.

 

 

자원 반납

//Autocloseable 사용해서 구현함
public class Resource implements AutoCloseable {
    private String name;
    public ResourceV2(String name) {
        this.name = name;
    }
    public void call() {
        System.out.println(name + " call");
    }
    public void callEx() throws CallException {
        System.out.println(name + " callEx");
        throw new CallException(name + " ex");
    }
    @Override
    public void close() throws CloseException {
        System.out.println(name + " close");
        throw new CloseException(name + " ex");
    }
}
public class ResourceCloseMain {
    public static void main(String[] args) {
        try {
            logic();
        } catch (CallException e) {
            System.out.println("CallException 예외 처리");
            Throwable[] suppressed = e.getSuppressed();
            for (Throwable throwable : suppressed) {
                System.out.println("suppressedEx = " + throwable);
            }
            e.printStackTrace();
        } catch (CloseException e) {
            System.out.println("CloseException 예외 처리");
            e.printStackTrace();
        }
    }

    private static void logic() throws CallException, CloseException {
        try (ResourceV2 resource1 = new ResourceV2("resource1");
             ResourceV2 resource2 = new ResourceV2("resource2")) {
            resource1.call();
            resource2.callEx(); // CallException;
        } catch (CallException e) {
            System.out.println("ex: " + e);
            throw e; // CallException;
        }
    }
}

Try with resources 장점

  • 리소스 누수 방지 : 모든 리소스가 제대로 닫히도록 보장한다. 실수로 finally 블록을 적지 않거나,  finally블럭 안에서 자원 해제 코드를 누락하는 문제들을 예방할 수 있다.
  •  코드 간결성 및 가독성 향상 : 명시적인  close() 호출이 필요 없어 코드가 더 간결하고 읽기 쉬워진다.
  •  스코프 범위 한정 : 예를 들어 리소스로 사용되는 resource1,2 변수의 스코프가  try 블럭 안으로 한정된다. 따라서 코드 유지보수가 더 쉬워진다.
  •  조금 더 빠른 자원 해제 : 기존에는 try catch finally로 catch 이후에 자원을 반납했다. Try with resources 구분은  try 블럭이 끝나면 즉시  close() 를 호출한다.
  •  자원 정리 순서 : 먼저 선언한 자원을 나중에 정리한다.
728x90

'컴퓨터 네트워크' 카테고리의 다른 글

네트워크 exception  (0) 2024.12.24
Network program - 2  (0) 2024.12.24
네트워크 - 기본 이론  (1) 2024.12.24
InputStream, OutputStream  (1) 2024.12.22
컴퓨터 네트워크 4장- 라우터 내부  (0) 2024.05.18