我正在通过TCP套接字communicates
创建一个external service
带NSStream
的应用。
我在iPhone 7 running iOS 10.3.2
上遇到问题,我的应用程序的NSStream is congested and messages can't send fast enough
会做出反应。 no issue
或iPhone 6s running iOS 10.3.2
上有iPhone 6 running iOS 9
个two iPhone 7's running iOS 10.3.2
。我在both have the same issue
和TCPSocketManager
上尝试了此操作。
所以从本质上讲,我每秒都会向外部设备发送多条请求消息。
例如: 如果我每秒向外部服务发送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://...
并且它没有帮助我,因为它打破了消息请求和响应。它会在同一消息中发回多个响应,在解析消息时增加了更多的复杂性。
答案 0 :(得分:1)
我发现您应该尝试修复代码中的几个问题。
您可以同时访问_dataQueue
数组:您从另一个线程(具有全局队列)写入它但在主线程上读取。
由于您在主线程上使用流,因此应避免像处理NSStreamEventHasBytesAvailable
事件那样使用循环。只需将一些数据读入缓冲区并将其存储在NSMutableString
中。
数据可能不完整,因此您应手动检查缓冲区中是否有完整的行,如果是,则只处理它。当您使用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后,我的套接字消息似乎响应更快,而且我没有再遇到数据包合并。