소켓(Socket)

이경환·2023년 12월 7일

네트워크

목록 보기
4/5

들어가기

Was 구현을 하면서 Java Socket을 통해 TCP/IP 기반 네트워크 통신을 한 경험과 채팅 프로그램을 구현하며 Web Socket을 사용하면서 문득 소켓의 본질에 대해서는 깊게 학습 해 본 경험이 없다는 생각이 들었습니다.

결국은 Socket이라는 것을 통해 클라이언트와 서버가 연결 후 데이터를 주고받을 텐데 그 과정에서 HTTP 프로토콜이나 TCP등은 학습했지만, Socket의 본질에 대해서는 잘 알지 못해 이번 기회에 정리해 봤습니다.

소켓

소켓이란 무엇일까? 소켓의 본질이 무엇인가? 대답을 알기 전에 일단 파일 이란 무엇일지 생각해 봅시다.
1.2차 메모리(SSD, HDD, CD, 자기테이프, 광디스크) 에 저장된 단위 데이터 덩어리를 의미하는 파일
2.장치에 대한 추상화된 인터페이스를 제공하는 장치파일

결론부터 말하자면 소켓은 파일입니다.

파일에 대한 접근(Process가 접근) I/O를 한다고 가정한다면 OS 커널에 구현되어 있는 프로토콜 요소에 대한 추상화된 인터페이스를 Socket이라고 합니다.
말이 참 어렵습니다 밑에서 더 자세히 얘기해 보겠습니다.

ex) 어떠한 File에 연결된 장치가 만약 블루투스라면 이런 파일은 블루투스 Socket이라고 볼 수 있다.

  • 소켓은 파일입니다. 파일(대상체)에 대한 I/O의 주체는 Process가 됩니다.
    파일은 기본적으로 Open, Create, Close, Delete, Read ,Write를 할 수 있습니다.

  • 이때 Process가 읽는 파일이 Tcp 스택에 대한 추상화된 인터페이스를 제공한다면 이 파일을 Tcp Socket이라고 합니다.

    • 여기서 말하는 Socket 프로그래밍이란 Tcp Socket에 대한 I/O를 다루는 것을 말합니다.(정확히는 Tcp Socket 프로그래밍)
    • 여기서 Tcp Socket 에서는 위의 파일을 다루는 방법을 다르게 부릅니다.
      • Read -> Receive
      • Write -> Send

Socket(File)에 Read하면 2차 메모리에 데이터를 저장하는 일이 일어나는 것이 아니라 NIC을 움직여 정보를 송신합니다. 두 경우 모두 'Read' 입니다. 다만 소켓에서는 Read 대신 'Receive'이라는 표현으로 바꾼 것입니다.

  • File이라는 것은 Stream을 다룹니다. 결국 Tcp Socket은 파일이기 때문에
    패킷의 단위를 이어서(Stream) 다룹니다.

    퍼즐 하나가 패킷이면 퍼즐의 연속은 Stream이라고 보면된다.
    L4의 data 단위 segment, L3는 packet

결론

흔히 말하는 소켓 "네트워크 소켓"은 File이고 여기서 OS 커널에 구현되어 있는 TCP 프로토콜을 다루는 요소의 추상화된 인터페이스를 TCP/IP Socket 소켓이라고 합니다.

TCP/IP 소켓 프로그래밍

좀 더 나아가 클라이언트와 서버 관점에서 소켓을 바라보겠습니다.

클라이언트와 서버가 소통하기 위해서는 클라이언트가 서버 쪽에 요청해야 하고 서버는 요청을 처리해야 합니다. 이때 요청을 보내는 쪽 소켓을 클라이언트 소켓(Client Socket) 후자에 사용되는 소켓을 서버 소켓(Server Socket)이라고 합니다.
여기서 주의할 것이

서버는 하드웨어나 OS 부분은 클라이언트와 다르더라도 결국 네트워크에 관한 부분( LAN 어댑터), 프로토콜 스택, Soket라이브러리 등의 기능은 클라이언트와 조금도 다르지 않습니다.
소켓을 클라이언트 소켓(Client Socket)과 서버 소켓(Server Socket)으로 본다면 이 둘의 소켓은 별개의 소켓이 아닌 같은 소켓입니다. 역할에 따라 다른 API를 호출하거나 순서가 다를 뿐입니다.

또한 주의할 것이 소켓 연결이 완료된 다음 클라이언트 소켓과 서버 소켓이 직접 데이터를 주고받지 않습니다. 서버 소켓은 클라이언트 소켓의 연결 요청을 받아들이는 역할만 수행할 뿐, 직접적인 데이터 송수신은 서버 소켓의 연결 요청 수락의 결과로 만들어지는 새로운 소켓을 통해 처리됩니다.

서버는 네트워크상에서 항상 대기하며, 클라이언트의 요청을 받으면 해당 요청에 대한 응답을 생성하여 클라이언트에게 보냅니다.

클라이언트는 사용자가 서버에게 어떤 동작을 요청하는 것으로, 요청을 생성하고 서버에 전송하여 결과를 받는 역할을 합니다. 클라이언트는 서버로부터 받은 응답을 해석하고 필요에 따라 사용자에게 표시하는 역할을 담당합니다.

API 실행 순서

클라이언트 : socket() 생성 → connect() 연결요청 → (send/recv) 데이터 송수신 → close() 연결종료

서버 : socket() 생성 → bind() 소켓 주소할당 → listen() 연결요청 대기상태 → accept() 연결허용 → (send/recv)데이터 송수신 → close() 연결종료

  • bind란 서버가 사용할 IP 주소와 포트 번호를 생성한 소켓에 결합시키는 것입니다.

클라이언트 소켓

여기서 주의할 것이 send/recv 과정에서 API가 모두 블록(Block) 방식으로 동작합니다. 두 API 모두 실행 결과(성공, 실패, 종료)가 결정되기 전까지는 API가 반환되지 않습니다.

recv는 실행 결과(성공, 실패, 종료)가 결정되기 전까지 실행이 종료되지 않기 때문에
별도의 스레드에서 실행됩니다.

서버는 listen() 호출 이후부터 연결요청 대기 큐를 만들어 놓고, 그 이후에 클라이언트가 연결 요청을 할 수 있다. 이때 서버가 바로 accept()를 호출할 수 있는데, 연결되기 전까지 호출된 위치에서 블로킹 상태에 놓이게 됩니다.

참고로 TCP는 흐름제어와 혼잡 제어를 지원해서 데이터 순서를 보장해 줍니다.

서버의 수신 동작

클라이언트보다 서버는 비교적 복잡하니 자세히 알아보겠습니다.
먼저 Layer 단위의 동작부터 알아보겠습니다.

Layer 3 -> Layer 4

  • IP 헤더를 조사해서 나에게 오는 패킷인지 확인합니다. 헤더에는 목적지 IP 주소가 포함되어 있습니다. 이를 통해 패킷이 수신자의 IP 주소와 일치하는지 확인합니다.
  • fragmentation 필드를 통해 패킷이 분할되었는지 여부와 각 분할된 패킷의 오프셋 등의 정보를 확인할 수 있습니다. 된 패킷인지 판단한 후 L4에 전달합니다.

Layer 4 -> 애플리케이션

  • 목적지 포트 번호를 확인하여 해당 프로토콜을 식별하고, 패킷을 적절한 애플리케이션에 전달합니다.

서버 소켓

클라이언트에는 없는 bind()와 listen() accept()에 대해 알아보겠습니다.

  • bind()는 "소켓(Socket)과 포트 번호를 결합(bind)합니다" 소켓은 시스템이 관리하는 포트 중 하나의 포트를 사용하게 되는데 다른 포트 번호와 충돌이 나면 안 되기 때문에 bind() 함수를 통해 OS에 지정된 포트를 사용하겠다고 요청합니다.

  • listen()은 서버 소켓에 바인딩 된 포트 번호를 클라이언트가 connect() 연결 요청이 수신되면 종료됩니다. 에러가 발생하는 경우도 종료됩니다.

    listen() API가 성공한 경우라도, 리턴 값에 클라이언트의 요청에 대한 정보는 들어 있지 않습니다. listen()의 리턴 값으로 판단할 수 있는 것은 클라이언트 연결 요청이 수신되었는지(SUCCESS), 그렇지 않고 에러가 발생했는지(FAIL) 뿐입니다.

    클라이언트 연결 요청에 대한 정보는 시스템 내부적으로 관리되는 큐(Queue)에서 쌓이게 됩니다. 연결을 완료하기 위해서는 대기 큐에서 accept()를 호출합니다.

  • accept() 연결 요청을 받아들여(accept) 소켓 간 연결을 수립합니다.
    주의할 점은 위에서 말했듯이 데이터 통신을 위해 연결되는 소켓이, 앞서 bind() 또는 listen() API에서 사용한 서버 소켓이 아니라는 것입니다. 데이터를 통신하기 위한 소켓은 accept API 내부에서 새로 만들어지는 소켓입니다

    bind() 및 listen()을 통한 바인딩 후 대기 큐를 생성하여 클라이언트에 대기 중인 상태일 때 accept는 데이터 송수신을 위한 새로운 소켓(Socket)을 만들고 서버 소켓의 대기 큐에 쌓여있는 첫 번째 연결 요청을 매핑시킵니다. 이후에는 다시 listen 하거나 close 합니다.

정리

이번 시간으로 지금까지 고도로 추상화된 함수나 네트워크를 이용하면서 몰랐던 지식을 좀 더 낮은 레벨의 네트워크, cs를 통해 많이 배울 수 있는 시간이었습니다.

결국에는 WAS를 구현하며 Socket을 생성해 클라이언트와 연결해 HTTP Response를 보내주는 과정도 이러한 과정이 진행되고 Java에서는 이런 과정을 어떻게 추상화했는지 궁금해지는 시간이었습니다.

참고자료 :
https://engineering.linecorp.com/ko/blog/reactive-streams-with-armeria-1
https://recipes4dev.tistory.com/153
https://www.ibm.com/docs/ko/i/7.3?topic=programming-how-sockets-work

profile
개선하는 개발자, 이경환입니다

0개의 댓글