네트워크 프로그램

sungs·2025년 8월 9일

자바

목록 보기
62/95

네트워크 프로그램

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class SimpleServer {
    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(7777)) {
            System.out.println("서버가 시작되었습니다. 클라이언트 연결을 기다립니다...");
            
            Socket clientSocket = serverSocket.accept(); // 클라이언트 연결 수락
            System.out.println("클라이언트가 연결되었습니다: " + clientSocket.getInetAddress());

            DataInputStream in = new DataInputStream(clientSocket.getInputStream());
            DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream());

            String message = in.readUTF(); // 클라이언트로부터 문자열 수신
            System.out.println("클라이언트로부터 받은 메시지: " + message);

            String responseMessage = "안녕하세요, 서버입니다!";
            out.writeUTF(responseMessage); // 클라이언트에 응답 전송
            System.out.println("클라이언트에 응답 메시지 전송: " + responseMessage);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

public class SimpleClient {
    public static void main(String[] args) {
        String host = "localhost";
        int port = 7777;

        try (Socket socket = new Socket(host, port)) {
            System.out.println("서버에 연결되었습니다: " + host + ":" + port);

            DataOutputStream out = new DataOutputStream(socket.getOutputStream());
            DataInputStream in = new DataInputStream(socket.getInputStream());

            String message = "클라이언트입니다. 연결 요청합니다.";
            out.writeUTF(message); // 서버에 문자열 전송
            System.out.println("서버에 메시지 전송: " + message);

            String responseMessage = in.readUTF(); // 서버로부터 응답 수신
            System.out.println("서버로부터 받은 응답: " + responseMessage);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

이렇게 소켓을 활용하여 네트워크 프로그램을 만들 수 있다. 소켓은 서버와 데이터를 주고 받기 위한 스트림을 제공한다. 소켓에 ip와 port를 넣어주면 된다. 참고로 localhost는 127.0.0.1이라는 루프백 주소를 사용한다. 그래서 localhost라고 써주기만 해도 127.0.0.1이라는 ip가 사용된다.

클라이언트측에서 Socket을 만든 다음 Input/OutputStream을 이용해서 데이터를 주고 받으면 된다.

반면 서버측에선 ServerSocket이란 걸 열어주어야 한다. 그래야지 포트를 열고 연결될 수 있다. 그런 다음 accept로 호출해야지 클라이언트와 Input/OutputStream으로 데이터를 주고 받을 수 있다.

클라이언트와 서버 모두 자원 정리를 해줘야 한다. 특히 close는 반드시 해줘야 한다. 서버 소켓을 포함한 외부와 정보를 주고 받기 위해 사용했던 것들을 닫아줘야 한다.

원리에 대해 간단하게 설명해보자면 서버 측에서 ServerSocket으로 포트를 연다. 그러면 클라이언트가 연결을 시도하고 TCP 3 way handshake가 발생하여 연결이 성공적으로 마무리된다. 연결이 성공하였으면 OS backlog queue에다가 클라이언트 ip, port와 서버 ip, port를 기록한 연결 정보를 저장한다. 참고로 서버와 달리 클라이언트는 ip를 지정해줘야 할 일이 별로 없다. 그래서 주로 랜덤으로 지정된 ip를 사용한다.

클라이언트와 연결이 되었으면 이제 aceept을 호출해 Socket 객체를 만들어 호출한다. accept를 호출하면 backlog에서 연결 정보를 꺼내서 사용한다. 사용한 연결 정보는 backlog에서 제거된다. 그런 다음 소켓과 스트림으로 데이터를 주고받는 것이다.

하지만 이러면 한 개의 클라이언트만이 서버 측과 데티어를 주고받을 수 있다. 다른 클라이언트가 들어와 서버 소켓만으로 연결이 가능하므로 서버와 TCP 연결까지는 해도 accept가 한 번 밖에 발생하지 않아 또다른 소켓이 안 생겨 그 클라이언트와는 데이터를 주고받을 수 없는 것이다. 따라서 멀티 스레드를 이용해서 accept와 데이터를 주고받는 작업을 따로 멀티 스레드에 부여할 필요가 있다.


import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.util.Scanner;

public class ThreadedEchoServer {
    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(7777)) {
            System.out.println("서버가 시작되었습니다. 클라이언트 연결을 기다립니다...");

            while (true) {
                Socket clientSocket = serverSocket.accept(); // 클라이언트 연결 대기
                System.out.println("새로운 클라이언트가 연결되었습니다: " + clientSocket.getInetAddress());

                // 새로운 스레드에 클라이언트 소켓을 넘겨 통신을 맡김
                new Thread(new ClientHandler(clientSocket)).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class ClientHandler implements Runnable {
    private Socket clientSocket;

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

    @Override
    public void run() {
        try (DataInputStream in = new DataInputStream(clientSocket.getInputStream());
             DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream())) {
            
            // 클라이언트에게 환영 메시지 전송
            out.writeUTF("안녕하세요! 서버에 오신 것을 환영합니다.");
            
            while (true) {
                // 클라이언트로부터 메시지 수신
                String message = in.readUTF();
                System.out.println("클라이언트(" + clientSocket.getInetAddress() + "): " + message);
                
                // 클라이언트가 "종료"를 입력하면 연결 종료
                if (message.equalsIgnoreCase("종료")) {
                    out.writeUTF("연결을 종료합니다.");
                    break;
                }
                
                // 받은 메시지를 그대로 다시 클라이언트에게 전송 (에코)
                out.writeUTF("서버 에코: " + message);
            }
            
        } catch (IOException e) {
            // 클라이언트가 강제로 연결을 끊었을 때 발생하는 예외 처리
            System.out.println("클라이언트(" + clientSocket.getInetAddress() + ")와의 연결이 끊어졌습니다.");
        } finally {
            try {
                if (clientSocket != null) {
                    clientSocket.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("클라이언트(" + clientSocket.getInetaddress() + ") 연결 종료.");
        }
    }
}

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;

public class SimpleClient {
    public static void main(String[] args) {
        String host = "localhost";
        int port = 7777;

        try (Socket socket = new Socket(host, port);
             DataInputStream in = new DataInputStream(socket.getInputStream());
             DataOutputStream out = new DataOutputStream(socket.getOutputStream());
             Scanner scanner = new Scanner(System.in)) {

            System.out.println("서버에 연결되었습니다. 메시지를 입력하세요 (종료하려면 '종료' 입력).");
            
            // 서버의 첫 번째 메시지 수신 (환영 메시지)
            System.out.println("서버: " + in.readUTF());

            while (true) {
                // 사용자 입력 받기
                System.out.print("> ");
                String message = scanner.nextLine();
                
                // 서버에 메시지 전송
                out.writeUTF(message);
                
                // 서버로부터 응답 수신
                String response = in.readUTF();
                System.out.println("서버: " + response);

                if (message.equalsIgnoreCase("종료")) {
                    break;
                }
            }
        } catch (IOException e) {
            System.out.println("서버 연결에 실패했거나 연결이 끊어졌습니다.");
        }
    }
}

Scanner와 멀티 스레드를 이용해서 여러 클라이언트와 메세지를 주고받을 수 있다. 서버에서는 ServerSocket을 만들고 accept부터는 while문을 돌리므로 aceept을 클라이언트가 들어올 때마다 실행할 수 있어 여런 클라이언트와 메세지를 주고받을 수 없다는 점이 해결된다. 이렇게 하면 서버는 연결만 시켜주고 다른 스레드들이 메세지를 주고 받는다.

profile
앱 개발 공부 중

0개의 댓글