TCP套接字无法在运行iOS 10.3.2的iPhone 7上运行

时间:2017-06-27 16:20:53

标签: ios objective-c sockets tcp nsstream

我正在通过TCP套接字communicates创建一个external serviceNSStream的应用。

我在iPhone 7 running iOS 10.3.2上遇到问题,我的应用程序的NSStream is congested and messages can't send fast enough会做出反应。 no issueiPhone 6s running iOS 10.3.2上有iPhone 6 running iOS 9two iPhone 7's running iOS 10.3.2。我在both have the same issueTCPSocketManager上尝试了此操作。

所以从本质上讲,我每秒都会向外部设备发送多条请求消息。

例如: 如果我每秒向外部服务发送3条消息,则只有一条消息会发送响应。我编写了一个回调方法,只有当我从外部设备收到ACK时才会触发。我已经使用了NSLogs并且我已经确定请求永远不会通过套接字实际发送,这让我相信这是一个iOS问题(流可以阻止在等待响应时发送其他消息吗?)

这是我的@interface TCPSocketManager () @property (weak, nonatomic)NSMutableArray *jsonObject; @property (weak, nonatomic)NSMutableArray *dataQueue; @property (nonatomic)bool sentNotif; @end static NSString *hostIP; static int hostPORT; @implementation TCPSocketManager { BOOL flag_canSendDirectly; } -(instancetype)initWithSocketHost:(NSString *)host withPort:(int)port{ hostIP = host; hostPORT = port; _completionDict = [NSMutableDictionary new]; _dataQueue = [NSMutableArray new]; CFReadStreamRef readStream; CFWriteStreamRef writeStream; CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)(host), port, &readStream, &writeStream); _inputStream = (__bridge NSInputStream *)readStream; _outputStream = (__bridge NSOutputStream *)writeStream; [self openStreams]; return self; } -(void)openStreams { [_outputStream setDelegate:self]; [_inputStream setDelegate:self]; [_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [_inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [_outputStream open]; [_inputStream open]; } -(void)closeStreams{ [_outputStream close]; [_outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [_inputStream close]; [_inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; } - (void) messageReceived:(NSString *)message { [message enumerateLinesUsingBlock:^(NSString * _Nonnull msg, BOOL * _Nonnull stop) { [_messages addObject:msg]; NSError *error; NSMutableArray *copyJsonObject = [NSJSONSerialization JSONObjectWithData:[msg dataUsingEncoding:NSUTF8StringEncoding] options:0 error:&error]; _jsonObject = [copyJsonObject copy]; NSDictionary *rsp_type = [_jsonObject valueForKey:@"rsp"]; NSString *typeKey = rsp_type[@"type”]; CompleteMsgRsp response = _completionDict[typeKey]; //assign the response to the block if (response){ dispatch_async(dispatch_get_main_queue(), ^{ response(rsp_type); }); } [_completionDict removeObjectForKey:typeKey] }]; } - (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent { switch (streamEvent) { case NSStreamEventOpenCompleted: break; case NSStreamEventHasBytesAvailable: if (theStream == _inputStream){ uint8_t buffer[1024]; NSInteger len; while ([_inputStream hasBytesAvailable]) { len = [_inputStream read:buffer maxLength:sizeof(buffer)]; if (len > 0) { NSString *output = [[NSString alloc] initWithBytes:buffer length:len encoding:NSASCIIStringEncoding]; if (nil != output) { [self messageReceived:output]; //Do Something with the message } } } } break; case NSStreamEventHasSpaceAvailable:{ //send data over stream now that we know the stream is ready to send/ receive [self _sendData]; break; } case NSStreamEventErrorOccurred: [self initWithSocketHost:hostIP withPort:hostPORT]; break; case NSStreamEventEndEncountered: [theStream close]; [theStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; break; default: DLog(@"Unknown Stream Event"); } } - (void)sendData:(NSData *)data { //insert the request to the head of a queue [_dataQueue insertObject:data atIndex:0]; //if able to send directly, send it. This flag is set in _sendData if the array is empty //Message is sent when the stream has space available. if (flag_canSendDirectly) [self _sendData]; } -(void)_sendData { flag_canSendDirectly = NO; //get the last object of the array. NSData *data = [_dataQueue lastObject]; //if data is empty, set the send direct flag if (data == nil){ flag_canSendDirectly = YES; return; } //send request out over stream, store the amount of bytes written to stream NSInteger bytesWritten = [_outputStream write:[data bytes] maxLength:[data length]]; //if bytes written is more than 0, we know something was output over the stream if (bytesWritten >0) { //remove the request from the queue. [self.dataQueue removeLastObject]; } } - (void)sendRequest:(NSString*)request withCompletion:(void (^)(NSDictionary *rsp_dict))finishBlock{ self.uuid = [[NSUUID UUID] UUIDString]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ //Convert the request string to NSData. NSData *data = [[NSData alloc] initWithData:[request dataUsingEncoding:NSASCIIStringEncoding]]; //method to send the data over stream with a queue [self sendData:data]; //Completion Handler for Messages NSString *typeKey = reqType; [_completionDict setObject:[finishBlock copy] forKey:typeKey]; }); } @end 类的代码,其中管理套接字连接(我在后台线程上发送请求,然后一旦响应回来,我就在主线程上发送回调):

sample request

这是-(void)connectToSocket{ _socketMan = [[TCPSocketManager alloc] initWithSocketHost:@"192.168.1.10" withPort:50505]; } -(void)sendSomeRequest:(NSString *)request { [_socketMan sendRequest:request withCompletion:^(NSDictionary *rsp_dict) { NSString *result =[rsp_dict objectForKey:@"result"]; if ([result length] < 3 && [result isEqualToString:@"OK"]){ //Successful request with a response }else{ //Request has failed with no/ bad response } }]; } 和TCPSocketManager类的定义:

NSStream bug?

由于此问题仅出现在iPhone 7设备上。我想知道这是不是CocoaAsyncSocket有没有人遇到任何类似的问题。我会更好地使用像CocoaAsyncSocket这样的库吗?有没有办法在不使用外部库的情况下解决问题?

之前我已经设置了COPY my_table (col1, col2, col3) FROM s3://... 并且它没有帮助我,因为它打破了消息请求和响应。它会在同一消息中发回多个响应,在解析消息时增加了更多的复杂性。

2 个答案:

答案 0 :(得分:1)

我发现您应该尝试修复代码中的几个问题。

  1. 您可以同时访问_dataQueue数组:您从另一个线程(具有全局队列)写入它但在主线程上读取。

  2. 由于您在主线程上使用流,因此应避免像处理NSStreamEventHasBytesAvailable事件那样使用循环。只需将一些数据读入缓冲区并将其存储在NSMutableString中。

  3. 数据可能不完整,因此您应手动检查缓冲区中是否有完整的行,如果是,则只处理它。当您使用enumerateLinesUsingBlock:时,可能会有不完整的行,您将松开那条不完整的行。

答案 1 :(得分:0)

经过大量的反复试验,我得出的结论是,由于我快速发送相对较小的字节消息,TCP数据包在内核级别被合并。在查看这个问题时,我遇到了Nagles Algorithm

Nagle's algorithm works by combining a number of small outgoing messages, and sending them all at once. Specifically, as long as there is a sent packet for which the sender has received no acknowledgment, the sender should keep buffering its output until it has a full packet's worth of output, thus allowing output to be sent all at once.

我的问题的解决方案在question上找到。

基本上,为了解决我的问题,我需要像这样禁用Nagles算法:

#import <arpa/inet.h>       // for IPPROTO_TCP
#include <netinet/tcp.h>    // for TCP_NODELAY

- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {
    switch (streamEvent) {
      case NSStreamEventOpenCompleted:
        [self disableNaglesAlgorithmForStream:theStream];
        break;
    ...
}


//from tar500's answer in the linked question.
-(void)disableNaglesAlgorithmForStream:(NSStream *)stream {
    CFDataRef socketData;

    // Get socket data
    if ([stream isKindOfClass:[NSOutputStream class]]) {
        socketData = CFWriteStreamCopyProperty((__bridge CFWriteStreamRef)((NSOutputStream *)stream), kCFStreamPropertySocketNativeHandle);
    } else if ([stream isKindOfClass:[NSInputStream class]]) {
        socketData = CFReadStreamCopyProperty((__bridge CFReadStreamRef)((NSInputStream *)stream), kCFStreamPropertySocketNativeHandle);
    }

    // get a handle to the native socket
    CFSocketNativeHandle rawsock;

    CFDataGetBytes(socketData, CFRangeMake(0, sizeof(CFSocketNativeHandle)), (UInt8 *)&rawsock);
    CFRelease(socketData);

    // Disable Nagle's algorythm

    // Debug info
    BOOL isInput = [stream isKindOfClass:[NSInputStream class]];
    NSString * streamType = isInput ? @"INPUT" : @"OUTPUT";

    int err;
    static const int kOne = 1;
    err = setsockopt(rawsock, IPPROTO_TCP, TCP_NODELAY, &kOne, sizeof(kOne));
    if (err < 0) {
        err = errno;
        NSLog(@"Could Not Disable Nagle for %@ stream", streamType);
    } else {
        NSLog(@"Nagle Is Disabled for %@ stream", streamType);
    }
}

禁用Nagles后,我的套接字消息似乎响应更快,而且我没有再遇到数据包合并。