CFSocket で UDP を待ち受ける : Objective-C プログラミング
PROGRAM
CFSocket で UDP を待ち受ける
CFSocket を使用して UDP ポートの待ち受けを行う場合には、次のような流れになります。
ここでは、クラス "MyClass" に、UDP による待ち受けを実装しているものとして、話を進めて行くことにします。
MyClass.h 内での定義
// 必要なヘッダーは次のような感じです。
#import <Foundation/Foundation.h>
#import <netinet/in.h>
#import <sys/socket.h>
#import <arpa/inet.h>
@interface MyClass : NSObject
// パケットを受信したときに呼び出されるコールバック関数を定義します。(C 形式)
static void MyClassSocketReceivedCallback(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void* pData, void* pInfo);
// パケットを受信したときに呼び出されるコールバック関数を定義します。(Objective-C 形式)
- (void)socketReceivedCallback:(CFSocketRef)socket type:(CFSocketCallBackType)type address:(CFDataRef)address data:(NSData*)pData;
MyClass.mm での実装部分
// パケットを受信したときに呼び出されるコールバック関数を定義します。
static void MyClassSocketReceivedCallback(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void* pData, void* pInfo)
{
// 即座に Objective-C 形式のコールバックを呼ぶようにしています。
[(MyClass*)pInfo socketRecievedCallback:socket type:type address:address data:(NSData*)pData];
}
// Objective-C 形式のコールバックメソッドでパケット受信の処理を行います。
- (void)socketReceivedCallback:(CFSocketRef)socket type:(CFSocketCallBackType)type address:(CFDataRef)address data:(NSData*)pData
{
}
// UDP によるソケットの待ち受けを行います。
- (void)listening
{
CFSocketRef socket;
CFSocketContext context;
// ソケットに設定するコンテキストを準備します。
// version は必ず 0 を指定し、ここでは info に self を設定しています。
context.version = 0;
context.info = (void*)self;
context.retain = NULL;
context.release = NULL;
context.copyDescription = NULL;
// UDP ソケットを生成します。データを受信した時に呼び出すコールバック関数もここで指定します。
socket = CFSocketCreate(kCFAllocaterDefault, PF_INET, SOCK_DGRAM, IPPROTO_UDP, kCFSocketDataCallBack, (CFSocketCallBack)MyClassSocketReceivedCallback &context);
if (socket != NULL)
{
// UDP ソケットが生成できたら、続いてアドレスへのバインドを行います。
CFDataRef address;
struct sockaddr_in addr;
// アドレスを設定する構造体 sockaddr_in を初期化しています。
memset(&addr, 0, sizeof(struct sockaddr_in));
// 待ち受けに使用するアドレスとポートを設定します。
// 任意のアドレスで待ち受けたい場合は INADDR_ANY を sin_addr.s_addr に設定します。
addr.sin_family = AF_INET;
addr.sin_port = htons(10000);
addr sin_addr.s_addr = inet_addr("192.168.1.1");
// 先ほどの sockaddr_in を使用して、ソケットに割り当てるアドレスデータを生成します。
address = CFDataCreateWithBytesNoCopy(NULL, (UInt8*)&addr, sizeof(struct sockaddr_in), kCFAllocatorNull);
// ソケットをバインドします。
if (CFSocketSetAddress(socket, address) == kCFSocketSuccess)
{
// バインドに成功したら、CFRunLoop を使ってパケットが到着するのを待ちます。
CFRunLoopSourceRef source;
// ソケットから CFRunLoop に登録するための入力ソースを取得します。
source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
// ソケットの入力ソースをカレントランループに追加します。
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
// 登録し終わった、手元の入力ソースを解放します。
CFRelease(source);
}
else
{
// バインドに失敗した場合は、ソケットの後始末を行います。
CFSocketInvalidate(socket);
// 他、必要なエラー処理を行います。
}
// 登録し終えて、手元に残ったアドレスデータとソケットを解放します。
CFRelease(address);
CFRelease(socket);
}
else
{
// ソケットの生成に失敗した場合の処理です。
}
}
このようにすることで、上記の例では "192.168.1.1:10000/UDP" での待ち受けを開始し、にデータが到着した時に、コールバックメソッド "socketRecievedCallback:type:address:data:" が呼び出されるようになります。
勝手がわかるまでは長くて難しそうなコードですけど、順を追って読んでみると、それほど難しくない流れになっています。
このように CFRunLoop を使用すると、データが到着したかどうかとか、そういったデータの待ちを行う処理を全て Core Foundation に任せることができるので、プログラミングで必要なこととしては、待ち受けるアドレスやポートとプロトコルを設定するといった程度になります。
[ もどる ]