libcurl 드라이브 전송

드라이브 전송

libcurl은 전송을 수행하는 세 가지 다른 방법을 제공합니다. 귀하의 경우에 어떤 방법을 사용할지는 전적으로 귀하와 귀하가 필요로 하는 것에 달려 있습니다.

  • ‘쉬운’ 인터페이스를 사용하면 동기식으로 단일 전송을 수행할 수 있습니다. libcurl은 전체 전송을 수행하고 성공 여부와 상관없이 애플리케이션이 완료되면 제어를 다시 애플리케이션으로 반환합니다.
  • ‘멀티’ 인터페이스는 동시에 둘 이상의 전송을 수행하거나 비차단 전송을 원할 때 사용합니다.
  • ‘multi_socket’ 인터페이스는 일반 다중 인터페이스의 약간의 변형이지만 이벤트 기반이며 실제로 동시 전송 수를 수백 또는 수천 정도로 확장하려는 경우 사용하도록 제안되는 API입니다.

easy drive

‘easy’라는 이름은 libcurl을 사용하는 정말 쉬운 방법이기 때문에 선택되었으며, 물론 easy에는 몇 가지 제한 사항이 있습니다. 예를 들어, 한 번에 하나의 전송만 수행할 수 있고 단일 함수 호출에서 전체 전송을 수행하고 완료되면 반환합니다.

1
res = curl_easy_perform( easy_handle );

서버가 느리거나 전송이 크거나 네트워크에서 일부 불쾌한 시간 초과가 발생하거나 이와 유사한 경우 이 함수 호출에 시간이 오래 걸릴 수 있습니다. 물론 N초를 초과하지 않도록 타임아웃을 설정할 수 있지만 특정 조건에 따라 상당한 시간을 의미할 수도 있습니다.

libcurl이 쉬운 인터페이스로 전송하는 동안 애플리케이션이 다른 작업을 수행하도록 하려면 여러 스레드를 사용해야 합니다. easy 인터페이스를 사용할 때 여러 개의 동시 전송을 수행하려면 각각의 전송을 자체 스레드에서 수행해야 합니다.

Drive with multi

‘multi’라는 이름은 다중 병렬 전송에서와 같이 모두 동일한 단일 스레드에서 수행되는 다중에 대한 것입니다. 다중 API는 차단되지 않으므로 단일 전송에도 사용할 수 있습니다.
위에서 설명한 대로 전송은 여전히 “쉬운” CURL * 핸들로 설정되지만 다중 인터페이스를 사용하면 생성된 다중 CURLM * 핸들도 필요하며 이를 사용하여 모든 개별 전송을 구동해야 합니다. 다중 핸들은 하나 이상의 쉬운 핸들을 “잡을” 수 있습니다.

1
CURLM *multi_handle = curl_multi_init();

다중 핸들은 또한 curl_multi_setopt()로 수행하는 특정 옵션 세트를 얻을 수 있지만 가장 간단한 경우에는 거기에 설정할 것이 없을 수도 있습니다.
다중 인터페이스 전송을 구동하려면 먼저 다중 핸들로 전송되어야 하는 모든 개별 간편 핸들을 추가해야 합니다. 언제라도 멀티 핸들에 추가할 수 있으며 원할 때마다 다시 제거할 수 있습니다. 다중 핸들에서 쉬운 핸들을 제거하면 물론 연결이 제거되고 특정 전송이 즉시 중지됩니다.

멀티 핸들에 easy 핸들을 추가하는 것은 쉽습니다.

1
curl_multi_add_handle( multi_handle, easy_handle );

하나를 제거하는 것도 간단합니다.

1
curl_multi_remove_handle( multi_handle, easy_handle );

수행하려는 전송을 나타내는 쉬운 핸들을 추가한 후 전송 루프를 작성합니다. 다중 인터페이스를 사용하면 루핑을 수행하여 libcurl에 파일 설명자 세트와 시간 초과 값을 요청하고 스스로 select() 호출을 수행하거나 curl_multi_wait를 사용하여 이를 수행하는 약간 단순화된 버전을 사용할 수 있습니다. 가장 간단한 루프는 다음과 같을 수 있습니다. (실제 응용 프로그램은 반환 코드를 확인합니다)

1
2
3
4
5
int transfers_running;
do {
curl_multi_wait ( multi_handle, NULL, 0, 1000, NULL);
curl_multi_perform ( multi_handle, &transfers_running );
} while (transfers_running);

위의 예에서 1000으로 설정된 curl_multi_wait에 대한 네 번째 인수는 밀리초 단위의 시간 초과입니다. 어쨌든 반환되기 전에 함수가 활동을 기다리는 가장 긴 시간입니다. 시간 초과, 진행 콜백 등이 있으므로 curl_multi_perform을 다시 호출하기 전에 너무 오랫동안 잠그고 싶지 않습니다. 그렇게 하면 정밀도가 떨어질 수 있습니다.

대신 select()를 자체적으로 수행하기 위해 다음과 같이 libcurl에서 파일 설명자와 시간 초과 값을 추출합니다(실제 응용 프로그램은 반환 코드를 확인할 것입니다).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
int transfers_running;
do {
fd_set fdread;
fd_set fdwrite;
fd_set fdexcep;
int maxfd = -1;
long timeout;

/* extract timeout value */
curl_multi_timeout(multi_handle, &timeout);
if (timeout < 0)
timeout = 1000;

/* convert to struct usable by select */
timeout.tv_sec = timeout / 1000;
timeout.tv_usec = (timeout % 1000) * 1000;

FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
FD_ZERO(&fdexcep);

/* get file descriptors from the transfers */
mc = curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);

if (maxfd == -1) {
SHORT_SLEEP;
}
else
select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);

/* timeout or readable/writable sockets */
curl_multi_perform(multi_handle, &transfers_running);
} while ( transfers_running );

이 두 루프 모두 자신의 소켓이나 파이프 등에서 읽는 경우와 같이 대기할 파일 설명자를 하나 이상 사용할 수 있습니다.

또한 루핑 중 언제라도 멀티 핸들에 쉬운 핸들을 추가하거나 제거할 수 있습니다. 물론 전송 도중 핸들을 제거하면 해당 전송이 중단됩니다.

단일 전송은 언제 완료됩니까?

위의 예에서 볼 수 있듯이 프로그램은 transfers_running 변수가 감소하는 것을 확인하여 개별 전송이 완료되는 시점을 감지할 수 있습니다.

또한 curl_multi_info_read()를 호출하여 전송이 종료된 경우 구조체에 대한 포인터(“메시지”)를 반환하고 해당 구조체를 사용하여 해당 전송의 결과를 찾을 수 있습니다.

여러 병렬 전송을 수행하는 경우 동일한 curl_multi_perform 호출에서 둘 이상의 전송이 완료될 수 있으며 완료된 각 전송에 대한 정보를 얻기 위해 curl_multi_info_read에 대한 호출이 둘 이상 필요할 수 있습니다.

Drive with multi_socket

“multi_socket” 인터페이스로 구동

multi_socket은 일반 다중 인터페이스의 추가 매운 버전이며 이벤트 기반 응용 프로그램을 위해 설계되었습니다.

multi_socket은 모두 동일한 단일 스레드에서 수행되는 다중 병렬 전송을 지원하며 단일 애플리케이션에서 수만 건의 전송을 실행하는 데 사용되었습니다. 일반적으로 많은 수(>100 정도) 병렬 전송을 수행하는 경우 가장 적합한 API입니다.

이 경우 이벤트 구동은 응용 프로그램이 여러 소켓에 “구독”하는 시스템 수준 라이브러리 또는 설정을 사용하고 해당 소켓 중 하나를 읽거나 쓸 수 있을 때 응용 프로그램에 알려주고 정확히 어느 소켓을 알려줍니다.

이 설정을 통해 클라이언트는 다른 시스템보다 훨씬 더 많은 동시 전송 수를 확장하면서도 여전히 우수한 성능을 유지할 수 있습니다. 그렇지 않으면 “일반” API는 모든 소켓 목록을 스캔하는 데 너무 많은 시간을 낭비합니다.

하나를 선택

선택할 수 있는 이벤트 기반 시스템이 많이 있으며 libcurl은 사용자가 사용하는 시스템에 대해 완전히 불가지론적입니다. libevent, libev 및 libuv는 널리 사용되는 세 가지이지만 epoll, kqueue, /dev/poll, pollset 또는 Event Completion과 같은 운영 체제의 기본 솔루션으로 직접 이동할 수도 있습니다.

많은 쉬운 핸들

일반 다중 인터페이스와 마찬가지로 curl_multi_add_handle()을 사용하여 다중 핸들에 쉬운 핸들을 추가합니다. 수행하려는 각 전송에 대한 하나의 쉬운 핸들.

전송이 실행되는 동안 언제든지 추가할 수 있으며 curl_multi_remove_handle 호출을 사용하여 언제든지 간편 핸들을 유사하게 제거할 수도 있습니다. 그러나 일반적으로 전송이 완료된 후에만 핸들을 제거합니다.

multi_socket 콜백

위에서 설명했듯이 이 이벤트 기반 메커니즘은 libcurl이 어떤 소켓을 사용하고 libcurl이 해당 소켓에서 무엇을 기다리는지를 애플리케이션에 의존합니다. 소켓이 읽기, 쓰기 또는 둘 다 되기를 기다리는 경우!

또한 libcurl이 스스로 할 수 없는 모든 것을 제어하기 때문에 타임아웃 시간이 만료되면 libcurl에 알려야 합니다. 따라서 libcurl은 업데이트된 시간 초과 값도 응용 프로그램에 알려야 합니다.

소켓_콜백

libcurl은 CURLMOPT_SOCKETFUNCTION이라는 콜백으로 대기할 소켓 활동에 대해 애플리케이션에 알립니다. 애플리케이션은 다음과 같은 기능을 구현해야 합니다.

1
2
3
4
5
6
7
8
9
10
11
int socket_callback(CURL *easy,      /* easy handle */
curl_socket_t s, /* socket */
int what, /* what to wait for */
void *userp, /* private callback pointer */
void *socketp) /* private socket pointer */
{
/* told about the socket 's' */
}

/* set the callback in the multi handle */
curl_multi_setopt(multi_handle, CURLMOPT_SOCKETFUNCTION, socket_callback);

이것을 사용하여 libcurl은 애플리케이션이 모니터링해야 하는 소켓을 설정 및 제거합니다. 애플리케이션은 기본 이벤트 기반 시스템에 소켓을 기다리도록 지시합니다. 이 콜백은 대기할 소켓이 여러 개 있는 경우 여러 번 호출되며 상태가 변경되면 다시 호출되며 쓰기 가능한 소켓을 기다리는 대신 읽을 수 있게 될 때까지 기다리는 것으로 전환해야 합니다.

응용 프로그램이 libcurl을 대신하여 모니터링하는 소켓 중 하나가 요청에 따라 읽기 또는 쓰기가 가능해진다고 등록하면 curl_multi_socket_action()을 호출하고 영향을 받는 소켓과 어떤 소켓 활동이 있었는지 지정하는 관련 비트마스크를 전달하여 libcurl에 알려줍니다.

1
2
3
4
5
int running_handles;
ret = curl_multi_socket_action(multi_handle,
sockfd, /* the socket with activity */
ev_bitmask, /* the specific activity */
&running_handles);

타이머_콜백

응용 프로그램이 제어되고 소켓 활동을 기다립니다. 그러나 소켓 활동이 없더라도 libcurl이 해야 할 일이 있습니다. 시간 초과 문제, 진행 콜백 호출, 재시도를 다시 시작하거나 너무 오래 걸리는 전송 실패 등. 이 작업을 수행하려면 응용 프로그램은 libcurl이 설정하는 단일 샷 시간 초과도 처리해야 합니다.

libcurl은 timer_callback으로 시간 초과를 설정합니다. CURLMOPT_TIMERFUNCTION

1
2
3
4
5
6
7
8
9
int timer_callback(multi_handle,   /* multi handle */
timeout_ms, /* milliseconds to wait */
userp) /* private callback pointer */
{
/* the new time-out value to wait for is in 'timeout_ms' */
}

/* set the callback in the multi handle */
curl_multi_setopt(multi_handle, CURLMOPT_TIMERFUNCTION, timer_callback);

추가된 개별 easy 핸들 또는 진행 중인 전송의 수와 상관없이 전체 다중 핸들에 대해 애플리케이션이 처리하는 시간 초과는 한 번뿐입니다. 타이머 콜백은 현재 가장 가까운 대기 시간으로 업데이트됩니다. 소켓 활동으로 인해 시간 초과 만료 시간 전에 libcurl이 호출되면 만료되기 전에 시간 초과 값을 다시 업데이트할 수 있습니다.

선택한 이벤트 시스템이 결국 타이머가 만료되었다고 알려줄 때 libcurl에 이에 대해 알려야 합니다.

1
curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, &running);

… 많은 경우에 이것은 libcurl이 timer_callback을 다시 호출하고 다음 만료 기간에 대한 새로운 시간 초과를 설정하게 합니다.

모든 것을 시작하는 방법

다중 핸들에 하나 이상의 쉬운 핸들을 추가하고 다중 핸들에서 소켓 및 타이머 콜백을 설정하면 전송을 시작할 준비가 된 것입니다.

모든 것을 시작하기 위해 libcurl에게 시간 초과(모든 쉬운 핸들이 짧은 시간 초과로 시작하기 때문에)에 알리면 libcurl이 콜백을 호출하여 설정한 다음 이벤트 시스템이 구동되도록 할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
/* all easy handles and callbacks are setup */

curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, &running);

/* now the callbacks should have been called and we have sockets to wait for
and possibly a timeout, too. Make the event system do its magic */

event_base_dispatch(event_base); /* libevent2 has this API */

/* at this point we have exited the event loop */

언제 완료되나요?

curl_multi_socket_action이 반환한 ‘running_handles’ 카운터는 완료되지 않은 현재 전송 수를 보유합니다. 해당 숫자가 0에 도달하면 진행 중인 전송이 없음을 알 수 있습니다.

‘running_handles’ 카운터가 변경될 때마다 curl_multi_info_read()는 완료된 특정 전송에 대한 정보를 반환합니다.

공유하기