Coverage Report

Created: 2026-04-29 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/swift-nio/Sources/NIOPosix/ServerSocket.swift
Line
Count
Source
1
//===----------------------------------------------------------------------===//
2
//
3
// This source file is part of the SwiftNIO open source project
4
//
5
// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors
6
// Licensed under Apache License v2.0
7
//
8
// See LICENSE.txt for license information
9
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
10
//
11
// SPDX-License-Identifier: Apache-2.0
12
//
13
//===----------------------------------------------------------------------===//
14
15
#if !os(WASI)
16
17
import NIOCore
18
19
/// A server socket that can accept new connections.
20
class ServerSocket: BaseSocket, ServerSocketProtocol {
21
    typealias SocketType = ServerSocket
22
    private let cleanupOnClose: Bool
23
24
    public final class func bootstrap(
25
        protocolFamily: NIOBSDSocket.ProtocolFamily,
26
        host: String,
27
        port: Int
28
0
    ) throws -> ServerSocket {
29
0
        let socket = try ServerSocket(protocolFamily: protocolFamily)
30
0
        try socket.bind(to: SocketAddress.makeAddressResolvingHost(host, port: port))
31
0
        try socket.listen()
32
0
        return socket
33
0
    }
34
35
    /// Create a new instance.
36
    ///
37
    /// - Parameters:
38
    ///   - protocolFamily: The protocol family to use (usually `AF_INET6` or `AF_INET`).
39
    ///   - protocolSubtype: The subtype of the protocol, corresponding to the `protocol`
40
    ///         argument to the socket syscall. Defaults to 0.
41
    ///   - setNonBlocking: Set non-blocking mode on the socket.
42
    /// - Throws: An `IOError` if creation of the socket failed.
43
    init(
44
        protocolFamily: NIOBSDSocket.ProtocolFamily,
45
        protocolSubtype: NIOBSDSocket.ProtocolSubtype = .default,
46
        setNonBlocking: Bool = false
47
0
    ) throws {
48
0
        let sock = try BaseSocket.makeSocket(
49
0
            protocolFamily: protocolFamily,
50
0
            type: .stream,
51
0
            protocolSubtype: protocolSubtype,
52
0
            setNonBlocking: setNonBlocking
53
0
        )
54
0
        switch protocolFamily {
55
0
        case .unix:
56
0
            cleanupOnClose = true
57
0
        default:
58
0
            cleanupOnClose = false
59
0
        }
60
0
        try super.init(socket: sock)
61
0
    }
62
63
    /// Create a new instance.
64
    ///
65
    /// - Parameters:
66
    ///   - descriptor: The _Unix file descriptor_ representing the bound socket.
67
    ///   - setNonBlocking: Set non-blocking mode on the socket.
68
    /// - Throws: An `IOError` if socket is invalid.
69
    #if !os(Windows)
70
    @available(*, deprecated, renamed: "init(socket:setNonBlocking:)")
71
0
    convenience init(descriptor: CInt, setNonBlocking: Bool = false) throws {
72
0
        try self.init(socket: descriptor, setNonBlocking: setNonBlocking)
73
0
    }
74
    #endif
75
76
    /// Create a new instance.
77
    ///
78
    /// - Parameters:
79
    ///   - descriptor: The _Unix file descriptor_ representing the bound socket.
80
    ///   - setNonBlocking: Set non-blocking mode on the socket.
81
    /// - Throws: An `IOError` if socket is invalid.
82
0
    init(socket: NIOBSDSocket.Handle, setNonBlocking: Bool = false) throws {
83
0
        cleanupOnClose = false  // socket already bound, owner must clean up
84
0
        try super.init(socket: socket)
85
0
        if setNonBlocking {
86
0
            try self.setNonBlocking()
87
0
        }
88
0
    }
89
90
    /// Start to listen for new connections.
91
    ///
92
    /// - Parameters:
93
    ///   - backlog: The backlog to use.
94
    /// - Throws: An `IOError` if creation of the socket failed.
95
0
    func listen(backlog: Int32 = 128) throws {
96
0
        try withUnsafeHandle {
97
0
            _ = try NIOBSDSocket.listen(socket: $0, backlog: backlog)
98
0
        }
99
0
    }
100
101
    /// Accept a new connection
102
    ///
103
    /// - Parameters:
104
    ///   - setNonBlocking: set non-blocking mode on the returned `Socket`. On Linux this will use accept4 with SOCK_NONBLOCK to save a system call.
105
    /// - 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.
106
    /// - Throws: An `IOError` if the operation failed.
107
0
    func accept(setNonBlocking: Bool = false) throws -> Socket? {
108
0
        try withUnsafeHandle { fd in
109
            #if os(Linux)
110
0
            let flags: Int32
111
0
            if setNonBlocking {
112
0
                flags = Linux.SOCK_NONBLOCK
113
0
            } else {
114
0
                flags = 0
115
0
            }
116
0
            let result = try Linux.accept4(descriptor: fd, addr: nil, len: nil, flags: flags)
117
            #else
118
            let result = try NIOBSDSocket.accept(socket: fd, address: nil, address_len: nil)
119
            #endif
120
0
121
0
            guard let fd = result else {
122
0
                return nil
123
0
            }
124
0
125
0
            let sock = try Socket(socket: fd)
126
0
127
            #if !os(Linux)
128
            if setNonBlocking {
129
                do {
130
                    try sock.setNonBlocking()
131
                } catch {
132
                    // best effort
133
                    try? sock.close()
134
                    throw error
135
                }
136
            }
137
            #endif
138
0
            return sock
139
0
        }
140
0
    }
141
142
    /// Close the socket.
143
    ///
144
    /// After the socket was closed all other methods will throw an `IOError` when called.
145
    ///
146
    /// - Throws: An `IOError` if the operation failed.
147
0
    override func close() throws {
148
0
        let maybePathname = self.cleanupOnClose ? (try? self.localAddress().pathname) : nil
149
0
        try super.close()
150
0
        if let socketPath = maybePathname {
151
0
            try BaseSocket.cleanupSocket(unixDomainSocketPath: socketPath)
152
0
        }
153
0
    }
154
}
155
#endif  // !os(WASI)