키워드

소켓, 바이트 스트림, 데이터 송수신, HTTP, 클라이언트, 서버, accept ,write, read, close

 

Socket 이란

Socket은 양 끝단에서 바이트 스트림을 통해 데이터를 주고 받는 도구로 인터페이스 역할을 한다. 

소켓은 연결을 맺기 위한 IP와 Port 그리고 어떻게 데이터를 주고 받을지를 약속한 프로토콜로 정의되고, 역할에 따라 Server Socket과 Client Socket으로 구분된다. 

 

HTTP 통신과 Socket 통신의 차이점

데이터를 주고 받으려면 HTTP를 이용한 통신도 있지않은가? 왜 socket 통신이 필요한가?

- HTTP 통신은 단방향 통신이다.

 Client의 요청이 있을 때에만, Server가 응답하여 데이터를 전송하고 곧바로 연결을 종료한다.
서버의 응답에는 응답 코드가 같이 전송되며, 응답 코드와 메시지 바디를 통해 요청한 값을 전달받는다.
( + HTTP 통신은 매번 요청과 응답 사이에 커넥션은 맺고 끊어야하는데, 그 비용이 비싸서 최근에는 Keep Alive 옵션을 통해 일정 기간 동안 Client와 Server의 커넥션을 유지하는 방식의 통신을  한다.)

- Socket 통신은 양방향 통신이다.

Client와 Server가 특정 포트를 통해서 이어져 있기 때문에 양방향 통신이 가능하다. Client의 요청에 따른 응답만을 데이터로 주는 것이 아니라, Client와 Server는 실시간으로 데이터를 주고 받을 수 있다.
실시간 동영상 스트리밍이나 온라인 게임과 같은 경우에 사용한다.

여기까지가 둘의 차이점이고 더 깊게 들어가면 이렇다.

더보기

HTTP도 결국 소켓 통신이다. 소켓은 IP와 Port 를 사용해 만든 통신의 양 끝단인데, TCP 레이어 위에 올라간 HTTP 또한 같은 방식으로 통신한다. HTTP 통신의 내부 구현에서는 소켓을 사용한다. 
하지만, 둘을 구분하는 이유는 한 쪽(Client)에서만 요청을 하고 응답을 하는 웹 통신의 특성상 HTTP가 하나의 중요한 프로토콜로 구분이 되었기 때문이다. 
즉, HTTP 통신은 소켓 통신의 일종이지만, 소켓 통신이 HTTP 통신인 것은 아니다. 

 

소켓 통신의 흐름

Server (서버)

1. Socket()  -  소켓을 생성
2. bind() - 소켓 주소 할당
3. listen()  - 클라이언트 접근 요청에 수신 대기열 생성
4. accpet() -  클라이언트와의 연결 대기
5. 데이터 송수신 후, close()로 소켓 종료

Client (클라이언트)

1. Socket() -  소켓을 생성. 이 때, 통신을 맺을 호스트(서버)의 IP와 Port 할당
2. connet() - 소켓 연결 요청
3. 데이터 송수신 후, close()로 소켓 종료

 

기본적인 소켓 통신 코드

소켓이 뭔지 알았으니 기본적인 소켓 통신 코드를 보자.

  • Server
public class SocketServer {

    private int port = 4545;
    
    public void server(){
        try {

            // 서버 소켓 생성 Port 할당
            ServerSocket serverSocket = new ServerSocket(port);

            // 클라이언트 연결 대기.
            Socket socket = serverSocket.accept();

            // 데이터 송수신 스트림 생성
            InputStream is = socket.getInputStream();
            OutputStream os = socket.getOutputStream();

            // 바이트 배열 생성 후 바이트 배열만큼 read
            byte[] buffer = new byte[1024];
            int bytesRead = is.read(buffer);

            // 만약 InputStream 에서 읽을 값이 없으면 -1 을 반환한다.
            if (bytesRead != -1) {
                // 버퍼 배열 처음부터 받은 데이터 까지로 문자열 생성
                String message = new String(buffer, 0, bytesRead);
                System.out.println("recieved Message from client socket: " + message);

                String responseMessage = "Success.";
                os.write(responseMessage.getBytes());
            }
            //소켓 종료
            socket.close();


        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

 

  • Client

public class SocketClient {

    public void client() {
    
        try {
            // host IP와 Port 할당
            Socket socket = new Socket("127.0.0.1", 4545);

            // 데이터 송수신을 위한 스트림 생성
            InputStream is = socket.getInputStream();
            OutputStream os = socket.getOutputStream();

            String message = "send message ... from client socket";

            // 데이터 송신
            os.write(message.getBytes());

            // 서버로부터의 응답 수신
            byte[] buffer = new byte[1024];
            int bytesRead = is.read(buffer);

            if (bytesRead != -1) {
                String responseMessage = new String(buffer, 0, bytesRead);
                System.out.println("response from server socket: " + responseMessage);
            }

            // 소켓 종료
            socket.close();

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

 

결과

  • Server

서버 소켓 실행 결과

  • Client

클라이언트 소켓 실행 결과

 

정리

가장 앞서 작성했던 키워드 기반으로 정리해보자.
소켓은 양 끝단에서 바이트 스트림을 기반으로 데이터를 송수신 할 수 있도록 하는 도구이다. 데이터를 송수신할 수 있는 방법으로 HTTP통신도 있지만, HTTP는 클라이언트에서 서버로 요청할 때, 요청한 부분에 대한 값을 응답하는 구조로 이루어졌다. 즉, 단방향 통신이다. 소켓 통신은 아이피,포트,프로토콜로 정의되며 연결되어 있으면 양방향으로 데이터 송수신이 가능하다. 소켓은 역할에 따라 클라이언트 소켓, 서버 소켓으로 구분된다.
클라이언트 소켓은 소켓을 생성한 뒤, 서버 소켓으로 연결하여 데이터를 송수신한다.
서버 소켓은 소켓을 생성하고, 클라이언트 측의 연결을 대기하고 연결되면 데이터를 송수신한다. 

바이트 스트림을 기반으로 데이터를 송수신한다.
InputStream 은 바이트의 입력을 처리한다. 보내고자 하는 데이터를 바이트 배열을 이용해서 저장하고 전송하게 되는데..
얼만큼의 데이터를 다뤄야할지 모르는 상태라면, 넉넉하게 배열을 선언하면 다인가? 1000으로 선언한 바이트 배열중에 20만 사용되면 980은 낭비되는 데이터 아닌가? 얼만큼의 데이터가 전송될지 모르는 가변적인 데이터를 보내야할 때는?  

바이트 스트림과 보조 스트림 , 문자 기반 스트림을 활용하여 문제를 해결해야한다. 

2024.02.29 - [JAVA] - [Java] 입출력 스트림(I/O Stream) 쉽게 정리

 

[Java] 입출력 스트림(I/O Stream) 쉽게 정리

키워드 개울, 고속도로, 흐른다, 단방향, 선입선출, 연결, 통신, 바이트 스트림, 보조 스트림 , 스트링 스트림 입출력? 컴퓨터 내부 또는 외부의 장치와 프로그램 간에 데이터를 주고 받는 것. Input

burning-man.tistory.com



'JAVA' 카테고리의 다른 글

[Java] 입출력 스트림(I/O Stream) 쉽게 정리  (0) 2024.02.29

키워드

개울, 고속도로, 흐른다, 단방향, 선입선출, 연결, 통신, 바이트 스트림, 보조 스트림 , 스트링 스트림

스트림 키워드

 

입출력?

컴퓨터 내부 또는 외부의 장치와 프로그램 간에 데이터를 주고 받는 것.

Input/Output 으로 입력/출력을 이야기한다. 줄여서, I/O

 

스트림이란?

자바에서 입출력 스트림은 데이터의 흐름을 다루는데 사용된다. 
각 끝점의 연결 통로이다.

데이터의 흐름을 다룬다
-> 영어로 Stream은 흐르다 라는 뜻의 영어동사 이다. 구글에 Stream을 검색하면 개울 이미지가 나온다. 
-> 개울에는 물이 흐른다. 컴퓨터 세상에는 데이터가 흐른다.

Stream 개울

물은 흐를 때 위에서 아래로 흐른다. -> 흐름의 방향이 정해져있다. -> 스트림도 흐름의 방향이 정해져있다.

스트림의 특징

  • 스트림은 단방향이다. 한쪽 방향으로만 흐를 수 있다.
    ( 고속도로에 한번 올라타면 그대로 한방향으로 가야한다. 역주행할 수 없다.)
  • 하나의 스트림은 하나의 역할을 한다
    ( 고속도로는 목적지로 가기 위해서 주행 차선과 반대 차선 이렇게 나뉘어져있다. 즉, 입력과 출력을 위해서는 각각의 스트림이 필요하다.)
  • 스트림은 선입선출 , FIFO 구조이다.
    First In First Out, 먼저 들어온게 먼저 나간다.
  • 스트림은 여러개가 연결될 수 있다.
    -> 키보드에서 문자를 입력 받는 System.in과 바이트 단위로 읽는 형식을 문자 단위로 변환시켜주는 InputStreamReader를 연결할 수 있다. (new InputStreamReader(System.in))
  • 스트림은 지연될 수 있다.
    -> 입력 스트림이 비어 있으면, 컴퓨터는 읽을 데이터가 없으므로 기다린다.
    -> 출력 스트림이 꽉 차 있으면, 컴퓨터는 빈 공간이 생길 때 까지 기다린다.

 

스트림의 종류

1. 바이트 스트림 (InputStream, OutputStream)

스트림은 바이트 단위로 데이터를 전송하며 입출력 대상에 따라 여러가지의 입출력 스트림이 있다.
기본 클래스로 InputStream과 OutputStream이 있으며, 입출력 대상에 따라 여러 가지 자식들을 가지고 있다.

  • 파일 : FileInputStream, FileOutputStream
  • 메모리 : ByteArrayInputStream, ByteArrayOutputStream
  • 프로세스간의 통신: PipedInputStream, PipeOutputStream
  • 오디오장치: AudioInputStream, AudioOutputStream

1.1. 보조 스트림 

실제 데이터를 주고 받는 스트림은 아니기 때문에, 데이터를 입출력 할 수 있는 기능은 없지만 스트림의 기능을 향상시키거나 새로운 기능을 추가할 수 있다.
-> 아까 스트림의 특징이 기억나나? 스트림은 여러개가 연결될 수 있다. 입출력을 기능을 할 수 있는 스트림과 기능을 향상시키는 보조스트림을 "연결" 하여서 성능을 향상 시켜 사용할 수 있다.

예를 들어, text 파일을 읽기 위해 FileInputStream을 사용할 떄, 입력 성능을 향상시키기 위해 버퍼를 사용하는 보조스트림인 BufferedInputStream을 사용한다.

// 기반스트림을 바탕으로 보조스트림 연결 
BufferedInputStream bis = BufferedInputStream(new FileInputStream("test.txt"))

//보조스트림으로부터 데이터를 읽는다.
bis.read()

실제 입력기능은 바이트 스트림인 FileInputStream 이 수행하고, 보조 스트림인 BufferedInputStream은 버퍼만을 제공한다. 버퍼를 사용한 입출력과 사용하지 않은 입출력은 성능차이가 상당하기 때문에 대부분의 경우에 버퍼를 이용한 보조스트림을 사용한다.

  • 버퍼를 이용한 입출력 성능향상 : BuffredInputStream, BufferedOutputStream
  • 필터를 이용한 입출력 처리 : FilterInputStream, FilterOutputStream
  • 데이터를 객체 단위로 입출력 할 때 사용. 객체의 직렬화 때 사용 : ObjectInputStream, ObjectOutputStream

2. 문자기반 스트림 (Reader, Writer)

컴퓨터의 모든 데이터는 바이트 단위 데이터로 구성되어있다. -> 데이터의 입출력은 바이트 단위로 저장된다.
InputStream 또한 바이트 단위로 데이터를 보낸다. 입력 메소드인 read()는 1 바이트 단위로 읽어들인다.
1바이트 이상의 데이터는 나머지는 읽지않고 스트림에 남아잇다.

자바에서는 한 문자를 의미하는 char 형이 1byte가 아니라 2byte 이다. 한글은 3byte다. 
1byte만 인식하면, 정상적인 값을 전달받을 수 없게 된다. 
문자를 온전히 받기 위해서 InputStreamReader. 즉, 문자기반 스트림이 필요하다.

정리

가장 앞서 작성했던 키워드를 기반으로 정리를 해보자.
스트림은 데이터의 흐름이다. 개울의 물이 흐르는것 처럼 데이터 또한 스트림을 통해 흐른다. 또 스트림은 고속도로처럼 주행 차선과 반대 차선이 나눠져 있다. 흐름은 단방향으로만 갈 수 있기 때문에, 입력과 출력 각각 다른 스트림이 필요하다.

통신은 기본적으로 바이트 단위로 데이터를 주고 받는다. 바이트를 입출력 받기 위해서는 바이트 스트림이 필요하고, 바이트 스트림에 추가적인 성능 향상이나 기능 추가를 위해서는 보조스트림을 연결 한다. 바이트 스트림은 1 바이트 단위로 데이터를 주고 받기 때문에, 문자 데이터는 정상적으로 값을 받을 수 없다.그래서 값을 정상적으로 인식하기 위해 문자기반 스트림을 이용한다. 

+ Recent posts