使用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许可的。完整的许可文本可在许可证中使用。