« Core Foundationをもっと使おうじゃないか / CFHostで非同期に名前解決 | ホーム | もっとCore Foundation / サーバソケットとCFStreamと私 »

2011年5月16日

もっとCore Foundation / CFStreamこそがtoll-freeだよ

さて前のエントリでCFHostをうっかりtoll-freeだと勘違いして書いてしまったせいで、引き続きのこのエントリが内容がらりと変えることになってしまいましてね、と前置きをしつつの第2回であります。

さて無事クエリを解決できたCFHostを用いていよいよ通信の確立ですが。CFStream APIを使えばそのまま非同期接続が可能なのでそれで充分です。さっそく行ってみる。ざっくり。

static int
_setup_read_stream(CFReadStreamRef readStream)
{
    CFStreamClientContext context = { 0, NULL, NULL, NULL, NULL };
    if (!CFReadStreamSetClient(readStream,
                               kCFStreamEventOpenCompleted |
                               kCFStreamEventHasBytesAvailable |
                               kCFStreamEventErrorOccurred |
                               kCFStreamEventEndEncountered,
                               _read_stream_callback, &context)) {
        CFStreamError err = CFReadStreamGetError(readStream);
        NSLog(@"err = %d/%ld", err.error, err.domain);
        return -1;
    }
    CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
    
    if (!CFReadStreamOpen(readStream)) {
        // error.
        CFStreamError err = CFReadStreamGetError(readStream);
        NSLog(@"err = %d/%ld", err.error, err.domain);
        
        CFReadStreamSetClient(readStream, kCFStreamEventNone, NULL, NULL);
        CFReadStreamUnscheduleFromRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
        return -1;
    }
    
    return 0;
}

static int
_setup_write_stream(CFWriteStreamRef writeStream)
{
    CFStreamClientContext context = { 0, NULL, NULL, NULL, NULL };
    
    if (!CFWriteStreamSetClient(writeStream,
                                kCFStreamEventOpenCompleted |
                                kCFStreamEventCanAcceptBytes |
                                kCFStreamEventErrorOccurred |
                                kCFStreamEventEndEncountered,
                                _write_stream_callback, &context)) {
        CFStreamError err = CFWriteStreamGetError(writeStream);
        NSLog(@"err = %d/%ld", err.error, err.domain);
        return -1;
    }
    CFWriteStreamScheduleWithRunLoop(writeStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
    
    if (!CFWriteStreamOpen(writeStream)) {
        // error.
        CFStreamError err = CFWriteStreamGetError(writeStream);
        NSLog(@"err = %d/%ld", err.error, err.domain);
        
        CFWriteStreamSetClient(writeStream, kCFStreamEventNone, NULL, NULL);
        CFWriteStreamUnscheduleFromRunLoop(writeStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
        return -1;
    }
    
    return 0;
}

static void
_do_connect(CFHostRef host)
{
    NSLog(@"host = %@", host);
    
    CFReadStreamRef readStream = NULL;
    CFWriteStreamRef writeStream = NULL;
    
    CFStreamCreatePairWithSocketToCFHost(kCFAllocatorDefault, host, 80, &readStream, &writeStream);
    
    NSLog(@"readStream = %@", readStream);
    NSLog(@"writeStream = %@", writeStream);
    
    if (_setup_read_stream(readStream) != 0) {
        CFRelease(readStream);
        readStream = NULL;
    }
    if (_setup_write_stream(writeStream) != 0) {
        CFRelease(writeStream);
        writeStream = NULL;
    }

    NSLog(@"connect start.");
}

_do_connect()で使うCFStreamCreatePairWithSocketToCFHost()からCFReadStream/CFWriteStreamオブジェクトを取り出してこれをCFRunLoopにバインドするまでで一連の処理になります。read/writeともに、使用するAPIの流れは以下の通り。

  1. CFXxxStreamSetClient()
  2. CFXxxStreamScheduleWithRunLoop()
  3. CFXxxStreamOpen()

最初のCFXxxStreamSetClient()でコールバック関数を指定した後、Run Loopに登録。その後にCFXxxStreamOpen()でCFStreamを開けばコールバック関数に処理が渡り始める。

static void
_read_stream_callback(CFReadStreamRef readStream, CFStreamEventType eventType, void* info)
{
    NSLog(@"readStream = %p, eventType = %ld", readStream, eventType);
    
    switch (eventType) {
        case kCFStreamEventOpenCompleted:
            ...            
        case kCFStreamEventHasBytesAvailable:
            ...
        case kCFStreamEventEndEncountered:
            ...
        case kCFStreamEventErrorOccurred:
            ...
    }
}

static void
_write_stream_callback(CFWriteStreamRef writeStream, CFStreamEventType eventType, void* info)
{
    NSLog(@"writeStream = %p, eventType = %ld", writeStream, eventType);
    
    switch (eventType) {
        case kCFStreamEventOpenCompleted:
            ...
        case kCFStreamEventCanAcceptBytes:
            ...
        case kCFStreamEventEndEncountered:
            ...
        case kCFStreamEventErrorOccurred:
            ...
    }
}

一方で、ここまでコールバックを設定するのにCFHostClientContextおよびCFStreamClientContextという構造体があったけれども、これがコールバック関数の引数に渡ってくることになる。

struct CFHostClientContext {
  CFIndex			 version;
  void *			  info;
  CFAllocatorRetainCallBack  retain;
  CFAllocatorReleaseCallBack  release;
  CFAllocatorCopyDescriptionCallBack  copyDescription;
};
typedef struct CFHostClientContext	  CFHostClientContext;

typedef CALLBACK_API_C( void , CFHostClientCallBack )(CFHostRef theHost, CFHostInfoType typeInfo, const CFStreamError *error, void *info);

typedef struct {
    CFIndex version;
    void *info;
    void *(*retain)(void *info);
    void (*release)(void *info);
    CFStringRef (*copyDescription)(void *info);
} CFStreamClientContext;

typedef void (*CFReadStreamClientCallBack)(CFReadStreamRef stream, CFStreamEventType type, void *clientCallBackInfo);
typedef void (*CFWriteStreamClientCallBack)(CFWriteStreamRef stream, CFStreamEventType type, void *clientCallBackInfo);

CFHostClientContextおよびCFStreamClientContextのinfoフィールドで指定したアドレスが、CFHostClientCallBackの第4引数info、CFReadStreamClientCallBack/CFWriteStreamClientCallBackの第3引数clientCallBackInfoに渡ってくる。

このCFHostClientContext/CFStreamClientContextでは、infoフィールドのアドレスをCoreFoundationオブジェクトが標準でそうであるように、参照カウントの仕組みを前提としており、infoを引数にするretain/release関数を指定することができる。それぞれretain/releaseフィールドはこの用途で使われる。また、copyDescriptionフィールドは、デバッグ用途などでオブジェクトの内容を文字列化する際に使うことができる。CocoaでNSLogに書式化文字列を使って"%@"にオブジェクトを直接ぶち込み、オブジェクトの内容をコンソールに簡易に出力することができるアレのこと。

ただ、retain/release/copyDescriptionフィールドはあまり重要性が高くなく、Cocoaとの連携で考える限りにおいて参照カウントの管理についてもObjective-C側でのみ面倒を見ていれば大抵は用が足りると思われ、あんまりそこは気にしなくてよさそうな感触である。

トラックバック(0)

トラックバックURL: http://foursics.jp/cgi-bin/mt/mt-tb.cgi/339

コメントする

OpenID対応しています OpenIDについて

このブログ記事について

このページは、Hironobu Kouraが2011年5月16日 18:13に書いたブログ記事です。

ひとつ前のブログ記事は「Core Foundationをもっと使おうじゃないか / CFHostで非同期に名前解決」です。

次のブログ記事は「もっとCore Foundation / サーバソケットとCFStreamと私」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。