APIDoc Build Status - Master macOS iOS Linux Apache 2 Slack Status

BlueSocket

使用 Swift Package Manager 的 Swift Socket 框架。可在 iOS、macOS 和 Linux 上运行。

前提条件

Swift

macOS

iOS

注意

如果在 iOS 上创建 UDP 服务器,您可能需要遵循以下几个步骤

Linux

其他平台

附加组件

构建

要从命令行构建 Socket,请执行以下操作

% cd <path-to-clone>
% swift build

测试

要从命令行运行 Socket 的提供的单元测试,请执行以下操作

% cd <path-to-clone>
% swift build
% swift test

使用 BlueSocket

包含在您的项目中

Swift Package Manager

要将 BlueSocket 包含到 Swift Package Manager 软件包中,请将其添加到您的 Package.swift 文件中定义的 dependencies 属性。您可以使用 majorVersionminor 参数选择版本。例如

	dependencies: [
		.Package(url: "https://github.com/Kitura/BlueSocket.git", majorVersion: <majorVersion>, minor: <minor>)
	]

Carthage

要使用 Carthage 将 BlueSocket 包含到项目中,请在您的 Cartfile 中添加一行,其中包含 GitHub 组织和项目名称以及版本。例如

	github "Kitura/BlueSocket" ~> <majorVersion>.<minor>

CocoaPods

要使用 CocoaPods 将 BlueSocket 包含到项目中,只需将 BlueSocket 添加到您的 Podfile 中,例如

    platform :ios, '10.0'

    target 'MyApp' do
        use_frameworks!
        pod 'BlueSocket'
    end

开始之前

您需要做的第一件事是导入 Socket 框架。这可以通过以下方式完成

import Socket

Family, Type and Protocol Support (系列、类型和协议支持)

BlueSocket 支持以下系列、类型和协议

创建 socket。

BlueSocket 提供了四种不同的工厂方法来创建实例。 它们是

设置读取缓冲区大小。

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 实例之前完成此设置。

关闭 socket。

要关闭打开的实例的 socket,提供了以下函数

监听 socket (TCP/UNIX)。

要使用 BlueSocket 监听 socket 上的连接,提供了以下 API

示例

以下示例创建一个默认的 Socket 实例,然后立即开始在端口 1337 上监听。 注意:为了简洁起见,省略了异常处理,有关异常处理的示例,请参见下面的完整示例。

var socket = try Socket.create()
try socket.listen(on: 1337)

接受来自监听 socket 的连接 (TCP/UNIX)。

当监听 socket 检测到传入的连接请求时,控制权将返回到您的程序。 然后,您可以接受连接或继续监听,或者如果您的应用程序是多线程的,则可以同时接受连接和继续监听。 BlueSocket 支持两种不同的接受传入连接的方式。 它们是

将 socket 连接到服务器 (TCP/UNIX)。

除了上面描述的 create(connectedUsing:) 工厂方法之外,BlueSocket 还支持三个额外的实例函数,用于将 Socket 实例连接到服务器。 它们是

从 socket 读取数据 (TCP/UNIX)。

BlueSocket 支持四种不同的从 socket 读取数据的方式。 这些是(按推荐使用顺序)

将数据写入 Socket (TCP/UNIX)。

除了从 socket 读取数据之外,BlueSocket 还提供了四种将数据写入 socket 的方法。它们是(按建议的使用顺序):

监听数据报消息 (UDP)。

BlueSocket 支持三种不同的方式来监听传入的数据报。它们是(按建议的使用顺序):

读取数据报 (UDP)。

BlueSocket 支持三种不同的方式来读取传入的数据报。它们是(按建议的使用顺序):

写入数据报 (UDP)。

BlueSocket 还提供了四种将数据报写入 socket 的方法。它们是(按建议的使用顺序):

关于 NSData 和 NSMutableData 的重要说明

使用 NSDataNSMutableData 的上述 read 和 write API 可能在不久的将来被弃用

其他实用函数

完整示例

以下示例展示了如何使用基于新的 GCD based Dispatch API 创建一个相对简单的多线程回显服务器。 以下代码是一个简单的回显服务器,一旦运行,可以通过 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\nor '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("\nShutdown 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 上构建并运行回显服务器。 如果在 macOS 上运行,或者使用任何比 8/18 工具链更新的工具链,您可以省略 -Xcc -fblocks 开关,因为它不再需要。

$ 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。 加入我们的 Slack 来与团队见面!

许可

此库是在 Apache 2.0 许可下发布的。 完整的许可文本可在 LICENSE 中找到。