How to detect end of socket packet in Swift - swift

I am building an HTTP server in Swift. The server will eventually run on Linux, but right now only compiles on Mac OS.
Anyways, I have the socket connections working perfectly and I'm able to receive the data stream, but I am having difficulty finding the best way to determine how to detect the end of the socket data stream.
I know there's many ways todo this, and I know that sockets basically just spit out pure binary data and have no concept of a "packet" that has a beginning and end. My question what are the best techniques used to detect the end of a packet sent from a web browser requesting a web page? I know for JSON it's pretty easy, but I'm curious about when the client is requesting a web page specifically.
Thanks

InputStream did that for you, you don't have to care about that.
Try to use this:
import Foundation
typealias OnComing = (String) -> (Void)
class SocketClient: NSObject, StreamDelegate {
var host:String?
var port:Int?
var inputStream: InputStream?
var outputStream: OutputStream?
var status = false;
var output = ""
var bufferSize = 1024;
var onComing:OnComing!
func makeCFStreamConnection(host: String, port: Int, onComing:#escaping OnComing) {
self.host = host
self.port = port
self.onComing = onComing
Stream.getStreamsToHost(withName: host, port: port, inputStream: &self.inputStream, outputStream: &self.outputStream)
if self.inputStream != nil && self.outputStream != nil {
self.inputStream!.delegate = self
self.outputStream!.delegate = self
self.inputStream!.schedule(in: .main, forMode: RunLoopMode.defaultRunLoopMode)
self.outputStream!.schedule(in: .main, forMode: RunLoopMode.defaultRunLoopMode)
self.inputStream!.open()
self.outputStream!.open()
}
}
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
if aStream === self.inputStream {
switch eventCode {
case Stream.Event.errorOccurred:
break
case Stream.Event.openCompleted:
break
case Stream.Event.hasBytesAvailable:
break
read()
default:
break
}
} else if aStream === self.outputStream {
switch eventCode {
case Stream.Event.errorOccurred:
break
case Stream.Event.openCompleted:
break
case Stream.Event.hasSpaceAvailable:
break
default:
break
}
}
}
func read() {
var buffer = [UInt8](repeating: 0, count: bufferSize)
while (self.inputStream!.hasBytesAvailable) {
let bytesRead: Int = inputStream!.read(&buffer, maxLength: buffer.count)
if bytesRead >= 0 {
output += NSString(bytes: UnsafePointer(buffer), length: bytesRead, encoding: String.Encoding.ascii.rawValue)! as String
} else {
print("# Stream read() error")
}
}
self.onComing(output)
}
func write(message:String) {
let encodedDataArray = [UInt8]("\(message)\n".utf8)
self.outputStream?.write(encodedDataArray, maxLength: encodedDataArray.count)
}
}
Your problem is resolved by read()

Related

How to create a chat app using TCP in swift

I simply wants to pass a string from one phone to another (vice versa) using TCP connection. Hence, I have a few questions as I'm a bit confused.
Can this be achieved using two different simulators in two different mac machines ?
Can this be achieved using two different simulators in the same mac machine ?
If this can be done on simulators would replacing the IP of the mac would work with 8080 port ?
My partially completed code as bellow;
class NWTCPConnection: NSObject {
var connection: NWConnection!
func connectToTcp() {
let PORT: NWEndpoint.Port = 8080
let ipAddress :NWEndpoint.Host = "192.168.8.133" //Machines IP
let queue = DispatchQueue(label: "TCP Client Queue")
let tcp = NWProtocolTCP.Options.init()
tcp.noDelay = true
let params = NWParameters.init(tls: nil, tcp: tcp)
connection = NWConnection(to: NWEndpoint.hostPort(host: ipAddress, port: PORT), using: params)
connection.stateUpdateHandler = { (newState) in
switch (newState) {
case .ready:
print("Socket State: Ready")
UserDefaults.standard.set(true, forKey: "isConnected")
self.sendMSG()
self.receive()
default:
UserDefaults.standard.set(false, forKey: "isConnected")
break
}
}
connection.start(queue: queue)
}
func sendMSG() {
print("send data")
let message1 = "hello world"
let content: Data = message1.data(using: .utf8)!
connection.send(content: content, completion: NWConnection.SendCompletion.contentProcessed(({ (NWError) in
if (NWError == nil) {
print("Data was sent to TCP destination ")
} else {
print("ERROR! Error when data (Type: Data) sending. NWError: \n \(NWError!)")
}
})))
}
// How to trigger this ???
func receive() {
connection.receiveMessage { (data, context, isComplete, error) in
if (isComplete) {
print("Receive is complete, count bytes: \(data!.count)")
if (data != nil) {
print(data!)
} else {
print("Data == nil")
}
}
}
}
the moment I run the simulator and call the connectToTcp() I get the following error
nw_socket_handle_socket_event [C1:1] Socket SO_ERROR [61: Connection refused]

How to convert message received by Swift socket communication to String type

I want to do TCP communication using a library called SwiftSocket.
Below is the sample code of SwiftSocket.
func echoService(client: TCPClient) {
print("Newclient from:\(client.address)[\(client.port)]")
var d = client.read(1024*10)
client.send(data: d!)
client.close()
}
func testServer() {
let server = TCPServer(address: "127.0.0.1", port: 8080)
switch server.listen() {
case .success:
while true {
if var client = server.accept() {
echoService(client: client)
} else {
print("accept error")
}
}
case .failure(let error):
print(error)
}
}
I want to convert the received message to String in the third line of the above code
var d = client.read(1024*10)
How can I do that?
Try this;
if let string = String(bytes: d, encoding: .utf8)
{
print(string)
} else
{
print("not a valid UTF-8 sequence")
}

CFSocket in Swift (4)

I try to establish a connection to a TCP server, unfortunately without success. Here is my actual way. Does anyone knows, where the error is? I'm getting the error at CFSocketConnectToAddress (I get the .error result, so my code results in the error while connecting print).
Any ideas?
host = "192.168.0.20"
port = 8888
socket = CFSocketCreate(kCFAllocatorDefault,
AF_INET,
SOCK_STREAM,
IPPROTO_TCP,
CFSocketCallBackType.readCallBack.rawValue, { (socket: CFSocket?, callBackType: CFSocketCallBackType, address: CFData?, data: UnsafeRawPointer?, info: UnsafeMutableRawPointer?) -> Void in print("callback test") }, nil)
if socket == nil {
print("Error while creating socket")
} else {
var sin = sockaddr_in(
sin_len: UInt8(MemoryLayout<sockaddr_in>.size),
sin_family: sa_family_t(AF_INET),
sin_port: in_port_t(port),
sin_addr: in_addr(s_addr: inet_addr(host)),
sin_zero: (0,0,0,0,0,0,0,0)
)
addressData = NSData(bytes: &sin, length: MemoryLayout.size(ofValue: sin)) as CFData
let connectResult: CFSocketError = CFSocketConnectToAddress(socket, addressData!, 10)
switch connectResult {
case .success:
print("Connected")
case .error:
print("Error while connecting")
case .timeout:
print("Timeout while connecting")
}
The problem was the port! The htons macro is not available in swift. This is the working way now:
func connectToServer(timeout: Int=10) throws {
// check, if address is valid https://linux.die.net/man/3/inet_makeaddr
let inAddr = inet_addr(host)
if inAddr == INADDR_NONE {
throw SocketError.noValidAddress
}
let socket = CFSocketCreate(kCFAllocatorDefault,
AF_INET,
SOCK_STREAM,
IPPROTO_TCP,
CFSocketCallBackType.readCallBack.rawValue,
{ (socket, callBackType, address, data, info) in
TCPClientCallBack(socket: socket, callBackType: callBackType, address: address, data: data, info: info)
},
nil)
if socket == nil {
throw SocketError.socketCreationFailed
}
var sin = sockaddr_in() // https://linux.die.net/man/7/ip
sin.sin_len = __uint8_t(MemoryLayout.size(ofValue: sin))
sin.sin_family = sa_family_t(AF_INET)
sin.sin_port = UInt16(port).bigEndian
sin.sin_addr.s_addr = inAddr
let addressDataCF = NSData(bytes: &sin, length: MemoryLayout.size(ofValue: sin)) as CFData
let socketErr = CFSocketConnectToAddress(socket, addressDataCF, CFTimeInterval(timeout))
switch socketErr {
case .success:
print("connected")
case .error:
throw SocketError.connectionError
case .timeout:
throw SocketError.connectionTimeout
}
}
enum SocketError: Error {
case noValidAddress
case socketCreationFailed
case connectionError
case connectionTimeout
}

Reading an InputStream into a Data object

In Swift 3.x, we usually handle binary data using Data; from it you can generate most other important types, and there are useful functions on it.
But how do I create a Data from an InputStream? Is there a nice way?
I could not find a nice way. We can create a nice-ish wrapper around the unsafe stuff:
extension Data {
init(reading input: InputStream) throws {
self.init()
input.open()
defer {
input.close()
}
let bufferSize = 1024
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
defer {
buffer.deallocate()
}
while input.hasBytesAvailable {
let read = input.read(buffer, maxLength: bufferSize)
if read < 0 {
//Stream error occured
throw input.streamError!
} else if read == 0 {
//EOF
break
}
self.append(buffer, count: read)
}
}
}
This is for Swift 5. Find full code with test (and a variant that reads only some of the stream) here.
above the code, It can be infinite loop.
When I convert httpbodyInpustream to data, it happend.
So I add a condition.
extension Data {
init(reading input: InputStream) {
self.init()
input.open()
let bufferSize = 1024
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
while input.hasBytesAvailable {
let read = input.read(buffer, maxLength: bufferSize)
if (read == 0) {
break // added
}
self.append(buffer, count: read)
}
buffer.deallocate(capacity: bufferSize)
input.close()
}
}

How to test if my remote socket NSStream are correctly open

TL;DR : What's the way to check if my remote stream are opened correctly after a call to NSStream.getStreamsToHostWithName(...)?
My application is a mobile IOS8 swift application.
I am using NSStream for input and output socket communication with a remote server.
To connect to my server and open my stream I use this code:
func connect(host: String, port: Int) -> Bool
{
//clear the previous connection if existing (and update self.connected)
disconnect()
//updating the current connection
self.host = host
self.port = port
//pairing NSstreams with remote connection
NSStream.getStreamsToHostWithName(self.host!, port: self.port!, inputStream: &inputStream, outputStream: &outputStream)
if (self.inputStream != nil && self.outputStream != nil)
{
//open streams
self.inputStream?.open()
self.outputStream?.open()
}
if self.outputStream?.streamError == nil && self.inputStream?.streamError == nil
{
println("SOK") //PROBLEM 1
}
//error checking after opening streams // PROBLEM 2
if var inputStreamErr: CFError = CFReadStreamCopyError(self.inputStream)?
{
println("InputStream error : " + CFErrorCopyDescription(inputStreamErr))
}
else if var outputStreamErr: CFError = CFWriteStreamCopyError(self.outputStream)?
{
println("OutStream error : " + CFErrorCopyDescription(outputStreamErr))
}
else
{
//set the delegate to self
self.inputStream?.delegate = self
self.outputStream?.delegate = self
self.connected = true
}
//return connection state
return self.connected
}
My problem is located at //PROBLEM1 and //PROBLEM2.
At these points I try to determine if my sockets are opened correctly, but even if the server is not running this code still works, then the read and write operations are failing.
I would like to be able to determine if the connection failed or not.
Maybe I am doing it totally wrong, I don't get how to test this.
First of all, you have to schedule the stream on a runloop:
inputStream!.scheduleInRunLoop(.mainRunLoop(), forMode: NSDefaultRunLoopMode)
outputStream!.scheduleInRunLoop(.mainRunLoop(), forMode: NSDefaultRunLoopMode)
And, in your code, it's too soon to check the error. Because open() is async operation, you have to wait the result using delegate. Here is working example:
import Foundation
class Connection: NSObject, NSStreamDelegate {
var host:String?
var port:Int?
var inputStream: NSInputStream?
var outputStream: NSOutputStream?
func connect(host: String, port: Int) {
self.host = host
self.port = port
NSStream.getStreamsToHostWithName(host, port: port, inputStream: &inputStream, outputStream: &outputStream)
if inputStream != nil && outputStream != nil {
// Set delegate
inputStream!.delegate = self
outputStream!.delegate = self
// Schedule
inputStream!.scheduleInRunLoop(.mainRunLoop(), forMode: NSDefaultRunLoopMode)
outputStream!.scheduleInRunLoop(.mainRunLoop(), forMode: NSDefaultRunLoopMode)
print("Start open()")
// Open!
inputStream!.open()
outputStream!.open()
}
}
func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) {
if aStream === inputStream {
switch eventCode {
case NSStreamEvent.ErrorOccurred:
print("input: ErrorOccurred: \(aStream.streamError?.description)")
case NSStreamEvent.OpenCompleted:
print("input: OpenCompleted")
case NSStreamEvent.HasBytesAvailable:
print("input: HasBytesAvailable")
// Here you can `read()` from `inputStream`
default:
break
}
}
else if aStream === outputStream {
switch eventCode {
case NSStreamEvent.ErrorOccurred:
print("output: ErrorOccurred: \(aStream.streamError?.description)")
case NSStreamEvent.OpenCompleted:
print("output: OpenCompleted")
case NSStreamEvent.HasSpaceAvailable:
print("output: HasSpaceAvailable")
// Here you can write() to `outputStream`
default:
break
}
}
}
}
Then:
let conn = Connection()
conn.connect("www.example.com", port: 80)