Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
566 views
in Technique[技术] by (71.8m points)

sockets - How to find a free local port using Swift?

For a local server I need to specify a port, which must not be in use. There's a really neat solution in Python to get a free port. However, such a socket library is not available in Swift. So I tried Using BSD Sockets in Swift, but that actually wants a port to be specified upfront and I cannot get the bind command to work. Here's the code I tried:

    let socketFD = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if socketFD == -1 {
      print("Error creating BSD Socket")
      return
    }

    var hints = addrinfo(
      ai_flags: AI_PASSIVE,       // Assign the address of the local host to the socket structures
      ai_family: AF_UNSPEC,       // Either IPv4 or IPv6
      ai_socktype: SOCK_STREAM,   // TCP
      ai_protocol: 0,
      ai_addrlen: 0,
      ai_canonname: nil,
      ai_addr: nil,
      ai_next: nil)

    var servinfo: UnsafeMutablePointer<addrinfo>? = nil
    let addrInfoResult = getaddrinfo(
      nil,                        // Any interface
      "8000",                   // The port on which will be listenend
      &hints,                     // Protocol configuration as per above
      &servinfo);

    if addrInfoResult != 0 {
      print("Error getting address info: (errno)")
      return
    }

    let bindResult = Darwin.bind(socketFD, servinfo!.pointee.ai_addr, socklen_t(servinfo!.pointee.ai_addrlen));
    if bindResult == -1 {
      print("Error binding socket to Address: (errno)")
      return
    }

    let listenResult = Darwin.listen(socketFD, 1);
    if listenResult == -1 {
      print("Error setting our socket to listen")
      return
    }

    let port = Darwin.getsockname(socketFD, nil, nil);

The bind call always returns -1 and since I want to get a free port it makes no sense to specify one in getaddrinfo. What's the correct way here?


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Here's a working solution:

func findFreePort() -> UInt16 {
    var port: UInt16 = 8000;

    let socketFD = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if socketFD == -1 {
      //print("Error creating socket: (errno)")
      return port;
    }

    var hints = addrinfo(
      ai_flags: AI_PASSIVE,
      ai_family: AF_INET,
      ai_socktype: SOCK_STREAM,
      ai_protocol: 0,
      ai_addrlen: 0,
      ai_canonname: nil,
      ai_addr: nil,
      ai_next: nil
    );

    var addressInfo: UnsafeMutablePointer<addrinfo>? = nil;
    var result = getaddrinfo(nil, "0", &hints, &addressInfo);
    if result != 0 {
      //print("Error getting address info: (errno)")
      close(socketFD);

      return port;
    }

    result = Darwin.bind(socketFD, addressInfo!.pointee.ai_addr, socklen_t(addressInfo!.pointee.ai_addrlen));
    if result == -1 {
      //print("Error binding socket to an address: (errno)")
      close(socketFD);

      return port;
    }

    result = Darwin.listen(socketFD, 1);
    if result == -1 {
      //print("Error setting socket to listen: (errno)")
      close(socketFD);

      return port;
    }

    var addr_in = sockaddr_in();
    addr_in.sin_len = UInt8(MemoryLayout.size(ofValue: addr_in));
    addr_in.sin_family = sa_family_t(AF_INET);

    var len = socklen_t(addr_in.sin_len);
    result = withUnsafeMutablePointer(to: &addr_in, {
      $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
        return Darwin.getsockname(socketFD, $0, &len);
      }
    });

    if result == 0 {
      port = addr_in.sin_port;
    }

    Darwin.shutdown(socketFD, SHUT_RDWR);
    close(socketFD);

    return port;
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...