|
@@ -9,8 +9,8 @@ import Foundation
|
|
|
/* Low level routines for POSIX sockets */
|
|
/* Low level routines for POSIX sockets */
|
|
|
|
|
|
|
|
enum SocketError: ErrorType {
|
|
enum SocketError: ErrorType {
|
|
|
- case SocketInitializationFailed(String)
|
|
|
|
|
- case SocketOptionInitializationFailed(String)
|
|
|
|
|
|
|
+ case SocketCreationFailed(String)
|
|
|
|
|
+ case SocketSettingReUseAddrFailed(String)
|
|
|
case BindFailed(String)
|
|
case BindFailed(String)
|
|
|
case ListenFailed(String)
|
|
case ListenFailed(String)
|
|
|
case WriteFailed(String)
|
|
case WriteFailed(String)
|
|
@@ -19,87 +19,79 @@ enum SocketError: ErrorType {
|
|
|
case GetNameInfoFailed(String)
|
|
case GetNameInfoFailed(String)
|
|
|
case AcceptFailed(String)
|
|
case AcceptFailed(String)
|
|
|
case RecvFailed(String)
|
|
case RecvFailed(String)
|
|
|
-
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-let maxPendingConnection: Int32 = 20
|
|
|
|
|
-
|
|
|
|
|
-class Socket : Hashable {
|
|
|
|
|
- let socketId: CInt
|
|
|
|
|
-
|
|
|
|
|
- var hashValue: Int {
|
|
|
|
|
- return Int(self.socketId)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+public class Socket : Hashable {
|
|
|
|
|
|
|
|
- init(port: in_port_t = 8080) throws {
|
|
|
|
|
- self.socketId = socket(AF_INET, SOCK_STREAM, 0)
|
|
|
|
|
- if self.socketId == -1 {
|
|
|
|
|
- throw SocketError.SocketInitializationFailed(ErrorHandle.errorText)
|
|
|
|
|
|
|
+ public class func tcpSocketForListen(port: in_port_t = 8080, maxPendingConnection: Int32 = SOMAXCONN) throws -> Socket {
|
|
|
|
|
+ let socketFileDescriptor = socket(AF_INET, SOCK_STREAM, 0)
|
|
|
|
|
+ if socketFileDescriptor == -1 {
|
|
|
|
|
+ throw SocketError.SocketCreationFailed(Socket.descriptionOfLastError())
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
var value: Int32 = 1
|
|
var value: Int32 = 1
|
|
|
- if setsockopt(self.socketId, SOL_SOCKET, SO_REUSEADDR, &value, socklen_t(sizeof(Int32))) == -1 {
|
|
|
|
|
- let details = ErrorHandle.errorText
|
|
|
|
|
-// self.release()
|
|
|
|
|
- throw SocketError.SocketOptionInitializationFailed(details)
|
|
|
|
|
|
|
+ if setsockopt(socketFileDescriptor, SOL_SOCKET, SO_REUSEADDR, &value, socklen_t(sizeof(Int32))) == -1 {
|
|
|
|
|
+ let details = Socket.descriptionOfLastError()
|
|
|
|
|
+ Socket.release(socketFileDescriptor)
|
|
|
|
|
+ throw SocketError.SocketSettingReUseAddrFailed(details)
|
|
|
}
|
|
}
|
|
|
|
|
+ Socket.setNoSigPipe(socketFileDescriptor)
|
|
|
|
|
|
|
|
- self.nosigpipe()
|
|
|
|
|
-
|
|
|
|
|
- var addr = sockaddr_in(sin_len: __uint8_t(sizeof(sockaddr_in)),
|
|
|
|
|
- sin_family: sa_family_t(AF_INET),
|
|
|
|
|
- sin_port: Socket.port_htons(port),
|
|
|
|
|
- sin_addr: in_addr(s_addr: inet_addr("0.0.0.0")),
|
|
|
|
|
- sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
|
|
|
|
|
|
|
+ var addr = sockaddr_in(
|
|
|
|
|
+ sin_len: __uint8_t(sizeof(sockaddr_in)),
|
|
|
|
|
+ sin_family: sa_family_t(AF_INET),
|
|
|
|
|
+ sin_port: Socket.htonsPort(port),
|
|
|
|
|
+ sin_addr: in_addr(s_addr: inet_addr("0.0.0.0")),
|
|
|
|
|
+ sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
|
|
|
var sock_addr = sockaddr(sa_len: 0, sa_family: 0, sa_data: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
|
|
var sock_addr = sockaddr(sa_len: 0, sa_family: 0, sa_data: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
|
|
|
memcpy(&sock_addr, &addr, Int(sizeof(sockaddr_in)))
|
|
memcpy(&sock_addr, &addr, Int(sizeof(sockaddr_in)))
|
|
|
|
|
|
|
|
- if bind(self.socketId, &sock_addr, socklen_t(sizeof(sockaddr_in))) == -1 {
|
|
|
|
|
- let details = ErrorHandle.errorText
|
|
|
|
|
-// self.release()
|
|
|
|
|
|
|
+ if bind(socketFileDescriptor, &sock_addr, socklen_t(sizeof(sockaddr_in))) == -1 {
|
|
|
|
|
+ let details = Socket.descriptionOfLastError()
|
|
|
|
|
+ Socket.release(socketFileDescriptor)
|
|
|
throw SocketError.BindFailed(details)
|
|
throw SocketError.BindFailed(details)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if listen(self.socketId, maxPendingConnection ) == -1 {
|
|
|
|
|
- let details = ErrorHandle.errorText
|
|
|
|
|
-// self.release()
|
|
|
|
|
|
|
+ if listen(socketFileDescriptor, maxPendingConnection ) == -1 {
|
|
|
|
|
+ let details = Socket.descriptionOfLastError()
|
|
|
|
|
+ Socket.release(socketFileDescriptor)
|
|
|
throw SocketError.ListenFailed(details)
|
|
throw SocketError.ListenFailed(details)
|
|
|
}
|
|
}
|
|
|
|
|
+ return Socket(socketFileDescriptor: socketFileDescriptor)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private init(socketId: CInt) {
|
|
|
|
|
- self.socketId = socketId
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ private let socketFileDescriptor: CInt
|
|
|
|
|
|
|
|
- deinit {
|
|
|
|
|
- print("deinit socket")
|
|
|
|
|
- shutdown(self.socketId, SHUT_RDWR)
|
|
|
|
|
- close(self.socketId)
|
|
|
|
|
|
|
+ init(socketFileDescriptor: CInt) {
|
|
|
|
|
+ self.socketFileDescriptor = socketFileDescriptor
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- func nosigpipe() {
|
|
|
|
|
- // prevents crashes when blocking calls are pending and the app is paused ( via Home button )
|
|
|
|
|
- var no_sig_pipe: Int32 = 1;
|
|
|
|
|
- setsockopt(self.socketId, SOL_SOCKET, SO_NOSIGPIPE, &no_sig_pipe, socklen_t(sizeof(Int32)));
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ public var hashValue: Int { return Int(self.socketFileDescriptor) }
|
|
|
|
|
|
|
|
- class func port_htons(port: in_port_t) -> in_port_t {
|
|
|
|
|
- let isLittleEndian = Int(OSHostByteOrder()) == OSLittleEndian
|
|
|
|
|
- return isLittleEndian ? _OSSwapInt16(port) : port
|
|
|
|
|
|
|
+ public func release() {
|
|
|
|
|
+ Socket.release(self.socketFileDescriptor)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ public func acceptClientSocket() throws -> Socket {
|
|
|
|
|
+ var addr = sockaddr(sa_len: 0, sa_family: 0, sa_data: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
|
|
|
|
|
+ var len: socklen_t = 0
|
|
|
|
|
+ let clientSocket = accept(self.socketFileDescriptor, &addr, &len)
|
|
|
|
|
+ if clientSocket == -1 {
|
|
|
|
|
+ throw SocketError.AcceptFailed(Socket.descriptionOfLastError())
|
|
|
|
|
+ }
|
|
|
|
|
+ Socket.setNoSigPipe(clientSocket)
|
|
|
|
|
+ return Socket(socketFileDescriptor: clientSocket)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // MARK: - write methods
|
|
|
|
|
-
|
|
|
|
|
- func writeUTF8(string: String) throws {
|
|
|
|
|
|
|
+ public func writeUTF8(string: String) throws {
|
|
|
try self.writeString(string, withEncoding: NSUTF8StringEncoding)
|
|
try self.writeString(string, withEncoding: NSUTF8StringEncoding)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- func writeASCII(string: String) throws {
|
|
|
|
|
|
|
+ public func writeASCII(string: String) throws {
|
|
|
try self.writeString(string, withEncoding: NSASCIIStringEncoding)
|
|
try self.writeString(string, withEncoding: NSASCIIStringEncoding)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private func writeString(string: String, withEncoding encoding: NSStringEncoding) throws {
|
|
|
|
|
|
|
+ public func writeString(string: String, withEncoding encoding: NSStringEncoding) throws {
|
|
|
if let nsdata = string.dataUsingEncoding(encoding) {
|
|
if let nsdata = string.dataUsingEncoding(encoding) {
|
|
|
try self.writeData(nsdata)
|
|
try self.writeData(nsdata)
|
|
|
} else {
|
|
} else {
|
|
@@ -107,85 +99,77 @@ class Socket : Hashable {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- func writeData(data: NSData) throws {
|
|
|
|
|
|
|
+ public func writeData(data: NSData) throws {
|
|
|
var sent = 0
|
|
var sent = 0
|
|
|
let unsafePointer = UnsafePointer<UInt8>(data.bytes)
|
|
let unsafePointer = UnsafePointer<UInt8>(data.bytes)
|
|
|
while sent < data.length {
|
|
while sent < data.length {
|
|
|
- let s = write(self.socketId, unsafePointer + sent, Int(data.length - sent))
|
|
|
|
|
|
|
+ let s = write(self.socketFileDescriptor, unsafePointer + sent, Int(data.length - sent))
|
|
|
if s <= 0 {
|
|
if s <= 0 {
|
|
|
- throw SocketError.WriteFailed(ErrorHandle.errorText)
|
|
|
|
|
|
|
+ throw SocketError.WriteFailed(Socket.descriptionOfLastError())
|
|
|
}
|
|
}
|
|
|
sent += s
|
|
sent += s
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // MARK: -
|
|
|
|
|
|
|
+ public func read() -> Int {
|
|
|
|
|
+ var buffer = [UInt8](count: 1, repeatedValue: 0);
|
|
|
|
|
+ let next = recv(self.socketFileDescriptor as Int32, &buffer, Int(buffer.count), 0)
|
|
|
|
|
+ if next <= 0 {
|
|
|
|
|
+ return next
|
|
|
|
|
+ }
|
|
|
|
|
+ return Int(buffer[0])
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- func acceptClientSocket() throws -> Socket {
|
|
|
|
|
- var addr = sockaddr(sa_len: 0, sa_family: 0, sa_data: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
|
|
|
|
|
- var len: socklen_t = 0
|
|
|
|
|
- let clientSocket = accept(self.socketId, &addr, &len)
|
|
|
|
|
- if clientSocket == -1 {
|
|
|
|
|
- throw SocketError.AcceptFailed(ErrorHandle.errorText)
|
|
|
|
|
|
|
+ public func readLine() throws -> String {
|
|
|
|
|
+ var characters: String = ""
|
|
|
|
|
+ var n = 0
|
|
|
|
|
+ repeat {
|
|
|
|
|
+ n = self.read()
|
|
|
|
|
+ if ( n > 13 /* CR */ ) { characters.append(Character(UnicodeScalar(n))) }
|
|
|
|
|
+ } while n > 0 && n != 10 /* NL */
|
|
|
|
|
+ if n == -1 {
|
|
|
|
|
+ throw SocketError.RecvFailed(Socket.descriptionOfLastError())
|
|
|
}
|
|
}
|
|
|
- self.nosigpipe()
|
|
|
|
|
- return Socket(socketId: clientSocket)
|
|
|
|
|
|
|
+ return characters
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- func peername() throws -> String {
|
|
|
|
|
|
|
+ public func peername() throws -> String {
|
|
|
var addr = sockaddr(), len: socklen_t = socklen_t(sizeof(sockaddr))
|
|
var addr = sockaddr(), len: socklen_t = socklen_t(sizeof(sockaddr))
|
|
|
- if getpeername(self.socketId, &addr, &len) != 0 {
|
|
|
|
|
- throw SocketError.GetPeerNameFailed(ErrorHandle.errorText)
|
|
|
|
|
|
|
+ if getpeername(self.socketFileDescriptor, &addr, &len) != 0 {
|
|
|
|
|
+ throw SocketError.GetPeerNameFailed(Socket.descriptionOfLastError())
|
|
|
}
|
|
}
|
|
|
var hostBuffer = [CChar](count: Int(NI_MAXHOST), repeatedValue: 0)
|
|
var hostBuffer = [CChar](count: Int(NI_MAXHOST), repeatedValue: 0)
|
|
|
if getnameinfo(&addr, len, &hostBuffer, socklen_t(hostBuffer.count), nil, 0, NI_NUMERICHOST) != 0 {
|
|
if getnameinfo(&addr, len, &hostBuffer, socklen_t(hostBuffer.count), nil, 0, NI_NUMERICHOST) != 0 {
|
|
|
- throw SocketError.GetNameInfoFailed(ErrorHandle.errorText)
|
|
|
|
|
|
|
+ throw SocketError.GetNameInfoFailed(Socket.descriptionOfLastError())
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
guard let name = String.fromCString(hostBuffer) else {
|
|
guard let name = String.fromCString(hostBuffer) else {
|
|
|
throw SocketError.ConvertingPeerNameFailed
|
|
throw SocketError.ConvertingPeerNameFailed
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
return name
|
|
return name
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-// func release() {
|
|
|
|
|
-// print("release")
|
|
|
|
|
-// shutdown(self.socketId, SHUT_RDWR)
|
|
|
|
|
-// close(self.socketId)
|
|
|
|
|
-// }
|
|
|
|
|
|
|
|
|
|
- // MARK: - basic receiving
|
|
|
|
|
|
|
+ private class func descriptionOfLastError() -> String {
|
|
|
|
|
+ return String.fromCString(UnsafePointer(strerror(errno))) ?? "Error: \(errno)"
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- func nextInt8() -> Int {
|
|
|
|
|
- var buffer = [UInt8](count: 1, repeatedValue: 0);
|
|
|
|
|
- let next = recv(self.socketId as Int32, &buffer, Int(buffer.count), 0)
|
|
|
|
|
- if next <= 0 {
|
|
|
|
|
- return next
|
|
|
|
|
- }
|
|
|
|
|
- return Int(buffer[0])
|
|
|
|
|
|
|
+ private class func setNoSigPipe(socket: CInt) {
|
|
|
|
|
+ // prevents crashes when blocking calls are pending and the app is paused ( via Home button )
|
|
|
|
|
+ var no_sig_pipe: Int32 = 1;
|
|
|
|
|
+ setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &no_sig_pipe, socklen_t(sizeof(Int32)));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- func nextLine() throws -> String {
|
|
|
|
|
- var characters: String = ""
|
|
|
|
|
- var n = 0
|
|
|
|
|
- repeat {
|
|
|
|
|
- n = self.nextInt8()
|
|
|
|
|
- if ( n > 13 /* CR */ ) { characters.append(Character(UnicodeScalar(n))) }
|
|
|
|
|
- } while n > 0 && n != 10 /* NL */
|
|
|
|
|
- if n == -1 {
|
|
|
|
|
- throw SocketError.RecvFailed(ErrorHandle.errorText)
|
|
|
|
|
- }
|
|
|
|
|
- return characters
|
|
|
|
|
|
|
+ private class func release(socket: CInt) {
|
|
|
|
|
+ shutdown(socket, SHUT_RDWR)
|
|
|
|
|
+ close(socket)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private class func htonsPort(port: in_port_t) -> in_port_t {
|
|
|
|
|
+ let isLittleEndian = Int(OSHostByteOrder()) == OSLittleEndian
|
|
|
|
|
+ return isLittleEndian ? _OSSwapInt16(port) : port
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func ==(socket1: Socket, socket2: Socket) -> Bool {
|
|
|
|
|
- return socket1.socketId == socket2.socketId
|
|
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
-class ErrorHandle {
|
|
|
|
|
- class var errorText: String {
|
|
|
|
|
- return String.fromCString(UnsafePointer(strerror(errno))) ?? "error converting error text from C String"
|
|
|
|
|
- }
|
|
|
|
|
|
|
+public func ==(socket1: Socket, socket2: Socket) -> Bool {
|
|
|
|
|
+ return socket1.hashValue == socket2.hashValue
|
|
|
}
|
|
}
|