【问题标题】:Is it possible to activate TCP keepalive on Apple iOS devices是否可以在 Apple iOS 设备上激活 TCP keepalive
【发布时间】:2014-10-29 05:22:57
【问题描述】:

Apple 设备 === 路由器 === WiFi 模块

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

我的直播设置

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 post Keeping socket connection alive in iOS,我尝试激活keepalive

- (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 模块关闭时,连接仍然保持打开状态 30 分钟或更长时间,而我没有将数据发送到输出流。如果我将数据发送到输出流,我会在大约 60 秒后得到 NSStreamEventErrorOccurred - Error Domain=NSPOSIXErrorDomain Code=60 "The operation could not be completed. Operation timed out"。我用苹果设备试过这个。当我尝试使用 iOS 模拟器时,我没有看到使用 Wireshark 的 keepalive 数据包。

NSStream tcp keepalive in iOS 还描述了保活设置。 Martin R 示例代码为似乎错误的输入流激活了 keepalive。

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

【问题讨论】:

    标签: ios iphone sockets tcp


    【解决方案1】:

    感谢 rintaro 引导我走向正确的方向。

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

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

            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 秒间隔后开始发送 keepalive。当没有应答时,应用程序开始以 2 秒的间隔发送 keepalive 数据包,并在有 4 个没有应答的 keepalive 数据包时关闭流。这意味着当 WiFi 模块在成功的 keepalive 交换后关闭时,在连接丢失时最多需要 18 秒的空闲时间来关闭流。

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

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

    iPod 设置激活日志

    • setsockopt SO_KEEPALIVE ok
    • setsockopt TCP_KEEPCNT 正常
    • setsockopt TCP_KEEPALIVE 正常
    • setsockopt TCP_KEEPINTVL 正常
    • setsockopt TCP_RXT_CONNDROPTIME 正常
    • setsockopt TCP_NODELAY 正常

    模拟器设置激活日志

    • setsockopt SO_KEEPALIVE ok
    • setsockopt TCP_KEEPCNT 失败:协议不可用
    • setsockopt TCP_KEEPALIVE 正常
    • setsockopt TCP_KEEPINTVL 失败:协议不可用
    • setsockopt TCP_RXT_CONNDROPTIME 正常
    • setsockopt TCP_NODELAY 正常

    这意味着无法使用 iOS 模拟器进行激活和跟踪。 我没有找到为什么模拟器中显示错误消息“协议不可用”。

    【讨论】:

    • 感谢详细调查!
    • 如果上面代码中的一些定义没有在你的项目中声明,你必须有以下包含:#include #include #include
    • 本示例中选择的值如何映射到推荐值?我已经尝试过这些设置,但是使用一些设备(大约 5 个)网络会变得有些不稳定。我们怀疑这主要是由于retransmissionTimeout,你同意吗?
    • 顺便说一句,这些设置是否应该同时用于两个流(输入和输出)?
    • @AndréFratelli 您找到不稳定的原因了吗?是不是因为TCP_RXT_CONNDROPTIME的设置太低了?
    【解决方案2】:

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

    通常情况下,keepalive 时间 (net.inet.tcp.keepidle) 默认为 7200 秒。 我不知道这在 iOS 中是否正确,但 RFC 1122 明确要求“不少于 2 小时”。

    这个间隔必须是 可配置且必须默认不少于两个小时。

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

    #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 模拟器 (iPhone 5s / iOS 8.0)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-11-17
      • 2021-12-15
      • 1970-01-01
      • 2012-12-29
      • 2018-11-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多