开发者

Threading and Sockets in Objective-C

开发者 https://www.devze.com 2023-01-28 18:30 出处:网络
NOTE: I\'ve edited my question. I\'ve got it to connect and perform the first callback, but subsequent callbacks don\'t go through at all.

NOTE: I've edited my question. I've got it to connect and perform the first callback, but subsequent callbacks don't go through at all.

This is my first time writing Objective-C (with GNUstep; it's for a homework assignment). I've got the solution working, but I am trying to add something more to it. The app is a GUI client that connects to a server and gets data from it. Multiple clients can connect to the same server. If any one of the clients changes data that is residing on the server, the server sends a callback to all registered clients. This solution was originally implemented in Java (both client and server) and for the latest assignment, the professor wanted us to write an Objective-C client for it. He said that we don't need to handle callbacks, but I wanted to try anyway.

I am using NSThread and I wrote something that looks like this:

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 has been provided to us by the instructor. It looks like this:

#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

Our professor also provided us an example of a simple echo server and client (the server just spits back whatever the client sent it) and I've used the same pattern in the thread.

My initial problem was that my callback interceptor thread didn't accept any (callback) connections from the server. The server said that it could not connect back to the client (ConnectException from Java; it said "Connection refused"). I was able to fix this by changing my instructor's code. In the connect function (not shown), he had set the hints to use AF_UNSPEC instead of AF_INET. So Java was seeing my localhost IP come through as 0:0:0:0:0:0:0:1 (in IPv6 format). When Java tried to connect back to send a callback, it received an exception (not sure why it cannot connect to an IPv6 address).

After fixing this problem, I tried out my app again and this time the callback from the server was received by my client. However, subsequent callbacks fail to work. After receiving the first callback, the busy-loop keeps running (as it should). But when the server sends a second callback, it looks like the client cannot read it in. On the server side I can see that it sent the callback to the client successfully. It's just that the client is having trouble reading in the data. I added some print statements (see above) for debugging and this is what I get:

Client waiting for callbacks on port 2020
Connection a开发者_开发百科ccepted!
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)

Here is how I am starting the thread (from the GUI):

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


I think I've got it working. So from the Java side (the server), this was what I was doing:

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();

I put some debugging print-statements in my professor's code and noticed that in receiveBytes, recv was returning 0. The return value of recv is the length of the message that it received. So it received a zero-length string. But a return value of 0 also means that the peer closed the connection properly (which is exactly what I had done from the Java side with out.close()). So I figured that if I needed to respond to the second callback, I would need to accept the connection again. So I changed my busy loop to this:

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];

and that seemed to do the trick. I am not sure if this is the right way to do it, so I am open to suggestions for improvement!

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号