C19

0

이것이 자바다

목록 보기
17/18
post-thumbnail

네트워크 기초

네트워크는 여러 컴퓨터들을 통신 회선으로 연결한 것을 말한다. LAN은 가정, 회사 건물, 특정 영역에 존재하는 컴퓨터들을 연결한 것이고 WAN은 LAN을 연결한 것이다.

서버와 클라이언트

서비스를 제공하는 프로그램을 일반적으로 서버, 서비스를 요청하는 프로그램을 클라이언트라고 한다. 인터넷에서 두 프로그램이 통신하기 위해서는 먼저 클라이언트가 서비스를 요청, 서버는 처리 결과를 응답으로 제공해준다.

IP주소

컴퓨터의 고유 주소라고 할 수 있고 예를 들어 집의 주소라고 생각하면 된다. IP주소는 네트워크 어댑터마다 할당 되므로 두개의 랜카드가 있다면 IP주소는 2개 할당 가능하다. IP 주소를 모르면 프로그램들은 통신이 불가능하기 때문에 DNS를 이용해서 컴퓨터의 IP주소를 검색한다.

DNS는 도메인 이름으로, IP를 등록하는 저장소이다. 대중에게 서비스를 제공하는 대부분의 컴퓨터는 다음과 같이 도메인 이름으로 IP를 DNS에 미리 등록한다.

Port 번호

서버 프로그램은 여러개 실행이 가능하다. 이 경우 클라이언트는 어떤 서버와 통신해야 할지 결정해야 한다. IP는 컴퓨터의 네트워크 어댑터까지만 갈 수 있는 정보이기 때문에, 컴퓨터 내부에서 실행하는 서버를 선택하기 위해서는 추가적인 Port번호가 필요하다. Port는 운영체제가 관리하는 서버 프로그램의 연결 번호다. 서버는 시작할 때 특정 Port 번호에 바인딩한다. 클라이언트도 서버에서 보낸 정보를 받기 위해서는 Port 번호가 필요한데, 서버와 같이 고정적인 Port번호에 바인딩이 아니라 운영체제가 자동적으로 부여한다. 그리고 이 번호는 클라이언트가 서버로 요청할 때 함께 전송되어 서버가 클라이언트로 정보를 보낼 때 사용된다.

IP 주소 얻기

자바는 IP주소를 java.net 패키지의 InetAddress로 표현한다. InetAddress를 이용하면 로컬 컴퓨터의 IP주소를 얻을 수 있다.

InetAddress ia = InetAddress.getLocalHost();

컴퓨터의 도메인 이름을 알고 있다면 아래 두 개의 메소드를 사용하여 UnetAddress객체를 얻을 수 있다.

InetAddress ia = InetAddress.getByName(String domainName);
InetAddress[] iaArr = InetAddress.getAllByName(String domainName);

이 메소드들로부터 얻은 InetAddress 객체에서 IP주소를 얻으려면 getHostAddress() 메소드를 아래와 같이 호출하면 된다. 리턴값은 문자열로 된 IP 주소이다.

String ip = InetAddress.getHostAddress();

TCP 네트워킹

IP 주소로 프로그램들이 통신할 때는 약속된 데이터 전송 규약이 있고 이것을 전송용 프로토콜이라고 부른다. 인터넷에서 전송용 프로토콜은 TCP, UDP가 있다.

TCP는 연결형 프로토콜로 상대방이 연결된 상태에서 데이터를 주고 받는다. 클라이언트가 연결 요청을 하고 서버가 연결을 수락하면 통신 회선이 고정되고 데이터는 고정 회선을 통해 전달된다. TCP는 보낸 데이터가 순서대로 전달되며 손실이 발생하지 않는다.

TCP 서버

TCP 서버 프로그램을 개발하려면 우선 ServerSocket 객체를 생성해야한다

아래는 50001번 Port에 바인딩하는 ServerSocket을 생성하는 코드이다.

ServerSocket serverSocket = new ServerSoket(50001);

또 다른 방법으로는 기본 생성자로 객체 생성후 Port바인딩을 위해 bind() 메소드를 사용하는 것이다.

ServerSocket serverSocket = new ServerSoket();
serverSocket.bind(new InetSoketAddress(50001));

만약 서버 컴퓨터에 여러 개의 IP할당시, 특정 IP에서만 서비스를 하고 싶다면 InetSocketAddress의 첫 번째 매개값으로 해당 IP를 주면 된다.

ServerSocket serverSoket = new ServerSocket();
serverSocket.bind( new InetSoketAddress("첫번째 아이피", 50001));

만약 Port가 이미 다른 프로그램에서 사용 중이라면 BindException이 발생한다. 이 경우에는 다른 Port로 바인딩하거나 Port 사용중인 프로그램을 종료 후 다시 실행한다.

ServerSocket이 생성되었다면 연결 요청 수락을 위해 accept() 메소드를 실행해야 한다. accept()는 클라이언트가 연결 요청하기 전까지 블로킹된다. 클라이언트의 연결 요청이 들어오면 블로킹이 해제되고 통신용 Socket을 리턴

Socket socket = serverSocket.accept();

만약 리턴된 Socket을 통해 연결된 클라이언트의 IP 주소와 Port 번호를 얻고 싶다면 방법은 getRemoteSocketAddress() 메소드를 호출해서 InetSocketAddress를 얻은 다음 getHostName(), getPort() 메소드를 호출하면 된다.

TCP 클라이언트

클라이언트가 서버에 연결 요청을 하려면 Socket 객체를 생성할 때 생성자 매개값으로 서버 IP 주소와 Port 번호를 제공하면 된다. 로컬 컴퓨터에서 실행하는 서버로 연결 요청을 할 경우에는 IP주소 대신 localhost를 사용할 수 있다.

Socket socket = new Socket( "IP", 50001 );

만약 IP 주소 대신 도메인 사용시 DNS에서 IP 주소를 검색할 수 있도록 생성자 매개값으로 InetSocketAddress를 제공해야한다. 생성과 동시에 연결 요청을 하지 않고 Connect() 메소드로 연결 요청을 할 수 있다.

연결 요청 시 두 가지 예외가 발생할 수 있다.

UnknownHostExeception은 IP 주소가 잘못 표기되었을 때 발생하고,
IOException은 제공된 IP와 Port 번호로 연결할 수 없을 때 발생한다. 따라서 두 가지 예외를 모두 처리해야 한다 .

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

클라이언트가 연결 요청을 하고 서버가 연결 수락했다면, 다음 그림과 같이 양쪽의 Socket 객체로부터 각각 입력 스트림과 출력 스트림을 얻을 수 있다.

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

문자열을 좀 더 간편하게 보내고 싶다면 DataOutputStream 보조 스트림을 연결 사용

UDP 네트워킹

UDP는 발신자가 일방적으로 수신자에게 데이터를 보내는 방식으로, TCP처럼 연결 요청 및 수락 과정이 없기 때문에 TCP보다 데이터 전송 속도가 상대적으로 빠르다.

UDP는 TCP처럼 고정 회선이 아니라 여러 회선을 통해 데이터가 전송되기 때문에 특정 회선의 속도에 따라 데이터가 순서대로 전달되지 않거나 잘못된 회선으로 인해 데이터 손실이 발생할 수 있다.

속도가 중요하면 UDP, 데이터 전달의 신뢰성이 중요하다면 TCP를 사용

UDP 서버

UDP 서버를 위한 DatagramSocket 객체를 생성할 때에는 바인딩할 Port 번호를 생성자 매개값으로 제공해야 한다.

UDP 서버는 클라이언트가 보낸 DatagramPacket을 항상 받을 준비를 해야 한다. 이 역할을 하는 메소드가 receive()이다. receive() 메소드는 데이터를 수신할 때까지 블로킹되고, 데이터가 수신되면 매개값으로 주어진 DatagramPacket에 저장한다.

DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024);
datagramSocket.receive(receivePacket);

첫 번째 매개값은 수신된 데이터를 저장할 배열이고 두 번째 매개값은 수신할 수 있는 최대 바이트 수이다.
receive() 메소드 실행 후 수신된 데이터 바이트 수를 얻는 방법

byte[] bytes = receivePacket.getData();
int num = receivePacket.getLength();

이제 반대로 UDP 서버가 클라이언트에게 처리 내용을 보내려면 클라이언트 IP주소와 Port번호가 필요한데, DatagramPacket에서 얻을 수 있다. 그리고 getScoketAddress() 메소드를 호출하면 정보가 담긴 SocketAddress 객체를 얻을 수 있다.

이렇게 얻은 Socket Address 객체는 클라이언트로 보낼 DatagramPacket을 생성할 때 네 번째 매개값으로 사용된다. DatagramPacket 생성자의 첫 번째 매개값은 바이트 배열이고 두 번째는 인덱스, 세 번째는 보낼 바이트 수이다.

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

DatagramPacket을 클라이언트로 보낼 때는 DatagramSocket의 send() 메소드를 이용한다.

datagramSocket.send( sendPacket);

UDP 클라이언트

UDP 클라이언트는 서버에 요청 내용을 보내고 그 결과를 받는 역할을 한다. UDP 클라이언트 위한 DatagramSocket 객체는 기본 생성자로 생성한다. Port 번호는 자동으로 부여되기 때문에 따로 지정할 필요 없다.

요청 내용을 보내기 위한 DatagramPacket을 생성하는 방법

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

첫 번째 매개값은 바이트 배열, 두 번째 매개값은 바이트 배열에서 보내고자 하는 바이트 수이다. 세 번째 매개값은 UDP 서버의 IP와 Port 정보를 가지고 있는 InetSocketAddress 객체이다.

UDP서버에서 처리 결과가 언제 올지 모르므로 항상 받을 준비를 하기 위해 receive() 메소드를 호출한다. receive() 메소드는 데이터를 수신할 때까지 블로킹되고, 데이터가 수신되면 매개값으로 주어진 DatagramPacket에 저장한다.

서버의 동시 요청 처리

일반적으로 서버는 다수의 클라이언트와 통신을 한다. 서버는 클라이언트들로부터 동시에 요청을 받아서 처리하고, 처리 결과를 개별 클라이언트로 보내 줘야 한다.

accept(), receive()를 제외한 요청 처리 코드를 별도의 스레드에서 작업하는 것이 좋은데, 스레드를 처리할 때 주의할 점은 클라이언트의 폭증으로 인한 서버의 과도한 스레드 생성을 방지해야 한다는 것이다. 그래서 스레드풀을 사용하는 것이 바람직하다.

JSON 데이터 형식

네트워크로 전달하는 데이터가 복잡할수록 구조화된 형식이 필요하다. 네트워크 통신에서 가장 많이 사용되는 데이터 형식은 JSON이다.

두 개 이상의 속성이 있는 경우에는 객체 { }로 표기하고, 두 개 이상의 값이 있는 경우에는 배열 [ ]로 표기한다.

//회원 정보를 JSON으로 표기하면 다음과 같다.
{
	"id" : "Winter",
    "name" : "한겨울"
    "age" : 25,
    "student" : true,
    "tel" : { "home" : 02-123-1234, "mobile : "010-123-1234" },
    "skill" : { "java", "C", "C++" } 
}

JSON을 문자열로 직접 작성 할 수 있지만 대부분은 라이브러리를 이용해서 생성한다.

TCP 채팅 프로그램

채팅 서버와 클라이언트에서 사용할 클래스 이름

확인문제

1. 2번

2. 2, 4번

3.

new Socket("localhost", 5001); 
serverSocket.accept()

4.

InputStream / OutputStream
OutputStream / InputStream

5.

DatagramSocket // DatagramPacket // DatagramSocket // DatagramPacket // DatagramPacket

6. 4번

7.

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
    private int no;
    private String name;
    private int price;
    private int stock;
}
import java.io.DataInputStream;
        import java.io.DataOutputStream;
        import java.io.IOException;
        import java.net.ServerSocket;
        import java.net.Socket;
        import java.util.Iterator;
        import java.util.List;
        import java.util.Vector;
        import java.util.concurrent.ExecutorService;
        import java.util.concurrent.Executors;
        import org.json.JSONArray;
        import org.json.JSONObject;

public class ProductServer {
    private ServerSocket serverSocket;
    private ExecutorService threadPool;
    private List<Product> products;
    private int sequence;

    public void start() throws IOException {
        serverSocket = new ServerSocket(50001);
        threadPool = Executors.newFixedThreadPool(100);
        products = new Vector<Product>();
        System.out.println("[서버] 시작됨");
        while (true) {
            Socket socket = serverSocket.accept();
            SocketClient sc = new SocketClient(socket);
        }
    }

    public void stop() {
        try {
            serverSocket.close();
            threadPool.shutdownNow();
            System.out.println("[서버] 종료됨 ");
        } catch (IOException e1) {
        }
    }

    public class SocketClient {
        private Socket socket;
        private DataInputStream dis;
        private DataOutputStream dos;

        public SocketClient(Socket socket) {
            try {
                this.socket = socket;
                this.dis = new DataInputStream(socket.getInputStream());
                this.dos = new DataOutputStream(socket.getOutputStream());
                receive();
            } catch (IOException e) {
                close();
            }
        }

        public void receive() {
            threadPool.execute(() -> {
                try {
                    while (true) {
                        String receiveJson = dis.readUTF();
                        JSONObject request = new JSONObject(receiveJson);
                        int menu = request.getInt("menu");
                        switch (menu) {
                            case 0 -> list(request);
                            case 1 -> create(request);
                            case 2 -> update(request);
                            case 3 -> delete(request);
                        }
                    }
                } catch (IOException e) {
                    close();
                }
            });
        }

        public void list(JSONObject request) throws IOException {
            JSONArray data = new JSONArray();
            for (Product p : products) {
                JSONObject product = new JSONObject();
                product.put("no", p.getNo());
                product.put("name", p.getName());
                product.put("price", p.getPrice());
                product.put("stock", p.getStock());
                data.put(product);
            }
            JSONObject response = new JSONObject();
            response.put("status", "success");
            response.put("data", data);
            dos.writeUTF(response.toString());
            dos.flush();
        }

        public void create(JSONObject request) throws IOException {
            JSONObject data = request.getJSONObject("data");
            Product product = new Product();
            product.setNo(++sequence);
            product.setName(data.getString("name"));
            product.setPrice(data.getInt("price"));
            product.setStock(data.getInt("stock"));
            products.add(product);
            JSONObject response = new JSONObject();
            response.put("status", "success");
            response.put("data", new JSONObject());
            dos.writeUTF(response.toString());
            dos.flush();
        }

        public void update(JSONObject request) throws IOException {
            JSONObject data = request.getJSONObject("data");
            int no = data.getInt("no");
            for (int i = 0; i < products.size(); i++) {
                Product product = products.get(i);
                if (product.getNo() = =no){
                    product.setName(data.getString("name"));
                    product.setPrice(data.getInt("price"));
                    product.setStock(data.getInt("stock"));
                }
            } JSONObject response = new JSONObject();
            response.put("status", "success");
            response.put("data", new JSONObject());
            dos.writeUTF(response.toString());
            dos.flush();
        }

        public void delete(JSONObject request) throws IOException {
            JSONObject data = request.getJSONObject("data");
            int no = data.getInt("no");
            Iterator<Product> iterator = products.iterator();
            while (iterator.hasNext()) {
                Product product = iterator.next();
                if (product.getNo() = =no){
                    iterator.remove();
                }
            } JSONObject response = new JSONObject();
            response.put("status", "success");
            response.put("data", new JSONObject());
            dos.writeUTF(response.toString());
            dos.flush();
        }

        public void close() {
            try {
                socket.close();
            } catch (Exception e) {
            }
        }
    }

    public static void main(String[] args) {
        ProductServer productServer = new ProductServer();
        try {
            productServer.start();
        } catch (IOException e) {
            System.out.println(e.getMessage());
            productServer.stop();
        }
    }
}
import java.io.DataInputStream;
        import java.io.DataOutputStream;
        import java.io.IOException;
        import java.net.Socket;
        import java.util.Scanner;
        import org.json.JSONArray;
        import org.json.JSONObject;

public class ProductClient {
    private Socket socket;
    private DataInputStream dis;
    private DataOutputStream dos;
    private Scanner scanner;

    public void start() throws IOException {
        socket = new Socket("localhost", 50001);
        dis = new DataInputStream(socket.getInputStream());
        dos = new DataOutputStream(socket.getOutputStream());
        System.out.println("[클라이언트] 서버에 연결됨");
        scanner = new Scanner(System.in);
        list();
    }

    public void stop() {
        try {
            socket.close();
            scanner.close();
        } catch (Exception e) {
        }
        System.out.println("[클라이언트] 종료됨");
    }

    public void menu() throws IOException {
        System.out.println();
        System.out.println("---------------------------------------------------------------");
        System.out.println("메뉴: 1.Create | 2.Update | 3.Delete | 4.Exit");
        System.out.print("선택: ");
        String menuNo = scanner.nextLine();
        System.out.println();
        switch (menuNo) {
            case "1" -> create();
            case "2" -> update();
            case "3" -> delete();
            case "4" -> exit();
        }
    }

    public void list() throws IOException {
        System.out.println();
        System.out.println("[상품 목록]");
        System.out.println("---------------------------------------------------------------");
        System.out.printf("%-6s%-30s%-15s%-10s\n", "no", "name", "price", "stock");
        System.out.println("---------------------------------------------------------------");
        JSONObject request = new JSONObject();
        request.put("menu", 0);
        request.put("data", new JSONObject());
        dos.writeUTF(request.toString());
        dos.flush();
        JSONObject response = new JSONObject(dis.readUTF());
        if (response.getString("status").equals("success")) {
            JSONArray data = response.getJSONArray("data");
            for (int i = 0; i < data.length(); i++) {
                JSONObject product = data.getJSONObject(i);
                System.out.printf("%-6d%-30s%-15d%-10d\n", product.getInt("no"), product.getString("name"), product.getInt("price"), product.getInt("stock"));
            }
        }
        menu();
    }

    public void create() throws IOException {
        System.out.println("[상품 생성]");
        Product product = new Product();
        System.out.print("상품 이름: ");
        product.setName(scanner.nextLine());
        System.out.print("상품 가격: ");
        product.setPrice(Integer.parseInt(scanner.nextLine()));
        System.out.print("상품 재고: ");
        product.setStock(Integer.parseInt(scanner.nextLine()));
        JSONObject data = new JSONObject();
        data.put("name", product.getName());
        data.put("price", product.getPrice());
        data.put("stock", product.getStock());
        JSONObject request = new JSONObject();
        request.put("menu", 1);
        request.put("data", data);
        dos.writeUTF(request.toString());
        dos.flush();
        JSONObject response = new JSONObject(dis.readUTF());
        if (response.getString("status").equals("success")) {
            list();
        }
    }

    public void update() throws IOException {
        System.out.println("[상품 수정]");
        Product product = new Product();
        System.out.print("상품 번호: ");
        product.setNo(Integer.parseInt(scanner.nextLine()));
        System.out.print("이름 변경: ");
        product.setName(scanner.nextLine());
        System.out.print("가격 변경: ");
        product.setPrice(Integer.parseInt(scanner.nextLine()));
        System.out.print("재고 변경: ");
        product.setStock(Integer.parseInt(scanner.nextLine()));
        JSONObject data = new JSONObject();
        data.put("no", product.getNo());
        data.put("name", product.getName());
        data.put("price", product.getPrice());
        data.put("stock", product.getStock());
        JSONObject request = new JSONObject();
        request.put("menu", 2);
        request.put("data", data);
        dos.writeUTF(request.toString());
        dos.flush();
        JSONObject response = new JSONObject(dis.readUTF());
        if (response.getString("status").equals("success")) {
            list();
        }
    }

    public void delete() throws IOException {
        System.out.println("[상품 삭제]");
        System.out.print("상품 번호: ");
        int no = Integer.parseInt(scanner.nextLine());
        JSONObject data = new JSONObject();
        data.put("no", no);
        JSONObject request = new JSONObject();
        request.put("menu", 3);
        request.put("data", data);
        dos.writeUTF(request.toString());
        dos.flush();
        JSONObject response = new JSONObject(dis.readUTF());
        if (response.getString("status").equals("success")) {
            list();
        }
    }

    public void exit() {
        stop();
    }
	
    public static void main(String[] args) {
        ProductClient productClient = new ProductClient();
        try {
            productClient.start();
        } catch (IOException e) {
            System.out.println(e.getMessage());
            productClient.stop();
        }
    }
}

솔직히 마지막 답지 없으면 절대 못함 2시간하고 그냥 내다 던짐 나중엔 할 수 있겠지

0개의 댓글

관련 채용 정보