[Java] 6. Network & Socket Program

Nam_JU·2022년 7월 18일
0

KaKao Cloud School

목록 보기
11/19

네트워크

인터넷이란

전 세계적으로 연결되어있는 물리적인 네트워크 망

하나의 컴퓨터가 있으면 다른 컴퓨터가 데이터 통신을 주고받으려면 LAN WAN(무선, 유선)이든 네트워크를 연결해야한다. 물리적인 저장장치로 통신을 하기 위해서 하나의 단위로 묶는 것

네트워크를 크기 단위로 쪼갠 것

  • LAN : Local Area Network
  • WAN : Wide ARea Network

데이터 통신을 하려면

상대방 컴퓨터의 IP 주소를 알아야한다

  • Ipv4 : 32비트 짜리 주소 체계
  • IPv6 : 주소체계를 확장시켜 64bit 주소 체계를 만들었다

MAC

IP는 논리적인 주소라서 변경이 가능하다
물리적인 주소는 실제 제품에 주솟값이 찍혀져 있는 것이다
정확히는 RAM카드 뒤쪽에 보면 MAC이라는 유일한 주솟값이 찍혀져 있다
통신할때는 IP주소를 사용하지만 정확한 주소는 MAC주소로 바뀌어서 통신하게 된다

IP → MAC으로 누가 어떻게 바꿀까? ARP

  • IP 주소는 NIC에 할당한다 Network Interface Card [실제 물리적인 주소]
  • 이것을 도와주는게 ARP Address Resolution Protocol이다
    • IP 주소를 MAC 주소로 변환해준다
  • 우리는 IP 주소만 알면됨!

프로토콜 Protocol

  • 국가간의 조약
  • 데이터를 주고받기 위해서 서로 정해놓은 규칙들
  • 서로 규칙을 정해서 어떻게 의사소통을 하여 데이터를 주고받을지 하는 체계
    • 내가 보내려는 데이터의 특성에 따라, 용도에 따라 규칙이 존재한다

1. HTTP Protocol
웹서버와 웹브라우저끼리 사용하는 통신규칙

2. FTP Protocol
두개의 컴퓨터가 파일단위로 어떻게 통신을 할지 정한 규칙

3. SMTP Protocol
4. TCP/IP


포트 Port

  • 하나의 숫자 0~65535
  • 해당 컴퓨터에서 실행하고 있는 프로세스(프로그램)를 지칭한다
  • 프로그램이 다른 프로그램과 통신을 하고 싶으면 IP 주소를 알아야 한다
    주소를 알게된 뒤 컴퓨터(NIC)까지 오게됨
    안에있는 여러개의 프로세스 끼리 연결을 하면된다
    이 프로그램들에 논리적이 번호를 부여한것이 포트이다
    그렇게 해당 프로세스에 접근을 한다

통신을 하기 위해서는 IP주소, 프로토콜, 포트가 필요하다

컴퓨터에 들어갈 IP 주소를 알아야하고 컴퓨터 내부에 들어있는 프로세스의 주소 포트를 알아 어떻게 통신을 할지 프로토콜을 알아야 통신이 가능하다


Socket

  • 소켓을 사용하면 네트워크를 하기위해서 우리가 알아야하는 여러가지 네트워크 하단에 복잡한 프로토콜과 프로토콜에 상관없이 쉽게 프로그램을 작성할 수 있다
  • 대다수가 소켓 프로그램을 사용 (쉽기 때문에!)
  • 어플리케이션 단의 데이터 통신을 쉽게 할 수 있다
  • IP, port, protocol ⇒ 이것들을 알기 힘들어서 소켓을 사용하여 쉽게 데이터 통신 프로그램을 만들 수 있다


Java로 소켓을 만들기

  • 자바에서는 소켓을 추상화시킨 클래스를 제공해준다
  • 소켓이라는 클래스에 ip, prot, protocol을 넣어주면 원하는 통신이 가능하다!
  • 소켓이라는 클래스로 모델링하는 것

네트워크 프로그램은 CS 구조 (Client - Server)

  • Client - 능동적으로 접속하려는 프로그램
  • Server - 누군가 나한테 접속할때까지 기다리는 프로그램
  • 두개의 프로그램에 소켓 객체를 만들어서 통신할 수 있도록 해야함
  • IO를 사용하여 스트림을 열어 데이터를 전달
    • 클라이언트에서 데이터 보낼때 : outputStream
    • 서버에서 데이터를 받을때 : inputStream
    • 클라이언트가 서버가 보내준 데이터를 받을때 : inputStream
    • 서버가 클라이언트에게 데이터를 줄때 : outputStream

서버 Process

  • 클라이언트 프로세스
  • 서버 소켓 : 클라이언트가접속하기를 기다림
  • 클라이언트가 접속한것을 확인해 소켓을 만들면 서버소켓이 클라이언트용 소켓을 하나더 만든다

클라이언트 Process

  • 서버 프로세스에서 생성한 서버 소켓에 접속하도록 한다
    • ip, 포트번호를 가지고 접속시도
  • 그 후에 클라이언트쪽에서 소켓을 만든다

구현해야 하는 과정


1. 서버프로세스는 서버 소켓 생성(클라이언트의 접속을 받아들여서 소켓을 만드는일을함)
2. 만들어진 소켓을 가지고 클라이언트 프로세스가 ip, 포트번호를 가지고 접속시도
3. 접속이 확인되면 서버프로세스의 서버 소켓이 클라이언트와 통신하기 위한 용도의 소켓을 하나더 만든다(소켓끼리 연결)
4. 두개의 소켓을연결할 inputStream, outputStream 을 매핑하여 2개씩 만들어 데이터를 주고받을 수 있도록 해야한다



연습문제 1

  • 접속한 클라이언트에게 서버는 현재 날짜를 알려준다
  1. 클라이언트가 접속
  2. 서버는 클라이언트가 접속한것을 인지
  3. 서버가 클라이언트에게 현재시간을 보내준다

  • 클라이언트 코드
public class Exam01_DateClient extends Application {

    TextArea textArea; //텍스트 입력가능한 곳
    Button connBtn; //버튼생성

    @Override  //추상메소드 오버라딩
    public void start(Stage primaryStage) throws Exception {
        // 화면 구성
        BorderPane root = new BorderPane();
        root.setPrefSize(700,500); //window 크기

        textArea = new TextArea();
        root.setCenter(textArea); //화면 센터에 textarea 를 붙인다

        connBtn = new Button("Date 서버 접속"); ///접속 버튼 만듬
        connBtn.setPrefSize(150,40);
        connBtn.setOnAction(e ->{
            textArea.clear();
            try {
                //서버 process 에 접속을 시도 (ip, port에 접속할겁니다!)
                Socket socket = new Socket("localhost",5678);
                //접속에 성공했으니 이제 stream을 열자
                InputStreamReader ir = new InputStreamReader(socket.getInputStream());
                //스트림을 결합하여 가장 사용하기 편한 형태로 변신
                BufferedReader br = new BufferedReader(ir);

                String msg = br.readLine(); //blocking 메소드이다 - 읽을수 있는게 들어올때까지 대기 (서버가 보내주지 않으면)
                textArea.appendText(msg + "\n"); //받은 메세지를 화면에 붙여준다

                br.close();
                ir.close();
                socket.close();
                textArea.appendText("서버와의 연결이 종료되었습니다");

            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        });

        FlowPane flowPane = new FlowPane(); //판 하나 만드는것
        flowPane.setPadding(new Insets(10,10,10,10)); //판 위에 여백을 준다
        flowPane.setHgap(10);
        flowPane.getChildren().add(connBtn); //버튼을 붙인다

        root.setBottom(flowPane); //아래에 플로우팬을 붙인다

        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();

    }

    public static void main(String[] args) {
        //스레드 생성하면서 창을 띄운다
        launch();
    }
}
  • 서버 코드
public class Exam01_DateServer {

    public static void main(String[] args) {

        try {                             //뒤쪽에 임의로 내가 포트번호를 적어준다
            ServerSocket serverSocket = new ServerSocket(5678);
            System.out.println("Date Server 기동 - 5678");
            //클라이언트 소켓에 담는다
            Socket socket = serverSocket.accept();  //5678을 가지고 클라이언트를 기다려!(blocking)

            //접속에 성공했어요! printWriter => 조금더 나은형태의 데이터 스트림 - 문자열을 더 쉽게 보낼 수 있다
            PrintWriter pr = new PrintWriter(socket.getOutputStream());
            //현재 날짜를 구한다
            Date date = new Date();
            //만들어진 스트림으로 데이터를 보낸다
            pr.println(date.toString());
            /**
             버퍼에 들어가 있는 데이터를 스트림을 통해 내보내는 시점은
             1. 스트림이 강제로 종료될 경우
             2. 버퍼의 공간이 다 찰 경우
             3. method 를 이용해서 flush() 시킬 경우 -> 가능하면 3번으로 사용하기
             **/

            //역순으로 사용한 리소스 닫기
           // pr.close(); 보다 plush 사용하기
            pr.flush();
            socket.close();
            serverSocket.close();
            System.out.println("Date Server 종료");
        } catch (IOException e) { //자바의 특정 코드들은 try-catch 가 강제되는 코드들이 있다
        }
    }

}

코드 설명

  • javaFx로 구현한 부분을 제외한 형광펜 부분이 통신을 진행하는 구현 코드이다
  1. 분홍색에 체크한 connBtn.setOnAction을 수행하면
  2. [서버] 서버단에서 최초의 ServerSocket을 만든다 (포트번호)
  3. [서버] serverSocket.accept()를 사용하여 클라이언트가 올때까지 기다린다.
  4. [클라이언트] 클라이언트 역시 소켓을 생성
    최초의 ServerSocket이 클라이언트와 연결(통신)되면 바로 서버와 클라이언트 각각에 소켓이 생성됨
    값을 읽기위한 input 스트림을 생성한다 BufferdReader형태로 값을 읽기위한 편한 형태로 만들어주기
  5. [클라이언트] br.readLine()는 읽을 값이 들어오기까지 기다린다
  6. [서버] 클라이언트와 마찬가지로 데이터를 보낼 stream 통로를 만든다 날짜를 보내주기 위한 로직 생성
  7. [클라이언트] 에서 이용자가 값을 입력하고 버튼을 누르면 위의 과정이 실행되어 결과값으로 현재 시간을 보내준다!


연습문제 2

  • 간단한 에코 프로그램을 작성해보자
  1. 클라이언트가 입력해서 글자를 서버에 보내고
  2. 서버가 받아서 다시 보낸다 → 클라이언트가 “/exit” 문자열을 입력할때까지 반복 실행

  • 서버 코드
public class Exam02_EchoServer {
    public static void main(String[] args) {
        //서버소켓 선언
        ServerSocket serverSocket = null;
        Socket socket = null;
        PrintWriter pr = null;
        BufferedReader br = null;

        try {
            serverSocket = new ServerSocket(5678);
            System.out.println("Echo Server 기동 - 클라이언트 접속 대기");
            //클라이언트 소켓에 담는다
            socket = serverSocket.accept();  //5678을 가지고 클라이언트를 기다려!(blocking)

            //접속에 성공했어요! printWriter => 조금더 나은형태의 데이터 스트림 - 문자열을 더 쉽게 보낼 수 있다
            br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            pr = new PrintWriter(socket.getOutputStream());

            /**클라이언트가 exit 보내기 전까지 반복해서 읽고 보내는 작업을 제작*/
            String msg = null;
            while(true){
                msg = br.readLine();
                        //문자열비교는 equals
                    //클라이언트가 그만 입력 || 창 강제 종료 시킬 경우
                if (msg.equals("/exit") || (msg == null)){
                    break;
                }
                //제대로 값 도착할 경우 - 데이터를 그대로 보냄
                pr.println(msg);
                pr.flush(); //buffer를 기반으로 했기 때문에 원하는 데이터를 제대로 안보내줄 수 있다
            }
            } catch (IOException e) { //자바의 특정 코드들은 try-catch 가 강제되는 코드들이 있다
            }finally {
                //finally -> try~ 오류가 있던 없던 무조건 수행
                // 사용된 resource 를 해제
            try {
                if (br != null) br.close();
                if (pr != null) pr.close();
                if (socket != null) socket.close();
                if (serverSocket != null) serverSocket.close();
                System.out.println("Echo Server 종료!!");
              } catch (IOException e) {
                    throw new RuntimeException(e);
                }
        }
    }

}
  • 클라이언트 코드
public class Exam02_EchoClient extends Application {

    TextArea textArea; //텍스트 입력가능한 곳
    Button connBtn; //버튼생성
    TextField idField, textField;

    Socket socket;
    BufferedReader br;
    PrintWriter pr;

    @Override  //추상메소드 오버라딩
    public void start(Stage primaryStage) throws Exception {
        // 화면 구성
        BorderPane root = new BorderPane();
        root.setPrefSize(700,500); //window 크기

        //가운데 글상자
        textArea = new TextArea();
        root.setCenter(textArea); //화면 센터에 textarea 를 붙인다

        connBtn = new Button("echo 서버 접속"); ///접속 버튼 만듬
        connBtn.setPrefSize(150,40); //버튼의 크기
        connBtn.setOnAction(e -> {
                    textArea.clear();
                    try {
                        socket = new Socket("localhost", 5678);
                        br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                        pr = new PrintWriter(socket.getOutputStream());
                        textArea.appendText("Echo 서버 접속 성공!!" + "\n");
                    } catch (Exception e2) {

                    }
                });

        //아이디입력
        idField = new TextField();
        idField.setPrefSize(100, 40);

        textField = new TextField();
        textField.setPrefSize(200,40);
        textField.setOnAction(e -> {
            //보낼 문자열 만들기
            String msg = idField.getText() + " : " + textField.getText();
            pr.println(msg);
            pr.flush();

            //텍스트 입력한게 /exit 인지 일반 메세지인지 판단
            if (textField.getText().equals("/exit")){
                textArea.appendText("서버와의 연결을 종료합니다");
                textField.setDisable(true); //disable를 true로 세팅한다 = 사용자 입력이 안되도록 조치
            }else{
                try {
                    String serverMsg = br.readLine();
                    textArea.appendText(serverMsg + "\n");
                }catch (Exception e2){
                }
            }
            textField.clear();
        }); //입력상자에 글 입력 후 enter 입력하면 이벤트 처리가 된다

        FlowPane flowPane = new FlowPane(); //판 하나 만드는것
        flowPane.setPadding(new Insets(10,10,10,10)); //판 위에 여백을 준다
        flowPane.setHgap(10);
        flowPane.getChildren().add(connBtn); //버튼을 붙인다
        flowPane.getChildren().add(idField);
        flowPane.getChildren().add(textField);

        root.setBottom(flowPane); //아래에 플로우팬을 붙인다

        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        //스레드 생성하면서 창을 띄운다
        launch();
    }
}

코드 설명

  • javaFx로 구현한 부분을 제외한 형광펜 부분이 통신을 진행하는 구현 코드이다
  1. [서버]에서는 위의 과정과 마찬가지로 ServerSocket과 socket을 만든다. 여기서 socket은 accept()를 사용하여 클라이언트가 올때까지 기다리도록 한다
  2. [클라이언트]에서는 크게 두가지로 나뉘어진다
    주황색 형광팬이 그려진 사각형은 버튼을 누를 경우 Stream input, output이 모두 생성되도록 만들었다
    최초의 버튼이 눌려야 스트림이 생성되고 서버와 연결이된다!
  3. [서버] /exit문자열이 나오기 전까지는 무한 반복을 하기위해 while(true)로 제어문을 만든다
    read.Line()으로 클라이언트로부터 읽을 값이 들어오기 전까지는 멈추도록 함. 그뒤 특정 문자열이 아니면 들어온 값을 그대로 찍어서 다시 flush()로 보낸다
  4. [클라이언트] 입력한 문자열을 flush()를 사용해 서버에게 보낼수 있도록 함. 입력한 값이 키워드와같으면 종료를 하도록 하고 아닐 경우에는 서버에서 보내는 값이 올때까지 대기하도록 한다
  • 해당과정을 키워드/exit가 올때까지 반복


profile
개발기록

0개의 댓글