Backend/Java

Java - Socket(TCP)

elevne 2023. 5. 25. 12:04

TCP (Transmission Control Protocol) 는 연결 지향적 프로토콜이다. 연결 지향 프로토콜이란 클라이언트와 서버가 연결된 상태에서 데이터를 주고받는 프로토콜을 말한다. 클라이언트가 연결 요청을 하면 서버가 연결을 수락한 다음 통신 선로가 고정되고, 모든 데이터는 고정된 통신 선로를 통해 순차적으로 전달되는 것이다. TCP 는 데이터를 보내기 전에 반드시 연결이 형성되어야 하는데 이는 시간이 많이 걸리고, 고정된 통신 선로가 최단선이 아니면 UDP 보다 데이터 전송 속도가 느릴 수 있다는 단점이 있지만 데이터를 정확하고 안정적으로 전달한다는 장점이 있다. Java 에서는 이를 구현하기 위해 java.net.ServerSocketjava.net.Socket 클래스를 제공한다.

 

 

 

TCP 서버는 클라이언트가 연결 요청을 하면 연결을 수락하고, 연결된 클라이언트와 통신한다. 이 때 수락을 하는 것은 ServerSocket 클래스가, 통신은 Socket 클래스가 담당한다. 클라이언트가 연결 요청을 해오면 ServerSocket 이 수락하고 통신용 Socket 을 만드는 것이다.

 

 

클라이언트와 서버가 연결된다면 양쪽의 Socket 객체로부터 각각 InputStream, OutputStream 을 얻을 수 있다. getInputStream(), getOutputStream() 메소드가 사용된다. 상대방에게 데이터를 보내기 위해서는 보낼 데이터를 byte[] 배열로 생성, 이를 매개값으로 OutputStream 의 write() 메소드를 호출한다. 받을 때는 받은 데이터를 저장할 byte 배열을 한 생성하고, 이를 매개값으로 InputStream 의 read() 메소드를 호출한다.

 

 

 

Server

 

public class ServerExample {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress("localhost", 5001));
            while (true) {
                System.out.println("[연결 기다리는 중]");
                Socket socket = serverSocket.accept();
                InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
                System.out.println("[연결 수락]: "+isa.getHostName());

                byte[] bytes = null;
                String message = null;
                InputStream is = socket.getInputStream();
                bytes = new byte[100];
                int readByteCount = is.read(bytes);
                message = new String(bytes, 0, readByteCount, "UTF-8");
                System.out.println("[데이터 받기 성공]: "+message);

                OutputStream os = socket.getOutputStream();
                message = "HELLO CLIENT";
                bytes = message.getBytes("UTF-8");
                os.write(bytes);
                os.flush();
                System.out.println("[데이터 보내기 성공]");

                is.close();
                os.close();
                socket.close();
            }
        } catch (Exception e) {}
        if (!serverSocket.isClosed()) {
            try {
                serverSocket.close();
            } catch (Exception e) {

            }
        }
    }
}

 

 

 

Client

 

public class ClientExample {
    public static void main(String[] args) {
        Socket socket = null;
        try {
            socket = new Socket();
            System.out.println("[연결요청]");
            socket.connect(new InetSocketAddress("localhost", 5001));
            System.out.println("[연결성공]");

            byte[] bytes = null;
            String message = null;

            OutputStream os = socket.getOutputStream();
            message = "HELLO SERVER";
            bytes = message.getBytes("UTF-8");
            os.write(bytes);
            os.flush();
            System.out.println("[데이터 보내기 성공]");

            InputStream is = socket.getInputStream();
            bytes = new byte[100];
            int readByteCount = is.read(bytes);
            message = new String(bytes, 0, readByteCount, "UTF-8");
            System.out.println("[데이터 받기 성공]: "+message);

        } catch (Exception e) {

        }
        if (!socket.isClosed()) {
            try {
                socket.close();
            } catch (Exception e) {}
        }
    }
}

 

 

result (server)

 

 

 

위 코드에서는 Server 에서 우선 ServerSocket 객체를 만들고 5001 포트에 포트 바인딩을 진행한다. 또, 데이터를 받기 위해 InputStreamread() 메소드를 호출하면 상대방이 데이터를 보내기 전까지는 블로킹되는데, read() 메소드가 블로킹 해제되고 리턴되는 경우는 아래 3 가지라고 한다.

 

 

  1. 상대방이 데이터를 보내는 경우 (return: 읽은 바이트 수)
  2. 상대방이 정상적으로 Socket 의 close() 를 호출하는 경우 (return: -1)
  3. 상대방이 비정상적으로 종료하는 경우 (IOException 발생)

 

 

상대방이 정상적으로 Socketclose() 를 호출하고 연결을 끊었을 경우와 비정상적으로 끊었을 경우 모두 예외처리를 통해 Socket 을 close() 해주어야 한다.

 

 

 

 

 

 

Reference:

이것이 자바다