2013-10-12 17 views
7

Tôi đang tạo một ứng dụng trò chuyện bằng NSStreams kết nối với một máy chủ socket đơn giản. Luồng kết nối thành công và gửi dữ liệu nhưng không thể nhận dữ liệu. Đây là lớp Socket tôi có sử dụng NSStreams:NSStream Không nhận dữ liệu

socket.h

@interface Socket : NSObject <NSStreamDelegate> 

- (void)connectToServerWithIP:(NSString *)ip andPort:(int)port; 
- (NSString *)sendMessage:(NSString *)outgoingMessage; 

@end 

Socket.m

#import "Socket.h" 

@interface Socket() 

@property (strong, nonatomic) NSInputStream *inputStream; 
@property (strong, nonatomic) NSOutputStream *outputStream; 
@property (strong, nonatomic) NSString *output; 

@end 

@implementation Socket 

@synthesize inputStream; 
@synthesize outputStream; 
@synthesize output; 

- (void)connectToServerWithIP:(NSString *)ip andPort:(int)port 
{ 
    CFReadStreamRef readStream; 
    CFWriteStreamRef writeStream; 
    CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)ip, port, &readStream, &writeStream); 
    inputStream = (__bridge_transfer NSInputStream *)readStream; 
    outputStream = (__bridge_transfer NSOutputStream *)writeStream; 
    [inputStream setDelegate:self]; 
    [outputStream setDelegate:self]; 
    [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 
    [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 
    [inputStream open]; 
    [outputStream open]; 
} 

- (NSString *)sendMessage:(NSString *)outgoingMessage 
{ 
    NSData *messageData = [outgoingMessage dataUsingEncoding:NSUTF8StringEncoding]; 
    const void *bytes = [messageData bytes]; 
    uint8_t *uint8_t_message = (uint8_t*)bytes; 
    [outputStream write:uint8_t_message maxLength:strlen([outgoingMessage cStringUsingEncoding:[NSString defaultCStringEncoding]])]; 
    while (![inputStream hasBytesAvailable]) { 
     usleep(10); 
    } 
    uint8_t buffer[1024]; 
    [inputStream read:buffer maxLength:1023]; 
    NSString *outputString = [NSString stringWithUTF8String:(char *)buffer]; 
    return outputString; 
} 

- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent { 
    NSLog(@"Stream Event: %lu", streamEvent); 

    switch (streamEvent) { 
     case NSStreamEventOpenCompleted: 
      NSLog(@"Stream opened"); 
      break; 
     case NSStreamEventHasBytesAvailable: 
      if (theStream == inputStream) { 
       uint8_t buffer[1024]; 
       long len; 
       while ([inputStream hasBytesAvailable]) { 
        len = [inputStream read:buffer maxLength:sizeof(buffer)]; 
        if (len > 0) { 
         output = [[NSString alloc] initWithBytes:buffer length:len encoding:NSASCIIStringEncoding]; 
         if (output) { 
          NSLog(@"Data: %@", output); 
         } 
        } 
       } 
      } 
      break; 
     case NSStreamEventErrorOccurred: 
      NSLog(@"Can not connect to the host!"); 
      break; 
     case NSStreamEventEndEncountered: 
      [theStream close]; 
      [theStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 
      theStream = nil; 
      break; 
     default: 
      NSLog(@"Unknown event"); 
    } 
} 

@end 

ChatViewController.m

// 
// ChatViewController.m 
// Chat 
// 
// Created by James Pickering on 10/5/13. 
// Copyright (c) 2013 James Pickering. All rights reserved. 
// 

#import "ChatViewController.h" 
#import "LoginViewController.h" 
#import "StatusView.h" 

@interface ChatViewController() 

@property (strong) IBOutlet NSTableView *people; 
@property (strong) IBOutlet NSTextField *message; 
@property (strong) IBOutlet NSButton *send; 
@property (strong) IBOutlet NSButton *loginButton; 
@property (strong) IBOutlet NSButton *settingsButton; 
@property (strong) IBOutlet NSButton *panicButton; 

@property (strong, nonatomic) NSString *recievedText; 
@property (strong, nonatomic) NSMutableArray *tableData; 
@property (strong, nonatomic) NSInputStream *inputStream; 
@property (strong, nonatomic) NSOutputStream *outputStream; 


- (void)openChat:(id)sender; 

- (IBAction)panic:(id)sender; 
- (IBAction)loginToChat:(id)sender; 

@end 

@implementation ChatViewController 

@synthesize sock; 
@synthesize recievedText; 
@synthesize inputStream; 
@synthesize outputStream; 

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 
{ 
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 
    if (self) { 
     self.isLoggedIn = FALSE; 
     sock = [[Socket alloc] init]; 
     [sock connectToServerWithIP:@"127.0.0.1" andPort:5001]; 
     //[self updateUI]; 
    } 
    return self; 
} 

- (void)updateUI 
{ 
    if (self.isLoggedIn) { 
     recievedText = [sock sendMessage:@"getPeople"]; 
     self.tableData = [[NSMutableArray alloc] initWithArray:[recievedText componentsSeparatedByString:@";"]]; 
     NSLog(@"%@", self.tableData); 
     [self.people reloadData]; 
    } 
} 

- (void)openChat:(id)sender 
{ 
    NSLog(@"tru"); 
} 

- (IBAction)panic:(id)sender { 

} 

- (IBAction)loginToChat:(id)sender { 
    NSLog(@"Called"); 
    if (self.loginPopover == nil) { 
     NSLog(@"Login Popover is nil"); 
     self.loginPopover = [[NSPopover alloc] init]; 
     self.loginPopover.contentViewController = [[LoginViewController alloc] initWithNibName:@"LoginViewController" bundle:nil]; 
    } 
    if (!self.loginPopover.isShown) { 
     NSLog(@"Login Popover is opening"); 
     [self.loginButton setTitle:@"Cancel"]; 
     [self.settingsButton setEnabled:NO]; 
     [self.send setEnabled:NO]; 
     [self.message setEnabled:NO]; 
     [self.loginPopover showRelativeToRect:self.loginButton.frame ofView:self.view preferredEdge:NSMinYEdge]; 
    } 
    else { 
     NSLog(@"Login Popover is closing"); 
     [self.loginButton setTitle:@"Login"]; 
     [self.settingsButton setEnabled:YES]; 
     [self.send setEnabled:YES]; 
     [self.message setEnabled:YES]; 
     [self.loginPopover close]; 
    } 
} 

- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView 
{ 
    return [self.tableData count]; 
} 

- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex 
{ 
    return [self.tableData objectAtIndex:rowIndex]; 
} 

- (BOOL)canBecomeKeyWindow 
{ 
    return YES; 
} 

- (BOOL)loginWithUsername:(NSString *)username andPassword:(NSString *)password 
{ 
    // Error happens here 

    recievedText = [sock sendMessage:@"login"]; 
    if ([recievedText isEqualToString:@"roger"]) { 
     recievedText = [sock sendMessage:[NSString stringWithFormat:@"%@;%@", username, password]]; 
     if ([recievedText isEqualToString:@"access granted"]) { 
      return YES; 
     } 
     else { 
      return NO; 
     } 
    } 
    else { 
     return NO; 
    } 
} 

@end 

Vấn đề là nó bị treo trên một dòng mã này mãi mãi: while (![inputStream hasBytesAvailable]) {}, nhưng tôi không biết tại sao. Máy chủ sẽ gửi lại tin nhắn.

+0

Tôi đang gặp phải sự cố tương tự ... bất kỳ giải pháp nào? –

Trả lời

4

Vì vậy, hãy xem NSStreamDelegate của bạn, có vẻ như bạn chưa triển khai tất cả các trường hợp cho tuyên bố chuyển đổi đó. Gần đây tôi đã viết một ứng dụng IRC cho OS X sử dụng NSStreamNSStreamDelegate theo cách tương tự, và tôi khá chắc chắn rằng trình biên dịch nên khiếu nại khi bạn chưa kiểm tra tất cả các trường hợp ở đó.

Nhìn lại để some of my code có vẻ như bạn nên kiểm tra các trường hợp

  • NSStreamEventHasSpaceAvailable
  • NSStreamEventOpenCompleted
  • NSStreamEventHasBytesAvailable
  • NSStreamEventEndEncountered
  • NSStreamEventErrorOccurred

Vì vậy, trường hợp bạn chưa đăng ký là NSStreamEventHasSpaceAvailable, đó là thời điểm bạn có thể bắt đầu ghi vào luồng của mình.

chỉnh sửa: Đọc lại mã của bạn, tôi thấy trong hành động sendMessage mà bạn đang sử dụng đối tượng outputStream thay vì người được ủy quyền viết và sau đó tự mình thực hiện công việc để đọc từ inputStream. Tôi nghĩ rằng bạn có thể muốn sử dụng các đại biểu và không bao giờ đọc trực tiếp từ inputstream của bạn bởi vì nó sẽ rất đơn giản hóa cách mã của bạn nhận dữ liệu từ mạng. Từ những gì tôi hiểu, NSStream là có để cung cấp một lớp trừu tượng xung quanh thực tế là dữ liệu được đệm từ mạng, do đó bạn không cần phải làm những việc như gọi usleep trong khi inputstream của bạn không có byte để đọc.

edit2: Tôi đọc nội dung cập nhật về mã của bạn không bao giờ vượt qua while (![inputStream hasBytesAvailable]) và có vẻ như vấn đề là bạn không sử dụng chính xác luồng của mình. Theo cách tôi thấy, cách tốt nhất để sử dụng NSStream là phản hồi sự kiện, sử dụng phương thức handleEvent:(NSStreamEvent) event và không bao giờ trực tiếp nói nó viết byte hoặc ngủ cho đến khi có byte.

Trong mã tôi liên kết với bạn, tôi có readDelegate và writeDelegate cả hai đều xử lý NSStreams, bạn có thể muốn xem cách tôi sử dụng writeDelegate here của mình. Về cơ bản tôi có một phương thức, addCommand:(NSString *) command đặt một chuỗi để ghi vào luồng vào hàng đợi, và sau đó khi ủy nhiệm luồng của tôi có thể ghi byte (NSStreamEventHasSpaceAvailable), tôi viết nhiều byte nhất có thể. Tôi hi vọng cái này giúp được!

+0

Cảm ơn bạn đã trả lời. Lý do tôi có dòng đầu vào làm điều đó là bởi vì tôi không muốn nó trở về nil nếu không có sẵn byte, vì vậy tôi lặp lại cho đến khi nó có thứ gì đó để trả về. Điều thú vị là khi tôi lấy dòng 'if (isLoggedIn)' ra khỏi mã, mã trong giao diện người dùng cập nhật hoạt động hoàn hảo, cả gửi và nhận dữ liệu. Tôi đang xem xét đề xuất của bạn và tôi hy vọng nó hoạt động. – jamespick

+0

Không có gì 'NSStreamHasSpaceAvailable' thay vì có 'NSStreamEventHasSpaceAvailable'. –

Các vấn đề liên quan