들어가기 전

이번 포스팅에서는 Socket이 무엇인지와 Http와의 차이점을 알아보고 예제를 만들어가면서 Socket에 대해서 알아보겠습니다.

 

 

Socket이란?

 

소켓은 네트워크 통신을 위한 양쪽 끝단을 의미합니다.

IP*Port*의 조합으로 통신을 위한 세션을 설정하고 데이터를 송수신하는 데 사용합니다.

통신을 위해서 서버와 클라이언트는 소켓을 생성하고 생성된 소켓 간에 통신하여 데이터를 송수신을 합니다.
서버 소켓은 하나만 생성할 수 있고 클라이언트 소켓은 여러 개 생성하여 하나의 서버에 연결하여 통신할 수 있습니다.

 

 

IP란?
IP 주소는 인터넷에 연결된 각 기기를 구별하기 위해 사용되는 고유한 번호입니다.
IP는 프로토콜 그 자체이고 IP주소는 IP통신을 하기 위해 각 기기들을 구분하는 고유번호라고 이해하면 됩니다.
IP 주소는 네트워크 내에서 특정 기기를 찾아 데이터를 전송할 수 있도록 도와주며, 기기들이 서로 통신할 때 필수적인 요소입니다.

Port란?
네트워크에서 하나의 IP 주소 안에서 실행되는 여러 프로세스(서비스)를 구별하기 위해 사용되는 논리적 통신 단위입니다.
즉, IP 주소는 컴퓨터(호스트)를 식별 포트 번호는 그 컴퓨터 안의 애플리케이션(프로세스)을 식별하기 위한 논리적 번호입니다.

예시
A동과 B동 아파트가 있고, 각 동에는 1·2·3·4호가 있다고 가정하겠습니다.
여기서 A동과 B동은 IP 주소, 1·2·3·4호는 포트 번호에 해당합니다.
즉, A동 1호, A동 2호처럼 각 세대는 자신의 동과 호수로 들어가 생활합니다.

A동, B동 = IP 주소

→ 각각 하나의 건물(컴퓨터)을 의미합니다.
1, 2, 3, 4호 = 포트 번호
→ 건물 안에서 특정 세대(프로세스/서비스)를 구분하는 번호입니다.

 

Socket 통신

네트워크를 통해 두 프로그램(보통 서버와 클라이언트)이 소켓을 이용하여 데이터를 주고받는 통신방식을 말합니다.

 

 

위와 같은 과정을 통해 서버와 클라이언트가 소켓을 연결을 하고 통신을 합니다.

위 과정을 토대로 정상적으로 통신하는 경우와 그렇지 않은 경우에 대해서 알아보겠습니다.

 

 

정상 통신

클라이언트 소켓

 

1. socket() : 서버에 연결하기 위한 소켓 생성

2. connect() : 연결 요청 대기상태인 서버 소켓에 연결 요청

3. 연결 요청으로 인해 서버 소켓에서 응답이 오면 연결이 되어 Socket descriptor(소켓 자원을 식별하기 위한 번호)가 반환되고 데이터를 송수신할 준비

4. send() / recv() : 연결된 서버 소켓과 데이터 송수신

5. close() : 소켓 연결 종료

 

서버 소켓

 

1. socket() : 클라이언트와 통신하기 위한 준비용 소켓 생성(실제 통신이 이루어지지 않고 통신 대기용 소켓을 만드는 과정)

2. bind() : 생성한 소켓에 대해 Ip와 Port를 할당

3. listen() : 클라이언트의 연결 요청을 대기하는 상태로 전환

4. accept() : 클라이언트 측에서 연결 요청이 오면 수락 후 통신을 위한 소켓을 생성

5. send() / recv() : 연결된 클라이언트 소켓과 데이터 송수신

6. close() : 소켓 연결 종료

 

통신 순서

서버(socket()) -> 서버(bind()) -> 서버(listen()) -> 클라이언트(socket()) -> 클라이언트(connect()) -> 

클라이언트(send() / recv()), 서버(send() / recv()) ->  클라이언트(close()), 서버(close())

 

 

연결 실패

클라이언트 소켓

 

1. socket() : 서버에 연결하기 위한 소켓 생성

2. connect() : 서버 소켓에서 bind 하여 할당받은 ip, port에 연결 요청

3. 서버 측 소켓에서 연결을 거부할 시 요청을 대기큐에 저장 -> 큐의 크기가 커지면 새로운 요청 거절

 

 

서버 소켓

1. socket() : 클라이언트와 통신하기 위한 준비용 소켓 생성(실제 통신이 이루어지지 않고 통신 대기용 소켓을 만드는 과정)

2. 생성한 소켓에 대해 Ip와 Port를 할당

3. listen() : 클라이언트의 연결 요청을 대기하는 상태로 전환

4. accept() : 클라이언트 측 요청 거절

 

통신 순서

서버(socket()) -> 서버(bind()) -> 서버(listen()) -> 클라이언트(socket()) -> 클라이언트(connect()) -> 

서버(요청 거절) ->  요청 대기큐에 저장

 

 

지금까지  Socket에 대해서 알아보았습니다.
Socket 통신과 Http 통신의 차이점에 대해서 간단하게 알아보겠습니다.

 

Socket 통신 VS Http 통신

 

HTTP 통신은 TCP의 3-way handshake를 통해 서버와 클라이언트가 연결된 뒤 요청과 응답을 주고받습니다.

클라이언트가 요청한 데이터에 대해서 서버는 응답을 보내고 연결을 종료하는 단발성 통신 방식을 사용합니다.

 

반면 소켓 통신 역시 Http와 동일하게 TCP 기반이므로 3-way handshake를 통해 서버와 클라이언트가 연결합니다.
그런데 HTTP와 달리 서버가 클라이언트의 데이터를 한 번 처리했다고 해서 연결을 종료하지 않고 연결을 지속적으로 유지하면서 양방향으로 데이터를 계속 주고받을 수 있는 것이 특징입니다.

 

(참고 : 해당 포스팅은 소켓에 대해서 다루는 내용이라 Http, Tcp, 3-way handshake에 대해서는 자세히 알아보지 않습니다.)

 

 

 

소켓과 Http의 차이점에 대해서 알아보았습니다.
이제 Java를 활용하여 소켓을 생성, 연결, 통신하는 방법에 대해서 알아보겠습니다.

 

클라이언트 소켓

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 ClientSocketEx {

    public static void main(String[] args) throws IOException {
        BufferedReader in = null;
        PrintWriter out = null;

        Scanner scanner = new Scanner(System.in);
        try (Socket socket = new Socket("127.0.0.1", 8080);) { // -- 1. socket(), 2. connect()

            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream());

            while (true) {
                System.out.print("전송하기>>> ");
                String outputMessage = scanner.nextLine();
                out.println(outputMessage); // 클라이언트 콘솔에 출력
                out.flush(); // -- 4. send() 서버 소켓에 데이터 전송
                if ("exit".equalsIgnoreCase(outputMessage)) { 
                    break;
                }

                String inputMessage = in.readLine(); // -- 4. recv()
                System.out.println("From Server: " + inputMessage);
                if ("exit".equalsIgnoreCase(inputMessage)) { 
                    break;
                }
            }
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } finally {
            in.close();
            out.close();
            scanner.close(); // -- 5. close()
            System.out.println("서버연결종료");
        }
    }

}

 

  1. Socket socket = new Socket("127.0.0.1", "8080") -> 클라이언트 소켓 생성, 서버 소켓에 연결 요청
  2. out.flush() -> 서버 소켓에 데이터 송신
  3. String inputMessage = in.readLine() -> 서버 소켓으로부터 데이터 수신
  4. try 구문에 선언되어 있는 Socket socket = new Socket("127.0.0.1", "8080") -> try-with-resource 구문으로 인해 try 문 끝나면 자동으로 자원해제하여 소켓 연결 종료

 

 

서버 소켓

 

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 ServerSocketEx {

    public static void main(String[] args) throws IOException {
        BufferedReader receiveMessageReader = null;
        Scanner scanner = new Scanner(System.in);
        PrintWriter out = null;
        Socket acceptSocket = null;
        // 1. socket():준비용 소켓 생성, 2. bind() : ip, port 할당, 3. listen() : 연결 요청을 받기 위한 대기 상태
        try (ServerSocket serverSocket = new ServerSocket(8080)) {

            System.out.println("연결 대기중 ..");
            acceptSocket = serverSocket.accept(); // 4. accept() : 연결 소켓 생성
            System.out.println("클라이언트 연결");
            //5. recv() : 데이터 수신
            receiveMessageReader = new BufferedReader(
                new InputStreamReader(acceptSocket.getInputStream())); //5. recv       
            out = new PrintWriter(acceptSocket.getOutputStream());
            while (true) {
                String receiveMessage = receiveMessageReader.readLine();
                if (receiveMessage.equalsIgnoreCase("exit")) {
                    break;
                }
                System.out.println("receive message: " + receiveMessage);
                System.out.println("[Server] 전송 > ");
                String sendMessage = scanner.nextLine();
                out.println(sendMessage);
                out.flush(); //5. send() : 데이터 송신
                if ("quit".equalsIgnoreCase(sendMessage)) {
                    break;
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            receiveMessageReader.close();
            scanner.close();
            out.close();
            acceptSocket.close(); //6. close() : 연결 종료
        }
    }
}

 

  1. ServerSocket serverSocket = new ServerSocket(8080) -> 서버 준비용 소켓 생성, ip, port 할당, 연결 요청을 받기 위해 대기상태 전환
  2. serverSocket.accept() : 클라이언트 연결 요청 수락 후 연결 소켓 생성
  3. receiveMessageReader = new BufferedReader(new InputStreamReader(acceptSocket.getInputStream())) : 클라이언트 데이터 수신
  4. out.flush : 클라이언트로 데이터 송신
  5. acceptSocket.close() -> 연결용 소켓 종료
  6. try 구문에 선언되어 있는 Socket socket = new ServerSocket("8080") -> try-with-resource 구문으로 인해 try 문 끝나면 자동으로 자원해제하여 소켓 연결 종료