[Linux] C++ 프로젝트 실습-실습

주황색

일반적인 IO(네트워크 IO)의 두 단계

I와 O는 각각 입력과 출력을 나타냅니다. 네트워크 통신은 실제로 커널의 버퍼인 네트워크 소켓을 통해 구현됩니다.

네트워크 IO는 데이터 준비와 데이터 읽기 및 쓰기의 두 단계로 구분됩니다(이 두 개념은 다르므로 혼동할 수 없음).

데이터 준비: 시스템 IO 작업의 준비 상태에 따라 다음과 같이 구분됩니다.阻塞、非阻塞

데이터 읽기 및 쓰기: 커널과 애플리케이션 간의 상호 작용 모드에 따라 다음과 같이 구분됩니다.同步、异步

IO를 처리할 때 차단과 비차단이 모두 동기식이며, 특수 API를 호출할 때만 비동기식입니다.

여기에 이미지 설명을 삽입하세요.
요약:
     기존 네트워크 IO는 데이터 준비와 데이터 읽기 및 쓰기의 두 단계로 나뉩니다. 데이터 준비 상태는 차단과 비차단으로 구분됩니다. Blocking은 IO 메소드를 호출하는 것을 의미하며 스레드는 Blocking 상태(데이터가 도착하지 않을 때 반환하지 않고 블록하고 데이터가 도착할 때까지 대기), Non-Blocking(데이터가 도착하지 않아도 반환, 반환 값)은 스레드의 상태를 변경하지 않으며 반환 값으로 판단됩니다. Read인지 Recv인지에 관계없이 반환 값이 -1이면 반환 상태를 기준으로 버퍼에 데이터가 없는지, 신호에 의해 부드럽게 중단되는지, 다시 호출할지 여부를 판단합니다. .

     데이터 읽기와 쓰기는 동기식과 비동기식으로 구분되는데, 소켓은 커널 내부의 버퍼라는 것을 알고 있다. .다른 것들. 비동기식을 사용하면 커널이 데이터 읽기 및 쓰기 작업을 완료하는 데 도움을 줄 수 있습니다. 데이터 읽기 및 쓰기가 완료되면 알림 메서드를 통해 애플리케이션에 알림이 전달됩니다. 이러한 방식으로 커널이 데이터를 읽는 동안 애플리케이션은 다른 작업을 수행할 수 있습니다. , 데이터 읽기 및 쓰기가 완료되면 데이터 읽기 및 쓰기를 처리할 수 있으며 일반적으로 파일 설명자(소켓), buf(데이터용 컨테이너) 및 알림 방법(sigio signal)을 사용해야 합니다. 특수 API 호출을 통해 비동기식으로 aio_read aio_write

     동기식 방법은 비동기식 방법보다 효율성이 낮고 비동기식 방법은 구현하기가 더 복잡합니다.

Linux의 5가지 IO 모델

1. 차단

호출자는 함수를 호출하고 함수가 반환될 때까지 기다립니다. 이 기간 동안 그는 아무것도 하지 않고 함수가 반환되었는지 계속 확인합니다. 다음 작업을 수행하기 전에 함수가 반환될 때까지 기다려야 합니다. 스레드(또는 프로세스)는 다음과 같습니다. 차단되어 CPU를 소비하지 않지만
여기에 이미지 설명을 삽입하세요.
스레드(프로세스)는 다른 작업을 수행할 수 없습니다.

2. 논블로킹

비차단 대기, 가끔씩 IO 이벤트가 준비되었는지 확인합니다. 다른 일을 할 준비를 하지 마십시오. Non-Blocking I/O 실행 시스템 호출은 이벤트 발생 여부와 관계없이 항상 즉시 반환하며, 이벤트가 발생하지 않은 경우 -1을 반환하는데, 이때 errno를 기준으로 두 가지 상황을 구분할 수 있다. recv 및 send, 이벤트가 발생하지 않았습니다. errno는 일반적으로 EAGAIN으로 설정됩니다.

여기에 이미지 설명을 삽입하세요.
일반적으로 while polling 방식을 사용하는데, 버퍼에 데이터가 도착하지 않으면 다른 작업을 수행할 수 있다. 상대적으로 시스템 리소스를 소비합니다. 비차단은 fcntl을 통해 설정할 수 있습니다. 예를 들어 데이터를 보낼 클라이언트가 10,000개 있고 각 주기에는 10,000개의 시스템 호출이 있습니다.

3. IO 재사용

Linux는 IO 다중화 모델을 구현하기 위해 select/poll/epoll 기능을 사용합니다. 이러한 기능은 프로세스도 차단하지만 IO 차단과의 차이점은 이러한 기능이 동시에 여러 IO 작업을 차단할 수 있다는 것입니다. 또한 여러 읽기 작업과 쓰기 작업의 IO 기능을 동시에 감지할 수 있습니다. lO 연산 함수는 읽거나 쓸 데이터가 있을 때까지 실제로 호출되지 않습니다.
여기에 이미지 설명을 삽입하세요.
여러 IO를 동시에 감지할 수 있으며 모니터링 작업은 일반적으로 커널에 맡겨집니다. 데이터가 도착하면 애플리케이션은 데이터를 읽고 쓰기 위해 read를 호출합니다. 이는 Non-Blocking 및 Busy Polling 방식을 개선한 것으로 이해될 수 있습니다. 하나의 스레드 또는 프로세스에서 여러 클라이언트를 동시에 감지할 수 있습니다.

4. 신호 드라이버

Linux는 신호 구동 IO를 위해 소켓을 사용하고 신호 처리 기능을 설치하며 프로세스는 차단 없이 계속 실행되며 IO 이벤트가 준비되면 프로세스는 SIGIO 신호를 수신한 후 IO 이벤트를 처리합니다.
여기에 이미지 설명을 삽입하세요.
SIGIO 신호를 통해 처리되며 데이터가 준비되면 신호를 보낸 다음 신호를 캡처하고 read를 호출하여 데이터를 읽고 씁니다.

커널은 첫 번째 단계에서는 비동기식이고 두 번째 단계에서는 동기식입니다. 비차단 IO와의 차이점은 메시지 알림 메커니즘을 제공하므로 사용자 프로세스가 지속적으로 폴링하고 확인할 필요가 없으므로 시스템 API 수가 줄어듭니다. 통화 및 효율성 향상.

질문: IO 멀티플렉싱과 신호 드라이버의 차이점은 무엇입니까? 데이터가 준비되면 버퍼의 데이터를 처리하도록 프로그램에 알리지 않습니까?
답변: 멀티플렉싱은 커널이 여러 파일 설명자를 모니터링하고 선택과 같은 모니터링되는 기능을 차단하는 것을 의미합니다. 데이터 복사도 차단됩니다. 멀티플렉싱은 특정 IO 이벤트를 차단한 후 프로세스가 제때에 다른 IO를 처리하지 못하도록 방지합니다. 신호 드라이버는 먼저 신호 처리 기능을 등록하고, 데이터가 준비되면 커널은 처리를 위해 프로세스에 신호를 보냅니다. 신호 드라이버는 데이터 준비 프로세스에서는 차단하지 않지만 데이터 복사에서는 차단하므로 둘 다 동기식 IO입니다.

5. 비동기식

Linux에서는 aio_read 함수를 호출하여 디스크립터 버퍼 포인터와 버퍼 크기, 파일 오프셋 및 알림 방법을 커널에 알려주고 즉시 반환할 수 있으며, 커널이 데이터를 버퍼에 복사하면 애플리케이션에 알립니다.
여기에 이미지 설명을 삽입하세요.
커널은 모니터링과 읽기 및 쓰기 작업을 완료하고, 데이터 읽기 및 쓰기가 완료되면 응용 프로그램에 데이터를 처리하라고 알립니다.

/* Asynchronous I/O control block. */
struct aiocb
{
    
    
int aio_fildes; /* File desriptor. */
int aio_lio_opcode; /* Operation to be performed. */
int aio_reqprio; /* Request priority offset. */
volatile void *aio_buf; /* Location of buffer. */
size_t aio_nbytes; /* Length of transfer. */
struct sigevent aio_sigevent; /* Signal number and value. */
/* Internal members. */
struct aiocb *__next_prio;
int __abs_prio;
int __policy;
int __error_code;
__ssize_t __return_value;
#ifndef __USE_FILE_OFFSET64
__off_t aio_offset; /* File offset. */
char __pad[sizeof (__off64_t) - sizeof (__off_t)];
#else
__off64_t aio_offset; /* File offset. */
#endif
char __glibc_reserved[32];
};

웹 서버

웹 서버는 서버 소프트웨어(프로그램) 또는 서버 소프트웨어를 실행하는 하드웨어(컴퓨터)입니다. 주요 기능은 HTTP 프로토콜을 통해 클라이언트(일반적으로 브라우저)와 통신하여 클라이언트로부터 HTTP 요청을 수신, 저장 및 처리하고, 요청에 대해 HTTP 응답을 하고, 요청된 정보를 클라이언트에 반환하는 것입니다. , 웹 페이지 등) 또는 오류 메시지를 반환합니다.
여기에 이미지 설명을 삽입하세요.
일반적으로 사용자는 웹 브라우저를 사용하여 해당 서버와 통신합니다. 브라우저에 "도메인 이름" 또는 "P 주소: 포트 번호"를 입력하면 브라우저는 먼저 도메인 이름을 해당 IP 주소로 확인하거나 IP 주소를 기반으로 해당 웹 서버에 직접 HTTP 요청을 보냅니다. 이 프로세스는 먼저 TCP 프로토콜의 3방향 핸드셰이크를 통해 대상 웹 서버와 연결을 설정한 다음 HTTP 프로토콜이 대상 웹 서버에 대한 HTTP 요청 메시지를 생성하고 이를 TCP, IP를 통해 대상 웹 서버로 전송합니다. 그리고 다른 프로토콜.

HTTP 프로토콜(애플리케이션 계층 프로토콜)

소개

HTTP(Hypertext Transfer Protocol)는 일반적으로 TCP 위에서 실행되는 간단한 요청-응답 프로토콜입니다. 클라이언트가 서버에 보낼 수 있는 메시지 종류와 서버가 받는 응답 종류를 지정합니다. 요청 및 응답 메시지의 헤더는 ASCII 형식으로 제공되며 메시지 내용은 MIME 형식입니다. HTTP는 World Wide Web에서의 데이터 통신의 기초입니다.

개요

HTTP는 클라이언트 터미널(사용자)과 서버측(웹사이트) 요청 및 응답(TCP)에 대한 표준입니다. 클라이언트는 웹 브라우저, 웹 크롤러 또는 기타 도구를 사용하여 서버의 지정된 포트(기본 포트는 80)에 대한 HTTP 요청을 시작합니다. 우리는 이 클라이언트를 사용자 에이전트라고 부릅니다. 응답 서버는 HTML 파일, 이미지 등의 리소스를 저장합니다. 우리는 이 응답 서버를 원본 서버라고 부릅니다. 사용자 에이전트와 원본 서버 사이에는 프록시 서버, 게이트웨이 또는 터널과 같은 여러 "중간 계층"이 있을 수 있습니다.

TCP/IP 프로토콜은 인터넷에서 가장 널리 사용되는 응용 프로그램이지만 HTTP 프로토콜에서 이를 사용하거나 지원하는 계층을 사용해야 한다는 요구 사항은 없습니다. 실제로 HTTP는 모든 인터넷 프로토콜이나 다른 네트워크를 통해 구현될 수 있습니다. HTTP는 기본 프로토콜이 안정적인 전송을 제공한다고 가정합니다. 따라서 그러한 보장을 제공할 수 있는 모든 프로토콜을 사용할 수 있습니다. 따라서 TCPIP 프로토콜 제품군의 전송 계층으로 TCP를 사용합니다.

일반적으로 HTTP 클라이언트는 서버의 지정된 포트(기본값은 포트 80)에 대한 TCP 연결을 생성하라는 요청을 시작합니다. HTTP 서버는 해당 포트에서 클라이언트 요청을 수신합니다. 요청이 수신되면 서버는 "HTTP/1.1 200 OK"와 같은 상태와 요청된 파일, 오류 메시지 또는 기타 정보와 같은 반환된 콘텐츠를 클라이언트에 반환합니다.

작동 원리

HTTP 프로토콜은 웹 클라이언트가 웹 서버에서 웹 페이지를 요청하는 방법과 서버가 웹 페이지를 클라이언트에 전달하는 방법을 정의합니다. HTTP 프로토콜은 요청/응답 모델을 사용합니다. 클라이언트는 서버에 요청 메시지를 보냅니다. 요청 메시지에는 요청 방법, URL, 프로토콜 버전, 요청 헤더 및 요청 데이터가 포함됩니다. 서버는 프로토콜 버전, 성공 또는 오류 코드, 서버 정보, 응답 헤더 및 응답 데이터가 포함된 상태 줄로 응답합니다.

HTTP 요청/응답 단계는 다음과 같습니다.

  1. 웹 서버에 대한 클라이언트 연결 일반적으로 브라우저인 HTTP 클라이언트는 웹 서버의 HTTP 포트(기본값은 80)와 TCP 소켓 연결을 설정합니다. 예를 들어 http://www.baidu.com입니다.
  2. HTTP 요청을 보내기 위해 클라이언트는 TCP 소켓을 통해 웹 서버에 텍스트 요청 메시지를 보냅니다. 요청 메시지는 요청 라인, 요청 헤더, 빈 라인, 요청 데이터의 네 부분으로 구성됩니다.
  3. 서버는 요청을 수락하고 HTTP 응답을 반환하며, 웹 서버는 요청을 구문 분석하고 요청된 리소스를 찾습니다. 서버는 클라이언트가 읽는 TCP 소켓에 리소스 복사본을 씁니다. 응답은 상태 줄, 응답 헤더, 빈 줄, 응답 데이터의 4개 부분으로 구성됩니다.
  4. TCP 연결을 해제합니다. 연결 모드가 닫혀 있으면 서버는 능동적으로 TCP 연결을 닫고, 클라이언트는 수동적으로 연결을 닫고 TCP 연결을 해제합니다. 연결 모드가 keepalive이면 일정 시간 동안 연결이 유지됩니다. , 이 시간 동안에도 요청을 계속 받을 수 있습니다. ;
  5. 클라이언트 브라우저는 HTML 콘텐츠를 구문 분석합니다. 클라이언트 브라우저는 먼저 상태 줄을 구문 분석하여 요청이 성공했는지 여부를 나타내는 상태 코드를 확인합니다. 그런 다음 각 응답 헤더가 구문 분석되고 응답 헤더는 바이트 수인 다음 HTML 문서와 문서의 문자 집합을 알려줍니다. 클라이언트 브라우저는 응답 데이터 HTML을 읽고 HTML 구문에 따라 형식을 지정하여 브라우저 창에 표시합니다.

예를 들어, 브라우저 주소 표시줄에 URL을 입력하고 Enter 키를 누르면 다음 프로세스를 거치게 됩니다.

  1. 브라우저는 DNS 서버에 URL의 도메인 이름에 해당하는 IP 주소를 확인하도록 요청합니다.
  2. IP 주소를 분석한 후 IP 주소와 기본 포트 80을 기반으로 서버와의 TCP 연결을 설정합니다.
  3. 브라우저는 파일(URL에서 도메인 이름 다음 부분에 해당하는 파일)을 읽기 위해 HTTP 요청을 발행하고 요청 메시지는 TCP 3방향 핸드셰이크의 세 번째 메시지 데이터로 서버에 전송됩니다.
  4. 서버는 브라우저 요청에 응답하고 해당 HTML 텍스트를 브라우저로 보냅니다.
  5. TCP 연결을 해제합니다.
  6. 브라우저는 이 HTML 텍스트를 가져와 콘텐츠를 표시합니다.

HTTP 요청 형식

여기에 이미지 설명을 삽입하세요.
요청 라인, 요청 헤더, 빈 라인, 요청 데이터의 네 부분으로 나누어져 있는 것을 볼 수 있습니다.
여기에 이미지 설명을 삽입하세요.

첫 번째 라인은 요청 라인이고 나머지는 요청 헤더입니다. 요청 데이터는 여기에 표시되지 않은 바이너리 코드 문자열입니다.

HTTP 응답 메시지 형식

여기에 이미지 설명을 삽입하세요.
상태 줄, 응답 헤더, 빈 줄, 응답 데이터의 네 부분으로 나누어져 있는 것을 볼 수 있습니다.

여기에 이미지 설명을 삽입하세요.

HTTP 요청 방법(이해를 위해서만)

HTTP/1.1 프로토콜은 지정된 리소스를 다양한 방식으로 작동하는 총 8가지 메서드("작업"이라고도 함)를 정의합니다.

  1. GET: 지정된 리소스에 대해 "표시" 요청을 만듭니다. GET 메서드는 데이터를 읽는 데에만 사용해야 하며 웹 애플리케이션과 같이 "부작용"을 생성하는 작업에는 사용해서는 안 됩니다. 한 가지 이유는 웹 스파이더 등이 GET에 무작위로 액세스할 수 있기 때문입니다.
  2. HEAD: GET 메소드와 마찬가지로 지정된 자원을 서버에 요청합니다. 단지 서버가 리소스의 텍스트 부분을 반환하지 않는다는 것입니다. 이 방법을 사용하면 전체 내용을 전송하지 않고도 "리소스에 대한 정보"(메타정보 또는 메타데이터)를 얻을 수 있다는 장점이 있습니다.
  3. POST: 지정된 리소스에 데이터를 제출하고 서버에 처리(예: 비밀번호로 로그인, 양식 제출 또는 파일 업로드)를 요청합니다. 데이터는 요청 기사에 포함되어 있습니다. 이 요청은 새 리소스를 생성하거나 기존 리소스를 수정하거나 둘 다를 수행할 수 있습니다.
  4. PUT: 최신 콘텐츠를 지정된 리소스 위치에 업로드합니다.
  5. DELETE: Request-URI로 식별된 리소스를 삭제하도록 서버에 요청합니다.
  6. TRACE: 주로 테스트나 진단에 사용되는 서버에서 받은 요청을 에코합니다.
  7. 옵션: 이 방법을 사용하면 서버가 리소스에서 지원하는 모든 HTTP 요청 방법을 반환할 수 있습니다. ""를 사용하여 리소스 이름을 바꾸고 ОPTIONS 요청을 웹 서버에 보내 서버 기능이 제대로 작동하는지 테스트합니다.
  8. CONNECT: HTTP/1.1 프로토콜은 파이프라인에 대한 연결을 변경할 수 있는 프록시 서버용으로 예약되어 있습니다. 일반적으로 SSL 암호화 서버에 대한 링크에 사용됩니다(암호화되지 않은 HTTP 프록시를 통해).

HTTP 상태 코드

모든 HTTP 응답의 첫 번째 줄은 현재 HTTP 버전 번호, 3자리 상태 코드, 상태를 설명하는 문구가 공백으로 구분되어 포함되어 있는 상태 줄입니다.
상태 코드의 첫 번째 숫자는 현재 응답 유형을 나타냅니다.

1xx 메시지 - 서버가 요청을 받았습니다. 처리를 계속합니다.

2xx 성공 - 서버에서 요청을 성공적으로 수신하고, 이해하고, 수락했습니다.

3xx 리디렉션 - 이 요청을 완료하려면 후속 조치가 필요합니다.

4xx 요청 오류 - 요청에 어휘 오류가 포함되어 있거나 실행할 수 없습니다.

5xx 서버 오류 - 올바른 요청을 처리하는 동안 서버에서 오류가 발생했습니다.

RFC 2616에는 "200 OK", "404 Not Found"와 같은 상태를 설명하는 권장 문구가 있지만 웹 개발자는 지역화된 상태 설명이나 사용자 정의 정보를 표시하는 데 사용할 문구를 결정할 수 있습니다.

서버 프로그래밍의 기본 프레임워크

서버 프로그램의 종류는 다양하지만 기본 프레임워크는 동일하며 차이점은 논리적 처리에 있습니다.

여기에 이미지 설명을 삽입하세요.
I/O 처리 장치는 클라이언트 연결을 관리하는 서버 모듈입니다. 일반적으로 새 클라이언트 연결을 기다리고 수락하고, 클라이언트 데이터를 수신하고, 서버 응답 데이터를 클라이언트에 반환하는 작업을 완료합니다. 그러나 데이터의 송수신은 반드시 I/O 처리 장치에서 수행될 필요는 없고, 논리 장치에서도 수행될 수 있으며, 구체적인 위치는 이벤트 처리 모드에 따라 다르다. 논리 단위는 일반적으로 프로세스 또는 스레드입니다. 고객 데이터를 분석 및 처리하고 결과를 I/O 처리 장치 또는 클라이언트에 직접 전달합니다(이벤트 처리 모델에 따라 다름). 서버에는 일반적으로 여러 클라이언트 작업을 동시에 처리할 수 있는 여러 논리 장치가 있습니다. 네트워크 저장 장치는 데이터베이스, 캐시, 파일이 될 수 있지만 반드시 그럴 필요는 없습니다. 요청 대기열은 장치가 통신하는 방식을 추상화한 것입니다. I/O 처리 장치가 클라이언트 요청을 받으면 요청을 처리하도록 논리 장치에 어떻게든 알려야 합니다. 마찬가지로 여러 논리 장치가 동시에 저장 장치에 액세스하는 경우 경쟁 조건을 조정하고 처리하기 위한 일부 메커니즘도 필요합니다. 요청 대기열은 일반적으로 풀의 일부로 구현됩니다.

두 가지 효율적인 이벤트 처리 모드

서버 프로그램은 일반적으로 I/O 이벤트, 신호 및 시간 제한 이벤트의 세 가지 유형의 이벤트를 처리해야 합니다. 효율적인 이벤트 처리 모드에는 Reactor와 Proactor의 두 가지가 있으며 동기식 I/O 모델은 일반적으로 Reactor 모드를 구현하는 데 사용되며 비동기식 I/O 모델은 일반적으로 Proactor 모드를 구현하는 데 사용됩니다.

반응기 패턴

메인 스레드(I/O 처리 장치)는 파일 디스크립터에서 이벤트가 발생하는지 여부만 모니터링해야 하며, 발생하면 즉시 작업자 스레드(논리 장치)에 이벤트를 알리고 읽기 및 쓰기 가능한 소켓 이벤트를 해당 이벤트에 넣습니다. 요청 큐를 작업자 스레드에 넘겨줍니다. 이 외에도 메인 스레드는 다른 실질적인 작업을 수행하지 않습니다. 데이터 읽기 및 쓰기, 새 연결 수락, 고객 요청 처리는 모두 작업자 스레드에서 수행됩니다.

동기식 I/O(예: epoll_wait)를 사용하여 구현된 Reactor 모드의 작업 흐름은 다음과 같습니다.

  1. 메인 스레드는 epoll 커널 이벤트 테이블의 소켓에 대한 읽기 준비 이벤트를 등록합니다.
  2. 메인 스레드는 epoll_wait를 호출하여 소켓에서 데이터를 읽을 때까지 기다립니다.
  3. 소켓에 읽을 데이터가 있으면 epoll_wait는 메인 스레드에 이를 알립니다. 메인 스레드는 소켓 읽기 가능 이벤트를 요청 큐에 넣습니다.
  4. 요청 큐에서 휴면 중인 작업자 스레드가 활성화되어 소켓에서 데이터를 읽고 클라이언트 요청을 처리한 다음 epoll 커널 이벤트 테이블의 소켓에 쓰기 준비 이벤트를 등록합니다.
  5. 메인 스레드가 epoll_wait를 호출하면 소켓이 쓰기 가능해질 때까지 기다립니다.
  6. 소켓이 쓰기 가능해지면 epoll_wait는 메인 스레드에 이를 알립니다. 메인 스레드는 소켓 쓰기 가능 이벤트를 요청 큐에 넣습니다.
  7. 요청 큐에서 휴면 중인 작업자 스레드가 깨어나고 서버가 클라이언트 요청을 처리한 결과를 소켓에 씁니다.

여기에 이미지 설명을 삽입하세요.

프로액터 모드

Proactor 모드는 처리(읽기 및 쓰기)를 위해 모든 I/O 작업을 메인 스레드와 커널에 전달하고 작업자 스레드는 비즈니스 로직만 담당합니다.
비동기 I/O 모델(aio_read 및 aio_write를 예로 사용)을 사용하여 구현된 Proactor 모드의 작업 흐름은 다음과 같습니다.

  1. 메인 스레드는 aio_read 함수를 호출하여 소켓에 대한 읽기 완료 이벤트를 커널에 등록하고, 커널 사용자에게 읽기 버퍼의 위치와 읽기 작업이 완료되면 애플리케이션에 알리는 방법을 알려줍니다(여기서는 신호를 받습니다). 예로서).
  2. 메인 스레드는 다른 로직을 계속 처리합니다.
  3. 소켓의 데이터가 사용자 버퍼로 읽혀지면 커널은 애플리케이션에 신호를 보내 애플리케이션에 데이터를 사용할 수 있음을 알립니다.
  4. 애플리케이션의 미리 정의된 신호 처리 기능은 클라이언트 요청을 처리할 작업자 스레드를 선택합니다. 작업자 스레드는 클라이언트 요청을 처리한 후 aio_write 함수를 호출하여 소켓에 대한 쓰기 완료 이벤트를 커널에 등록하고 커널 사용자에게 쓰기 버퍼의 위치와 쓰기 작업이 완료될 때 애플리케이션에 알리는 방법을 알려줍니다. .
  5. 메인 스레드는 다른 로직을 계속 처리합니다.
  6. 사용자 버퍼의 데이터가 소켓에 기록되면 커널은 데이터가 전송되었음을 애플리케이션에 알리기 위해 애플리케이션에 신호를 보냅니다.
  7. 애플리케이션에서 미리 정의한 신호 처리 함수는 작업자 스레드를 선택하여 소켓 닫기 여부 결정과 같은 여파 처리를 수행합니다.

여기에 이미지 설명을 삽입하세요.

Proactor 모드 시뮬레이션

Proactor 모드를 시뮬레이션하기 위해 동기식 I/O 방법을 사용합니다. 원칙은 메인 스레드가 데이터 읽기 및 쓰기 작업을 수행하고, 읽기 및 쓰기가 완료된 후 메인 스레드가 작업 스레드에 이 "완료 이벤트"를 알린다는 것입니다. 그래서 워커 스레드 입장에서는 데이터를 읽고 쓴 결과를 직접 얻고, 그 다음으로 해야 할 일은 읽고 쓴 결과를 논리적으로 처리하는 일이다.
동기식 I/O 모델(예: epoll_wait)을 사용하여 시뮬레이션된 Proactor 모드의 작업 흐름은 다음과 같습니다.

  1. 메인 스레드는 epoll 커널 이벤트 테이블의 소켓에 대한 읽기 준비 이벤트를 등록합니다.
  2. 메인 스레드는 epoll_wait를 호출하여 소켓에서 데이터를 읽을 때까지 기다립니다.
  3. 소켓에 읽을 데이터가 있으면 epoll_wait는 메인 스레드에 이를 알립니다. 메인 스레드는 읽을 데이터가 더 이상 없을 때까지 루프의 소켓에서 데이터를 읽은 다음 읽은 데이터를 요청 객체로 캡슐화하고 요청 큐에 삽입합니다.
  4. 요청 큐에서 휴면 중인 작업자 스레드가 활성화되어 요청 객체를 획득하고 클라이언트 요청을 처리한 다음 epoll 커널 이벤트 테이블의 소켓에 쓰기 준비 이벤트를 등록합니다.
  5. 메인 스레드는 epoll_wait를 호출하여 소켓이 쓰기 가능해질 때까지 기다립니다.
  6. 소켓이 쓰기 가능해지면 epoll_wait는 메인 스레드에 이를 알립니다. 메인 스레드는 서버가 클라이언트 요청을 처리한 결과를 소켓에 씁니다.
    여기에 이미지 설명을 삽입하세요.

스레드 풀

스레드 풀은 서버에서 미리 생성한 하위 스레드 그룹으로, 스레드 풀에 포함되는 스레드 수는 CPU 수와 대략 동일해야 합니다. 스레드 풀의 모든 하위 스레드는 동일한 코드를 실행합니다. 새로운 작업이 도착하면 메인 스레드는 어떤 방식으로든 이를 처리하기 위해 스레드 풀에서 하위 스레드를 선택합니다. 동적으로 하위 스레드를 생성하는 것과 비교할 때 기존 하위 스레드를 선택하는 데 드는 비용은 분명히 훨씬 적습니다.

메인 스레드가 새 작업을 제공하기 위해 선택하는 하위 스레드에 대해서는 여러 가지 방법이 있습니다.

  • 메인 스레드는 일부 알고리즘을 사용하여 하위 스레드를 적극적으로 선택합니다. 가장 간단하고 가장 일반적으로 사용되는 알고리즘은 무작위 알고리즘과 라운드 로빈 알고리즘이지만, 더 우수하고 스마트한 알고리즘은 작업을 다양한 작업자 스레드에 더 균등하게 분배하여 서버에 대한 전반적인 부담을 줄입니다.
  • 기본 스레드와 모든 하위 스레드는 공유 작업 대기열을 통해 동기화되고 하위 스레드는 작업 대기열에서 휴면 상태로 유지됩니다. 새 작업이 도착하면 기본 스레드는 해당 작업을 작업 대기열에 추가합니다. 이렇게 하면 작업을 기다리고 있는 하위 스레드가 깨어나지만 단 하나의 하위 스레드만 새 작업의 "인계 권한"을 갖게 됩니다. 작업 대기열에서 작업을 가져와 실행할 수 있으며 다른 하위 스레드는 계속됩니다. 작업 대기열에서 자다.

스레드 풀의 일반적인 모델은 다음과 같습니다.

여기에 이미지 설명을 삽입하세요.
스레드 풀의 스레드 수에 대한 가장 직접적인 제한 요소는 중앙 처리 장치(CPU)의 프로세서/코어 수 N입니다. CPU가 4코어인 경우 CPU 집약적인 작업(예: 비디오 편집) CPU 컴퓨팅 리소스를 소비하는 작업의 경우 스레드 풀의 스레드 수를 4(또는 다른 요인으로 인한 스레드 차단을 방지하려면 +1)로 설정하는 것이 가장 좋습니다. CPU 수 스레드가 CPU 컴퓨팅 리소스가 아닌 IO를 위해 경쟁하기 때문에 코어 수입니다. IO 처리는 일반적으로 더 느립니다. 코어가 더 많은 스레드는 CPU를 위해 더 많은 작업을 위해 경쟁하며 스레드가 처리하는 동안 CPU가 유휴 상태가 되지 않습니다. IO. 자원 낭비로 이어집니다.

공간은 시간과 교환되고, 서버 하드웨어 자원은 운영 효율성을 위해 낭비됩니다. 풀은 서버가 시작될 때 완전히 생성되고 초기화되는 리소스의 집합을 말하며, 이를 정적 리소스라고 합니다. 서버가 정식 운영 단계에 진입하여 고객 요청 처리를 시작할 때, 관련 자원이 필요한 경우 동적 할당 없이 풀에서 직접 확보할 수 있습니다. 서버가 클라이언트 연결 처리를 마치면 리소스를 해제하기 위한 시스템 호출을 실행하지 않고도 관련 리소스를 다시 풀에 넣을 수 있습니다.

  • 공간은 시간과 교환되고, 서버 하드웨어 자원은 운영 효율성을 위해 낭비됩니다.
  • 풀은 서버가 시작될 때 완전히 생성되고 초기화되는 리소스의 집합을 말하며, 이를 정적 리소스라고 합니다.
  • 서버가 정식 운영 단계에 진입하여 고객 요청 처리를 시작할 때, 관련 자원이 필요한 경우 동적 할당 없이 풀에서 직접 확보할 수 있습니다.
  • 서버가 클라이언트 연결 처리를 마치면 리소스를 해제하기 위한 시스템 호출을 실행하지 않고도 관련 리소스를 다시 풀에 넣을 수 있습니다.

에폴론샷 이벤트

ET 모드를 사용할 수 있더라도 소켓의 이벤트는 여전히 여러 번 트리거될 수 있습니다. 이로 인해 동시 프로그램에 문제가 발생할 수 있습니다. 예를 들어 스레드가 특정 소켓에서 데이터를 읽은 후 데이터 처리를 시작하면 데이터 처리 과정에서 소켓에서 새 데이터를 읽을 수 있게 됩니다(EPOLLIN이 다시 트리거됨). 이때 다른 스레드가 깨어납니다. 이 새로운 내용을 읽어보세요. 데이터. 그래서 두 개의 쓰레드가 동시에 하나의 소켓을 동작시키는 상황이 발생합니다. 소켓 연결은 언제든지 하나의 스레드에 의해서만 처리되며 이는 epoll의 EPOLLONESHOT 이벤트를 사용하여 달성할 수 있습니다.

EPOLLONESHOT 이벤트에 등록된 파일 설명자의 경우 운영 체제는 파일 설명자에 등록된 EPOLLONESHOT 이벤트를 재설정하기 위해 epoll_ctl 함수를 사용하지 않는 한 등록된 읽기 가능, 쓰기 가능 또는 예외 이벤트를 최대 한 개만 트리거합니다. 이런 방식으로 한 스레드가 특정 소켓을 처리할 때 다른 스레드가 해당 소켓을 작동할 기회를 가질 수 없습니다. 그러나 반대로 생각하면 EPOLLONESHOT 이벤트에 등록된 소켓이 스레드에 의해 처리되면 스레드는 소켓에서 EPOLLONESHOT 이벤트를 즉시 재설정하여 다음에 소켓을 읽을 수 있을 때 EPOLLIN 이벤트가 트리거될 수 있도록 해야 합니다. . 이는 다른 작업자 스레드에 이 소켓을 계속 처리할 수 있는 기회를 제공합니다.

유한 상태 머신

논리 장치 내부의 효율적인 프로그래밍 방법: 유한 상태 기계. 일부 애플리케이션 계층 프로토콜 헤더에는 패킷 유형 필드가 포함되어 있으며, 각 유형은 논리 유닛의 실행 상태에 매핑될 수 있으며, 서버는 이를 기반으로 해당 처리 논리를 작성할 수 있습니다. 다음은 상태 독립적인 유한 상태 기계입니다.

STATE_MACHINE( Package _pack )
{
    
    
PackageType _type = _pack.GetType();
switch( _type )
{
    
    
case type_A:
process_package_A( _pack );
break;
case type_B:
process_package_B( _pack );
break;
}
}

이는 상태 머신의 각 상태가 서로 독립적이라는 점, 즉 상태 간 전송이 없다는 점을 제외하면 단순한 유한 상태 머신입니다. 상태 간 전환에는 다음 코드에 표시된 대로 상태 머신의 내부 드라이버가 필요합니다.

STATE_MACHINE()
{
    
    
State cur_State = type_A;
 
while( cur_State != type_C )
{
    
    
Package _pack = getNewPackage();
switch( cur_State )
{
    
    
case type_A:
process_package_state_A( _pack );
cur_State = type_B;
break;
case type_B:
process_package_state_B( _pack );
cur_State = type_C;
break;
}
}
}

상태 기계에는 type_A, type_B 및 type_C의 세 가지 상태가 포함되어 있습니다. 여기서 type_A는 상태 기계의 시작 상태이고 type_C는 상태 기계의 종료 상태입니다. 상태 머신의 현재 상태는 cur_State 변수에 기록됩니다. 루프 동안 상태 머신은 먼저 getNewPackage 메서드를 통해 새 데이터 패킷을 얻은 다음 cur_State 변수의 값을 기반으로 데이터 패킷을 처리하는 방법을 결정합니다. 데이터 패킷이 처리된 후 상태 머신은 대상 상태 값을 cur_State 변수에 전달하여 상태 전환을 구현합니다. 그런 다음 상태 머신이 다음 주기에 진입하면 새 상태에 해당하는 논리를 실행합니다.

Supongo que te gusta

Origin blog.csdn.net/mhyasadj/article/details/131490664
Recomendado
Clasificación