네트워크와 쓰레드(Thread)

제민·2024년 7월 31일

Java 개념 공부

목록 보기
21/21
post-thumbnail

서버와 클라이언트

네트워크로 연결된 컴퓨터 간의 관계를 역할(role)로 구분한 개념입니다. 서버와 클라이언트는 각각의 역할에 따라 다른 기능을 수행합니다.

  • 서버(Server): 서비스를 제공하는 프로그램으로 클라이언트의 연결을 수락하고 요청 내용을 처리한 후 응답을 보내는 역할을 합니다.
  • 클라이언트(Client): 서비스를 받는 프로그램으로, 네트워크 데이터를 필요로 하는 모든 애플리케이션이 해당됩니다.

IP 주소

IP 주소는 네트워크 상에서 컴퓨터를 식별하는 번호로, 네트워크 어댑터(랜카드)마다 할당되어 있습니다. 서버에 요청을 보내기 위해서는 서버의 IP 주소와 포트 번호를 알아야 합니다.

포트(Port)

같은 컴퓨터 내에서 프로그램을 식별하는 번호입니다. 클라이언트는 서버 연결 요청 시 IP 주소와 포트 번호를 알아야 합니다.

InetAddress 클래스

InetAddress 클래스는 IP 주소를 다루기 위해 자바에서 제공하는 클래스입니다. 이를 통해 IP 주소와 도메인 이름을 쉽게 다룰 수 있습니다.

예제: InetAddress 클래스 사용

package p.network;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class Run {
    public static void main(String[] args) {
        try {
            InetAddress localhost = InetAddress.getLocalHost();
            System.out.println(localhost);
            System.out.println("내 PC명 : " + localhost.getHostName());
            System.out.println("내 IP주소 : " + localhost.getHostAddress());
           System.out.println("=====================================");
            InetAddress googleHost = InetAddress.getByName("www.google.com");
            System.out.println("google 서버명 : " + googleHost.getHostName());
            System.out.println("google IP주소 : " + googleHost.getHostAddress());
            System.out.println("=====================================");
            InetAddress[] naverHost = InetAddress.getAllByName("www.naver.com");
            System.out.println("네이버의 호스트 개수 " + naverHost.length);
            for (InetAddress n : naverHost) {
                System.out.println("네이버 서버명 : " + n.getHostName());
                System.out.println("네이버 IP주소 : " + n.getHostAddress());
            }
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
    }
}

위 코드는 로컬 호스트와 구글, 네이버의 IP 주소를 가져오는 방법을 보여줍니다.

소켓 프로그래밍

소켓을 이용한 통신 프로그래밍으로, 프로세스 간의 통신에 사용되는 양쪽 끝 단을 의미합니다. 자바에서는 TCP와 UDP 소켓 프로그래밍을 통해 네트워크 통신을 구현할 수 있습니다.

TCP와 UDP의 차이

  • TCP (Transmission Control Protocol): 데이터 전송 속도가 느리지만, 정확하고 안정적으로 전달할 수 있는 연결 지향적 프로토콜입니다.
  • UDP (User Datagram Protocol): 데이터 전송 속도가 빠르지만, 신뢰성이 없는 데이터를 전송하는 비연결 지향적 프로토콜입니다.

TCP 소켓 프로그래밍

클라이언트와 서버 간의 1:1 소켓 통신입니다. 서버가 먼저 실행되어 클라이언트의 요청을 기다려야 하고, 서버용 프로그램과 클라이언트용 프로그램을 따로 구현해야 합니다. 자바에서는 TCP 소켓 프로그래밍을 위해 java.net 패키지에서 ServerSocketSocket 클래스를 제공합니다.

서버용 TCP 소켓 프로그래밍 순서

  1. 서버의 포트 번호를 정합니다.
  2. 서버용 소켓 객체를 생성합니다.
  3. 클라이언트 쪽에서 접속 요청이 오길 기다립니다.
  4. 접속 요청이 오면 요청을 수락 후 해당 클라이언트에 대한 소켓 객체를 생성합니다.
  5. 연결된 클라이언트와 입출력 스트림을 생성합니다.
  6. 보조 스트림을 통해 성능을 개선합니다.
  7. 스트림을 통해 데이터를 읽고 씁니다.
  8. 통신을 종료합니다.

클라이언트용 TCP 소켓 프로그래밍 순서

  1. 서버의 IP 주소와 서버가 정한 포트 번호를 매개변수로 하여 클라이언트용 소켓 객체를 생성합니다.
  2. 서버와의 입출력 스트림을 오픈합니다.
  3. 보조 스트림을 통해 성능을 개선합니다.
  4. 스트림을 통해 데이터를 읽고 씁니다.
  5. 통신을 종료합니다.

예제: TCP 클라이언트

package p.network;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class TCPClient {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        BufferedReader br = null;
        PrintWriter pw = null;
        int port = 3000;
        String serverIP = "192.168.30.9";
        Socket socket = null;
       
        try {
            socket = new Socket(serverIP, port);
            if (socket != null) {
                System.out.println("서버와 연결 성공");
                br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                pw = new PrintWriter(socket.getOutputStream());
                while (true) {
                    System.out.print("서버에게 보낼 내용 : ");
                    String sendMessage = sc.nextLine();
                    pw.println(sendMessage);
                    pw.flush();
                    String message = br.readLine();
                    System.out.println("서버로부터 전달받은 메세지 : " + message);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                pw.close();
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

예제: TCP 서버

package p.network;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TCPServer {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        BufferedReader br = null;
        PrintWriter pw = null;
        int port = 3000;
       
        try {
            ServerSocket server = new ServerSocket(port);
            System.out.println("클라이언트 요청을 기다리고 있습니다.");
            Socket socket = server.accept();
           System.out.println(socket.getInetAddress().getHostAddress() + "가 연결을 요청함...");
            br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            pw = new PrintWriter(socket.getOutputStream());
           
            while (true) {
                String message = br.readLine();
                System.out.println("클라이언트로부터 전달받은 메세지 : " + message);
                System.out.print("클라이언트에게 보낼 내용 : ");
                String sendMessage = sc.nextLine();
                pw.println(sendMessage);
                pw.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                pw.close();
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

UDP 소켓 프로그래밍

UDP는 연결 지향적이지 않기 때문에 연결 요청을 받아줄 서버 소켓이 필요 없습니다. java.net 패키지에서 제공하는 DatagramSocketDatagramPacket 클래스를 사용하여 UDP 소켓 프로그래밍을 구현할 수 있습니다.

서버용 UDP 소켓 프로그래밍 순서

  1. 서버의 포트 번호를 정합니다.
  2. DatagramSocket 객체를 생성합니다.
  3. 연결할 클라이언트의 IP 주소를 가진 InetAddress 객체를 생성합니다.
  4. 전송할 메시지를 byte[]로 변환합니다.
  5. 전송할 메시지를 DatagramPacket 객체에 담습니다.
  6. 소켓 레퍼런스를 사용하여 메시지를 전송합니다.
  7. 소켓을 닫습니다.

클라이언트용 UDP 소켓 프로그래밍 순서

  1. 서버가 보낸 메시지를 받을 byte[]를 준비합니다.
  2. DatagramSocket 객체를 생성합니다.
  3. 메시지를 받을 DatagramPacket 객체를 준비합니다.
  4. byte[]로 받은 메시지를 String으로 변환하여 출력합니다.
  5. 소켓을 닫습니다.

예제: UDP 클라이언트

package p.network;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Scanner;

public class UDPClient {
    public static void main(String[] args) {
        Scanner

 sc = new Scanner(System.in);
        int port = 3000;
        InetAddress serverIP = null;
        DatagramSocket socket = null;
       
        try {
            serverIP = InetAddress.getByName("localhost");
            socket = new DatagramSocket();
            while (true) {
                System.out.print("서버로 보낼 메세지 입력 : ");
                String message = sc.nextLine();
                byte[] sendMessage = message.getBytes();
                DatagramPacket sendPacket = new DatagramPacket(sendMessage, sendMessage.length, serverIP, port);
                socket.send(sendPacket);
               
                byte[] receiveMessage = new byte[1024];
                DatagramPacket receivePacket = new DatagramPacket(receiveMessage, receiveMessage.length);
                socket.receive(receivePacket);
                String receivedData = new String(receivePacket.getData()).trim();
                System.out.println("서버로부터 전달받은 메세지 : " + receivedData);
            }
        } catch (UnknownHostException | SocketException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            socket.close();
        }
    }
}

예제: UDP 서버

package p.network;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UDPServer {
    public static void main(String[] args) {
        int port = 3000;
        DatagramSocket socket = null;
       
        try {
            socket = new DatagramSocket(port);
            while (true) {
                byte[] receiveMessage = new byte[1024];
                DatagramPacket receivePacket = new DatagramPacket(receiveMessage, receiveMessage.length);
                socket.receive(receivePacket);
                String receivedData = new String(receivePacket.getData()).trim();
                System.out.println("클라이언트로부터 전달받은 메세지 : " + receivedData);
               
                String message = "서버의 응답 메세지 : " + receivedData;
                byte[] sendMessage = message.getBytes();
                DatagramPacket sendPacket = new DatagramPacket(sendMessage, sendMessage.length, receivePacket.getAddress(), receivePacket.getPort());
                socket.send(sendPacket);
            }
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            socket.close();
        }
    }
}

쓰레드 프로그래밍

쓰레드는 프로그램 내에서 실행의 흐름을 가지고 있는 최소 단위입니다. 자바에서는 Thread 클래스를 상속받거나 Runnable 인터페이스를 구현하여 쓰레드를 생성할 수 있습니다.

쓰레드 생성 방법

  1. Thread 클래스 상속: Thread 클래스를 상속받아 run 메소드를 오버라이드합니다.
  2. Runnable 인터페이스 구현: Runnable 인터페이스를 구현하고 run 메소드를 오버라이드한 후, Thread 객체를 생성하여 실행합니다.
package q.thread.ex1;

public class Run {
    public static void main(String[] args) {
        Task t1 = new Task();
        Runnable task = new MyRunnable();
        Thread t2 = new Thread(task);
        t1.start

();
        t2.start();
    }
}

class Task extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("thread1 : " + i);
        }
    }
}

class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("thread2 : " + i);
        }
    }
}

위 코드에서는 Task 클래스를 상속받아 쓰레드를 생성하고, MyRunnable 클래스를 구현하여 두 개의 쓰레드를 동시에 실행합니다.

쓰레드 프로그래밍과 네트워크 프로그래밍을 결합하여 멀티 클라이언트 서버를 구현할 수 있습니다. 다음 예제는 TCP 서버를 멀티쓰레드로 구현한 것입니다.

package q.thread.ex4;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class Run {
    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(3000)) {
            while (true) {
                Socket clientSocket = serverSocket.accept();
                new ClientHandler(clientSocket).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class ClientHandler extends Thread {
    private Socket clientSocket;

    public ClientHandler(Socket clientSocket) {
        this.clientSocket = clientSocket;
    }

    @Override
    public void run() {
        try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
             PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)) {
            String message;
            while ((message = in.readLine()) != null) {
                System.out.println("Received: " + message);
                out.println("Echo: " + message);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

위 코드에서는 클라이언트의 요청을 처리하는 ClientHandler 쓰레드를 생성하여 클라이언트와의 통신을 담당합니다.

네트워크 통신에서는 TCP와 UDP 방식의 소켓 프로그래밍을 사용하여 서버와 클라이언트 간의 데이터 전송을 구현할 수 있으며, 멀티쓰레드를 통해 동시에 여러 클라이언트를 처리하는 서버를 구현할 수 있습니다.
이와 같은 네트워크와 쓰레드 프로그래밍은 자바 애플리케이션의 성능과 효율성을 높이는 데 중요한 역할을 합니다.

profile
초보부터 시작하는 개발자 생활

0개의 댓글