使用Swift軟件包管理器的Swift套接字框架。在iOS,macOS和Linux上工作。
swift-5.1-RELEASE工具鏈(最新版本所需的最低要求)swift-5.4-RELEASE工具鏈(推薦)筆記:
如果在iOS上創建UDP服務器,則可能需要遵循幾個步驟:
從命令行構建插座:
% cd <path-to-clone>
% swift build
從命令行運行插座的提供的單元測試:
% cd <path-to-clone>
% swift build
% swift test
要將BlueSocket包含在Swift軟件包管理器軟件包中,請將其添加到您的Package.swift文件中定義的dependencies項屬性中。您可以使用majorVersion和minor參數選擇版本。例如:
dependencies: [
.Package(url: "https://github.com/Kitura/BlueSocket.git", majorVersion: <majorVersion>, minor: <minor>)
]
要使用迦太基將BlueSocket包括在項目中,請使用GitHub組織以及項目名稱和版本在您的Cartfile中添加一條線。例如:
github "Kitura/BlueSocket" ~> <majorVersion>.<minor>
要使用Cocoapods將BlueSocket包括在項目中,您只需將BlueSocket添加到Podfile中,例如:
platform :ios, '10.0'
target 'MyApp' do
use_frameworks!
pod 'BlueSocket'
end
您需要做的第一件事是導入套接字框架。這是通過以下內容完成的:
import Socket
BlueSocket支持以下家庭,類型和協議:
Socket.ProtocolFamily.inetSocket.ProtocolFamily.inet6Socket.ProtocolFamily.unixSocket.SocketType.streamSocket.SocketType.datagramSocket.SocketProtocol.tcpSocket.SocketProtocol.udpSocket.SocketProtocol.unixBlueSocket提供了四種用於創建實例的工廠方法。這些都是:
create() - 這將創建一個完整的默認套接字。使用family: .inet , type: .stream和proto: .tcp 。create(family family: ProtocolFamily, type: SocketType, proto: SocketProtocol) - 此API允許您創建根據需要自定義的配置的Socket實例。您可以自定義協議系列,套接字類型和套接字協議。create(connectedUsing signature: Signature) - 此API將允許您創建一個Socket實例,並嘗試根據您在Socket.Signature中傳遞的信息來連接到服務器。create(fromNativeHandle nativeHandle: Int32, address: Address?) - 此API使您可以包裝本機文件描述符,該文件描述符在Socket的新實例中描述現有套接字。 BlueSocket允許您設置將使用的讀取緩衝區的大小。然後,根據應用程序的需求,您可以將其更改為更高或更低的值。默認值設置為Socket.SOCKET_DEFAULT_READ_BUFFER_SIZE ,其值為4096 。最小讀取緩衝區大小為Socket.SOCKET_MINIMUM_READ_BUFFER_SIZE ,設置為1024 。下面說明瞭如何更改讀取緩衝區大小(簡潔而省略了例外處理):
let mySocket = try Socket.create()
mySocket.readBufferSize = 32768
上面的示例將默認的讀取緩衝區大小設置為32768 。此設置應在首次使用Socket實例之前進行。
要關閉開放實例的插座,提供了以下功能:
close() - 此功能將執行必要的任務,以便清晰關閉開放套接字。要使用BlueSocket在插座上收聽連接,請提供以下API:
listen(on port: Int, maxBacklogSize: Int = Socket.SOCKET_DEFAULT_MAX_BACKLOG, allowPortReuse: Bool = true, node: String? = nil)是第一個參數port ,是用於偵聽的端口。第二個參數maxBacklogSize允許您設置隊列保持待處理連接的大小。該功能將基於指定的port確定適當的套接字配置。為了方便MACOS,可以將常量的Socket.SOCKET_MAX_DARWIN_BACKLOG設置為使用最大允許的積壓大小。所有平台的默認值為Socket.SOCKET_DEFAULT_MAX_BACKLOG ,當前設置為50 。對於服務器使用,可能有必要增加此值。要允許重複使用偵聽端口,請將allowPortReuse設置為true 。如果設置為false ,則如果您嘗試在已經使用的端口上偵聽,則會發生錯誤。 DEFAULT行為是allow端口重複使用。最後一個參數node可用於在特定地址上偵聽。傳遞的值是一個包含數值網絡地址的可選字符串(對於IPv4,數字和點表示法,對於IPv6,六邊性彈簧)。 DEFAULT行為是搜索適當的接口。如果node格式不正確,將返回socket_err_getaddrinfo_failed錯誤。如果node格式正確但沒有指定的地址,則將返回socket_err_bind_failed 。listen(on path: String, maxBacklogSize: Int = Socket.SOCKET_DEFAULT_MAX_BACKLOG)此API只能與.unix協議系列一起使用。第一個參數path是用於聆聽的路徑。第二個參數maxBacklogSize允許您設置隊列保持待處理連接的大小。該功能將基於指定的port確定適當的套接字配置。為了方便MACOS,可以將常量的Socket.SOCKET_MAX_DARWIN_BACKLOG設置為使用最大允許的積壓大小。所有平台的默認值為Socket.SOCKET_DEFAULT_MAX_BACKLOG ,當前設置為50 。對於服務器使用,可能有必要增加此值。 以下示例創建一個默認的Socket實例,然後立即開始在端口1337上偵聽。注意:省略了省略的異常處理,有關例外處理的示例,請參見下面的完整示例。
var socket = try Socket . create ( )
try socket . listen ( on : 1337 )當偵聽插座檢測到傳入的連接請求時,將控制返回您的程序。然後,如果您的應用程序是多線程的,則可以接受連接或繼續偵聽。 BlueSocket支持接受傳入連接的兩種不同的方式。他們是:
acceptClientConnection(invokeDelegate: Bool = true) - 此功能接受連接並根據新連接的套接字返回新的Socket實例。不受影響的實例。如果invokeDelegate為false ,並且Socket具有附加的SSLService委託,則必須使用此函數返回的Socket實例調用invokeDelegateOnAccept方法。invokeDelegateOnAccept(for newSocket: Socket) - 如果Socket實例具有SSLService委託,則將調用代表接受函數以執行SSL協商。應使用acceptClientConnection返回的Socket實例來調用它。如果使用錯誤的Socket實例調用,多次調用,或者Socket實例沒有SSLService委託,則此功能將引發異常。acceptConnection() - 此功能接受傳入連接,替換並關閉現有的聽力套接字。以前與聽力插座相關的屬性被與新連接的套接字相關的屬性所取代。除了上面描述的create(connectedUsing:)工廠方法外, Bluesocket還支持將Socket實例連接到服務器的三個附加實例功能。他們是:
connect(to host: String, port: Int32, timeout: UInt = 0) - 此API允許您根據提供的hostname和port連接到服務器。注意:如果port的值不在1-65535範圍內,則此功能將被此功能exception 。您可以選擇地將timeout設置為等待連接的毫秒數。注意:如果插座處於阻塞模式,則如果提供了大於零(0)的timeout ,則將暫時更改為非阻滯模式。返回的插座將設置回其原始設置(阻塞或非阻塞) 。如果將插座設置為非阻滯且未提供超時值,則將拋出異常。另外,您可以在成功連接後將插座設置為非阻滯。connect(to path: String) - 此API只能與.unix協議家族一起使用。它允許您根據提供的path連接到服務器。connect(using signature: Signature) - 此API允許您通過提供包含信息的Socket.Signature實例來指定連接信息。有關更多信息,請參閱Socket.Signature 。BlueSocket支持從插座讀取數據的四種不同方法。這些是(按建議的使用順序):
read(into data: inout Data) - 此功能讀取套接字上可用的所有數據,並將其返回在傳遞的Data對像中。read(into data: NSMutableData) - 此功能讀取套接字上可用的所有數據,並將其返回在傳遞的NSMutableData對像中。readString() - 此功能將讀取套接字上可用的所有數據,並將其返回為String 。如果沒有數據可讀取,則返回nil 。read(into buffer: UnsafeMutablePointer<CChar>, bufSize: Int, truncate: Bool = false) - 此功能使您可以通過提供對該緩衝區和整數的不安全指針來將數據讀取到指定大小的緩衝區中,表示該緩衝尺寸的大小。此API(除其他類型的異常外)還將拋出一個Socket.SOCKET_ERR_RECV_BUFFER_TOO_SMALL如果所提供的緩衝區太小,則除非truncate = true ,否則套接字將像插座一樣起作用(在下一步通話中返回bufSize bufsize bufsize bufsize bufsize bufsize bufsize bytes)。如果truncate = false ,您將需要再次使用適當的緩衝區大小致電(有關更多信息,請參見socket.socket.socket.buffersizened Error.bufferSizeNeeded 。readString()外,上面的所有讀取API都可以返回零(0)。這可以表明遠程連接已關閉,也可以指示插座會阻塞(假設您已關閉阻塞)。要區分兩者,可以檢查屬性remoteConnectionClosed 。如果為true ,則套接字遠程合作夥伴已關閉連接,並且該Socket實例應關閉。除了從插座讀取外, BlueSocket還提供了四種將數據寫入套接字的方法。這些是(按建議的使用順序):
write(from data: Data) - 此函數將數據對Data中包含的數據寫入插座。write(from data: NSData) - 此函數將NSData對像中包含的數據寫入插座。write(from string: String) - 此功能寫入提供給套接字的String中的數據。write(from buffer: UnsafeRawPointer, bufSize: Int) - 此功能通過提供指向該緩衝區的不安全指針和表示該緩衝區大小的整數來寫入指定大小的緩衝區中的數據。Bluesocket支持聆聽傳入數據報的三種不同方法。這些是(按建議的使用順序):
listen(forMessage data: inout Data, on port: Int, maxBacklogSize: Int = Socket.SOCKET_DEFAULT_MAX_BACKLOG) - 此功能會聽取傳入數據報的聆聽,讀取並將其返回並在傳遞的Data對像中返回。它返回包含字節數讀數和數據發起位置的Address的元組。listen(forMessage data: NSMutableData, on port: Int, maxBacklogSize: Int = Socket.SOCKET_DEFAULT_MAX_BACKLOG) - 此函數會聆聽傳入數據報的讀,讀取並將其返回並在傳遞的NSMutableData對像中返回。它返回包含字節數讀數和數據發起位置的Address的元組。listen(forMessage buffer: UnsafeMutablePointer<CChar>, bufSize: Int, on port: Int, maxBacklogSize: Int = Socket.SOCKET_DEFAULT_MAX_BACKLOG) - 此功能orged in of傳入數據摩擦,讀取並將其返回並在傳遞的Data對像中返回。它返回包含字節數讀數和數據發起位置的Address的元組。port確定適當的套接字配置。將port的值設置為零(0)將導致該功能確定合適的自由端口。maxBacklogSize允許您設置排隊掛起連接的大小。該功能將基於指定的port確定適當的套接字配置。為了方便MACOS,可以將常量的Socket.SOCKET_MAX_DARWIN_BACKLOG設置為使用最大允許的積壓大小。所有平台的默認值為Socket.SOCKET_DEFAULT_MAX_BACKLOG ,當前設置為50 。對於服務器使用,可能有必要增加此值。Bluesocket支持三種不同的讀取傳入數據報的方式。這些是(按建議的使用順序):
readDatagram(into data: inout Data) - 此功能讀取傳入的數據報,並將其返回在傳遞的Data對像中。它返回包含字節數讀數和數據發起位置的Address的元組。readDatagram(into data: NSMutableData) - 此功能讀取傳入的數據報並將其返回在傳遞的NSMutableData對像中。它返回包含字節數讀數和數據發起位置的Address的元組。readDatagram(into buffer: UnsafeMutablePointer<CChar>, bufSize: Int) - 此功能讀取傳入的數據報並將其返回在傳遞的Data對像中。它返回包含字節數讀數和數據發起位置的Address的元組。如果讀取的數據量超過bufSize ,則僅將返回bufSize 。其餘的數據讀取將被丟棄。BlueSocket還提供了四種將數據報編寫到套接字的方法。這些是(按建議的使用順序):
write(from data: Data, to address: Address) - 此功能寫入Data對像中包含的數據報到插座。write(from data: NSData, to address: Address) - 此功能將NSData對像中包含的數據報寫入套接字。write(from string: String, to address: Address) - 此功能寫入提供給套接字的String中的數據報。write(from buffer: UnsafeRawPointer, bufSize: Int, to address: Address) - 此功能通過提供指定大小的緩衝區中包含的數據,通過提供指向該緩衝區的不安全指針和表示該緩衝區大小的整數。address參數代表您發送數據報的目標的地址。上面的讀寫API使用NSData或NSMutableData可能會在不久的將來被棄用。
hostnameAndPort(from address: Address) - 此類功能提供了一種從給定的Socket.Address提取主機名和端口的方法。成功完成後,返回包含hostname和port元組。checkStatus(for sockets: [Socket]) - 此類功能允許您檢查Socket實例數組的狀態。完成後,返回一個包含兩個Socket陣列的元組。第一個數組包含Socket實例,其中有可讀取數據,第二個數組包含可以寫入的Socket實例。此API不會阻止。它將檢查每個Socket實例的狀態,然後返回結果。wait(for sockets: [Socket], timeout: UInt, waitForever: Bool = false) - 此類功能允許監視Socket實例數組,等待發生超時或在一個受監視的Socket實例之一上可讀的數據。如果指定了零(0)的超時,則此API將檢查每個插座並立即返回。否則,它將等到超時到期或從一個或多個受監視的Socket實例中讀取數據。如果發生超時,此API將返回nil 。如果一個或多個受監視的Socket實例可用數據,則這些實例將在數組中返回。如果waitForever標誌設置為true,則該函數將無限期地等待數據可用,而無論指定的超時值如何。createAddress(host: String, port: Int32) -此類功能允許創建給定host和port的Address枚舉。在成功時,如果指定的host不存在,則此功能將返回Address或nil 。isReadableOrWritable(waitForever: Bool = false, timeout: UInt = 0) - 此實例函數允許確定Socket實例是否可讀和/或可寫。返回一個包含兩個Bool值的元組。第一個(如果為true)表示Socket實例有數據要讀取,第二個(如果為true)表示可以寫入Socket實例。 waitForever如果為true,則導致此例程等到Socket是可讀或可寫的,或者發生錯誤。如果為false, timeout參數指定等待多長時間。如果為超時值指定了零(0)的值,則此功能將檢查當前狀態並立即返回。此功能返回一個包含兩個布爾值的元組,第一個readable和第二個writable 。如果Socket可讀或重複可寫的,則將它們設置為True。如果兩個都不設置為true,則會發生超時。注意:如果您試圖寫入新連接的套接字,則應確保在嘗試操作之前它是可寫的。setBlocking(shouldBlock: Bool) - 此實例函數允許您控制該Socket實例是否應以阻塞模式放置。注意:默認情況下,所有Socket實例都是在阻止模式下創建的。setReadTimeout(value: UInt = 0) - 此實例函數允許您為讀取操作設置超時。 value是UInt ,指定讀取操作在返回之前等待的時間。如果超時,讀取操作將返回0字節讀取,而errno將設置為EAGAIN 。setWriteTimeout(value: UInt = 0) - 此實例函數允許您為寫操作設置超時。 value是UInt ,指定寫操作在返回之前等待的時間。如果超時,寫操作將返回0字節書面,而errno將EAGAIN為for TCP和UNIX插座,則對於UDP ,無論超時值如何,寫操作都將成功。udpBroadcast(enable: Bool) - 此實例函數用於在UDP插座上啟用廣播模式。通過true啟用廣播, false禁用。如果Socket實例不是UDP套接字,則此功能將引發異常。下面的示例顯示瞭如何使用GCD based新的Dispatch API創建相對簡單的多線程ECHO服務器。接下來是可以通過telnet ::1 1337訪問的簡單迴聲服務器的代碼。
import Foundation
import Socket
import Dispatch
class EchoServer {
static let quitCommand : String = " QUIT "
static let shutdownCommand : String = " SHUTDOWN "
static let bufferSize = 4096
let port : Int
var listenSocket : Socket ? = nil
var continueRunningValue = true
var connectedSockets = [ Int32 : Socket ] ( )
let socketLockQueue = DispatchQueue ( label : " com.kitura.serverSwift.socketLockQueue " )
var continueRunning : Bool {
set ( newValue ) {
socketLockQueue . sync {
self . continueRunningValue = newValue
}
}
get {
return socketLockQueue . sync {
self . continueRunningValue
}
}
}
init ( port : Int ) {
self . port = port
}
deinit {
// Close all open sockets...
for socket in connectedSockets . values {
socket . close ( )
}
self . listenSocket ? . close ( )
}
func run ( ) {
let queue = DispatchQueue . global ( qos : . userInteractive )
queue . async { [ unowned self ] in
do {
// Create an IPV6 socket...
try self . listenSocket = Socket . create ( family : . inet6 )
guard let socket = self . listenSocket else {
print ( " Unable to unwrap socket... " )
return
}
try socket . listen ( on : self . port )
print ( " Listening on port: ( socket . listeningPort ) " )
repeat {
let newSocket = try socket . acceptClientConnection ( )
print ( " Accepted connection from: ( newSocket . remoteHostname ) on port ( newSocket . remotePort ) " )
print ( " Socket Signature: ( String ( describing : newSocket . signature ? . description ) ) " )
self . addNewConnection ( socket : newSocket )
} while self . continueRunning
}
catch let error {
guard let socketError = error as? Socket . Error else {
print ( " Unexpected error... " )
return
}
if self . continueRunning {
print ( " Error reported: n ( socketError . description ) " )
}
}
}
dispatchMain ( )
}
func addNewConnection ( socket : Socket ) {
// Add the new socket to the list of connected sockets...
socketLockQueue . sync { [ unowned self , socket ] in
self . connectedSockets [ socket . socketfd ] = socket
}
// Get the global concurrent queue...
let queue = DispatchQueue . global ( qos : . default )
// Create the run loop work item and dispatch to the default priority global queue...
queue . async { [ unowned self , socket ] in
var shouldKeepRunning = true
var readData = Data ( capacity : EchoServer . bufferSize )
do {
// Write the welcome string...
try socket . write ( from : " Hello, type 'QUIT' to end session n or 'SHUTDOWN' to stop server. n " )
repeat {
let bytesRead = try socket . read ( into : & readData )
if bytesRead > 0 {
guard let response = String ( data : readData , encoding : . utf8 ) else {
print ( " Error decoding response... " )
readData . count = 0
break
}
if response . hasPrefix ( EchoServer . shutdownCommand ) {
print ( " Shutdown requested by connection at ( socket . remoteHostname ) : ( socket . remotePort ) " )
// Shut things down...
self . shutdownServer ( )
return
}
print ( " Server received from connection at ( socket . remoteHostname ) : ( socket . remotePort ) : ( response ) " )
let reply = " Server response: n ( response ) n "
try socket . write ( from : reply )
if ( response . uppercased ( ) . hasPrefix ( EchoServer . quitCommand ) || response . uppercased ( ) . hasPrefix ( EchoServer . shutdownCommand ) ) &&
( !response . hasPrefix ( EchoServer . quitCommand ) && !response . hasPrefix ( EchoServer . shutdownCommand ) ) {
try socket . write ( from : " If you want to QUIT or SHUTDOWN, please type the name in all caps. ? n " )
}
if response . hasPrefix ( EchoServer . quitCommand ) || response . hasSuffix ( EchoServer . quitCommand ) {
shouldKeepRunning = false
}
}
if bytesRead == 0 {
shouldKeepRunning = false
break
}
readData . count = 0
} while shouldKeepRunning
print ( " Socket: ( socket . remoteHostname ) : ( socket . remotePort ) closed... " )
socket . close ( )
self . socketLockQueue . sync { [ unowned self , socket ] in
self . connectedSockets [ socket . socketfd ] = nil
}
}
catch let error {
guard let socketError = error as? Socket . Error else {
print ( " Unexpected error by connection at ( socket . remoteHostname ) : ( socket . remotePort ) ... " )
return
}
if self . continueRunning {
print ( " Error reported by connection at ( socket . remoteHostname ) : ( socket . remotePort ) : n ( socketError . description ) " )
}
}
}
}
func shutdownServer ( ) {
print ( " n Shutdown in progress... " )
self . continueRunning = false
// Close all open sockets...
for socket in connectedSockets . values {
self . socketLockQueue . sync { [ unowned self , socket ] in
self . connectedSockets [ socket . socketfd ] = nil
socket . close ( )
}
}
DispatchQueue . main . sync {
exit ( 0 )
}
}
}
let port = 1337
let server = EchoServer ( port : port )
print ( " Swift Echo Server Sample " )
print ( " Connect with a command line window by entering 'telnet ::1 ( port ) ' " )
server . run ( )可以通過使用SWIFT 4指定以下Package.swift文件來構建該服務器。
import PackageDescription
let package = Package (
name : " EchoServer " ,
dependencies : [
. package ( url : " https://github.com/Kitura/BlueSocket.git " , from : " 1.0.8 " ) ,
] ,
targets : [
. target (
name : " EchoServer " ,
dependencies : [
" Socket "
] ) ,
]
)或者,如果您仍在使用Swift 3,請通過指定以下Package.swift文件。
import PackageDescription
let package = Package (
name : " EchoServer " ,
dependencies : [
. Package ( url : " https://github.com/Kitura/BlueSocket.git " , majorVersion : 1 , minor : 0 ) ,
] ,
exclude : [ " EchoServer.xcodeproj " ]
)以下命令序列將在Linux上構建並運行ECHO服務器。如果在MACOS上或使用比8/18工具鏈更新的工具鏈運行,則可以省略-Xcc -fblocks Switch,因為它不再需要。
$ swift build -Xcc -fblocks
$ .build/debug/EchoServer
Swift Echo Server Sample
Connect with a command line window by entering 'telnet ::1 1337'
Listening on port: 1337
我們喜歡談論服務器端Swift和Kitura。加入我們的懈怠與團隊見面!
該庫是根據Apache 2.0許可的。完整的許可文本可在許可證中使用。