1. edo_m18

    Posted

    edo_m18
Changes in title
+[C言語] curl libを使ってみる
Changes in tags
+C
Changes in body
Source | HTML | Preview
@@ -0,0 +1,286 @@
+
+[前回](http://qiita.com/edo_m18/items/41770cba5c166f276a83)、[前々回](http://qiita.com/edo_m18/items/cef278d0c14d1371db3b)とHTTP(S)クライアントについて書きました。
+が、当然すでにこうしたクライアントはライブラリが存在し、その中でも`curl`はコマンドラインでも使う有名なライブラリです。
+
+今回はこれを使ってみようと思います。
+
+## curlのインストール
+
+`curl`コマンドは入っていると思いますが、ライブラリはどうやらMacでは入っていないようなのでインストールを行います。
+
+```console
+$ brew install curl
+# // ... installing message ...
+ -L/usr/local/opt/curl/lib
+ -I/usr/local/opt/curl/include
+```
+
+という感じで、インストール先が表示されるのでこれをコンパイル時に指定します。
+
+```console
+$ gcc test.c -I/usr/local/opt/curl/include -lcurl
+```
+
+## 実行してみる
+
+まずは簡単に実行してみます。
+
+```c:test.c
+int main(void) {
+ CURL *curl;
+
+ // curlのセットアップ
+ curl = curl_easy_init();
+ curl_easy_setopt(curl, CURLOPT_URL, "https://qiita.com/");
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
+
+ // 実行
+ curl_easy_perform(curl);
+
+ // 後始末
+ curl_easy_cleanup(curl);
+
+ return EXIT_SUCCESS;
+}
+```
+
+これを実行すると、qiita.comの情報が標準出力に表示されます。
+curlはデフォルトの出力先は標準出力になっています。
+
+しかし、ただ表示されるだけではまったく使い物になりません。
+例えばAPIにアクセスした場合はJSONなりでデータが返ってくると思いますが、これを保持できなければなりませんね。
+
+
+## callback関数を定義する
+
+curlではcallback関数を指定することで、サーバから届いたデータの塊を伴ってその関数を呼び出してくれます。
+
+### callback関数の定義
+
+以下のような形で関数を作り、curlのセット用関数で指定します。
+
+```c
+size_t callback(char *ptr, size_t size, size_t nmemb, void *stream) {
+ int block = size * nmemb;
+ // do somoting.
+}
+```
+
+`char *ptr`が実際に取得されたデータです。
+`size`と`nmemb`はちょっとよく分かりませんが、おそらくデータのサイズを表しているんだと思います。(色々調べたけど具体的に言及してるのが見つからなかったorz)
+
+そして最後のポインタが重要です。
+ここに渡ってくるポインタは、上記の関数と同様に、セット用関数で設定したポインタが渡ってきます。
+設定には自分で定義した構造体などのポインタを指定します。
+
+それぞれ具体的には以下のように設定します。
+
+```c
+CURL *curl;
+curl = curl_easy_init();
+
+// callback関数の最後の引数に渡ってくるポインタを指定
+curl_easy_setopt(curl, CURLOPT_WRITEDATA, anyPointer);
+
+// callback関数を設定
+curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, funcPointer);
+```
+
+
+### CURLOPT_WRITEDATAを定義する
+
+`CURLOPT_WRITEDATA`で指定したポインタが、上記で書いた「最後のポインタ」のに渡ってくる部分です。
+ここのポインタは、関数の宣言を見てもらうとわかりますが`void *`型になっています。
+つまりなんでもいいわけですね。
+
+文章だけだと分かりづらいので、curlが呼ばれた回数をカウントアップするだけの簡単なサンプルコードを示します。
+
+```c
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <curl/curl.h>
+
+struct counter {
+ int count;
+};
+
+size_t countup(char *ptr, size_t size, size_t nmemb, void *stream) {
+ struct counter *counter = (struct counter *)stream;
+ int block = size * nmemb;
+
+ if (!counter) {
+ return block;
+ }
+
+ counter->count++;
+
+ return block;
+}
+
+int main(void) {
+ CURL *curl;
+ struct counter *counter = (struct counter *)malloc(sizeof(struct counter));
+
+ counter->count = 0;
+
+ curl = curl_easy_init();
+ curl_easy_setopt(curl, CURLOPT_URL, "https://qiita.com/");
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, counter);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, countup);
+
+ curl_easy_perform(curl);
+ curl_easy_cleanup(curl);
+
+ printf("Total count: %d\n", counter->count);
+
+ free(counter);
+
+ return EXIT_SUCCESS;
+}
+```
+
+
+## データを保持する
+
+上記の例でなんとなく使い方が分かるかと思います。
+しかしカウントアップだけできてもしょうがないので、最後は実際にデータを保持して出力するサンプルを作ります。
+
+```c
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <curl/curl.h>
+
+struct Buffer {
+ char *data;
+ int data_size;
+};
+
+size_t buffer_writer(char *ptr, size_t size, size_t nmemb, void *stream) {
+ struct Buffer *buf = (struct Buffer *)stream;
+ int block = size * nmemb;
+ if (!buf) {
+ return block;
+ }
+
+ if (!buf->data) {
+ buf->data = (char *)malloc(block);
+ }
+ else {
+ buf->data = (char *)realloc(buf->data, buf->data_size + block);
+ }
+
+ if (buf->data) {
+ memcpy(buf->data + buf->data_size, ptr, block);
+ buf->data_size += block;
+ }
+
+ return block;
+}
+
+int main(void) {
+
+ CURL *curl;
+ struct Buffer *buf;
+
+ buf = (struct Buffer *)malloc(sizeof(struct Buffer));
+ buf->data = NULL;
+ buf->data_size = 0;
+
+ curl = curl_easy_init();
+ curl_easy_setopt(curl, CURLOPT_URL, "https://qiita.com/");
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, buf);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, buffer_writer);
+
+ curl_easy_perform(curl);
+ curl_easy_cleanup(curl);
+
+
+ printf("%s\n", buf->data);
+
+ free(buf->data);
+ free(buf);
+
+ return EXIT_SUCCESS;
+}
+```
+
+見てもらうと分かる通り、カウントアップのサンプルとほぼ同じです。
+異なる点はバッファの保存部分です。
+
+具体的には以下の部分です。
+
+```c
+// バッファ用構造体
+struct Buffer {
+ char *data;
+ int data_size;
+};
+
+size_t buffer_writer(char *ptr, size_t size, size_t nmemb, void *stream) {
+ struct Buffer *buf = (struct Buffer *)stream;
+ int block = size * nmemb;
+ if (!buf) {
+ return block;
+ }
+
+ if (!buf->data) {
+ buf->data = (char *)malloc(block);
+ }
+ else {
+ buf->data = (char *)realloc(buf->data, buf->data_size + block);
+ }
+
+ if (buf->data) {
+ memcpy(buf->data + buf->data_size, ptr, block);
+ buf->data_size += block;
+ }
+
+ return block;
+}
+```
+
+バッファを取り扱うための構造体を宣言しています。
+このバッファに、受信したデータを貯めていきます。
+その処理がこの部分。
+
+```c
+if (!buf->data) {
+ buf->data = (char *)malloc(block);
+}
+else {
+ buf->data = (char *)realloc(buf->data, buf->data_size + block);
+}
+
+if (buf->data) {
+ memcpy(buf->data + buf->data_size, ptr, block);
+ buf->data_size += block;
+}
+```
+
+最初の受信時、つまり`buf->data`が`NULL`の場合は受信したデータ分のメモリを確保します。
+
+すでにデータがある場合は既存のデータから、受信したデータ分のメモリを足した領域を再確保します。(`realloc`)
+
+メモリの確保ができたらデータをメモリに書き込みます。
+
+```c
+if (buf->data) {
+ memcpy(buf->data + buf->data_size, ptr, block);
+ buf->data_size += block;
+}
+```
+
+やっていることは、受信したデータを新しく確保した場所にコピーするだけです。
+
+`buf->data + buf->data_size`の部分はポインタのアドレスを、現在受信済のデータ分だけ進めています。その場所から、今回受信したデータを追加で書き込みます。
+最後に、受信したデータサイズを更新して終わりです。
+
+受信が終わったら、バッファに保存したデータを表示しています。
+コネクションに成功すれば`qiita.com`のHTMLがだーっと表示されます。
+
+HTMLを表示してもあまり面白みはありませんが、例えばAPIなどにアクセスしてJSONを取得し、それを使用する、という場合も同じように処理することで可能になります。
+これでAPIからデータを取ってきてなにかする、ということができるようになりますね。