2010-12-05 13 views
2

注::質問を編集しました。接続しての最初のコールバックを実行しますが、後続のコールバックはまったく実行されません。Objective-Cのスレッディングとソケット

これは私の初めてのObjective-C(GNUstep付き、これは宿題用です)です。私は解決策を持っているが、私はそれに何かを追加しようとしている。このアプリは、サーバーに接続し、そこからデータを取得するGUIクライアントです。複数のクライアントが同じサーバーに接続できます。いずれかのクライアントがサーバー上のデータを変更すると、サーバーは登録されているすべてのクライアントにコールバックを送信します。このソリューションはもともとJava(クライアントとサーバーの両方)に実装されていました。最新の課題では、教授はObjective-Cクライアントを書くことを希望しました。彼はコールバックを処理する必要はないと言ったが、とにかく試してみたかった。

私はNSThreadを使用していると私はこのようなものを書いた:に提供されているCallbackInterceptorThread.m

#import <Foundation/Foundation.h> 
#import "CallbackInterceptorThread.h" 

#define MAXDATASIZE 4096 

@implementation CallbackInterceptorThread 

- (id) initWithClientPort: (NSString*) aClientPort 
       appDelegate: (AppDelegate*) anAppDelegate { 

    if((self = [super init])) { 
     [clientPort autorelease]; 
     clientPort = [aClientPort retain]; 
     [appDelegate autorelease]; 
     appDelegate = [anAppDelegate retain]; 
    } 

    return self; 
} 

- (void) main { 

    GSRegisterCurrentThread(); 

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 

    char* buffer = malloc(MAXDATASIZE); 
    Cst420ServerSocket* socket = [[Cst420ServerSocket alloc] initWithPort: clientPort]; 
    [socket retain]; 

    NSString* returnString; 

    while(YES) { 
     printf("Client waiting for callbacks on port %s\n", [clientPort cString]); 

     if([socket accept]) { 
     printf("Connection accepted!\n"); 
     while(YES) { 
      printf("Inner loop\n"); 
      sleep(1); 

      returnString = [socket receiveBytes: buffer maxBytes: MAXDATASIZE beginAt: 0]; 
      printf("Received from Server |%s|\n", [returnString cString]); 
      if([returnString length] > 0) { 
       printf("Got a callback from server\n"); 

       [appDelegate populateGui]; 
      } 

      printf("Going to sleep now\n"); 
      sleep(1); 
     } 

     [socket close]; 
     } 
    } 
} 

@end 

Cst420ServerSocket

CallbackInterceptorThread.h

#import <Foundation/Foundation.h> 
#import "AppDelegate.h" 

@interface CallbackInterceptorThread : NSThread { 
    @private 
    NSString* clientPort; 
    AppDelegate* appDelegate; 
} 

- (id) initWithClientPort: (NSString*) aClientPort 
       appDelegate: (AppDelegate*) anAppDelegate; 
- (void) main; 
@end 

を私たちはインストラクター。それは次のようになります。

#import "Cst420Socket.h" 
#define PORT "4444" 

/** 
* Cst420Socket.m - objective-c class for manipulating stream sockets. 
* Purpose: demonstrate stream sockets in Objective-C. 
* These examples are buildable on MacOSX and GNUstep on top of Windows7 
*/ 

// get sockaddr, IPv4 or IPv6: 
void *get_in_addr(struct sockaddr *sa){ 
    if (sa->sa_family == AF_INET) { 
     return &(((struct sockaddr_in*)sa)->sin_addr); 
    } 
    return &(((struct sockaddr_in6*)sa)->sin6_addr); 
} 

@implementation Cst420ServerSocket 

- (id) initWithPort: (NSString*) port{ 
    self = [super init]; 
    int ret = 0; 
    memset(&hints, 0, sizeof hints); 
    hints.ai_family = AF_INET; 
    hints.ai_socktype = SOCK_STREAM; 
    hints.ai_flags = AI_PASSIVE; // use my IP 
    const char* portStr = [port UTF8String]; 
    if ((rv = getaddrinfo(NULL, portStr, &hints, &servinfo)) != 0) { 
     fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); 
     ret = 1; 
    }else{ 
     for(p = servinfo; p != NULL; p = p->ai_next) { 
     if ((sockfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP))==-1){ 
      perror("server: socket create error"); 
      continue; 
     } 
     if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) { 
#if defined(WINGS) 
     closesocket(sockfd); 
#else 
     close(sockfd); 
#endif 
      perror("server: bind error"); 
      continue; 
     } 
     break; 
     } 
     if (p == NULL) { 
     fprintf(stderr, "server: failed to bind\n"); 
     ret = 2; 
     }else{ 
     freeaddrinfo(servinfo); // all done with this structure 
     if (listen(sockfd, BACKLOG) == -1) { 
      perror("server: listen error"); 
      ret = 3; 
     } 
     } 
     if (ret == 0){ 
     return self; 
     } else { 
     return nil; 
     } 
    } 
} 

- (BOOL) accept { 
    BOOL ret = YES; 
#if defined(WINGS) 
    new_fd = accept(sockfd, NULL, NULL); 
#else 
    new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size); 
#endif 
    if (new_fd == -1) { 
     perror("server: accept error"); 
     ret = NO; 
    } 
    connected = ret; 
    return ret; 
} 

- (int) sendBytes: (char*) byteMsg OfLength: (int) msgLength Index: (int) at{ 
    int ret = send(new_fd, byteMsg, msgLength, 0); 
    if(ret == -1){ 
     NSLog(@"error sending bytes"); 
    } 
    return ret; 
} 

- (NSString*) receiveBytes: (char*) byteMsg 
        maxBytes: (int) max 
        beginAt: (int) at { 
    int ret = recv(new_fd, byteMsg, max-1, at); 
    if(ret == -1){ 
     NSLog(@"server error receiving bytes"); 
    } 
    byteMsg[ret+at] = '\0'; 
    NSString * retStr = [NSString stringWithUTF8String: byteMsg]; 
    return retStr; 
} 

- (BOOL) close{ 
#if defined(WINGS) 
    closesocket(new_fd); 
#else 
    close(new_fd); 
#endif 
    connected = NO; 
    return YES; 
} 

- (void) dealloc { 
#if defined(WINGS) 
    closesocket(sockfd); 
#else 
    close(sockfd); 
#endif 
    [super dealloc]; 
} 

@end 

私たちの教授はまた、私たちの単純なエコーサーバとクライアント(サーバが戻ったばかりのクライアントがそれを送ったものは何でも吐く)の例を提供し、私はスレッドで同じパターンを使用しました。

私の最初の問題は、私のコールバックインターセプタスレッドがサーバからの(コールバック)接続を受け入れなかったことです。サーバーはクライアントに接続できないと言いました(ConnectException Javaから、 "Connection refused"と言われました)。私はインストラクターのコードを変更することでこれを解決することができました。 connect関数(図示せず)では、AF_INETの代わりにAF_UNSPECを使用するようヒントを設定していました。そこで、Javaは自分のlocalhost IPが0:0:0:0:0:0:0:1(IPv6形式)となっているのを見ていました。 Javaがコールバックを送信するために戻って接続しようとすると、例外を受け取りました(なぜIPv6アドレスに接続できないのかわかりません)。

この問題を修正した後、もう一度アプリを試してみましたが、今回はクライアントからサーバーからのコールバックを受信しました。ただし、以降のコールバックは機能しません。最初のコールバックを受信した後、ビジーループは(必要に応じて)実行し続けます。しかし、サーバーが2番目のコールバックを送信すると、クライアントはそれを読み取れないように見えます。サーバー側では、コールバックをクライアントに正常に送信したことがわかります。クライアントがデータの読み込みに問題があるだけです。私はデバッグのために(上記参照)いくつかのprint文を追加し、これは私が得るものです:ここでは

Client waiting for callbacks on port 2020 
Connection accepted! 
Inner loop 
Received from Server |A callback from server to 127.0.0.1:2020| 
Got a callback from server 
Going to sleep now 
Inner loop 
Received from Server || 
Going to sleep now 
Inner loop 
Received from Server || 
Going to sleep now 
Inner loop 
... (and it keeps going regardless of the second callback being sent) 

は、私は(GUI)からスレッドを開始しています方法です:

CallbackInterceptorThread* callbackInterceptorThread = [[CallbackInterceptorThread alloc] initWithClientPort: clientPort appDelegate: self]; 
[callbackInterceptorThread start]; 
+0

正しいIPとポートにバインドしていて、クライアントが実際に接続していることを確認してください。それは間違っていると私の推測だろう。 'ハングする'と思われる理由は、あなたのソケットがブロッキング用に設定され、accept callブロックが設定されているためです。これは、以前にバインドしたソケットに何かが実際に接続しようとするまで、 –

+0

@Jasonはい、私はそれがブロッキングコールであると考えました(そして、それは私が欲しいものです)。あなたは部分的に正しいと思われる:)それは、IPv4の代わりに(サーバ側から)IPv6アドレスにバインドしようとしていて、それが問題を引き起こしているようだ。私はその問題を修正することができましたが、今は後続コールバックの問題が働いていません。 –

答えて

0

私は」だと思いますそれは働いている。だから、Java側(サーバー)から、これは私がやっていたものだった。

Socket socket = new Socket(clientAddress, clientPort); 
BufferedOutputStream out = new BufferedOutputStream(socket.getOutputStream()); 
out.write(("A callback from server to " + clientAddress + ":" + clientPort).getBytes()); 
out.flush(); 
out.close(); 

を私は教授のコードでは、いくつかのデバッグプリントステートメントを入れてreceiveBytesで、recvは0です。戻り値を返していましたことに気づきましたrecvは、受信したメッセージの長さです。そこで、長さゼロの文字列を受け取りました。しかし戻り値0は、ピアが正しく接続を閉じたことを意味します(正確には私がJava側からout.close()で行ったことです)。だから私は2番目のコールバックに応答する必要がある場合、私はaccept接続を再度必要があると考えました。だから私はビジー・ループをこれに変更しました:

printf("Client waiting for callbacks on port %s\n", [clientPort cString]); 
while([socket accept]) { 
    printf("Connection accepted!\n");  

    returnString = [socket receiveBytes: buffer maxBytes: MAXDATASIZE beginAt: 0]; 
    printf("Received from Server |%s|\n", [returnString cString]); 

    if([returnString length] > 0) { 
     printf("Got a callback from server\n"); 
     [appDelegate populateGui]; 
    } 
} 

[socket close]; 

これはやっているようでした。これがの場合はの方法であるかどうかわかりませんので、改善のための提案があります。