swift-nio/Sources/NIOPosix/ServerSocket.swift

140 lines
5.3 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import NIOCore
/// A server socket that can accept new connections.
/* final but tests */ class ServerSocket: BaseSocket, ServerSocketProtocol {
typealias SocketType = ServerSocket
private let cleanupOnClose: Bool
public final class func bootstrap(protocolFamily: NIOBSDSocket.ProtocolFamily, host: String, port: Int) throws -> ServerSocket {
let socket = try ServerSocket(protocolFamily: protocolFamily)
try socket.bind(to: SocketAddress.makeAddressResolvingHost(host, port: port))
try socket.listen()
return socket
}
/// Create a new instance.
///
/// - parameters:
/// - protocolFamily: The protocol family to use (usually `AF_INET6` or `AF_INET`).
/// - protocolSubtype: The subtype of the protocol, corresponding to the `protocol`
/// argument to the socket syscall. Defaults to 0.
/// - setNonBlocking: Set non-blocking mode on the socket.
/// - throws: An `IOError` if creation of the socket failed.
init(protocolFamily: NIOBSDSocket.ProtocolFamily, protocolSubtype: NIOBSDSocket.ProtocolSubtype = .default, setNonBlocking: Bool = false) throws {
let sock = try BaseSocket.makeSocket(protocolFamily: protocolFamily, type: .stream, protocolSubtype: protocolSubtype, setNonBlocking: setNonBlocking)
switch protocolFamily {
case .unix:
cleanupOnClose = true
default:
cleanupOnClose = false
}
try super.init(socket: sock)
}
/// Create a new instance.
///
/// - parameters:
/// - descriptor: The _Unix file descriptor_ representing the bound socket.
/// - setNonBlocking: Set non-blocking mode on the socket.
/// - throws: An `IOError` if socket is invalid.
#if !os(Windows)
@available(*, deprecated, renamed: "init(socket:setNonBlocking:)")
convenience init(descriptor: CInt, setNonBlocking: Bool = false) throws {
try self.init(socket: descriptor, setNonBlocking: setNonBlocking)
}
#endif
/// Create a new instance.
///
/// - parameters:
/// - descriptor: The _Unix file descriptor_ representing the bound socket.
/// - setNonBlocking: Set non-blocking mode on the socket.
/// - throws: An `IOError` if socket is invalid.
init(socket: NIOBSDSocket.Handle, setNonBlocking: Bool = false) throws {
cleanupOnClose = false // socket already bound, owner must clean up
try super.init(socket: socket)
if setNonBlocking {
try self.setNonBlocking()
}
}
/// Start to listen for new connections.
///
/// - parameters:
/// - backlog: The backlog to use.
/// - throws: An `IOError` if creation of the socket failed.
func listen(backlog: Int32 = 128) throws {
try withUnsafeHandle {
_ = try NIOBSDSocket.listen(socket: $0, backlog: backlog)
}
}
/// Accept a new connection
///
/// - parameters:
/// - setNonBlocking: set non-blocking mode on the returned `Socket`. On Linux this will use accept4 with SOCK_NONBLOCK to save a system call.
/// - returns: A `Socket` once a new connection was established or `nil` if this `ServerSocket` is in non-blocking mode and there is no new connection that can be accepted when this method is called.
/// - throws: An `IOError` if the operation failed.
func accept(setNonBlocking: Bool = false) throws -> Socket? {
return try withUnsafeHandle { fd in
#if os(Linux)
let flags: Int32
if setNonBlocking {
flags = Linux.SOCK_NONBLOCK
} else {
flags = 0
}
let result = try Linux.accept4(descriptor: fd, addr: nil, len: nil, flags: flags)
#else
let result = try NIOBSDSocket.accept(socket: fd, address: nil, address_len: nil)
#endif
guard let fd = result else {
return nil
}
let sock = try Socket(socket: fd)
#if !os(Linux)
if setNonBlocking {
do {
try sock.setNonBlocking()
} catch {
// best effort
try? sock.close()
throw error
}
}
#endif
return sock
}
}
/// Close the socket.
///
/// After the socket was closed all other methods will throw an `IOError` when called.
///
/// - throws: An `IOError` if the operation failed.
override func close() throws {
let maybePathname = self.cleanupOnClose ? (try? self.localAddress().pathname) : nil
try super.close()
if let socketPath = maybePathname {
try BaseSocket.cleanupSocket(unixDomainSocketPath: socketPath)
}
}
}