libevによる非同期プログラミング  #3

libevを使用したSSLでのHTTPSクライアントの例です。

xcodeに次のパスやリンクを追加します。

|ヘッダファイルパス|ライブラリファイルパス|依存ライブラリ|
|---|---|---|
|usr/local/opt/openssl/include|/usr/local/opt/openssl/lib|libcrypto.a、libssl.a|

コネクションまではブロッキングでやっています。通信に問題なければコンソールにgoogleのトップページのhtmlが表示されます。

libevのインストールなどは[#1](http://qiita.com/0xfffffff7/items/c5287067623fdbd2c3cb)をご覧ください。

```
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string>
#include <ev.h>
#include <iostream>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#define SOCKET int
#define SD_BOTH SHUT_WR

#include <openssl/ssl.h>
#include <openssl/err.h>

#define READ_BUF_SIZE 4096
#define BUF_SIZE 4097
#define PORT 443

SSL *_ssl;

void close_socket(SOCKET socket, SSL_CTX *_ctx, SSL *_ssl){
    
    // SSL/TLS接続をシャットダウンする。
    SSL_shutdown(_ssl);
    SSL_free(_ssl);
    
    ::shutdown(socket, SD_BOTH);
    ::close(socket);
    
    SSL_CTX_free(_ctx);
    ERR_free_strings();
    
}

int get_error(){
    return errno;
}

// SSLの準備
SSL_CTX * ssl_setup(){
    
    // SSLライブラリの初期化.
    SSL_library_init();
    
    // エラーを文字列化するための準備.
    SSL_load_error_strings();
    
    // グローバルコンテキスト初期化.
    const SSL_METHOD *meth = SSLv23_method();
    
    return SSL_CTX_new(meth);
}

// TCP接続.
SOCKET tcp_connect(const char* host){
    
    struct hostent *hp;
    struct sockaddr_in addr;
    SOCKET _socket;
    
    if (!(hp = gethostbyname(host))){
        return -1;
    }
    memset(&addr, 0, sizeof(addr));
    addr.sin_addr = *(struct in_addr*)hp->h_addr_list[0];
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);
    
    if ((_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))<0){
        return -1;
    }
    if (connect(_socket, (struct sockaddr *)&addr, sizeof(addr))<0){
        return -1;
    }
    
    return _socket;
}


static void io_cb (EV_P_ ev_io *w, int revents)
{
    char buf[BUF_SIZE] = { 0 };
    char* p = buf;
    int r = SSL_read(_ssl, p, READ_BUF_SIZE);
    
    if(r > 0){
        std::cout << p;
    }
    
    
    // タイムアウトで終了させるため以下をコメントアウト.
    
    // I/Oとループの関連づけを解除.
    //ev_io_stop (EV_A_ w);
    
    // イベントループの解除.
    //ev_unloop (EV_A_ EVUNLOOP_ALL);
}

static void timeout_cb (EV_P_ ev_timer *w, int revents)
{
    std::cout << "timeout\n";

    // タイマーをストップする.
    ev_timer_stop (EV_A_ w);
    
    // イベントループの解除.
    ev_unloop (EV_A_ EVUNLOOP_ONE);
}

int main(int argc, const char * argv[])
{
    
    std::string host = "www.google.co.jp";
    
    SOCKET _socket = tcp_connect(host.c_str());
    
    SSL_CTX *_ctx = ssl_setup();
    
    // sslセッションオブジェクトを作成する.
    _ssl = SSL_new(_ctx);
    
    // ソケットと関連づける.
    SSL_set_fd(_ssl, _socket);

    // SSL接続.
    int error = 0;
    if (SSL_connect(_ssl) <= 0){
        error = get_error();
        ::shutdown(_socket, SD_BOTH);
        close_socket(_socket, _ctx, _ssl);
        return 0;
    }
    
    // ノンブロッキングに設定する.
    int flag = fcntl(_socket, F_GETFL, 0);
    fcntl(_socket, F_SETFL, flag | O_NONBLOCK);
    
    
    // イベントループを作成する.
    struct ev_loop *loop = ev_loop_new (0);

    // I/Oの監視オブジェクト.
    ev_io io_watcher;
    
    // I/Oの監視オブジェクトに対して、
    // コールバックとFDとイベントのタイプを関連付ける.
    ev_io_init (&io_watcher, io_cb, _socket, EV_READ);
    
    // I/Oの監視オブジェクトとイベントループを関連付ける.
    ev_io_start (loop, &io_watcher);


    // タイマーオブジェクトを作成する.
    ev_timer timeout_watcher;
    
    // タイマーオブジェクトにコールバックを関連づけ、タイムアウトを指定する.
    ev_timer_init (&timeout_watcher, timeout_cb, 20, 0.);
    
    // タイマーオブジェクとイベントループを関連付ける.
    ev_timer_start (loop, &timeout_watcher);
    
    
    std::string str = "GET / HTTP/1.1\r\nHost: " + host;
    str = str + "\r\n\r\n";
    int w = SSL_write(_ssl, str.c_str(), static_cast<int>(str.length()));
    
    
    std::cout << "write count" << w << "\n";
    
    // ループ開始.
    ev_loop (loop, 0);

    // ブロッキングしたくない場合はこうする.
    //ev_loop (loop, EVLOOP_NONBLOCK);

    
    std::cout << "end\n";
    
    // 作成したイベントループの破棄
    ev_loop_destroy(loop);
    
    close_socket(_socket, _ctx, _ssl);
    
    return 0;
}
```

gccでのコンパイル

```
gcc -o test test.cpp -lev -I/usr/local/Cellar/libev/4.15/include/ -L/usr/local/Cellar/libev/4.15/lib/ -L/usr/local/opt/openssl/lib -I/usr/local/opt/openssl/include -lcrypto -lssl -lstdc++
```