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.
Related
I'm trying to save some objects locally so when I close my application and disconnect from my Wi-Fi I'll still be able to have the data but this does not work.
I have created this function to save my database.
func saveAllQueriesLocally() {
let queries = PFQuery(className: "Directory")
PFObject.unpinAllObjectsInBackground()
queries.findObjectsInBackground { (object, error) in
if let error = error {
// The query failed
print(error.localizedDescription)
} else if let object = object {
// The query succeeded with a matching result
for i in object{
i.pinInBackground()
}
} else {
// The query succeeded but no matching result was found
}
}
}
and I have called this function inside viewDidLoad() of my main ViewController. I am not sure but I am guessing the function searches the database when it is offline and since it does not retrieve anything it rewrites the cache as empty.
While being connected to the internet, objects get retrieved correctly.
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.
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 want to use Client.ContainerStats(ctx context.Context, containerID string, stream bool) method to get streaming stats of a container.
From what I understand, if I pass true to stream parameter, Docker will not close connection and periodically sends JSON containing stats of a container.
However, I don't know how decode JSON because I don't know where JSON data start and end.
What I'm using right now is that I don't use stream option and just fetch data periodically then decode it like this.
stats, err := dockerClient.ContainerStats(ctx, container.ContainerID, false)
msgBytes, _ := ioutil.ReadAll(stats.Body)
var containerStats ContainerStats
err = json.Unmarshal(msgBytes, &containerStats)
What I'm looking for is a function that block when I call it, then when it receives JSON data (I mean complete JSON data that can be decoded) it will return struct containing data that was decoded from JSON and then I can call that function again to get next stat without having to make a new request to Docker.
In your case, you have multiple options:
Map the result on a custom struct
Map the result on a map[string]interface{}
If you want to map by using a custom struct you can do something like this:
type myStruct struct {
Id string `json:"id"`
Read string `json:"read"`
Preread string `json:"preread"`
}
// perform actions to retrieve logs in stats
//...
var containerStats myStruct
json.NewDecoder(stats.Body).Decode(&containerStats)
fmt.Println(containerStats.Id)
With this solution, you have to decide which fields you want to map.
However, if you do not want to specify fields, you can perform something like this:
//Perform actions to retrieve logs in stats
//...
var containerStats map[string]interface{}
json.NewDecoder(stats.Body).Decode(&containerStats)
fmt.Println(containerStats["id"])
To conclude, if you have to manipulate your data, I recommend you to use the first solution by using custom structure.
EDITED: handle stream
By passing stream parameter to true, the docker api will return an io.ReadCloser which will be updated. Then, it's up to the caller to close the io.ReadCloser returned.
What you have to do is to perdiodically read the buffer value.
type myStruct struct {
Id string `json:"id"`
Read string `json:"read"`
Preread string `json:"preread"`
CpuStats cpu `json:"cpu_stats"`
}
type cpu struct {
Usage cpuUsage `json:"cpu_usage"`
}
type cpuUsage struct {
Total float64 `json:"total_usage"`
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
cli, e := client.NewEnvClient()
if e != nil {
panic(e)
}
stats, e := cli.ContainerStats(ctx, "container_id", true)
if e != nil {
fmt.Errorf("%s", e.Error())
}
decoder := json.NewDecoder(stats.Body)
var containerStats myStruct
for {
select {
case <-ctx.Done():
stats.Body.Close()
fmt.Println("Stop logging")
return
default:
if err := decoder.Decode(&containerStats); err == io.EOF {
return
} else if err != nil {
cancel()
}
fmt.Println(containerStats.CpuStats.Usage.Total)
}
}
}
In this example, we are decoding the stats.Body ReadCloser when new data arrives, printing the total cpu usage, and closing the stream after 5 seconds.
I am trying to access whatever record was returned from my QBRequest and send it to a separate function (in this case, I'm trying to set whatever I got from the request, convert its contents to strings, and then display in cells). However, I am unable to do this by direct referencing outside of the QBRequest nor setting the object to outside variable.
var posts = [QBCOCustomObject]()
func queryForPost(city: String) -> [QBCOCustomObject] {
let requestParameters = NSMutableDictionary();
[requestParameters.setObject(city, forKey: "Location")];
QBRequest.objectsWithClassName("UserFeed", extendedRequest: requestParameters, successBlock: { (response: QBResponse, record, page) in
print(record![0].fields!.allValues[0])
print(record![1].fields!.allValues[0])
//the two above prints return the desired values
self.posts = record!
}, errorBlock: {(response: QBResponse) in
// Handle error here
NSLog("error QBRequest - objectsWithClassName")
})
print(self.posts.count, "POSTS.COUNT") //can't access posts or record - this returns 0, and THEN returns whatever print functions are inside the QBRequest.
print(self.posts, "POSTS")
return self.posts
}
"print(self.posts.count, "POSTS.COUNT")" returns 0, and THEN returns whatever print functions are inside the QBRequest. Seems as if the QBRequest is performing after going through the whole function. Very confused and not sure how to fix this...
Been struggling with this for a while now :( If anybody could provide some guidance I would very much appreciate it.
Thank you in advance!
Lance