如何检查 NSFileHandle 是否有可用数据?

发布于 2024-12-05 16:43:04 字数 830 浏览 1 评论 0原文

我正在使用 NSTask,配置了 3 个 NSPipe,并且想要从 standardOutput 和 standardError 中读取。我在 while 内执行此操作 - 第一个用于标准输出,第二个用于标准错误。 我不能使用 readInBackgroundAndNotify 和 waitForDataInBackgroundAndNotify,因为我的代码已经在单独的线程中运行,并且我不想在那里运行 NSRunLoop 并分离新的后台线程...但是读取两者 - stdout 和 stderr 会导致在没有可用数据时出现一些挂起数据存在于这些通道之一中。

所以我使用这段代码:

@implementation NSFileHandle (isReadableAddon)

- (BOOL)isReadable
{
    int fd = [self fileDescriptor];
    fd_set fdset;
    struct timeval tmout = { 0, 0 }; // return immediately
    FD_ZERO(&fdset);
    FD_SET(fd, &fdset);
    if (select(fd + 1, &fdset, NULL, NULL, &tmout) <= 0)
        return NO;
    return FD_ISSET(fd, &fdset);
}

@end

但是,当 isReadable 返回 YES 并且我调用 [fileHandle availableData] 时,我的线程仍然阻塞。 为什么? 我期望当 isReadable 返回 YES 时调用 availableData 方法而不阻塞。

I'm working with NSTask, configured with 3 NSPipe, and want to read from standardOutput and standardError. I do it inside while - 1st for stdout, next for stderr.
I can't use readInBackgroundAndNotify and waitForDataInBackgroundAndNotify, since my code is already running in separate thread, and I don't want run NSRunLoop there and detach new background threads... But reading both - stdout and stderr cause some hang on availableData when no data present in one of these channels.

So I use this code:

@implementation NSFileHandle (isReadableAddon)

- (BOOL)isReadable
{
    int fd = [self fileDescriptor];
    fd_set fdset;
    struct timeval tmout = { 0, 0 }; // return immediately
    FD_ZERO(&fdset);
    FD_SET(fd, &fdset);
    if (select(fd + 1, &fdset, NULL, NULL, &tmout) <= 0)
        return NO;
    return FD_ISSET(fd, &fdset);
}

@end

However, when isReadable returns YES and I call [fileHandle availableData], my thread still blocks.
Why?
I expected call availableData method without blocking when isReadable returned YES.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

塔塔猫 2024-12-12 16:43:04

我也更喜欢使用 select 但如果您想使用非阻塞,请阅读这里的要点,这对我有用。

Swift 4

var flags = fcntl(fileDescriptor, F_GETFL)
_ = fcntl(fileDescriptor, F_SETFL, flags | O_NONBLOCK)

public var nonBlockingAvailableData: Data? {
        return self.__readDataOfLength(Int.max, untilEOF: false)
    }

 internal func __readDataOfLength(_ length: Int, untilEOF: Bool) -> Data? {
        let _closed: Bool = false

        let _fd = self.fileDescriptor
        var statbuf = stat()
        var dynamicBuffer: UnsafeMutableRawPointer? = nil
        var total = 0
        if _closed || fstat(_fd, &statbuf) < 0 {
            fatalError("Unable to read file")
        }
        if statbuf.st_mode & S_IFMT != S_IFREG {
            /* We get here on sockets, character special files, FIFOs ... */
            var currentAllocationSize: size_t = 1024 * 8
            dynamicBuffer = malloc(currentAllocationSize)

            var remaining = length
            while remaining > 0 {

                let amountToRead = min(1024 * 8, remaining)
                // Make sure there is always at least amountToRead bytes available in the buffer.
                if (currentAllocationSize - total) < amountToRead {
                    currentAllocationSize *= 2

                    dynamicBuffer = reallocf(dynamicBuffer!, currentAllocationSize)
                    if dynamicBuffer == nil {
                        fatalError("unable to allocate backing buffer")
                    }
                }

                let amtRead = read(_fd, dynamicBuffer!.advanced(by: total), amountToRead)
                    //Needs better errorhandling, check ERRNO? 
                if amtRead == -1 {
                    return nil
                }

                if 0 > amtRead {
                    free(dynamicBuffer)
                    fatalError("read failure")
                }
                if 0 == amtRead {
                    break // EOF
                }

                total += amtRead
                remaining -= amtRead

                if total == length || untilEOF == false {
                    break // We read everything the client asked for.
                }
            }
        }

        if length == Int.max && total > 0 {
            dynamicBuffer = reallocf(dynamicBuffer!, total)
        }

        if total == 0 {
            free(dynamicBuffer)
        }
        else if total > 0 {
            let bytePtr = dynamicBuffer!.bindMemory(to: UInt8.self, capacity: total)
            return Data(bytesNoCopy: bytePtr, count: total, deallocator: .free)
        }
        else {
            assertionFailure("The total number of read bytes must not be negative")
            free(dynamicBuffer)
        }

        return Data()
    }

查看 Linux select手册页:

在 Linux 下,select() 可能会报告
套接字文件描述符为“准备好
阅读”,尽管如此
后续读取块。这可以
例如当数据有时发生
已到达但经检查有误
校验和并被丢弃。可能有
是文件的其他情况
描述符被虚假报告为
准备好。因此使用起来可能更安全
O_NONBLOCK 在不应该的套接字上
块。

I also prefer using select but incase you want to work with a non-blocking read here's a gist that works for me.

Swift 4

var flags = fcntl(fileDescriptor, F_GETFL)
_ = fcntl(fileDescriptor, F_SETFL, flags | O_NONBLOCK)

public var nonBlockingAvailableData: Data? {
        return self.__readDataOfLength(Int.max, untilEOF: false)
    }

 internal func __readDataOfLength(_ length: Int, untilEOF: Bool) -> Data? {
        let _closed: Bool = false

        let _fd = self.fileDescriptor
        var statbuf = stat()
        var dynamicBuffer: UnsafeMutableRawPointer? = nil
        var total = 0
        if _closed || fstat(_fd, &statbuf) < 0 {
            fatalError("Unable to read file")
        }
        if statbuf.st_mode & S_IFMT != S_IFREG {
            /* We get here on sockets, character special files, FIFOs ... */
            var currentAllocationSize: size_t = 1024 * 8
            dynamicBuffer = malloc(currentAllocationSize)

            var remaining = length
            while remaining > 0 {

                let amountToRead = min(1024 * 8, remaining)
                // Make sure there is always at least amountToRead bytes available in the buffer.
                if (currentAllocationSize - total) < amountToRead {
                    currentAllocationSize *= 2

                    dynamicBuffer = reallocf(dynamicBuffer!, currentAllocationSize)
                    if dynamicBuffer == nil {
                        fatalError("unable to allocate backing buffer")
                    }
                }

                let amtRead = read(_fd, dynamicBuffer!.advanced(by: total), amountToRead)
                    //Needs better errorhandling, check ERRNO? 
                if amtRead == -1 {
                    return nil
                }

                if 0 > amtRead {
                    free(dynamicBuffer)
                    fatalError("read failure")
                }
                if 0 == amtRead {
                    break // EOF
                }

                total += amtRead
                remaining -= amtRead

                if total == length || untilEOF == false {
                    break // We read everything the client asked for.
                }
            }
        }

        if length == Int.max && total > 0 {
            dynamicBuffer = reallocf(dynamicBuffer!, total)
        }

        if total == 0 {
            free(dynamicBuffer)
        }
        else if total > 0 {
            let bytePtr = dynamicBuffer!.bindMemory(to: UInt8.self, capacity: total)
            return Data(bytesNoCopy: bytePtr, count: total, deallocator: .free)
        }
        else {
            assertionFailure("The total number of read bytes must not be negative")
            free(dynamicBuffer)
        }

        return Data()
    }

see the Linux select man page:

Under Linux, select() may report a
socket file descriptor as "ready for
reading", while nevertheless a
subsequent read blocks. This could
for example happen when data has
arrived but upon examination has wrong
checksum and is discarded. There may
be other circumstances in which a file
descriptor is spuriously reported as
ready. Thus it may be safer to use
O_NONBLOCK on sockets that should not
block.

陪我终i 2024-12-12 16:43:04

Swift 4 版本(适用于 macOS):

extension FileHandle {

   var isReadable: Bool {
      var fdset = fd_set()
      FileDescriptor.fdZero(&fdset)
      FileDescriptor.fdSet(fileDescriptor, set: &fdset)
      var tmout = timeval()
      let status = select(fileDescriptor + 1, &fdset, nil, nil, &tmout)
      return status > 0
   }
}

/// References:
/// - http://swiftrien.blogspot.com/2015/11/swift-code-library-replacements-for.html
/// - https://github.com/kylef-archive/fd/blob/master/Sources/FDSet.swift
public struct FileDescriptor {

   public static func fdZero(_ set: inout fd_set) {
      set.fds_bits = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
   }

   public static func fdSet(_ fd: Int32, set: inout fd_set) {
      let intOffset = Int32(fd / 32)
      let bitOffset = fd % 32
      let mask = Int32(1) << bitOffset
      switch intOffset {
      case 0: set.fds_bits.0 = set.fds_bits.0 | mask
      case 1: set.fds_bits.1 = set.fds_bits.1 | mask
      case 2: set.fds_bits.2 = set.fds_bits.2 | mask
      case 3: set.fds_bits.3 = set.fds_bits.3 | mask
      case 4: set.fds_bits.4 = set.fds_bits.4 | mask
      case 5: set.fds_bits.5 = set.fds_bits.5 | mask
      case 6: set.fds_bits.6 = set.fds_bits.6 | mask
      case 7: set.fds_bits.7 = set.fds_bits.7 | mask
      case 8: set.fds_bits.8 = set.fds_bits.8 | mask
      case 9: set.fds_bits.9 = set.fds_bits.9 | mask
      case 10: set.fds_bits.10 = set.fds_bits.10 | mask
      case 11: set.fds_bits.11 = set.fds_bits.11 | mask
      case 12: set.fds_bits.12 = set.fds_bits.12 | mask
      case 13: set.fds_bits.13 = set.fds_bits.13 | mask
      case 14: set.fds_bits.14 = set.fds_bits.14 | mask
      case 15: set.fds_bits.15 = set.fds_bits.15 | mask
      case 16: set.fds_bits.16 = set.fds_bits.16 | mask
      case 17: set.fds_bits.17 = set.fds_bits.17 | mask
      case 18: set.fds_bits.18 = set.fds_bits.18 | mask
      case 19: set.fds_bits.19 = set.fds_bits.19 | mask
      case 20: set.fds_bits.20 = set.fds_bits.20 | mask
      case 21: set.fds_bits.21 = set.fds_bits.21 | mask
      case 22: set.fds_bits.22 = set.fds_bits.22 | mask
      case 23: set.fds_bits.23 = set.fds_bits.23 | mask
      case 24: set.fds_bits.24 = set.fds_bits.24 | mask
      case 25: set.fds_bits.25 = set.fds_bits.25 | mask
      case 26: set.fds_bits.26 = set.fds_bits.26 | mask
      case 27: set.fds_bits.27 = set.fds_bits.27 | mask
      case 28: set.fds_bits.28 = set.fds_bits.28 | mask
      case 29: set.fds_bits.29 = set.fds_bits.29 | mask
      case 30: set.fds_bits.30 = set.fds_bits.30 | mask
      case 31: set.fds_bits.31 = set.fds_bits.31 | mask
      default: break
      }
   }

   public static func fdClr(_ fd: Int32, set: inout fd_set) {
      let intOffset = Int32(fd / 32)
      let bitOffset = fd % 32
      let mask = ~(Int32(1) << bitOffset)
      switch intOffset {
      case 0: set.fds_bits.0 = set.fds_bits.0 & mask
      case 1: set.fds_bits.1 = set.fds_bits.1 & mask
      case 2: set.fds_bits.2 = set.fds_bits.2 & mask
      case 3: set.fds_bits.3 = set.fds_bits.3 & mask
      case 4: set.fds_bits.4 = set.fds_bits.4 & mask
      case 5: set.fds_bits.5 = set.fds_bits.5 & mask
      case 6: set.fds_bits.6 = set.fds_bits.6 & mask
      case 7: set.fds_bits.7 = set.fds_bits.7 & mask
      case 8: set.fds_bits.8 = set.fds_bits.8 & mask
      case 9: set.fds_bits.9 = set.fds_bits.9 & mask
      case 10: set.fds_bits.10 = set.fds_bits.10 & mask
      case 11: set.fds_bits.11 = set.fds_bits.11 & mask
      case 12: set.fds_bits.12 = set.fds_bits.12 & mask
      case 13: set.fds_bits.13 = set.fds_bits.13 & mask
      case 14: set.fds_bits.14 = set.fds_bits.14 & mask
      case 15: set.fds_bits.15 = set.fds_bits.15 & mask
      case 16: set.fds_bits.16 = set.fds_bits.16 & mask
      case 17: set.fds_bits.17 = set.fds_bits.17 & mask
      case 18: set.fds_bits.18 = set.fds_bits.18 & mask
      case 19: set.fds_bits.19 = set.fds_bits.19 & mask
      case 20: set.fds_bits.20 = set.fds_bits.20 & mask
      case 21: set.fds_bits.21 = set.fds_bits.21 & mask
      case 22: set.fds_bits.22 = set.fds_bits.22 & mask
      case 23: set.fds_bits.23 = set.fds_bits.23 & mask
      case 24: set.fds_bits.24 = set.fds_bits.24 & mask
      case 25: set.fds_bits.25 = set.fds_bits.25 & mask
      case 26: set.fds_bits.26 = set.fds_bits.26 & mask
      case 27: set.fds_bits.27 = set.fds_bits.27 & mask
      case 28: set.fds_bits.28 = set.fds_bits.28 & mask
      case 29: set.fds_bits.29 = set.fds_bits.29 & mask
      case 30: set.fds_bits.30 = set.fds_bits.30 & mask
      case 31: set.fds_bits.31 = set.fds_bits.31 & mask
      default: break
      }
   }

   public static func fdIsSet(_ fd: Int32, set: inout fd_set) -> Bool {
      let intOffset = Int(fd / 32)
      let bitOffset = fd % 32
      let mask = Int32(1) << bitOffset
      switch intOffset {
      case 0: return set.fds_bits.0 & mask != 0
      case 1: return set.fds_bits.1 & mask != 0
      case 2: return set.fds_bits.2 & mask != 0
      case 3: return set.fds_bits.3 & mask != 0
      case 4: return set.fds_bits.4 & mask != 0
      case 5: return set.fds_bits.5 & mask != 0
      case 6: return set.fds_bits.6 & mask != 0
      case 7: return set.fds_bits.7 & mask != 0
      case 8: return set.fds_bits.8 & mask != 0
      case 9: return set.fds_bits.9 & mask != 0
      case 10: return set.fds_bits.10 & mask != 0
      case 11: return set.fds_bits.11 & mask != 0
      case 12: return set.fds_bits.12 & mask != 0
      case 13: return set.fds_bits.13 & mask != 0
      case 14: return set.fds_bits.14 & mask != 0
      case 15: return set.fds_bits.15 & mask != 0
      case 16: return set.fds_bits.16 & mask != 0
      case 17: return set.fds_bits.17 & mask != 0
      case 18: return set.fds_bits.18 & mask != 0
      case 19: return set.fds_bits.19 & mask != 0
      case 20: return set.fds_bits.20 & mask != 0
      case 21: return set.fds_bits.21 & mask != 0
      case 22: return set.fds_bits.22 & mask != 0
      case 23: return set.fds_bits.23 & mask != 0
      case 24: return set.fds_bits.24 & mask != 0
      case 25: return set.fds_bits.25 & mask != 0
      case 26: return set.fds_bits.26 & mask != 0
      case 27: return set.fds_bits.27 & mask != 0
      case 28: return set.fds_bits.28 & mask != 0
      case 29: return set.fds_bits.29 & mask != 0
      case 30: return set.fds_bits.30 & mask != 0
      case 31: return set.fds_bits.31 & mask != 0
      default: return false
      }
   }
}

Swift 4 version (for macOS):

extension FileHandle {

   var isReadable: Bool {
      var fdset = fd_set()
      FileDescriptor.fdZero(&fdset)
      FileDescriptor.fdSet(fileDescriptor, set: &fdset)
      var tmout = timeval()
      let status = select(fileDescriptor + 1, &fdset, nil, nil, &tmout)
      return status > 0
   }
}

/// References:
/// - http://swiftrien.blogspot.com/2015/11/swift-code-library-replacements-for.html
/// - https://github.com/kylef-archive/fd/blob/master/Sources/FDSet.swift
public struct FileDescriptor {

   public static func fdZero(_ set: inout fd_set) {
      set.fds_bits = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
   }

   public static func fdSet(_ fd: Int32, set: inout fd_set) {
      let intOffset = Int32(fd / 32)
      let bitOffset = fd % 32
      let mask = Int32(1) << bitOffset
      switch intOffset {
      case 0: set.fds_bits.0 = set.fds_bits.0 | mask
      case 1: set.fds_bits.1 = set.fds_bits.1 | mask
      case 2: set.fds_bits.2 = set.fds_bits.2 | mask
      case 3: set.fds_bits.3 = set.fds_bits.3 | mask
      case 4: set.fds_bits.4 = set.fds_bits.4 | mask
      case 5: set.fds_bits.5 = set.fds_bits.5 | mask
      case 6: set.fds_bits.6 = set.fds_bits.6 | mask
      case 7: set.fds_bits.7 = set.fds_bits.7 | mask
      case 8: set.fds_bits.8 = set.fds_bits.8 | mask
      case 9: set.fds_bits.9 = set.fds_bits.9 | mask
      case 10: set.fds_bits.10 = set.fds_bits.10 | mask
      case 11: set.fds_bits.11 = set.fds_bits.11 | mask
      case 12: set.fds_bits.12 = set.fds_bits.12 | mask
      case 13: set.fds_bits.13 = set.fds_bits.13 | mask
      case 14: set.fds_bits.14 = set.fds_bits.14 | mask
      case 15: set.fds_bits.15 = set.fds_bits.15 | mask
      case 16: set.fds_bits.16 = set.fds_bits.16 | mask
      case 17: set.fds_bits.17 = set.fds_bits.17 | mask
      case 18: set.fds_bits.18 = set.fds_bits.18 | mask
      case 19: set.fds_bits.19 = set.fds_bits.19 | mask
      case 20: set.fds_bits.20 = set.fds_bits.20 | mask
      case 21: set.fds_bits.21 = set.fds_bits.21 | mask
      case 22: set.fds_bits.22 = set.fds_bits.22 | mask
      case 23: set.fds_bits.23 = set.fds_bits.23 | mask
      case 24: set.fds_bits.24 = set.fds_bits.24 | mask
      case 25: set.fds_bits.25 = set.fds_bits.25 | mask
      case 26: set.fds_bits.26 = set.fds_bits.26 | mask
      case 27: set.fds_bits.27 = set.fds_bits.27 | mask
      case 28: set.fds_bits.28 = set.fds_bits.28 | mask
      case 29: set.fds_bits.29 = set.fds_bits.29 | mask
      case 30: set.fds_bits.30 = set.fds_bits.30 | mask
      case 31: set.fds_bits.31 = set.fds_bits.31 | mask
      default: break
      }
   }

   public static func fdClr(_ fd: Int32, set: inout fd_set) {
      let intOffset = Int32(fd / 32)
      let bitOffset = fd % 32
      let mask = ~(Int32(1) << bitOffset)
      switch intOffset {
      case 0: set.fds_bits.0 = set.fds_bits.0 & mask
      case 1: set.fds_bits.1 = set.fds_bits.1 & mask
      case 2: set.fds_bits.2 = set.fds_bits.2 & mask
      case 3: set.fds_bits.3 = set.fds_bits.3 & mask
      case 4: set.fds_bits.4 = set.fds_bits.4 & mask
      case 5: set.fds_bits.5 = set.fds_bits.5 & mask
      case 6: set.fds_bits.6 = set.fds_bits.6 & mask
      case 7: set.fds_bits.7 = set.fds_bits.7 & mask
      case 8: set.fds_bits.8 = set.fds_bits.8 & mask
      case 9: set.fds_bits.9 = set.fds_bits.9 & mask
      case 10: set.fds_bits.10 = set.fds_bits.10 & mask
      case 11: set.fds_bits.11 = set.fds_bits.11 & mask
      case 12: set.fds_bits.12 = set.fds_bits.12 & mask
      case 13: set.fds_bits.13 = set.fds_bits.13 & mask
      case 14: set.fds_bits.14 = set.fds_bits.14 & mask
      case 15: set.fds_bits.15 = set.fds_bits.15 & mask
      case 16: set.fds_bits.16 = set.fds_bits.16 & mask
      case 17: set.fds_bits.17 = set.fds_bits.17 & mask
      case 18: set.fds_bits.18 = set.fds_bits.18 & mask
      case 19: set.fds_bits.19 = set.fds_bits.19 & mask
      case 20: set.fds_bits.20 = set.fds_bits.20 & mask
      case 21: set.fds_bits.21 = set.fds_bits.21 & mask
      case 22: set.fds_bits.22 = set.fds_bits.22 & mask
      case 23: set.fds_bits.23 = set.fds_bits.23 & mask
      case 24: set.fds_bits.24 = set.fds_bits.24 & mask
      case 25: set.fds_bits.25 = set.fds_bits.25 & mask
      case 26: set.fds_bits.26 = set.fds_bits.26 & mask
      case 27: set.fds_bits.27 = set.fds_bits.27 & mask
      case 28: set.fds_bits.28 = set.fds_bits.28 & mask
      case 29: set.fds_bits.29 = set.fds_bits.29 & mask
      case 30: set.fds_bits.30 = set.fds_bits.30 & mask
      case 31: set.fds_bits.31 = set.fds_bits.31 & mask
      default: break
      }
   }

   public static func fdIsSet(_ fd: Int32, set: inout fd_set) -> Bool {
      let intOffset = Int(fd / 32)
      let bitOffset = fd % 32
      let mask = Int32(1) << bitOffset
      switch intOffset {
      case 0: return set.fds_bits.0 & mask != 0
      case 1: return set.fds_bits.1 & mask != 0
      case 2: return set.fds_bits.2 & mask != 0
      case 3: return set.fds_bits.3 & mask != 0
      case 4: return set.fds_bits.4 & mask != 0
      case 5: return set.fds_bits.5 & mask != 0
      case 6: return set.fds_bits.6 & mask != 0
      case 7: return set.fds_bits.7 & mask != 0
      case 8: return set.fds_bits.8 & mask != 0
      case 9: return set.fds_bits.9 & mask != 0
      case 10: return set.fds_bits.10 & mask != 0
      case 11: return set.fds_bits.11 & mask != 0
      case 12: return set.fds_bits.12 & mask != 0
      case 13: return set.fds_bits.13 & mask != 0
      case 14: return set.fds_bits.14 & mask != 0
      case 15: return set.fds_bits.15 & mask != 0
      case 16: return set.fds_bits.16 & mask != 0
      case 17: return set.fds_bits.17 & mask != 0
      case 18: return set.fds_bits.18 & mask != 0
      case 19: return set.fds_bits.19 & mask != 0
      case 20: return set.fds_bits.20 & mask != 0
      case 21: return set.fds_bits.21 & mask != 0
      case 22: return set.fds_bits.22 & mask != 0
      case 23: return set.fds_bits.23 & mask != 0
      case 24: return set.fds_bits.24 & mask != 0
      case 25: return set.fds_bits.25 & mask != 0
      case 26: return set.fds_bits.26 & mask != 0
      case 27: return set.fds_bits.27 & mask != 0
      case 28: return set.fds_bits.28 & mask != 0
      case 29: return set.fds_bits.29 & mask != 0
      case 30: return set.fds_bits.30 & mask != 0
      case 31: return set.fds_bits.31 & mask != 0
      default: return false
      }
   }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文