iOS Websocket Connection to LG Smart TV with Starscream - swift

I'm using Swift to connect an iOS app to a LG Smart TV (both on the same wifi network) via websockets and each time the connection gets a success message and then gets disconnected in less than a second. Below is the code I am using to establish the connection. I masked the TV IP address but have confirmed I am using the correct one.
public init(){
self.request = URLRequest(url: URL(string: "ws://192.xxx.x.xx:3000")!)
self.socket = WebSocket(request: request)
self.socket.delegate = self
}
public func connect(){
print("in connect")
self.socket.connect()
print("end of connect")
}
func didReceive(event: WebSocketEvent, client: WebSocket) {
switch event {
case .connected(_):
print("WebSocket is connected")
case .disconnected(let reason, let code):
print("Disconnected: code=\(code), reason=\(reason)")
case .text(let message):
print("Received: \(message)")
case .binary(_):
print("binary")
break
case .pong(_):
print("pong")
break
case .ping(_):
print("ping")
break
case .error(let error):
print(error ?? "")
case .viabilityChanged(_):
print("viability changed")
break
case .reconnectSuggested(_):
print("reconnect suggested")
break
case .cancelled:
print("WebSocket is cancelled")
}
}
The connect() method gets called when a button is pressed on a separate view and I can tell it is working based on the print messages. This is the output I get every time
in connect
end of connect
viability changed
WebSocket is connected
Disconnected: code=1008, reason=invalid origin
WebSocket is cancelled
I've tried searching for this 1008 error code but it seems very generic. I'm new to websockets/network programming and would appreciate any help with this

Related

Not retrieving .notConnectedToInternet URLSession

So I'm trying to get URLSession URLError.code .notConnectedToInternet but all I'm getting is Code(rawValue: -1020) when using URLSession when disconnecting from the internet.
URLSession.shared.dataTask(with: url) { data, _, error in
if let error = error {
print("error.code \((error as! URLError).code)")
//prints - error.code Code(rawValue: -1020)
if (error as! URLError).code == .notConnectedToInternet {
print("no internet")
//doesn't print
return
}
}
error code -1020 means dataNotAllowed. It means the connection failed because data use isn’t currently allowed on the device.
What you are trying to catch notConnectedToInternet error code -1009 means connection failed because the device isn’t connected to the internet.
So notConnectedToInternet is not same as dataNotAllowed. In this case check your device settings.

AFNetworking 5 - NetworkReachabilityManager Listener

I recently upgraded from AFNetworking 4 to 5.
This was the old way of initializing the listener:
let net = NetworkReachabilityManager()
net?.listener = { status in
if net?.isReachable ?? false {
switch status {
case .reachable(.ethernetOrWiFi):
print("The network is reachable over the WiFi connection")
case .reachable(.wwan):
print("The network is reachable over the WWAN connection")
case .notReachable:
print("The network is not reachable")
case .unknown :
print("It is unknown whether the network is reachable")
}
}
net?.startListening()
The new documentation reads this:
#discardableResult
open func startListening(onQueue queue: DispatchQueue = .main,
onUpdatePerforming listener: #escaping Listener) -> Bool
https://alamofire.github.io/Alamofire/Classes/NetworkReachabilityManager.html
In my code, I am trying this:
let listener = NetworkReachabilityManager.Listener()
self.reachabilityManager?.startListening(onUpdatePerforming: listener){
}
The compilation error I am getting is Extra argument 'onUpdatePerforming' in call. It is a syntactical issue, I am transitioning form Objective C to Swift.
What I attempt to pass a closure, I also cannot seem to get the syntax correct:
self.reachabilityManager?.startListening(onUpdatePerforming: { (NetworkReachabilityManager.Listener) in
})
Listener is just a typealias for the closure type expected, so you need to pass a closure.
self.reachabilityManager?.startListening { status in
switch status {
...
}
}
Here is the code which runs after the AFNetworking update:
self.reachabilityManager?.startListening(onUpdatePerforming: {networkStatusListener in
print("Network Status Changed:", networkStatusListener)
switch networkStatusListener {
case .notReachable:
self.presentAlert(message: "The network is not reachable. Please reconnect to continue using the app.")
print("The network is not reachable.")
case .unknown :
self.presentAlert(message: "It is unknown whether the network is reachable. Please reconnect.")
print("It is unknown whether the network is reachable.")
case .reachable(.ethernetOrWiFi):
print("The network is reachable over the WiFi connection")
case .reachable(.cellular):
print("The network is reachable over the WWAN connection")
}
})

Correct way to use NWConnection for long-running TCP socket

I've been fighting with NWConnection to receive data on a long-running TCP socket all day. I've finally got it working after inflicting the following errors on myself due to lack of documentation:
Incomplete data (due to only calling receive once)
Getting TCP data out-of-order (due to "polling" receive from a timer...resulting in multiple simultaneous closures waiting to get data).
Suffering infinite loops (due to restarting receive after receiving without checking the "isComplete" Bool--once the socket is terminated from the other end this is....bad...very bad).
Summary of what I've learned:
Once you are in the .ready state you can call receive...once and only once
Once you receive some data, you can call receive again...but only if you are still in the .ready state and the isComplete is false.
Here's my code. I think this is right. But if it's wrong please let me know:
queue = DispatchQueue(label: "hostname", attributes: .concurrent)
let serverEndpoint = NWEndpoint.Host(hostname)
guard let portEndpoint = NWEndpoint.Port(rawValue: port) else { return nil }
connection = NWConnection(host: serverEndpoint, port: portEndpoint, using: .tcp)
connection.stateUpdateHandler = { [weak self] (newState) in
switch newState {
case .ready:
debugPrint("TcpReader.ready to send")
self?.receive()
case .failed(let error):
debugPrint("TcpReader.client failed with error \(error)")
case .setup:
debugPrint("TcpReader.setup")
case .waiting(_):
debugPrint("TcpReader.waiting")
case .preparing:
debugPrint("TcpReader.preparing")
case .cancelled:
debugPrint("TcpReader.cancelled")
}
}
func receive() {
connection.receive(minimumIncompleteLength: 1, maximumLength: 8192) { (content, context, isComplete, error) in
debugPrint("\(Date()) TcpReader: got a message \(String(describing: content?.count)) bytes")
if let content = content {
self.delegate.gotData(data: content, from: self.hostname, port: self.port)
}
if self.connection.state == .ready && isComplete == false {
self.receive()
}
}
}
I think you can use a short time connection many times. For example a client connects to the host and asks the host to do something and then tells the host to shutdown the connection. The host switches to the waiting mode to ready a new connection. See the diagram below.
You should have the connection timer to shutdown opened connection when the client don't send the close connection or answer event to the host for a particular time.
On a long-running TCP socket, you should implement customized heartbeat for monitor the connection status is alive or disconnected.
The heartbeat can as message or encrypt data to send, usually according to the server spec documents to implement.
Below as sample concept code to explain the flow for reference (without network packet content handler).
I can no guarantee is this common and correct way, but that's work for my project.
import Network
class NetworkService {
lazy var heartbeatTimeoutTask: DispatchWorkItem = {
return DispatchWorkItem { self.handleHeartbeatTimeOut() }
}()
lazy var connection: NWConnection = {
// Create the connection
let connection = NWConnection(host: "x.x.x.x", port: 1234, using: self.parames)
connection.stateUpdateHandler = self.listenStateUpdate(to:)
return connection
}()
lazy var parames: NWParameters = {
let parames = NWParameters(tls: nil, tcp: self.tcpOptions)
if let isOption = parames.defaultProtocolStack.internetProtocol as? NWProtocolIP.Options {
isOption.version = .v4
}
parames.preferNoProxies = true
parames.expiredDNSBehavior = .allow
parames.multipathServiceType = .interactive
parames.serviceClass = .background
return parames
}()
lazy var tcpOptions: NWProtocolTCP.Options = {
let options = NWProtocolTCP.Options()
options.enableFastOpen = true // Enable TCP Fast Open (TFO)
options.connectionTimeout = 5 // connection timed out
return options
}()
let queue = DispatchQueue(label: "hostname", attributes: .concurrent)
private func listenStateUpdate(to state: NWConnection.State) {
// Set the state update handler
switch state {
case .setup:
// init state
debugPrint("The connection has been initialized but not started.")
case .waiting(let error):
debugPrint("The connection is waiting for a network path change with: \(error)")
self.disconnect()
case .preparing:
debugPrint("The connection in the process of being established.")
case .ready:
// Handle connection established
// this means that the handshake is finished
debugPrint("The connection is established, and ready to send and receive data.")
self.receiveData()
self.sendHeartbeat()
case .failed(let error):
debugPrint("The connection has disconnected or encountered an: \(error)")
self.disconnect()
case .cancelled:
debugPrint("The connection has been canceled.")
default:
break
}
}
// MARK: - Socket I/O
func connect() {
// Start the connection
self.connection.start(queue: self.queue)
}
func disconnect() {
// Stop the connection
self.connection.stateUpdateHandler = nil
self.connection.cancel()
}
private func sendPacket() {
var packet: Data? // do something for heartbeat packet
self.connection.send(content: packet, completion: .contentProcessed({ (error) in
if let err = error {
// Handle error in sending
debugPrint("encounter an error with: \(err) after send Packet")
} else {
// Send has been processed
}
}))
}
private func receiveData() {
self.connection.receive(minimumIncompleteLength: 1, maximumLength: 8192) { [weak self] (data, context, isComplete, error) in
guard let weakSelf = self else { return }
if weakSelf.connection.state == .ready && isComplete == false, var data = data, !data.isEmpty {
// do something for detect heart packet
weakSelf.parseHeartBeat(&data)
}
}
}
// MARK: - Heartbeat
private func sendHeartbeat() {
// sendHeartbeatPacket
self.sendPacket()
// trigger timeout mission if the server no response corresponding packet within 5 second
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 5.0, execute: self.heartbeatTimeoutTask)
}
private func handleHeartbeatTimeOut() {
// this's sample time out mission, you can customize this chunk
self.heartbeatTimeoutTask.cancel()
self.disconnect()
}
private func parseHeartBeat(_ heartbeatData: inout Data) {
// do something for parse heartbeat
// cancel heartbeat timeout after parse packet success
self.heartbeatTimeoutTask.cancel()
// send heartbeat for monitor server after computing 15 second
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 15.0) {
self.sendHeartbeat()
}
}
}

How to use an ip address Directly with Alamofire?

I posted a heavy question recently surrounding this topic, but I need to cover the basics first before I can start doing other tasks. I would like to know how to get data from an ip address instead of a standard url that returns JSON data like so:
func simpleGetRequest()
{
let parameters = ["takepic":"true"]
Alamofire.request("http://XXX.XXX.X.XX", parameters:parameters).responseJSON { (response) in
if let status = response.response?.statusCode
{
switch(status)
{
case 200:
print("Successfully taken a pic")
case 500:
print("Failed to connect")
default:
print("Error with everything")
}
}
if let result = response.result.value
{
print(result)
}
}
}
I've heard some things about IPv6 and IPv4, but am not sure as to what they mean. I'll continue to do more research, however I can't find a single source that shows how to communicate with non-local web servers.

How to let IBM BlueSocket run on a GUI application and support multi connections

Recently, I found a pure swift socket server and client called IBM BlueSocket.
It is suitable for me that it does server-cleint communication.
It has a pretty simple sample. but I encountered some problems.
1. How to run it on a GUI application's run loop?
2. How to run it and support multi connections?
For what it's worth, I present the world's simplest chat client.
import Foundation
import Socket
// Very simplified chat client for BlueSocket - no UI, it just connects to the echo server,
// exchanges a couple of messages and then disconnects.
// You can run two instances of Xcode on your Mac, with the BlueSocketEchoServer running in one and
// this program running in the other. It has been tested running in the iPhone simulator, i.e.,
// under iOS, without problems.
// License: Public domain.
public class BlueSocketChatClient {
public func runClient() {
do {
let chatSocket = try Socket.create(family: .inet6)
try chatSocket.connect(to: "127.0.0.1", port: 1337)
print("Connected to: \(chatSocket.remoteHostname) on port \(chatSocket.remotePort)")
try readFromServer(chatSocket)
try chatSocket.write(from: "Hello to you too!")
try readFromServer(chatSocket)
try chatSocket.write(from: "Bye now!\n")
try chatSocket.write(from: "QUIT")
sleep(1) // Be nice to the server
chatSocket.close()
}
catch {
guard let socketError = error as? Socket.Error else {
print("Unexpected error ...")
return
}
print("Error reported:\n \(socketError.description)")
}
}
// This is very simple-minded. It blocks until there is input, and it then assumes that all the
// relevant input has been read in one go.
func readFromServer(_ chatSocket : Socket) throws {
var readData = Data(capacity: chatSocket.readBufferSize)
let bytesRead = try chatSocket.read(into: &readData)
guard bytesRead > 0 else {
print("Zero bytes read.")
return
}
guard let response = String(data: readData, encoding: .utf8) else {
print("Error decoding response ...")
return
}
print(response)
}
}
Bill Abt: If you can use this in any way you're welcome to it.
The sample has been recently updated and illustrates use of the GCD based Dispatch API to do multi-threading and supporting multiple connections. It also should give you a idea on how to run it on the main queue (which'll work for either a GUI or server application).