2

:質問を編集しました。接続して最初のコールバックを実行する必要がありますが、後続のコールバックはまったく実行されません。

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

私は使用NSThreadしていて、次のようなものを書きました。

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

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インストラクターから提供されました。次のようになります。

#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

私たちの教授は、単純なエコーサーバーとクライアントの例も提供してくれました(サーバーは、クライアントが送信したものをすべて吐き出します)。私はスレッドで同じパターンを使用しました。

私の最初の問題は、コールバックインターセプタースレッドがサーバーからの(コールバック)接続を受け入れなかったことでした。サーバーは、クライアントに接続できなかったと言いました(ConnectExceptionJavaから、「接続が拒否されました」と言いました)。インストラクターのコードを変更することで、これを修正することができました。connect関数(図には示されていません)で、彼はの代わりに使用するヒントを設定しましAF_UNSPECAF_INET。そのため、Javaは私のローカルホストIPが0:0:0:0:0:0:0:1(IPv6形式で)通過するのを見ていました。Javaがコールバックを送信するために接続し直そうとしたとき、例外を受け取りました(IPv6アドレスに接続できない理由がわかりません)。

この問題を修正した後、アプリをもう一度試してみましたが、今回はサーバーからのコールバックがクライアントに受信されました。ただし、後続のコールバックは機能しません。最初のコールバックを受信した後、busy-loopは(必要に応じて)実行を続けます。しかし、サーバーが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];
4

1 に答える 1

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教授のコードにデバッグ用の印刷ステートメントをいくつか入れたところ、が0を返していることに気付きましたrecv。の戻り値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];

そしてそれはトリックをするようでした。これが正しい方法かどうかはわかりませんので、改善のための提案を受け付けています。

于 2010-12-05T09:03:48.173 に答える