关于iPhone:是否可以在Apple iOS设备上激活TCP Keepalive

Is it possible to activate TCP keepalive on Apple iOS devices

苹果设备===路由器=== WiFi模块

Apple设备(iPhone)正在通过TCP连接连接到WiFi模块端口2000。我想激活在Apple设备上发送的TCP keepalive数据包,以查找与WiFi模块的TCP连接丢失(模块关闭)。

我的流设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;

CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)CFBridgingRetain(moduleIPaddress), port2000, &readStream, &writeStream);

outputStream = (NSOutputStream *)CFBridgingRelease(writeStream);
inputStream = (NSInputStream *)CFBridgingRelease(readStream);



[outputStream setDelegate:(id)self];
[inputStream setDelegate:(id)self];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream open];
[inputStream open];

我试图根据David H的帖子激活keepalive,使iOS中的套接字连接保持活动状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
- (void) stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {
    switch (streamEvent) {

    case NSStreamEventOpenCompleted:

            if (theStream == outputStream) {

                /*
                 CFDataRef data = (CFDataRef)CFWriteStreamCopyProperty((__bridge CFWriteStreamRef)theStream, kCFStreamPropertySocketNativeHandle);
                 if(data) {
                 CFSocketNativeHandle socket_handle = *(CFSocketNativeHandle *)CFDataGetBytePtr(data);
                 CFRelease(data);

                 NSLog(@"SOCK HANDLE: %x", socket_handle);

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

                NSData *data = (NSData *)[theStream propertyForKey:(__bridge NSString *)kCFStreamPropertySocketNativeHandle];
                if(data) {
                    CFSocketNativeHandle socket_handle = *(CFSocketNativeHandle *)[data bytes];
                    NSLog(@"SOCK HANDLE: %x", socket_handle);

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



            }

这两个选项都打印出SOCK HANDLE:9,没有错误消息。当WiFi模块关闭时,当我不发送数据至outputstream时,连接仍保持打开状态30分钟或更长时间。如果我将数据发送到输出流,则会收到NSStreamEventErrorOccurred-错误域= NSPOSIXErrorDomain代码= 60大约60秒后,"操作无法完成。操作超时"。我在Apple设备上尝试过此操作。当我尝试使用iOS Simulator时,Wireshark没有看到keepalive数据包。

iOS中的NSStream tcp keepalive还介绍了keepalive设置。 Martin R示例代码为看似错误的输入流激活了保持活动状态。

是否可以在iPhone等Apple iOS设备上激活TCP keepalive(应根据David H)?如果有可能应该怎么做(我的代码中缺少什么)?


谢谢林太郎指导我正确的方向。

流设置与我的问题相同。
我测试了不同的设置,但没有发现问题中描述的套接字句柄检测示例之间的差异。

使用iPod设备和iOS 7.1激活keepalive的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
        case NSStreamEventOpenCompleted:
        @try {

            if (theStream == outputStream)
            {
                NSData *data = (NSData *)[theStream propertyForKey:(__bridge NSString *)kCFStreamPropertySocketNativeHandle];

                if(data)
                {
                    CFSocketNativeHandle socket_handle = *(CFSocketNativeHandle *)[data bytes];
                    //NSLog(@"SOCK HANDLE: %x", socket_handle);

                    //SO_KEEPALIVE option to activate
                    int option = 1;
                    //TCP_NODELAY option to activate
                    int option2 = 1;
                    //Idle time used when SO_KEEPALIVE is enabled. Sets how long connection must be idle before keepalive is sent
                    int keepaliveIdle = 10;
                    //Interval between keepalives when there is no reply. Not same as idle time
                    int keepaliveIntvl = 2;
                    //Number of keepalives before close (including first keepalive packet)
                    int keepaliveCount = 4;
                    //Time after which tcp packet retransmissions will be stopped and the connection will be dropped.Stream is closed
                    int retransmissionTimeout = 5;


                    if (setsockopt(socket_handle, SOL_SOCKET, SO_KEEPALIVE, &option, sizeof (int)) == -1)
                    {
                        NSLog(@"setsockopt SO_KEEPALIVE failed: %s", strerror(errno));
                    }else
                    {
                        NSLog(@"setsockopt SO_KEEPALIVE ok");
                    }

                    if (setsockopt(socket_handle, IPPROTO_TCP, TCP_KEEPCNT, &keepaliveCount, sizeof(int)) == -1)
                    {
                        NSLog(@"setsockopt TCP_KEEPCNT failed: %s", strerror(errno));
                    }else
                    {
                        NSLog(@"setsockopt TCP_KEEPCNT ok");
                    }

                    if (setsockopt(socket_handle, IPPROTO_TCP, TCP_KEEPALIVE, &keepaliveIdle, sizeof(int)) == -1)
                    {
                        NSLog(@"setsockopt TCP_KEEPALIVE failed: %s", strerror(errno));
                    }else
                    {
                        NSLog(@"setsockopt TCP_KEEPALIVE ok");
                    }

                    if (setsockopt(socket_handle, IPPROTO_TCP, TCP_KEEPINTVL, &keepaliveIntvl, sizeof(int)) == -1)
                    {
                        NSLog(@"setsockopt TCP_KEEPINTVL failed: %s", strerror(errno));
                    }else
                    {
                        NSLog(@"setsockopt TCP_KEEPINTVL ok");
                    }

                    if (setsockopt(socket_handle, IPPROTO_TCP, TCP_RXT_CONNDROPTIME, &retransmissionTimeout, sizeof(int)) == -1)
                    {
                        NSLog(@"setsockopt TCP_RXT_CONNDROPTIME failed: %s", strerror(errno));
                    }else
                    {
                        NSLog(@"setsockopt TCP_RXT_CONNDROPTIME ok");
                    }

                    if (setsockopt(socket_handle, IPPROTO_TCP, TCP_NODELAY, &option2, sizeof(int)) == -1)
                    {
                        NSLog(@"setsockopt TCP_NODELAY failed: %s", strerror(errno));
                    }else
                    {
                        NSLog(@"setsockopt TCP_NODELAY ok");
                    }
                }
            }

TCP连接空闲时,应用程序将在10秒间隔后开始发送保持活动状态。如果没有答案,则应用会以2秒的间隔开始发送Keepalive数据包,并在有4个Keepalive数据包无应答时关闭流。这意味着,在成功进行Keepalive交换后关闭WiFi模块时,如果连接断开,空闲时最多需要18秒钟才能关闭流。

另一个参数是重传超时值。默认值似乎在6秒左右。当第一次有数据包重传时,此计时器启动。应用会尝试重传数据包,但如果5秒钟内数据包重传失败,则流将关闭。

注意!设备和模拟器的结果不同。

iPod设置激活日志

  • setsockopt SO_KEEPALIVE好的
  • setsockopt TCP_KEEPCNT确定
  • setsockopt TCP_KEEPALIVE好的
  • setsockopt TCP_KEEPINTVL好的
  • setsockopt TCP_RXT_CONNDROPTIME好的
  • setsockopt TCP_NODELAY确定

模拟器设置激活日志

  • setsockopt SO_KEEPALIVE好的
  • setsockopt TCP_KEEPCNT失败:协议不可用
  • setsockopt TCP_KEEPALIVE好的
  • setsockopt TCP_KEEPINTVL失败:协议不可用
  • setsockopt TCP_RXT_CONNDROPTIME好的
  • setsockopt TCP_NODELAY确定

这意味着无法使用iOS模拟器激活和跟踪。
我没有发现为什么模拟器中显示错误消息" Protocol not available"。


参见:http://en.wikipedia.org/wiki/Keepalive#TCP_keepalive

通常,默认情况下,保持活动时间(net.inet.tcp.keepidle)为7200秒。
我不知道iOS是否正确,但是RFC 1122明确要求"不少于2小时"。

This interval MUST be
configurable and MUST default to no less than two hours.

那么,如何在应用程序中配置它呢?尝试:

1
2
3
4
5
6
7
8
9
#import <sys/types.h>
#import <sys/socket.h>
#import <netinet/in.h>
#import <netinet/tcp.h>

int on = 1;
int delay = 120;
setsockopt(socket_handle, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on));
setsockopt(socket_handle, IPPROTO_TCP, TCP_KEEPALIVE, &delay, sizeof(delay));

参见man tcp

我确认这可以在iOS Simulator(iPhone 5s / iOS 8.0)上使用。