Java로 네트워크 통신 구현하기

zihooy·2023년 5월 11일
2

Java Programming

목록 보기
19/21
post-thumbnail

Java의 Thread를 활용하여 네트워크 통신을 구현해보자.

🏹 네트워크 통신 기초 지식

우선 server와 client에 대해 알아보자.

  • server: 다른 컴퓨터에 서비스를 제공하기 위한 컴퓨터 또는 프로그램이다.
  • client: server에서 보내 주는 정보 서비스를 받는 측 또는 요구하는 측의 컴퓨터 또는 프로그램

위 그림과 같이
1개의 server와 3개의 client가 있다고 가정해보자.

우선 통신을 위해서는 server가 열려 있어야 한다.
이를 위해 client가 접속하면, server에서 접속을 받는 부분이 있다.
이 부분을 socket이라고 하고, 접속을 받는 소켓을 접속 소켓이라고 한다.

소켓에는 2가지 종류가 있는데, 1) 접속과 관련된 소켓, 2) 데이터를 주고 받는 소켓이다.
접속을 받는 소켓을 접속 소켓이라고 한다.
그리고 server에 접속이 되면, 데이터를 주고 받기 위해 사용하는 소켓을 return하는데, 이를 통신 소켓이라고 한다.

client에도 소켓이 필요한데, 1개의 동일한 소켓으로 접속도 하고, 통신도 한다.

client2가 있다고 가정하면, client2도 server의 접속 소켓으로 연결이 된 후 server에서 데이터를 주고받는 통신 소켓을 return한다.

server의 각 통신 소켓은 어떤 client와 통신하는지 알고 있다.
따라서 c1에게 메시지를 받으면, c3에게 전달할 수 있다. 통신할 때는,send(client번호) 와 같이 사용해야 해당 client에 전송된다.

server의 통신소켓 번호는 Collections의 Linked List를 활용한다. 또한 channel을 만들어서 server의 통신 소켓을 한 방에 둘 수 있다.

🏹 Thread를 활용한 통신 관리

그런데, 문제가 하나 있다.
만약 일반적으로 socket을 활용한다면 server에서 접속과 통신을 동시에 하지 못하게 된다.

하지만 Thread를 활용한다면?
접속 socket과 통신 socket을 각각의 Thread에서 사용한다고 생각해보면, 독립적인 실행이 가능하므로 1명과 접속하는 동안 다른 1명과 통신도 할 수 있게 된다.
만약 Thread에 대해 더 알아보고 싶다면 클릭 !

이제 Thread를 활용하여 통신을 관리하는 2가지 방법에 대해 알아보자.

1) Client마다 Thread 생성하기

server는 자기 자신이 관리하는 UI가 있어야 한다(관리자모드). 이 때,몇 명이 접속해있는지 나타내주는 상태값이 필요하다.
그런데, UI는 blocking 상태이기 때문에, 접속 Thread를 만들어서 UI와 접속 둘 모두 실행되도록 해야 한다.

접속 Thread는 Client에게 접속만 받는다.
그런데 접속 Thread 역시 blocking 상태이기 때문에, 접속이 된 이후에는 통신 Thread를 만들어서 데이터 전송이 가능하도록 해야 한다.

여러 client가 접속한다면 계속 접속할 때마다 통신 Thread를 만든다.
위 방법의
장점: 프로그램이 simple하다. -> Thread 1개당 User 1개 관리
단점: 사용자가 많아지면 느려진다.

2) 통신용 Thread 생성하기

처음부터 통신을 전담하는 Thread를 한개 만들어 놓고,
Client 1 2 3 이 모두 그 Thread와 통신한다. 사용자 관리는 Linked List를 활용한다.
장점: Thread 3개로 해결 가능
단점: 프로그램이 복잡해진다.

🏹 코드로 알아보기 전에..!

server와 client를 구성해보자.

간단히 설명하자면
server 안에는
1) 접속 Thread() class ConnectThread extends Thread {}
2) 통신 Thread() class ClientThread extends Thread {} 가 있다.

ConnetThread()는 새로운 ServerSocket을 하나 생성하고, 바인딩(소켓과 포트 번호를 결합)을 한다.
그리고 Client가 접속할 때까지 계속 기다린다.
만약 Client 한 명이 접속했다면, ClientThread를 하나 생성한다.
이때, server의 socket을 공유하게 된다.

Client의 입장에서 생각해보자.
어떤 서버에 접속하기 위해서는 1)ip주소와 2)포트번호를 알고 있어야 한다.
Client에서는 socket.connect("ip주소", {포트번호})를 통해 서버에 접속하게 된다.
접속이 되었다면, 데이터를 보낼 차례이다.
이때는 socket.getOutputStream()을 활용하여 데이터를 송신할 수 있다.

다시 Server로 돌아가자.
방금 Client가 보낸 데이터는, ClientThread를 통해 들어오게 된다.
이때는 socket.getInputStream()을 활용하여 데이터를 수신할 수 있다.

여기까지 이했다면 계속 이어서..

🏹 코드를 통한 이해

server는 다음과 같다.
원래는 주석처리되어 있는 while문을 해제하여 여러명의 client가 접속 가능하도록 해야 하지만, 현재는 기본적인 이해를 위해 1명의 client만 접속 가능하도록 했다.

package com.example.project02fx;

import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.Button;
import javafx.stage.Stage;
import javafx.scene.layout.VBox;
import javafx.scene.Scene;

//데이터 송수신 Thread 만들기
class ClientThread extends Thread {
    Socket socket;
    ClientThread(Socket socket){
        this.socket = socket;
    }
    public void run() {
        try {
            while(true) {
                InputStream is = socket.getInputStream();   //socket사용 위해 인자 추가
                byte[] data = new byte[512];
                int size = is.read(data);   //blocking 함수
                String result = new String(data, 0, size, "utf-8");

                System.out.println(size + " : " + result);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
//접속 Thread 만들기
class ConnectThread extends Thread {
    public void run() {
        try {
            ServerSocket ss = new ServerSocket();
            //192.168.0.76
            ss.bind(new InetSocketAddress("localhost", 5001));
            System.out.println("바인딩 완료");
            //접속 받기
            //소켓 값을 받기
            //while(true) {
                Socket socket = ss.accept();
                System.out.println("누군가 접속됨");
                ClientThread ct = new ClientThread(socket);
                ct.start();
            //}

            InputStream is = socket.getInputStream();
            byte[] data = new byte[512];
            int size = is.read(data);   //blocking 함수
            String result = new String(data, 0, size, "utf-8");

            System.out.println(size + " : " + result);
            //socket이 닫히면 다음 접속자를 못받으므로 close는 나중에
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class Server extends Application{
    @Override
    public void start(Stage arg0) throws Exception {
        //V(ertical)Box, H(orizontal) Box
        VBox root = new VBox();
        root.setPrefSize(400, 300); //px단위, 가로, 세로
        // --------------------
        //응용 프로그램이 들어 가는 영역
        Button btn1 = new Button("서버 오픈");
        btn1.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent actionEvent) {
                ConnectThread ct = new ConnectThread();
                ct.start();
            }
        });
        root.getChildren().addAll(btn1);
        // --------------------
        //적용하기 위해서는 scene을 만들어서 등록해야 한다.
        Scene scene = new Scene(root);

        //scene과 arg0 연결
        arg0.setScene(scene);
        arg0.setTitle("Server");
        arg0.show();
    }
    public static void main(String[] args) {
        launch();
    }
}

client는 다음과 같다.

package com.example.project02fx;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;


public class Client extends Application{
    Socket socket = null;
    @Override
    public void start(Stage arg0) throws Exception {
        //V(ertical)Box, H(orizontal) Box
        VBox root = new VBox();
        root.setPrefSize(400, 300); //px단위, 가로, 세로
        // --------------------
        //응용 프로그램이 들어 가는 영역
        Button btn1 = new Button("접속");
        btn1.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent actionEvent) {
                try {
                    //서버에 접속
                    socket = new Socket();
                    socket.connect(new InetSocketAddress("localhost", 5001));
                    //socket.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

        //버튼을 누르면 데이터를 전송할 수 있도록
        Button btn2 = new Button("데이터 전송");
        btn2.setOnAction(new EventHandler<ActionEvent>() {
            int num = 0;
            @Override
            public void handle(ActionEvent actionEvent) {
                //데이터 스트림: 데이터를 전달하는 통로
                try {
                    OutputStream os = socket.getOutputStream();
                    String s = "apple" + num++;
                    //byte type으로 변환
                    byte[] bt = s.getBytes("utf-8");
                    os.write(bt);
                    System.out.println("데이터 전송");
                } catch (Exception e) {

                }
            }
        });

        root.getChildren().addAll(btn1, btn2);
        // --------------------
        //적용하기 위해서는 scene을 만들어서 등록해야 한다.
        Scene scene = new Scene(root);

        //scene과 arg0 연결
        arg0.setScene(scene);
        arg0.setTitle("Client");
        arg0.show();
    }
    public static void main(String[] args) {
        launch();
    }
}
profile
thisIsZihooLog

0개의 댓글