I'm making a simple test app using Swift Network framework. One server, one client (both iOS simulators), and successfully established tcp connection between them. I'm trying to send a series of short messages.
Server is sending strings made of natural numbers from 1 to 999. Each number is sent as separate Data, isComplete and contentContext default values are true and .defaultMessage correspondingly.
var count = 0
func send(data: Data) {
self.connection.send(content: data, completion: .contentProcessed( { error in
if let error = error {
self.connectionDidFail(error: error)
return
}
self.count += 1
let newData = "\(self.count)".data(using: .utf8)!
if self.count < 1000 {
self.send(data: newData)
}
print("connection \(self.id) did send, data: \(newData as NSData)")
}))
}
Client is receiving them...
private func setupReceive() {
nwConnection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { (data, contentContext, isComplete, error) in
if let data = data, !data.isEmpty {
print("isComplete: \(isComplete)")
print("isFinal: \(contentContext.isFinal)")
let message = String(data: data, encoding: .utf8)
print("connection did receive, data: \(data as NSData) string: \(message ?? "-" )")
}
if let error = error {
self.connectionDidFail(error: error)
} else {
self.setupReceive()
}
}
}
... but there is something wrong. Some messages look like their bytes are stuck together (for example consecutive messages "2", "3", "4", "5" could be received like a single message "2345")
For all received messages isComplete equals false and contentContext property isFinal equals true, while .defaultMessage.isFinal should be equal to false.
For now, i'm stuck. Am i just using wrong parameters (I've tried various combinations, but none seems working to me)? Is NWConnection hideously changing messages while sending them?
How can one send a series of separate messages?
I am not familiar with this Network framework. But from reading documentation, it seems like you are directly using the transport layer to transmit messages.
Without an application layer protocol, there probably isn't a way for the client to distinguish between different messages. For example, using http as your application protocol has different parameters in the request to identify if its a complete message or not (Content-Length, Content-Encoding, Transfer-Encoding etc...)(Hope an expert can confirm on this)
You may define you own simple protocol so that decoding is possible on client end. For example, you can wrap each message with <Message>your-message</Message> and use it to identify different messages at the client (you will face some drawbacks of something so simple along the way)
There are many things to consider when developing a custom protocol. Better do some reading on this subject if you are serious on it.
Upon further reading, it seems that the following receive is provided:
final func receiveMessage(completion: #escaping (Data?, NWConnection.ContentContext?, Bool, NWError?) -> Void)
... which is able to read complete data. The Discussion section will provide some valuable insight on transport type implications and framing logic required.
Related
I am writing an application which sends commands and receives data via UDP network connection with a device.
I am trying to create an object which handles all the network related tasks.
I want the object to make a connection, send a command string to the connection, and receive data from the connection. I can get all the pieces to work but they happen out of sync with the running program. It is hard to explain but let me show you the code first then explain the issue.
import Foundation
import Network
var myConnection: NWConnection?
var backToString = "test"
class NetworkUDP: NSObject{
func makeConnection(){
let myPort = NWEndpoint.Port(rawValue: 50536)
let myHost = NWEndpoint.Host("192.168.7.239")
myConnection = NWConnection(host: myHost, port: myPort!, using: .udp)
myConnection?.start(queue: .main)
}
func send(myCommand: String) {
myConnection?.send(content: myCommand.data(using: String.Encoding.utf8), completion: NWConnection.SendCompletion.contentProcessed({(NWError) in print(NWError as Any)}))
print(myCommand)
}
func receive() {
myConnection?.receiveMessage { (data, context, isComplete, error) in
if (data != nil) {
backToString = String(decoding: data!, as : UTF8.self)
print(backToString)
} else {
print("Data = nil")
}
}
}
}
So if I instantiate this object and issue makeConnection(), send(myCommand: aCommand), receive() statements from a ViewController everything works but the functions run and return before the commands are actually sent and values returned.
In other words, I can't return the value of backToString as a return value from the function receive(). If I do it will always return "test" which is the initial value. It will eventually be replaced with the string value returned from the device but only after a delay and only after the function has already returned.
What I want to do is make a receive function of the form receive() -> String where String is the text string returned from the device that was sent a command. But I guess this isn't possible because the receive function returns before backToString actually receives any data. I am a bit confused by this. It seems like the function should stay halted until a value is received but it doesn't it just returns before the backToString variable receives the data from the UDP connection. However the print statement in the receive function does print the correct data but it only does so after the function receive has already returned.
Top Level Question:
I want to know how, within a retry, I can modify its source observable if it is an observable shared between multiple subscribers (in this case a BehaviorSubject/Relay).
Solution(s) I have considered:
The suggestion of using defer from this post doesn't seem to naturally port over if the source observable needs to be shared.
Use case (to fully elaborate the question)
Say I have a server connection object that, when initialized, connects to an url. Once it is created, I can also use it to get a data stream for a particular input.
class ServerConnection {
var url: URL
init(url: URL)
func getDataStream(input: String) -> Observable<Data> // the observable also errors when the instance is destroyed.
}
However, one particular url or another may be broken or overloaded. So I may want to obtain the address of a mirror and generate a new ServerConnection object. Let's say I have such a function.
// At any point in time, gets the mirror of the url with the lowest load
func getLowestLoadMirror(url: URL) -> URL {}
Ideally, I want this "mirror url" switching should be an implementation detail. The user of my code may only care about the data they receive. So we would want to encapsulate this logic in a new class:
class ServerConnectionWithMirrors {
private var currentConnection: BehaviorRelay<ServerConnection>
init(startingURL: URL)
func dataStream(for inputParams: String) -> Observable<Data>
}
// usage
let connection = ServerConnectionWithMirrors(startingURL: "www.example.com")
connection.dataStream(for: "channel1")
.subscribe { channel1Data in
// do something with channel1Data
}.disposed(by: disposeBag)
connection.dataStream(for: "channel2")
.subscribe { channel2Data in
// do something with channel2Data
}.disposed(by: disposeBag)
How should I write the dataStream() function for ServerConnectionWithMirrors? I should be using retries, but I need to ensure that the retries, when faced with a particular error (ServerOverLoadedError) update the value on the behaviorRelay.
Here is code that I have so far that demonstrates the crux at what I am trying to do. One problem is that multiple subscribers to the behaviorRelay may all update it in rapid succession when they get an error, where only one update would do.
func dataStream(for inputParams: String) -> Observable<Data> {
self.currentConnection.asObservable()
.flatMapLatest { server in
return server.getDataStream(input: inputParams)
}
.retryWhen { errors in
errors.flatMapLatest { error in
if error is ServerOverLoadedError {
self.currentConnection.accept(ServerConnection(url: getLowestLoadURL()))
} else {
return Observable.error(error)
}
}
}
}
The answer to your top level question:
I want to know how, within a retry, I can modify its source observable if it is an observable shared between multiple subscribers (in this case a BehaviorSubject/Relay).
You cannot modify a retry's source observable from within the retry. (full stop) You cannot do this whether it is shared or not. What you can do is make the source observable in such a way that it naturally updates its data for every subscription.
That is what the question you referred to is trying to explain.
func getData(from initialRequest: URLRequest) -> Observable<Data> {
return Observable.deferred {
var correctRequest = initialRequest
let correctURL = getLowestLoadMirror(url: initialRequest.url!)
correctRequest.url = correctURL
return Observable.just(correctRequest)
}
.flatMapLatest {
getDataFromServer(request: $0)
}
.retryWhen { error in
error
.do(onNext: {
guard $0 is ServerOverloadedError else { throw $0 }
})
}
}
With the above code, every time deferred is retried, it will call its closure and every time its closure is called, the URL will the lowest load will be used.
I read some post says that the best practice to deal with RxSwift is to only pass fatal error to the onError and pass Result to the onNext.
It makes sense to me until I realise that I can't deal with retry anymore since it only happen on onError.
How do I deal with this issue?
Another question is, how do I handle global and local retry mixes together?
A example would be, the iOS receipt validation flow.
1, try to fetch receipt locally
2, if failed, ask Apple server for the latest receipt.
3, send the receipt to our backend to validate.
4, if success, then whole flow complete
5, if failed, check the error code if it's retryable, then go back to 1.
and in the new 1, it will force to ask for new receipt from apple server. then when it reaches 5 again, the whole flow will stop since this is the second attempt already. meaning only retry once.
So in this example, if using state machine and without using rx, I will end up using state machine and shares some global state like isSecondAttempt: Bool, shouldForceFetchReceipt: Bool, etc.
How do I design this flow in rx? with these global shared state designed in the flow.
I read some post says that the best practice to deal with RxSwift is to only pass fatal error to the onError and pass Result to the onNext.
I don't agree with that sentiment. It is basically saying that you should only use onError if the programmer made a mistake. You should use errors for un-happy paths or to abort a procedure. They are just like throwing except in an async way.
Here's your algorithm as an Rx chain.
enum ReceiptError: Error {
case noReceipt
case tooManyAttempts
}
struct Response {
// the server response info
}
func getReceiptResonse() -> Observable<Response> {
return fetchReceiptLocally()
.catchError { _ in askAppleForReceipt() }
.flatMapLatest { data in
sendReceiptToServer(data)
}
.retryWhen { error in
error
.scan(0) { attempts, error in
let max = 1
guard attempts < max else { throw ReceiptError.tooManyAttempts }
guard isRetryable(error) else { throw error }
return attempts + 1
}
}
}
Here are the support functions that the above uses:
func fetchReceiptLocally() -> Observable<Data> {
// return the local receipt data or call `onError`
}
func sendReceiptToServer(_ data: Data) -> Observable<Response> {
// send the receipt data or `onError` if the server failed to receive or process it correctly.
}
func isRetryable(_ error: Error) -> Bool {
// is this error the kind that can be retried?
}
func askAppleForReceipt() -> Observable<Data> {
return Observable.just(Bundle.main.appStoreReceiptURL)
.map { (url) -> URL in
guard let url = url else { throw ReceiptError.noReceipt }
return url
}
.observeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated))
.map { try Data(contentsOf: $0) }
}
In AWSKinesisRecorder (here), how can we check if our records are submitted to the server / reached the AWS or check if we have records on disk that are not yet submitted?
kinesisRecorder.submitAllRecords()?.continueOnSuccessWith(block: { (task: AWSTask<AnyObject>) -> Any? in
if let error = task.error as NSError? {
Logger.log(method: .error, "\(#function) \(#line) \(#file)", "Error: \(error)")
}
if let result = task.result {
Logger.log(method: .info, "\(#function) \(#line) \(#file)", "Result: \(result)")
}
print("FINISHED AWSTask kinesisRecorder", task, task.error, task.isCompleted, task.isFaulted, task.isCancelled)
return nil
})
The completion block never returns an error neither does the task.result is also nil, even if the internet is turned off on the device.
Not Possible
Seems like there is no public API available to fetch the records that are written onto the local mobile storage, neither you can read the sent records from Kinesis.
Its aim is to stream data in a unidirectional way.
I had to create another API to get the details of records received on the server end and had to rely on the Kinesis that each record is 100% written safely onto the local storage. So, far I have not seen any data loss.
I've been stuck on this problem for several days now (I'm working remote for a company in the US and debugging on site is not possible).
The application I'm writing uses a Redpark RS232 serial cable which talks to a HAAS CNC machine using Q Codes. I wrote a Node.js app several months back that used a separate RS232 device (MOXA RS232 Wi-fi device) which worked almost perfectly but unfortunately Wi-Fi is not always stable on site so the Node server has been discontinued in favour of an iOS app that can persist machine state to the cloud as of when a network connection is available.
First and foremost the configuration of the HAAS machine is as follows (these settings used to work with the old RS232 device + server app):
I then map this configuration in my app with:
func updateRS232Configuration() {
if let _ = RS232Manager {
settings.setNewValues(baudRate: 9600, dataBits: settings.DEFAULT_DATA_BITS, parity: settings.DEFAULT_PARITY, stopBits: settings.DEFAULT_STOP_BITS, rts: 1, cts: 1, DEBUG: true, ShowTxRx: true)
RS232Manager?.setBaud(settings.baudRate!) // 9600
RS232Manager?.setDataSize(settings.dataBits!) // 7
RS232Manager?.setParity(settings.parity!) // Even
RS232Manager?.setStopBits(settings.stopBits!) // 1
var portConfig = serialPortConfig()
RS232Manager?.getPortConfig(&portConfig)
portConfig.rxFlowControl = settings.rts! // 0 (does this need to be enabled?)
portConfig.txFlowControl = settings.cts! // 0
portConfig.xonChar = 1
portConfig.xoffChar = 1
RS232Manager?.setPortConfig(&portConfig, requestStatus: false)
self.performSelector(onMainThread: #selector(updateTextView), with: "Set the port configuration to: \(settings.toString())", waitUntilDone: false)
}
}
This method gets called whenever the cableConnected callback is triggered (for now I'm hard coding the values but these will eventually be configurable).
When the cable connects and the serial port has been configured I sent a message to the serial cabe containing Q100\r\n I use the \r\n as stated in the HAAS configuration. The only thing I'm worried about is that writeString (redparks write method) appends a null terminating byte, I thought it may be possible that the null terminate byte could cause the message to be misinterpreted on the HAAS so I set up two writes one after another:
func testWrite() {
let str = commands.Q100
let bytesToWrite = str.characters.count
for index in 0...bytesToWrite-1 {
let i = str.index(str.startIndex, offsetBy: index)
txBuffer[index] = Array(String(str[i]).utf8)[0]
}
RS232Manager?.write(&txBuffer, length: UInt32(bytesToWrite))
DispatchQueue.main.asyncAfter(deadline: .now() + 0.7, execute: {
self.RS232Manager?.write("Q100\r\n")
})
}
Normally I'd expect the HAAS to reply with something like (this is how the old app I wrote would display the response):
Instead I get a response like:
When consulting the HAAS Q Code documentation, this leads me to believe that I may be getting a response back from the machine and it just doesn't understand the message.
If the control is busy, it outputs Status, Busy. If a request is not recognized, the control outputs Unknown and a new prompt >.
From the above screenshot we can see that a > is appended to the terminal but I'm not seeing an Unknown string, leading me to believe that it was not a malformed request.
I read the response back with:
func readBytesAvailable(_ length: UInt32) {
if let bytesRead = RS232Manager?.read(&rxBuffer, length: length) {
let byteStream : [UInt8] = Array(rxBuffer[0...Int(bytesRead)])
if let resultString = String(bytes: byteStream, encoding: String.Encoding.ascii) {
self.performSelector(onMainThread: #selector(updateTextView), with: "Got string: \(resultString)", waitUntilDone: false)
} else {
self.performSelector(onMainThread: #selector(updateTextView), with: "No string, got stream: \(byteStream)", waitUntilDone: false)
}
} else {
self.performSelector(onMainThread: #selector(updateTextView), with: "No bytes available from host.", waitUntilDone: false)
}
}
I have also tried using getStringFromBytesAvailable but that produces the same output, my read method above reads directly from the rxBuffer.
Does anyone have any ideas as to what I could be doing wrong? Is my serial port configuration non-matching toward the one in the first image or am I writing my string incorrectly/reading incorrectly. I've been stuck on this for days and feel like I'm getting nowhere.