在iOS中保持套接字连接的活动状态

11

我有以下的Objective-C代码,用于向套接字写入数据。服务器在Ubuntu上运行node.js:

NSString *url = @"anIPAddress";        
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;            
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)url, 9000, &readStream, &writeStream);
self.inputStream = (NSInputStream *)readStream;            
self.outputStream = (NSOutputStream *)writeStream;
[self.inputStream setDelegate:self];            
[self.outputStream setDelegate:self];            
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];            
[self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];            
[self.inputStream open];            
[self.outputStream open];

我能够连接到服务器并发送信息。然而我注意到连接在几分钟后会超时(我想大约是5分钟左右?)。如何保持这个连接处于活动状态?我知道有一个断开连接,因为如果我在终端下连接到服务器,同样的事情也会发生,我必须让连接保持活动状态。我想我的代码中也正在发生类似的事情。谢谢您的帮助!


请参考以下答案,获取可用的Objective-C代码:https://dev59.com/yIPba4cB1Zd3GeqPnhhg#25725181 - Despotovic
4个回答

6
最简单的方法是尝试使用SO_KEEPALIVE套接字选项。
在没有数据在端点之间流动的情况下,很难检测到断开的连接,这就是为什么这个选项在某些方面是无用的,因为它不使用数据来检测断开的连接。但是,将其添加到您的代码中很容易查看。添加并查看是否有帮助...
以下是在C或C++中执行此操作的方法。
int opt = 1;
// Get the native socket handle from your socket class here...
int socket_handle = getNativeSocketHandle( self.outputStream );

/*Enabling keep alive*/
if( setsockopt( socket_handle, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof( opt ) ) < 0 )
{
   // failed setting socket option
}

不那么简单的答案是在你的协议中添加一个ping包,并定期发送该ping包,以便检测到断开的连接。


嗯,有没有办法在Objective-C中使用上述API来实现这个? - Hahnemann
@Hahnemann:我不太了解Objective-C,所以不确定。但是,我愿意打赌肯定有一种逃生口可以调用C库。 - JimR
@Hahnemann:请看这里:http://developer.apple.com/library/ios/DOCUMENTATION/CoreFoundation/Reference/CFStreamConstants/Reference/reference.html#//apple_ref/doc/c_ref/kCFStreamPropertySocketNativeHandle ,看看是否有帮助。 - JimR
@JimR:我收到了这个错误信息:“无法设置保持连接!ERRNO:非套接字上的套接字操作”。可能的原因和解决方案是什么? - Jayprakash Dubey
你好,getNativeSocketHandle()是什么? - Hardik Mamtora

3
假设有一个 NSOutputStream *oStream;在iOS上进行如下操作:
#if 1 // Using CF
CFDataRef data = (CFDataRef)CFWriteStreamCopyProperty((__bridge CFWriteStreamRef)oStream, kCFStreamPropertySocketNativeHandle);
if(data) {
    CFSocketNativeHandle socket_handle = *(CFSocketNativeHandle *)CFDataGetBytePtr(data);
    CFRelease(data);
#else // Using Foundation
NSData *data = (NSData *)[oStream propertyForKey:(__bridge NSString *)kCFStreamPropertySocketNativeHandle];
if(data) {
    CFSocketNativeHandle socket_handle = *(CFSocketNativeHandle *)[data bytes];
#endif
    NSLog(@"SOCK HANDLE: %x", socket_handle);

    /*Enabling keep alive*/
    if( setsockopt( socket_handle, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof( opt ) ) < 0 )
    {
       NSLog(@"Yikes 2: failed to set keepalive! ERRNO: %s", strerror(errno));
    }

我发表这篇文章是因为我刚花了数小时解决这个问题,希望能够帮助其他人节省时间。


socket_handle、SOL_SOCKET、SO_KEEPALIVE 的值是什么? - Rohan
在 Xcode 文档视图中查找 man 2 setsockopt 或 setsockopt。 - David H
2
只有苹果才能把一件如此简单的事情变得如此复杂。我使用伯克利套接字编程网络应用程序,它比这个还要简单。 - deusprogrammer
这个方法需要写在哪里? - Hardik Mamtora
@HardikMamtora 在获取输出流的引用后。 - David H

3

您需要定期通过连接发送一些垃圾数据。可以尝试以下方法:

NSTimer* t = [NSTimer scheduledTimerWithTimeInterval:240 target:self selector:@selector(timerMethod:) userInfo:[NSNumber numberWithInt:sockfd] repeats:YES];

然后在self中声明这个内容:
-(void)timerMethod:(NSTimer*)t
{
  send([[t userInfo] intValue], "Keepalive!", 11, 0);
}

注意:将“Keepalive!”替换为您的协议所需的任何内容。唯一的要求是远程端(套接字服务器)忽略该消息。

有设置超时限制吗?我想知道应该以多少频率发送垃圾数据。 - Just a coder
@user:连接会在多久后断开?在那之前稍微安排一下。 - Linuxios
1
在某些情况下,您可能希望将其视为套接字服务器上的“心跳数据”,而不是“垃圾数据”。点赞! :) - tony gil
@Linuxios:sockfd是什么? - Jayprakash Dubey
@tonygil:我正在使用URL而不是IP地址。会有问题吗?另外,我该如何解决它? - Jayprakash Dubey
@JayprakashDubey 我完全不知道,抱歉。你试过之后能告诉我们吗?谢谢。 - tony gil

3
我找到了如何做。由于服务器正在运行node.js,我使用了
socket.setKeepAlive(true, 2000);

通过这种方式,Node会保持每个套接字的打开状态。默认值为false,因此这就是为什么它会超时的原因。我希望这对其他人有用。感谢您的回复,我认为保持心跳(keep-alive)也是一个好主意,但使用这种本地方法,Node会自己处理。


你之前从iOS流开始,现在转向使用Socket对象了?你在这里使用什么样的API? - Itay Levin
1
@ItayLevin 他正在向你展示他是如何在服务器上完成的 - 请参见下面我的答案,了解如何在iOS上完成。 - David H

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接