#3. 소켓 프로그래밍

게임 서버 프로그래밍/게임 서버 프로그래밍 교과서

2020. 2. 17. 16:01

3.소켓 프로그래밍

소켓 프로그래밍


1. 블로킹 소켓
  • 블로킹

    디바이스에 처리 요청을 걸어 놓고 응답을 대기하는 함수를 호출할 때

    스레드에서 발생하는 대기 현상

  • 블로킹이 발생하는 스레드에서는 CPU 연산을 하지 않는다

    CPU 사용량이 0% , 스레드는 Waitable 상태

  • 기록하려는 데이터가 디스크에 완전히 기록될 때까지 Waitable 상태를

    유지한다

2. 블로킹과 소켓 버퍼
  • 송신버퍼

    • 일련의 바이트 배열
    • 크기는 고정되어 있으나, 마음대로 크기를 변경할 수 있음
    • FIFO 형태로 작동
    • 송신버퍼가 가득 차 있을 때 푸시하면 블로킹 발생
    • 송신버퍼는 디폴트로 수천 바이트를 담을수 있다
3. 네트워크 연결받기 및 수신
  • 수신할 수 있는 데이터가 없으면 블로킹이 일어난다
4. 수신 버퍼가 가득 차면 발생하는 현상
  • 수신 버퍼에서 데이터를 꺼내는 속도가 수신 버퍼의 데이터를 채우는 속도보다

    느릴 경우

    • TCP인 경우 연결이 끊어지지는 않는다. 단지 실제 송신속도가 느린 쪽에 맞추어 작동할 뿐이다
    • UDP인 경우 데이터그램이 그냥 버려진다. 송신함수에 블로킹이 발생하지 않는다
  • 라우터에 연결된 한곳 A에서 도착하는 패킷이 압도적으로 많으면 A이외의

    다른 곳들은 네트워크 경쟁에서 밀리게 된다

    • TCP는 송신자측 운영체제가 알아서 초당 송신량을 줄여 다른 네트워킹이 경쟁에서 밀리지 않는다

    • UDP는 속도 제한 없이 마구 송신하면 주변 네트워킹이 경쟁에서 밀린다

      주변 네트워킹이 두절되기도 하는데 이른 혼잡 현상이라 한다.

5. 논블록 소켓
  • 네트워킹 해야하는 대상이 여럿일 때

    • 네트워킹 대상 개수만큼 스레드를 만든다

      • 네트워킹 대상이 많으면 스레드 간에 컨텍스트 스위치가 대량 발생

        자원낭비로 이어짐

    • 운영체제의 API 사용

  • 논블록 소켓 API 사용법

    • 소켓을 논블록 소켓으로 전환
    • 송신,수신,연결과 관련된 함수 호출
    • 무조건 이 함수 호출에 대해 즉시 리턴
    • 리턴 값은 성공 혹은 Would block
  • Would block

    블로킹이 걸릴 상황이었다는걸 말해준다

  • 논블록 소켓을 사용하면 한 스레드에서 여러 소켓을 한꺼번에 다룰 수 있다

  • 논블록 소켓을 사용하면 블로킹이 난무하는 문제가 사라진다

    • 많은 수의 소켓 데이터를 지연 시간 없이 처리할 수 있다
  • Connect() 함수가 would block이 리턴된 경우

    • 0바이트 송신을 이용해 would block이 끝났는지를 확인
  • 0바이트 송신

    TCP는 스트림 기반 프로토콜이기 때문에 0바이트를 보내는 것은 사실상 아무것도 하지 않는 셈

  • select / poll

    • 송신 버퍼에 빈 공간이 생기거나 수신 버퍼에 뭔가가 들어온다면 그 상황을 알려주는 함수이다

    • 소켓에 I/O 처리가 가능한 소켓이 하나라도 있을 경우 즉시 리턴하고 그렇지 않은 경우 타임아웃 시간만큼 기다린다

    • I/O 가능

      해당 소켓에 대해 소켓 함수를 호출하면 would block이 아닌 다른결과가 나온다는 의미

6. Overlapped I/O 혹은 비동기 I/O
  • 논블록 소켓의 장점

    • 중도 취소 같은 통제가 가능
    • 소켓을 여러 개 다룰 수 있음
    • 연산량이 낭비되지 않음
    • 호출 스택 메모리도 낭비되지 않음
  • 논블록 소켓의 단점

    • 리턴한 코드가 would block인 경우 헛발질을 한다

      • TCP의 send, receive UDP의 receive는 문제가 없다

      • UDP인 경우 Send() 처리에서 문제가 있다

        • TCP처럼 데이터의 일부만 보낼 수 없으므로 송신 버퍼에 데이터 전체가 들어갈 공간이 없으면 Would block이 된다
        • I/O 가능(송신버퍼에 1byte라도 자리가 있음)이지만 데이터 그램의 크기가 1byte를 넘긴다면 쓸데없이 연산량을 늘리는 셈(I/O가능이니 재시도를 계속하게 됨)
    • 입력하는 데이터 블록에 대한 복사 연산이 발생

      • CPU 안의 캐시에 없는 데이터를 엑세스할 때 RAM을 엑세스하는데 이 속도는 굉장히 느리다
    • sned()함수나 receive()함수는 재시도 호출해야하는 API가 일관되지 않는다는 문제

  • Overlapped, 비동기 I/0

    • 논블록 소켓의 단점을 모두 해결해 준다

    • I/O처리를 스레드가 하는 것이아니라 Device Driver에 권한을 넘김으로써 cpu가 놀지 않고 다른일을 할 수 있게 해준다

    • 중첩의 의미

      우리의 코드가 무언가를 하고 있을 때 이와 별개로 운영체제가 마음대로 작업하던 데이터를 건드림

    • Overlapped I/O 전용 함수가 비동기로 하는일이 완료될 때 까지는 소켓 API에 인자로 넘긴 데이터 블록을 제거하거나 내용을 변경해서는 안된다

    • Overlapped I/O 전용 송수신 함수를 호출하면 운영체제는 송신할 데이터가 저장되어 있는 메모리 블록 자체를 송신 버퍼로 사용해 버린다

    • 수신할 떄도 마찬가지이며 복사가 생기지 않는다

    • 윈도우에서만 제공해준다

  • Overlapped 비동기 I/O와 Non-Blocked Socket의 차이점

    • I/O 처리를 비동기로 하여 복사하는 작업 동안에 Block이 되지 않는다

    • I/O 처리를 순서대로 하지 않는다.(디스크에 가까운 순서대로 처리)

    • ZERO-COPY를 통해 소켓 버퍼로의 복사를 건너 뛰고 바로 유저 버퍼에 데이터를 복사한다

      따라서 작업하는 동안에 유저 블록을 터치해서는 안되는 것.

    • 너무 많은 양의 I/O가 생길 경우 에러를 발생시킨다.

  • 리액터 패턴

    • 상태 확인 후 무언가를 한다
    • 논블록 소켓
    • I/O를 시도한다(성공할 수도 실패할 수도 있다)
    • 실패할 때는 I/O 가능을 기다린 후 I/O를 재시도한다
    • 성공할 때는 상황을 종료한다
  • 프로액터 패턴

    • 무조건 저지른 후 결과를 확인한다
  • Overlapped I/O (비동기)

    • I/O를 시행(무조건 성공)한다
    • I/O 완료를 기다린다
    • 상황을 종료한다
  • 프로액터는 이벤트에 명령을 내리는것. 리액터는 이벤트에 수행 가능하다는 것을 알리는 것

7. epoll
  • 소켓이 I/O 가능 상태가 되면 이를 감지해서 사용자에게 알림을 해주는 역할을 한다
  • 소켓이 1만 개라고 하더라도 이 중에서 I/O 가능이된 것들만 epoll을 이용해서 바로 얻을 수 있다
  • 리눅스와 안드로이드에서만 사용 가능
  • 스레드 풀 구현이 어렵다
8. IOCP
  • 소켓의 Overlapped I/O가 완료되면 이를 감지해서 사용자에게 알려 주는 역할을 한다
  • 소켓 개수가 1만 개라고 하더라도 이 중에서 I/O가 완료된 것들만 IOCP를 이용해서 바로 얻을 수 있기 때문에 모든 소켓에서 루프를 돌지 않아도 된다
  • 윈도우에서만 사용 가능하다
  • 스레드 풀을 쉽게 구현할 수 있다