Linux 네트워크 프로그래밍-비동기 I / O에 대한 토론

개념

첫 번째는 I / O 차단입니다. I / O에서 시작된 읽기 요청을 차단하면 커널 데이터가 준비 될 때까지 스레드가 일시 중지되고 커널 영역에서 응용 프로그램 버퍼로 데이터가 복사되며 복사 프로세스가 완료되면 읽기 요청 호출이 반환됩니다. 다음으로 애플리케이션은 버퍼의 데이터를 분석 할 수 있습니다.

                                                         

두 번째는 비 차단 I / O입니다. non-blocking 읽기 요청은 데이터가 준비되지 않은 경우 즉시 반환됩니다. 응용 프로그램은 데이터가 준비 될 때까지 커널을 지속적으로 폴링 할 수 있습니다. 커널은 데이터를 응용 프로그램 버퍼에 복사하고 읽기 호출을 완료합니다. 여기서 마지막 읽기 호출 인 데이터를 얻는 프로세스는 동기 프로세스입니다. 여기서 동기화는 커널 영역의 데이터를 버퍼 영역으로 복사하는 프로세스를 의미합니다.

                                                     

애플리케이션 프로세스가 폴링 프로세스 동안 아무것도 할 수 없기 때문에 매번 커널 I / O가 준비되었는지 여부를 폴링하도록 애플리케이션에 요청하는 것은 경제적이지 않습니다. 그 결과 선택 및 폴링과 같은 I / O 멀티플렉싱 기술이 무대에 올랐습니다. I / O 이벤트 분배를 통해 커널 데이터가 준비되면 애플리케이션이 작동하도록 알립니다. 이 접근 방식은 애플리케이션 프로세스의 CPU 사용률을 크게 향상 시키며 애플리케이션 프로세스는 알림없이 다른 작업을 수행하기 위해 CPU를 사용할 수 있습니다.

읽기를 호출하고 데이터를 얻는 프로세스도 동기 프로세스입니다.

                                                  

I / O를 차단하는 첫 번째 유형 인 애플리케이션은 데이터를 얻을 때까지 일시 중지됩니다. 두 번째 논 블로킹 I / O 및 세 번째 논 블로킹 I / O 기반 다중화 기술은 데이터 획득 작업이 차단되지 않습니다. 여기에서는 모두 동기식 호출 기술 입니다. 왜 그런 말을 해? 동기 호출 및 비동기 호출 의 용어는 데이터를 얻는 프로세스를위한 것이므로 최종적으로 데이터를 얻는 처음 몇 번의 읽기 작업 호출은 모두 동 기적입니다. 읽기 호출 중에 커널은 커널 공간에서 애플리케이션으로 데이터를 복사합니다. ,이 프로세스는 읽기 기능에서 동 기적으로 수행되며, 커널에 의해 구현 된 복사 효율이 매우 낮 으면이 동기화 프로세스에서 읽기 호출에 상대적으로 오랜 시간이 소요됩니다.

그러나 실제 비동기 호출은이 문제에 대해 걱정하지 않습니다. 우리는 aio_read를 시작했을 때 즉시 반환 되는 네 번째 I / O 기술 을 도입 할 것입니다. 내에서 자동으로 커널 공간에서 애플리케이션 공간으로 데이터를 복사합니다. 비동기식이며 커널은 자동으로 수행되며 이전의 동기식 작업과 달리 응용 프로그램은 복사 작업을 시작할 필요가 없습니다.

                                                   

위의 I / O 모델을 요약하는 표가 여기에 있습니다.

                                     

aio_read 및 aio_write 사용

첫째, 프로그램 예 :

const int BUF_SIZE = 512;

int main() {
    int err;
    int result_size;
    // 创建一个临时文件
    char tmpname[256];
    snprintf(tmpname, sizeof(tmpname), "/tmp/aio_test_%d", getpid());
    unlink(tmpname);
    int fd = open(tmpname, O_CREAT | O_RDWR | O_EXCL, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        error(1, errno, "open file failed ");
    }
    char buf[BUF_SIZE];
    struct aiocb aiocb;

    //初始化buf缓冲,写入的数据应该为0xfafa这样的,
    memset(buf, 0xfa, BUF_SIZE);
    memset(&aiocb, 0, sizeof(struct aiocb));
    aiocb.aio_fildes = fd;
    aiocb.aio_buf = buf;
    aiocb.aio_nbytes = BUF_SIZE;
    //开始写
    if (aio_write(&aiocb) == -1) {
        printf(" Error at aio_write(): %s\n", strerror(errno));
        close(fd);
        exit(1);
    }
    //因为是异步的,需要判断什么时候写完
    while (aio_error(&aiocb) == EINPROGRESS) {
        printf("writing... \n");
    }
    //判断写入的是否正确
    err = aio_error(&aiocb);
    result_size = aio_return(&aiocb);
    if (err != 0 || result_size != BUF_SIZE) {
        printf(" aio_write failed() : %s\n", strerror(err));
        close(fd);
        exit(1);
    }

    //下面准备开始读数据
    char buffer[BUF_SIZE];
    struct aiocb cb;
    cb.aio_nbytes = BUF_SIZE;
    cb.aio_fildes = fd;
    cb.aio_offset = 0;
    cb.aio_buf = buffer;
    // 开始读数据
    if (aio_read(&cb) == -1) {
        printf(" air_read failed() : %s\n", strerror(err));
        close(fd);
    }
    //因为是异步的,需要判断什么时候读完
    while (aio_error(&cb) == EINPROGRESS) {
        printf("Reading... \n");
    }
    // 判断读是否成功
    int numBytes = aio_return(&cb);
    if (numBytes != -1) {
        printf("Success.\n");
    } else {
        printf("Error.\n");
    }

    // 清理文件句柄
    close(fd);
    return 0;
}

여기에서 사용되는 주요 기능은 다음과 같습니다.

  • aio_write : 커널에 비동기 쓰기 작업을 제출하는 데 사용됩니다.
  • aio_read : 비동기 읽기 작업을 커널에 제출하는 데 사용됩니다.
  • aio_error : 현재 비동기 작업의 상태를 가져옵니다.
  • aio_return : 비동기 작업에서 읽고 쓴 바이트 수를 가져옵니다.

처음에이 프로그램은 aio_write 메소드를 사용하여 비동기 파일 쓰기 작업을 커널에 제출합니다. aiocb 구조는 응용 프로그램에서 운영 체제 커널로 전달하는 비동기 응용 프로그램 데이터 구조입니다. 여기서는 파일 설명자, 버퍼 포인터 aio_buf 및 aio_nbytes를 작성해야하는 바이트 수를 사용합니다.

struct aiocb {
   int       aio_fildes;       /* File descriptor */
   off_t     aio_offset;       /* File offset */
   volatile void  *aio_buf;     /* Location of buffer */
   size_t    aio_nbytes;       /* Length of transfer */
   int       aio_reqprio;      /* Request priority offset */
   struct sigevent    aio_sigevent;     /* Signal number and value */
   int       aio_lio_opcode;       /* Operation to be performed */
};

다음으로 aio_read를 사용하여 파일에서 데이터를 읽었습니다. 이를 위해 우리는 커널에 데이터를 버퍼에 복사해야한다는 것을 알리는 새로운 aiobc 구조를 준비했는데, 이는 비동기 쓰기와 동일합니다. 비동기 읽기가 시작된 후 비동기 읽기 작업의 결과는 항상 물었다.

다음으로이 프로그램을 실행하면 화면에 일련의 문자가 인쇄되어이 작업이 백그라운드에서 커널에 의해 수행됨을 보여줍니다.

./aio01
writing... 
writing... 
writing... 
writing... 
writing... 
writing... 
writing... 
writing... 
writing... 
writing... 
writing... 
writing... 
writing... 
writing... 
Reading... 
Reading... 
Reading... 
Reading... 
Reading... 
Reading... 
Reading... 
Reading... 
Reading... 
Success.

/ tmp 디렉토리에서 aio_test_xxxx 파일을 열면이 파일이 예상 한 데이터를 성공적으로 기록했음을 알 수 있습니다.

                                

Linux에서 소켓 소켓에 대한 비동기 지원

aio 일련의 함수는 POSIX에서 정의한 비동기 작업 인터페이스입니다. 불행히도 Linux에서 aio 작업은 실제 운영 체제 수준에서 지원되지 않습니다. 사용자 공간의 GNU libc 라이브러리 함수에 의해서만 pthread에 의해 구현되고 For 디스크 I / O, 소켓 I / O는 지원되지 않습니다 .

운영 체제 커널에서 aio를 직접 지원하려는 많은 Linux 개발자도 있습니다. 예를 들어 Ben LaHaise라는 사람이 aio를 2.5.32에 성공적으로 병합했습니다.이 기능은 패치로 존재하지만 여전히 지원하지 않습니다. 소켓.

솔라리스는 실제 시스템에 다른 aio 시스템을 가지고 있지만 소켓에서 어떻게 수행되는지, 특히 디스크 I / O와 어떻게 비교되는지는 확실하지 않습니다.

위의 결론에 따르면 Linux에서 비동기 작업에 대한 지원은 매우 제한적이며, 이것이 epoll 및 non-blocking I / O와 같은 다중 배포 기술을 사용하여 높은 동시성 및 고성능 네트워크 I 문제를 해결하는 근본적인 이유입니다. / O Linux에서.

Linux와 달리 Windows는 소켓을 지원하는 완전한 비동기 프로그래밍 인터페이스 집합을 구현합니다.이 인터페이스 집합을 일반적으로 IOCP (IOCompletetionPort)라고합니다. 이러한 방식으로 IOCP를 기반으로하는 소위 Proactor 모드가 생성됩니다. Reactor 모드이든 Proactor 모드이든 이벤트 분포를 기반으로하는 네트워크 프로그래밍 모드입니다. Reactor 모드는 완료 할 I / O 이벤트를 기반으로하고 Proactor 모드는 완료된 I / O 이벤트를 기반으로합니다. 둘 다의 본질은 호환되고 확장 가능하도록 설계된 이벤트 배포 개념에 기반합니다. , 인터페이스 친화적 인 프로그램 프레임 워크 집합입니다.

 

즉, 비동기 I / O의 읽기 및 쓰기 동작은 커널에 의해 자동으로 완료되지만 현재 Linux에서는 로컬 파일을 기반으로하는 간단한 비동기 작업 만 지원 됩니다 . 이로 인해 고성능 네트워크를 작성할 때 Reactor 모드를 선호합니다. epoll과 같은 I / O 분배 기술이 개발되었으며 , Windows에서 IOCP는 비동기 I / O 기술로 Reactor만큼 유명한 Proactor 모드를 생성했습니다.이 모드를 사용하면 Windows에서 고성능을 얻을 수 있습니다. 네트워크 프로그래밍.

 

과거를 검토하여 새로운 것을 배우십시오!

 

 

추천

출처blog.csdn.net/qq_24436765/article/details/104829772