iOS客户端/服务器实现和“正在进行操作”错误

时间:2012-06-13 00:24:11

标签: objective-c ios network-programming client-server nsstream

背景

我一直在从各种示例代码和经典的 Unix网络编程教科书中学习插座和网络编程,同时尝试将知识用于我正在处理的应用程序中马上。我目前正在处理应用程序的一部分,需要简单的客户端 - 服务器设置。

现在这是怎么回事(好吧,应该怎么做):

  1. 服务器使用NSNetService发布自己并使用CFSocketCreateWithNative()
  2. 创建套接字
  3. 客户端找到具有NSNetServiceBrowser
  4. 的服务器
  5. 客户端解析已发现的服务
  6. 服务器从CFSocket获取回调,它会创建一个类(MyConnection)的新实例来处理连接。使用CFStreamCreatePairWithSocket()
  7. 实现连接的读写流
  8. 客户端向服务器发送消息(@“hi”)
  9. 服务器将从客户端收到的数据发送回客户端(这就是我的问题)
  10. 客户端在UIAlertView
  11. 中显示字符串

    两个问题

    1. 当我尝试让服务器将数据发送回客户端时,我收到“正在进行操作”错误,如下面的连接代码中所示。我相信这是因为NSOutputStream没有可用的空间。处理这个问题的最佳方法是什么?我知道它应该等待NSStreamEventHasSpaceAvailable事件,但似乎它没有发生......
      编辑:duh ...当我收到此错误时,我只使用iPhone模拟器测试应用程序并让它充当服务器和客户端因为我的新公寓还没有互联网:P它似乎是一个使用两个真实设备时没有问题。

    2. 是否可以制作此服务器,以便从每个连接对象发送和接收数据不会阻止从其他连接对象发送和接收数据?每个新的连接对象是否需要在新的runloop或线程或类似的东西上?我已经围绕苹果并发文档进行了捕获,但没有任何内容跳出来...目标是尽可能快地向客户端发送回复,无论有多少其他客户端连接到服务器。
      更新:除了允许并发连接到此服务器之外,我正在考虑将连接排队并一次处理一个,因为需要发送到每个客户端的数据量非常小。这是最好的决定吗?如果队列中有数百个客户端怎么办?第二个想法,这可能是一个坏主意,因为在快速的本地网络上建立连接需要一两秒,并且需要更长时间才能使用蓝牙...我希望在这个问题上有一些专家建议:)

    3. 相关代码

      注意:APNetService和APNetServiceBrowser类似于NSNetService和NSNetServiceBrowser

      服务器代码

      - (void) startServerForGroup:(NSString *)name
      {
        self.groupName = name;
      
        NSInteger port = [self prepareListeningSocket];
      
        self.service = [[APNetService alloc] initWithDomain:@"local."
                                                       type:@"_example._tcp." 
                                                       name:self.groupName
                                                       port:port];
        self.service.delegate = self;
        [self.service publish];
      }
      
      - (NSInteger) prepareListeningSocket
      {
        int     listenfd, err, junk, port;  
        BOOL    success;
      
        struct sockaddr_in addr;
      
        port = 0;
      
        listenfd = socket(AF_INET, SOCK_STREAM, 0);
        success = (listenfd != -1);
      
        if (success) {
          bzero(&addr, sizeof(addr));  
          addr.sin_len    = sizeof(addr);
          addr.sin_family = AF_INET;
          addr.sin_port   = 0;
          addr.sin_addr.s_addr = INADDR_ANY;
          err = bind(listenfd, (const struct sockaddr *) &addr, sizeof(addr));
          success = (err == 0);
        }
        if (success) {
          err = listen(listenfd, 5);
          success = (err == 0);
        }
        if (success) {
          socklen_t   addrLen;
      
          addrLen = sizeof(addr);
          err = getsockname(listenfd, (struct sockaddr *) &addr, &addrLen);
          success = (err == 0);
      
          if (success) {
            assert(addrLen == sizeof(addr));
            port = ntohs(addr.sin_port);
          }
        }
        if (success) {
          CFSocketContext context = { 0,(__bridge void*) self, NULL, NULL, NULL };
      
          CFSocketRef socket = CFSocketCreateWithNative(
                                                        NULL, 
                                                        listenfd, 
                                                        kCFSocketAcceptCallBack, 
                                                        AcceptCallback, 
                                                        &context
                                                        );
          if (socket) {
            self.listeningSocket = socket;
            CFRelease(socket);
            success = YES;
          }
      
          if (success) {
            CFRunLoopSourceRef  rls;
      
            listenfd = -1;
      
            rls = CFSocketCreateRunLoopSource(NULL, self.listeningSocket, 0);
            assert(rls != NULL);
      
            CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
      
            CFRelease(rls);
          }
        }
      
        if ( success ) {
          return port;
        } 
        else {
          NSLog(@"FAILED TO START SERVER");
      
          if (listenfd != -1) {
            junk = close(listenfd);
            assert(junk == 0);
          }
          return -1;
        }
      }
      
      #pragma mark - Callback
      
      // Called by CFSocket when someone connects to the listening socket
      static void AcceptCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
      {
        MyServer *  obj;
      
        obj = (__bridge MyServer *) info;
      
        assert(s == obj->_listeningSocket);
      
        MyConnection *newCon = [[MyConnection alloc] initWithFileDescriptor:*(int*)data];
      
        [newCon startReceive];
      
          //add the new connection object to the servers mutable array of connections
        [obj.connections addObject:newCon];
      
      }
      

      连接代码

      - (void) startReceive
      {
        CFReadStreamRef     readStream;
        CFWriteStreamRef    writeStream;
      
        CFStreamCreatePairWithSocket(NULL, self.fd, &readStream, &writeStream);
      
        self.inputStream =  (__bridge_transfer NSInputStream *) readStream;
        self.outputStream = (__bridge_transfer NSOutputStream*) writeStream;
      
        [self.inputStream setProperty:(id)kCFBooleanTrue forKey:(NSString *)kCFStreamPropertyShouldCloseNativeSocket];
        [self.outputStream setProperty:(id)kCFBooleanTrue forKey:(NSString *)kCFStreamPropertyShouldCloseNativeSocket];
      
        self.inputStream.delegate = self;
        self.outputStream.delegate = self;
      
      
        [self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
      
        [self.inputStream open];
        [self.outputStream open];
      }
      
      
      #pragma mark - NSStreamDelegate
      
      - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
      {
        switch (eventCode) {
      
          case NSStreamEventHasBytesAvailable: {
            NSInteger       bytesRead;
            uint8_t         buffer[32768];
      
            bytesRead = [self.inputStream read:buffer maxLength:sizeof(buffer)];
            if (bytesRead == -1)...
            else if (bytesRead == 0)...
            else {
                      NSData  *data = [NSData dataWithBytes:buffer length:bytesRead];
                      [self didReceiveData:data];
            }
          } break;
          case NSStreamEventHasSpaceAvailable: {
            self.space = YES;
          } break;
      
          . . .
        }
      }
      
      - (void) didReceiveData:(NSData *)data
      {
        if (self.space) 
          NSLog(@"SPACE");
        else
          NSLog(@"NO SPACE"); //this gets printed
      
        NSInteger i = [self.outputStream write:data.bytes maxLength:data.length];
      
        if (i < 0) {
          printf("%s",strerror(errno)); //"Operation now in progress" error
        }  
      }
      

      客户代码

      #pragma mark - APNetServiceBrowserDelegate
      
      - (void) browser:(APNetServiceBrowser *)browser didAddService:(APNetService *)service moreComing:(BOOL)moreComing
      {
              //omitting checks that determine which server to connect to, if multiple
      
          service.delegate = self;
          [service resolveWithTimeout:20]; 
      }
      
      
      #pragma mark - APNetServiceDelegate
      
      - (void) netServiceDidResolveAddress:(APNetService *)service
      {    
        NSInputStream   *input; 
        NSOutputStream  *output;
      
        [service getInputStream:&input outputStream:&output];
      
        self.inputStream = input;
        self.outputStream = output;
      
        self.inputStream.delegate = self;
        self.outputStream.delegate = self;
      
        [self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
        [self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
      
        [self.inputStream open];
        [self.outputStream open];
      }
      
      
      #pragma mark - NSStreamDelegate
      
      - (void) stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
      {
          switch (eventCode) {      
      
          case NSStreamEventHasBytesAvailable: {
            NSInteger       bytesRead;
            uint8_t         buffer[32768];
      
            bytesRead = [self.inputStream read:buffer maxLength:sizeof(buffer)];
      
            if (bytesRead == -1) NSLog(@"Error reading data");
            else if (bytesRead == 0) NSLog(@"no bytes read");
            else {
                      NSData  *data = [NSData dataWithBytes:buffer length:bytesRead];
                      [self didReceiveData:data];
            }
          } break;
      
          case NSStreamEventHasSpaceAvailable: {
            if (!self.isWaitingForReply) {
              [self sendHelloMessage];
            }
          } break;
              //omitted other NSStreamEvents    
        }
      }
      
      - (void) sendHelloMessage
      {
        NSData *d = [NSKeyedArchiver archivedDataWithRootObject:@"hi"];
      
        [self.outputStream write:d.bytes maxLength:d.length];
        self.isWaiting = YES;
      }
      
      
      - (void) didReceiveData:(NSData *)data
      {
        NSString  *string = [NSKeyedUnarchiver unarchiveObjectWithData:data];
      
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Message" 
                                                        message:string
                                                       delegate:self
                                              cancelButtonTitle:@"OK"
                                              otherButtonTitles:nil];
        [alert show];
      }
      

1 个答案:

答案 0 :(得分:0)

从我看到的,你想要制作一个能够非常快速地处理许多连接的服务器。一个很好的起点文档可以是c10k页面:http://www.kegel.com/c10k.html。是的排队可能是一个好主意,它将消耗比分叉更少的资源,并将更快地响应。但是,这也意味着您的计算保持相对较小,以便您仍然能够快速回复。一个好的开始可能http://libevent.org/,检查它是如何做到的,它是这种用途的设计。

祝你好运。