Objective-C 中的线程和套接字

发布于 2024-10-05 17:01:18 字数 6561 浏览 0 评论 0原文

注意:我已经编辑了我的问题。我已经连接并执行第一个回调,但后续回调根本不执行。

这是我第一次编写 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

我们的教授还为我们提供了一个简单的回显服务器和客户端的示例(服务器只是回吐客户端发送的任何内容),并且我在线程中使用了相同的模式。

我最初的问题是我的回调拦截器线程不接受来自服务器的任何(回调)连接。服务器表示无法连接回客户端(来自 Java 的 ConnectException;它表示“连接被拒绝”)。我能够通过更改讲师的代码来解决此问题。在 connect 函数(未显示)中,他设置了使用 AF_UNSPEC 而不是 AF_INET 的提示。因此,Java 看到我的本地主机 IP 为 0:0:0:0:0:0:0:1 (采用 IPv6 格式)。当 Java 尝试连接回来发送回调时,它收到了一个异常(不确定为什么它无法连接到 IPv6 地址)。

解决这个问题后,我再次尝试了我的应用程序,这次我的客户端收到了来自服务器的回调。但是,后续回调无法工作。收到第一个回调后,繁忙循环继续运行(正如它应该的那样)。但是当服务器发送第二个回调时,客户端似乎无法读入它。在服务器端我可以看到它已成功将回调发送到客户端。只是客户端在读取数据时遇到问题。我添加了一些打印语句(见上文)用于调试,这就是我得到的:

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

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

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

囍孤女 2024-10-12 17:01:33

我想我已经成功了。因此,从 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() 所做的事情)。所以我想如果我需要响应第二个回调,我需要再次接受连接。所以我将我的繁忙循环更改为:

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

这似乎成功了。我不确定这是否是正确的方法,因此我愿意接受改进建议!

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!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文