[MacOSX] Socket on MacOSX〜サーバ
BSDを使ったSocketでの通信
というのが,MacOSXではできるらしい.
まぁ,中身がBSDなんだから,当たり前っていえば当たり前なんだが.
まず,先に参考文献
・http://www.macdevcenter.com/pub/a/mac/2002/12/26/cocoa.html
・http://yudoufu.velvet.jp/sock.html
・http://www.cocoadev.com/index.pl?SocketClasses
どうやら,NSFileHandleを使うのがよさそうだ.
サーバー実装
NSFileHandleとSocket APIをカプセル化する前に,とりあえず,単純なCで実装してみる.
サーバーでの処理は,(1)ソケット作成→(2)クライアントのアクセスを待つ→(3)クライアントと接続→(4)通信の流れになる.
void main(void){
NSFileHandle pFileHandle;
int nConnection;
int nSocket;
int result=0;
struct sockaddr_in addr;
// ソケット作成
nSockfd = socket(AF_INET, SOCK_STREAM, 0);
// 構造体初期化
memset( &addr, 0, sizeof( addr ) );
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(5001);
// ソケットをバインド
result = bind(nSockfd, (struct sockaddr *)&addr, sizeof(addr));
if ( result < 0) {
[NSException raise: @"SockBindingException" format: @"Can't bind socket: %s", strerror(errno)];
[NSApp terminate:self];
}
result = listen(nSockfd, 1);
if ( result < 0) {
[NSException raise: @"SockListeningException" format: @"Can't listen socket: %s", strerror(errno)];
[ self close ];
[NSApp terminate:self];
}
ソケットを作って,クライアントからのアクセスを待つ.
このサンプルは,5001ポートを使ってアクセスする.
一応,Cocoaでの実装を前提としているので,NSApp等が残っている・・・・・.
exitに書き換えましょう・・・.
listenでクライアントからのアクセスを待っている間は,プログラムは止まってしまう.
acceptで,通信が開かれる.
戻り値がその接続先になる.
nConnection = accept(nSockfd, (struct sockaddr *)NULL, NULL);
if ( nConnection < 0) {
[NSException raise: @"SockAcceptException" format: @"Can't accept socket: %s", strerror(errno)];
[ self close ];
[NSApp terminate:self];
}
pFileHandle = [[NSFileHandle alloc] initWithFileDescriptor: nConnection];
NSData *data = [pFileHandle availableData];
[pFileHandle writeData: data];
}
nConnectionの値でNSFileHandleを初期化する.
そして,クライアントから受け付けているデータを,availableDataメッセージで取得する.
[pFileHandle writeData: data];
writeDataで,クライアント側にデータを投げることができる.
動作確認
ターミナルでtelnetを起動して,localhost内で通信する.
>telnet localhost 5001 >Trying ::1... >telnet: connect to address ::1: Connection refused >Trying 127.0.0.1... >Connected to localhost. >Escape character is '^]'.
ここで,なんか文字を入力すると,
>hello >hello
サーバー側から入力した文字が帰ってくる.はず.
reuse?
ここで,おそらく問題発生?
このままだと,プログラムを終了した後,すぐに起動すると,reuseエラーが出てしまう.
これは,ポートの開放が終わってないため?らしい.
以下のコードをbindを実行する前に記述しておく.
// reuse用の設定 int on=1; setsockopt(m_nSockfd, SOL_SOCKET, SO_REUSEADDR, (char * ) & on, sizeof (on));
参考文献
・http://mikilab.doshisha.ac.jp/dia/research/report/2002/0408/005/report20020408005.html
カプセル化!
んで,こいつをカプセル化してみる.
Socketを制御するものは,NSObjectを継承して作成してみた.
@interface NSSocketServer : NSObject
{
NSFileHandle *m_pFileHandle;
int m_nSockfd;
int m_nConnection;
}
// overwride
- (id) init;
- (void) close;
// outlet action
- (IBAction)startServer:(id)sender;
// other method
- (void) stopUntilAccept;
- (void) initializeFileHandle;
- (void) dealloc;
- (void) sendMessage;
- (void) receiveMessage:(NSNotification*)notification;
@end
後は,いちいち説明するのも面倒なので,詳しくはソースコードを見てください.
Notification
Cocoa上で実装すると,NSFileHandleのNotificationが使えたりするので,非常に便利である.
その辺をちょっと説明してみる.
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(receiveMessage:) name: NSFileHandleReadCompletionNotification object: m_pFileHandle]; [m_pFileHandle readInBackgroundAndNotify];
こいつで,NSNotificationCenterに,NSFileHandleがデータの読み取りが終わったことを通知してもらう.このNotificationのselectorの実装は決まっている.readInBackgroundAndNotifyは,バックグラウンドでNofiticationを送るように設定するメッセージである.この通知は一度通知されると二度と通知されないので,readInBackgroundAndNotifyを通知の度にNSFileHandleに送る必要がある.
- (void)receiveMessage: (NSNotification*)notification;
この関数を実装してデータを読み取る.
- (void)receiveMessage: (NSNotification*)notification {
// check error
NSNumber *pUNIXError = [ NSNumber numberWithInt:0 ];
pUNIXError = [[notification userInfo] objectForKey:@"NSFileHandleError"];
if( ![ pUNIXError intValue ] ){
NSData *data;
data = [[notification userInfo] objectForKey:NSFileHandleNotificationDataItem];
NSString *string = [[NSString alloc] initWithData: data encoding: NSASCIIStringEncoding];
NSLog(string);
[m_pFileHandle readInBackgroundAndNotify];
}
else{
close(m_nSockfd);
close(m_nConnection);
[m_pFileHandle release ];
}
}
安全のために,NSFileHandleErrorでNotification時のエラーを取得することができる.これで成功を一応,確認しておき,データの読み取りに移行させることができる.
http://code.google.com/p/sonson-code/source/browse/#svn/trunk/mac/socket