protocol UDPListener {
func handleResponse(_ client: UDPConnection, data: Data)
}
class UDPConnection{
var connection: NWConnection?
var listner: NWListener?
var delegate: UDPListener?
let parameters = NWParameters.udp
// for connect with device
func connect(withHost: NWEndpoint.Host, port: NWEndpoint.Port) {
parameters.allowLocalEndpointReuse = true
self.connection = NWConnection(host: withHost, port: port, using: parameters)
self.connection?.stateUpdateHandler = { (newState) in
switch (newState) {
case .ready:
print("connection ready")
case .setup:
print("connectionsetup")
case .cancelled:
print("connectioncancelled")
case .preparing:
print("connection Preparing")
default:
print("connection waiting or failed")
}
}
self.connection?.start(queue: .global())
}
// for sending UDP string
func sendUDP(_ content: String) {
let contentToSendUDP = content.data(using: String.Encoding.utf8)
self.connection?.send(content: contentToSendUDP, completion: NWConnection.SendCompletion.contentProcessed(({ (NWError) in
if (NWError == nil) {
print("Data was sent to UDP")
} else {
print("ERROR SEND! Error when data (Type: Data) sending. NWError: \n \(NWError!)")
}
})))
}
//for sending UDP data
func sendUDP(_ content: Data) {
self.connection?.send(content: content, completion: NWConnection.SendCompletion.contentProcessed(({ (NWError) in
if (NWError == nil) {
print("Data was sent to UDP")
} else {
print("ERROR Send! Error when data (Type: Data) sending. NWError: \n \(NWError!)")
}
})))
}
//////////////// UDP LISTNER /////////////////////
func listenUDP(port: NWEndpoint.Port) {
do {
parameters.allowLocalEndpointReuse = true
self.listner = try NWListener(using: parameters, on: port)
self.listner?.stateUpdateHandler = {(newState) in
switch newState {
case .ready:
print("Listner ready")
case .failed:
print("Listner failed")
case .cancelled:
print("Listner cancelled")
default:
break
}
}
self.listner?.newConnectionHandler = {(newConnection) in
newConnection.stateUpdateHandler = {newState in
switch newState {
case .ready:
print("new connection establish")
self.receive(on: newConnection)
case .failed:
print("new connection failed")
case .cancelled:
print("new connection cancelled")
default:
break
}
}
newConnection.start(queue: DispatchQueue(label: "new client"))
}
} catch {
print("unable to create listener")
}
self.listner?.start(queue: .global())
}
func receive(on connection: NWConnection) {
connection.receiveMessage { (data, context, isComplete, error) in
if !isComplete {
print("Not Complete")
}
if let error = error {
print("Error in REceive: - ",error)
return
}
if let data = data, !data.isEmpty {
// let backToString = String(decoding: data, as: UTF8.self)
// print("Received Data: ",backToString)
guard self.delegate != nil else {
print("Error Receive: UDPClient response handler is nil")
return
}
print("Data receive in UDPConenction;")
self.delegate?.handleResponse(self, data: data)
}
}
}
}
Here I attached my UDP file that contains connections and listener of UDP.
When I send some data for the first time, it will send the data and receive the data.
But when I send data again, then data will be sent, but I am not getting data second time and onwards. Listner is not listning anymore. Data will be sent but listener is not listening.
If I close the application and run it again, same issue occurs. First time load the data (listener lister the data) but second time listener is not working.
What is the issue in my code ? I was trying so much but not resolving the issue. Please anyone that can solve my issue would be so grateful to me.
Thank you in advance.
In your function receive you are using the NWConnection.receiveMessage if you check the documentation here:
https://developer.apple.com/documentation/network/nwconnection/3020638-receivemessage
You'll see that it schedules a single receive completion handler. That means that you'll have to do something to trigger it again. What I normally do is have a function like:
private func setupReceive() {
connection.receive(minimumIncompleteLength: 1, maximumLength: MTU) { (data, _, isComplete, error) in
if let data = data, !data.isEmpty {
let message = String(data: data, encoding: .utf8)
print("connection \(self.id) did receive, data: \(data as NSData) string: \(message ?? "-")")
self.send(data: data)
}
if isComplete {
self.connectionDidEnd()
} else if let error = error {
self.connectionDidFail(error: error)
} else {
self.setupReceive() // HERE I SET THE RECEIVE AGAIN
}
}
}
That way, after processing the read, you end up setting up another single receive completion handler.
If you want to see a full example, you can check my article on using Network.framework here:
https://rderik.com/blog/building-a-server-client-aplication-using-apple-s-network-framework/
The example uses TCP instead of UDP, but it should give a general idea.
Related
I am trying to send an image via an UDP connection.
class Connect: NSObject {
var connection: NWConnection?
func connectToUDP(host: String, port: UInt16) {
let host = NWEndpoint.Host(host)
guard let port = NWEndpoint.Port(rawValue: port) else { return }
let messageToUDP = "Bonjour"
connection = NWConnection(host: host, port: port, using: .udp)
connection?.stateUpdateHandler = { (newState) in
print("This is stateUpdateHandler:")
switch (newState) {
case .ready:
print("State: Ready\n")
self.sendUDP(messageToUDP)
self.receiveUDP()
default:
print("ERROR! State not defined!\n")
}
}
connection?.start(queue: .global())
}
func sendUDP(_ content: Data) {
connection?.send(content: content, completion: NWConnection.SendCompletion.contentProcessed(({ (NWError) in
if (NWError == nil) {
print("Data was sent to UDP")
} else {
print("ERROR! Error when data (Type: Data) sending. NWError: \n \(NWError!)")
}
})))
}
}
I converted the image into data and when I send it :
guard let image = UIGraphicsGetImageFromCurrentImageContext(),
let data = image.pngData() else { return }
connect.connectToUDP(host: "192.168.XX.XX", port: 10000)
connect.sendUDP(data)
I got this error
ERROR! Error when data (Type: Data) sending. NWError: POSIXErrorCode: Message too long
How can I split the data in packets and in order the data get fully reconstructed by the receiver ?
I'm creating a chat app using websocket.
I'm connecting to server with URLSessionWebSocketTask.
url = ws://"appname".herokuapp.com/chats/listen/
func connect(url: String) {
self.socket = session.webSocketTask(with: URL(string: url)!)
self.listen()
self.socket.resume()
}
func listen() {
self.socket.receive { [weak self] (result) in
guard let self = self else { return }
switch result {
case .failure(let error):
print(error)
return
case .success(let message):
switch message {
case .data(let data):
self.handle(data)
case .string(let str):
guard let data = str.data(using: .utf8) else { return }
self.handle(data)
#unknown default:
break
}
}
self.listen()
}
}
It's running okay on localhost, but after I deploy the server to Heroku, I get this message:
NSPOSIXErrorDomain Code=57 "Socket is not connected
I guest it was auto disconnect after a time out.
So, I add ping function to ping every 10s. Then, it works:
func sendPing() {
self.socket.sendPing { (error) in
if let error = error {
print("Sending PING failed: \(error)")
}
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
self.sendPing()
}
}
}
I want to establish a simple local Socket communication on iOS. After looking into other libraries, I found the websocket lib that is now native to Swift since last year works quite well, but comes with a nasty handshake that I would like to disable (otherwise I would have to adjust the server response etc.). My code looks like this:
let webSocketDelegate = WebSocket()
let session = URLSession(configuration: .default, delegate: webSocketDelegate, delegateQueue: OperationQueue())
let url = URL(string: "ws://192.168.43.1:6000")!
let webSocketTask = session.webSocketTask(with: url)
func send() {
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
send()
webSocketTask.send(.string("New Message")) { error in
if let error = error {
print("Error when sending a message \(error)")
}
}
}
}
webSocketTask.receive { result in
switch result {
case .success(let message):
switch message {
case .data(let data):
print("Data received \(data)")
case .string(let text):
print("Text received \(text)")
}
case .failure(let error):
print("Error when receiving \(error)")
}
}
webSocketTask.resume()
I googled and found nw_ws_options_set_skip_handshake, but I could not find an example how to use it. Any ideas?
As #SteffenUllrich suggested, I looked more into TCP socket communication and found a simple way to communicate. Here my solution, hope it will help someone else:
let queue = DispatchQueue(label: "TCP Client Queue")
let connection = NWConnection(to: NWEndpoint.hostPort(host: "192.168.99.1", port: 7000), using: NWParameters.init(tls: nil))
func connect() {
connection.stateUpdateHandler = { (newState) in
switch (newState) {
case .ready:
print("Socket: ready")
sendString(text: "Hello server!")
receiveMsg()
default: print("Socket: \(newState)")
}
}
connection.start(queue: queue)
}
func receiveMsg() {
connection.receive(minimumIncompleteLength: 1, maximumLength: 1000, completion: { (data, context, isComplete, error) in
if((data) != nil){
let text:String = String(decoding: data!, as: UTF8.self)
//send string to handler function, trim linebreak for single line commands
handleMsg(msg: text.trimmingCharacters(in: .newlines))
}
else{receiveMsg()//go back to receiving}
})
}
func receiveFile() {
print("Receiving file...")
//fileSize was sent before submission
connection.receive(minimumIncompleteLength: Int(fileSize), maximumLength: Int(fileSize)+1000, completion: { (data, context, isComplete, error) in
saveFile(data: data!)//save data to file
})
}
func sendString(text:String) {
let data:Data? = "\(text)\n".data(using: .utf8)//add linebreak to single line string, otherwise it does not release transmission
connection.send(content: data, 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!)")
}
})))
}
You just need to call connect() to start the connection, use sendString() to send some text. Receiving should run constantly. I didn't share the command handler, but it re-runs receiveMsg() on a regular command, and receiveFile() when a file transmission is initiated by the server (the init also contains the fileSize required to set the expected bytesize in the receive() function!).
I'm trying connect to local esp8266 UDP server. SwiftSocket haven't documentation. CocoaAsyncSocket doesn't work.
How to connect and send data to udp server? What i should do?
I wrote sample UDP python server and tried connect to them via SwiftSocket and CocoaAsyncSocket. I'm don't get feedback from app. Server don't receive connections.
For example- one of the most attempts:
var connection = NWConnection(host: "255.255.255.255", port: 9093, using: .udp)
connection.stateUpdateHandler = { (newState) in
switch (newState) {
case .ready:
print("ready")
case .setup:
print("setup")
case .cancelled:
print("cancelled")
case .preparing:
print("Preparing")
default:
print("waiting or failed")
break
}
}
connection.start(queue: .global())
connection.send(content: "Xyu".data(using: String.Encoding.utf8), completion: NWConnection.SendCompletion.contentProcessed(({ (NWError) in
print(NWError)
})))
connection.receiveMessage { (data, context, isComplete, error) in
print("Got it")
print(data)
}
Can't connect to the server
This solution work for me! Thanks #Paulw11
Swift 4, XCode 10.1, iOS 12.0
Simple connect to the public UDP server (This is NOT optimal version but works):
import UIKit
import Network
class ViewController: UIViewController {
var connection: NWConnection?
var hostUDP: NWEndpoint.Host = "iperf.volia.net"
var portUDP: NWEndpoint.Port = 5201
override func viewDidLoad() {
super.viewDidLoad()
// Hack to wait until everything is set up
var x = 0
while(x<1000000000) {
x+=1
}
connectToUDP(hostUDP,portUDP)
}
func connectToUDP(_ hostUDP: NWEndpoint.Host, _ portUDP: NWEndpoint.Port) {
// Transmited message:
let messageToUDP = "Test message"
self.connection = NWConnection(host: hostUDP, port: portUDP, using: .udp)
self.connection?.stateUpdateHandler = { (newState) in
print("This is stateUpdateHandler:")
switch (newState) {
case .ready:
print("State: Ready\n")
self.sendUDP(messageToUDP)
self.receiveUDP()
case .setup:
print("State: Setup\n")
case .cancelled:
print("State: Cancelled\n")
case .preparing:
print("State: Preparing\n")
default:
print("ERROR! State not defined!\n")
}
}
self.connection?.start(queue: .global())
}
func sendUDP(_ content: Data) {
self.connection?.send(content: content, completion: NWConnection.SendCompletion.contentProcessed(({ (NWError) in
if (NWError == nil) {
print("Data was sent to UDP")
} else {
print("ERROR! Error when data (Type: Data) sending. NWError: \n \(NWError!)")
}
})))
}
func sendUDP(_ content: String) {
let contentToSendUDP = content.data(using: String.Encoding.utf8)
self.connection?.send(content: contentToSendUDP, completion: NWConnection.SendCompletion.contentProcessed(({ (NWError) in
if (NWError == nil) {
print("Data was sent to UDP")
} else {
print("ERROR! Error when data (Type: Data) sending. NWError: \n \(NWError!)")
}
})))
}
func receiveUDP() {
self.connection?.receiveMessage { (data, context, isComplete, error) in
if (isComplete) {
print("Receive is complete")
if (data != nil) {
let backToString = String(decoding: data!, as: UTF8.self)
print("Received message: \(backToString)")
} else {
print("Data == nil")
}
}
}
}
}
You need to wait until your connection is in the ready state before you try and send or receive any data. You will also need to hold a strong reference to your connection in a property to prevent it from being released as soon as the function exits.
var connection: NWConnection?
func someFunc() {
self.connection = NWConnection(host: "255.255.255.255", port: 9093, using: .udp)
self.connection?.stateUpdateHandler = { (newState) in
switch (newState) {
case .ready:
print("ready")
self.send()
self.receive()
case .setup:
print("setup")
case .cancelled:
print("cancelled")
case .preparing:
print("Preparing")
default:
print("waiting or failed")
}
}
self.connection?.start(queue: .global())
}
func send() {
self.connection?.send(content: "Test message".data(using: String.Encoding.utf8), completion: NWConnection.SendCompletion.contentProcessed(({ (NWError) in
print(NWError)
})))
}
func receive() {
self.connection?.receiveMessage { (data, context, isComplete, error) in
print("Got it")
print(data)
}
}
I am using below code for making HTTP request in server.Now I want to know whether it is connected to internet or not. Below is my code
let request = Alamofire.request(completeURL(domainName: path), method: method, parameters: parameters, encoding: encoding.value, headers: headers)
.responseJSON {
let resstr = NSString(data: $0.data!, encoding: String.Encoding.utf8.rawValue)
print("error is \(resstr)")
if $0.result.isFailure {
self.failure("Network")
print("API FAILED 4")
return
}
guard let result = $0.result.value else {
self.unKnownError()
self.failure("")
print("API FAILED 3")
return
}
self.handleSuccess(JSON(result))
}
For swift 5 and Alamofire 5.4.4 ,I created a swift class called Connectivity . Use NetworkReachabilityManager class from Alamofire and configure the isConnectedToInternet() method as per your need.
import Foundation
import Alamofire
class Connectivity {
class func isConnectedToInternet() -> Bool {
return NetworkReachabilityManager()?.isReachable ?? false
}
}
Usage:
if Connectivity.isConnectedToInternet() {
print("Yes! internet is available.")
// do some tasks..
}
EDIT:
Since swift is encouraging computed properties, you can change the above function like:
import Foundation
import Alamofire
class Connectivity {
class var isConnectedToInternet:Bool {
return NetworkReachabilityManager()?.isReachable ?? false
}
}
and use it like:
if Connectivity.isConnectedToInternet {
print("Yes! internet is available.")
// do some tasks..
}
Swift 2.3
Alamofire.request(.POST, url).responseJSON { response in
switch response.result {
case .Success(let json):
// internet works.
case .Failure(let error):
if let err = error as? NSURLError where err == .NotConnectedToInternet {
// no internet connection
} else {
// other failures
}
}
}
Swift 3.0
Alamofire.upload(multipartFormData: { multipartFormData in
}, to: URL, method: .post,headers: nil,
encodingCompletion: { (result) in
switch result {
case .success( _, _, _): break
case .failure(let encodingError ):
print(encodingError)
if let err = encodingError as? URLError, err.code == .notConnectedToInternet {
// no internet connection
print(err)
} else {
// other failures
}
}
})
Using NetworkReachabilityManager
let networkReachabilityManager = Alamofire.NetworkReachabilityManager(host: "www.apple.com")
func checkForReachability() {
self.networkReachabilityManager?.listener = { status in
print("Network Status: \(status)")
switch status {
case .notReachable:
//Show error here (no internet connection)
case .reachable(_), .unknown:
//Hide error here
}
}
self.networkReachabilityManager?.startListening()
}
//How to Use : Just call below function in required class
if checkForReachability() {
print("connected with network")
}
For Swift 3/4,
In Alamofire, there is a class called NetworkReachabilityManager which can be used to observer or check if internet is available or not.
let reachabilityManager = NetworkReachabilityManager()
reachabilityManager?.startListening()
reachabilityManager?.listener = { _ in
if let isNetworkReachable = self.reachabilityManager?.isReachable,
isNetworkReachable == true {
//Internet Available
} else {
//Internet Not Available"
}
}
Here, listener will get called every time when there is changes in state of internet. You can handle it as you would like.
If you goto NetworkReachabilityManager.swift you will see this
/// Whether the network is currently reachable.
public var isReachable: Bool { return isReachableOnWWAN || isReachableOnEthernetOrWiFi }
So I have written this in my APIhandlerClass
import AlamofireNetworkActivityIndicator
private let manager = NetworkReachabilityManager(host: "www.apple.com")
func isNetworkReachable() -> Bool {
return manager?.isReachable ?? false
}
So this tells me the status of network.
If Alamofire.upload result returns success then below is the way to check for internet availibility while uploading an image:
Alamofire.upload(multipartFormData: { multipartFormData in
for (key,value) in parameters {
multipartFormData.append((value).data(using: .utf8)!, withName: key)
}
multipartFormData.append(self.imageData!, withName: "image" ,fileName: "image.jpg" , mimeType: "image/jpeg")
}, to:url)
{ (result) in
switch result{
case .success(let upload, _, _):
upload.uploadProgress(closure: { (progress) in
print("Upload Progress: \(progress.fractionCompleted)")
})
upload.responseJSON { response in
if let statusCode = response.response?.statusCode{
if(statusCode == 201){
//internet available
}
}else{
//internet not available
}
}
case .failure(let encodingError):
print(encodingError)
}
}
In general if you can get the internet offline information from the actual call, its better than reachability. You can be certain that the actual API call has failed because the internet is down. If you test for reachability before you call an API and it fails then all you know is that when the test was done the internet was offline ( or Apple was down), you don't know that when you make the call the internet will be offline. You might think it is a matter of milliseconds after the reachability call returns, or you retrieved the stored value, but thats in fact non deterministic. The OS might have scheduled some arbitrary number of threads before reachability returns its values in its closure, or updates whatever global you are storing.
And reachability has historically had bugs in its own code.
This isn't to say that you shouldn't use alamofire's NetworkReachabilityManager to change your UI, listen to it and update all the UI components.
But if you have reason to call an API, at that API layer the test for reachability is redundant, or possibly will cause some subtle bugs.
func isConnectedToNetwork()-> Bool {
var zeroAddress = sockaddr_in()
zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
zeroAddress.sin_family = sa_family_t(AF_INET)
let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in
SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress)
}
}
//Commented code only work upto iOS Swift 2.3
// let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) {
//
// SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0))
// }
var flags = SCNetworkReachabilityFlags()
if !SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) {
return false
}
let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0
return (isReachable && !needsConnection)
}
// Call api method
func callApi(){
if isConnectedToNetwork() { // Network Connection status
// Call your request here
}else{
//"Your Internet connection is not active at this time."
}
}
Using RequestAdapter class of alamofire and throw error when no internet connectivity
class RequestInterceptor : RequestAdapter{
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
let reachable = NetworkReachabilityManager()?.isReachable ?? false
if !reachable{
throw NSError.NoInternet
}
var nUrlRequest = urlRequest
// modify request if needed
return nUrlRequest
}
}
extension NSError {
static func createWithLocalizedDesription(withCode code:Int = 204,localizedDescription:String) -> NSError{
return NSError(domain: "<your bundle id>", code:code, userInfo: [NSLocalizedDescriptionKey : localizedDescription])
}
static var NoInternet : NSError {
return createWithLocalizedDesription(withCode: -1009,localizedDescription:"Please check your internet connection")
}
}
Now set the adapter to Alamofire Session Manager
let sessionManager = Alamofire.SessionManager(configuration: configuration)
sessionManager.adapter = RequestInterceptor()
Now each time when You create Alamofire Request, catch the error in DataResponse. This mechanism will act common to all request
In Alamofire 5 when error is of type AFError you can switch it like that:
case .failure(let error): // error is type of AFError
if error.isSessionTaskError {
// seems that one only get triggered when no internet
print("Session task error")
print(error.localizedDescription)
}
switch error.responseCode {
case 401:
print("401 token invalid or expired")
default:
print("Error : \(error.responseCode ?? 0)")
print(error.localizedDescription)
}
print("All other errors:\(error)")
case .finished:
break
}