writeDataUnsupported in ChannelInboundHandler (Swift-NIO) - swift

I am trying to make a simple echo UDP server that sends back all incoming datagrams prefixed with a UTF8 string.
In my attempts to reach this goal, I succeeded in sending back the incoming data, but when I try to prefix this data with the string: "You sent: ", I get an error writeDataUnsupported
This is my code:
I made a ChannelInboundHandler called Echo all it does is: For each incoming datagram, it sends the string "You sent: " and then the data of the incoming datagram.
final class Echo: ChannelInboundHandler {
typealias InboundIn = ByteBuffer
typealias OutboundOut = ByteBuffer
var wroteResponse = false
static let response = "You sent: ".data(using: .utf8)!
func channelRead(ctx: ChannelHandlerContext, data: NIOAny) {
if !wroteResponse {
var buffer = ctx.channel.allocator.buffer(capacity: Echo.response.count)
buffer.write(bytes: Echo.response)
ctx.write(self.wrapOutboundOut(buffer), promise: nil)
wroteResponse = true
}
ctx.write(data, promise: nil)
}
func channelReadComplete(ctx: ChannelHandlerContext) {
ctx.flush()
wroteResponse = false
}
}
Then I made a single threaded event loop group and assigned a datagram bootsrap to it. Then I bound the bootstrap to port 4065.
let πŸ”‚ = MultiThreadedEventLoopGroup(numThreads: 1)
let bootstrap = DatagramBootstrap(group: πŸ”‚)
.channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
.channelInitializer { $0.pipeline.add(handler: Echo()) }
defer {
try! πŸ”‚.syncShutdownGracefully()
}
try bootstrap
.bind(host: "127.0.0.1", port: 4065)
.wait()
.closeFuture
.wait()
Why do I always get this writeDataUnsupported while trying to send the string: "You sent: "?

For DatagramChannel you need to wrap your ByteBuffer into an AddressEnvelope. Which also means your ChannelInboundHandler should operate on AddressedEnvelope<ByteBuffer>.

To make the ChannelInboundHandler operate on AddressedEnvelope<ByteBuffer>, as Norman Maurer suggests, you can rewrite Echo so it looks more like:
final class Echo: ChannelInboundHandler {
typealias InboundIn = AddressedEnvelope<ByteBuffer>
typealias OutboundOut = AddressedEnvelope<ByteBuffer>
static let response = "You sent: ".data(using: .utf8)!
func channelRead(ctx: ChannelHandlerContext, data: NIOAny) {
var incomingEnvelope = unwrapInboundIn(data)
var buffer = ctx.channel.allocator.buffer(capacity: Echo.response.count + incomingEnvelope.data.readableBytes)
buffer.write(bytes: Echo.response)
buffer.write(buffer: &incomingEnvelope.data)
let envelope = AddressedEnvelope(remoteAddress: incomingEnvelope.remoteAddress, data: buffer)
ctx.write(wrapOutboundOut(envelope), promise: nil)
}
func channelReadComplete(ctx: ChannelHandlerContext) {
ctx.flush()
}
}

Related

How to write a NWProtocolFramer for Network.framework that splits streams into frames using a delimiter?

I tried the following code to create a framer that splits a stream of ASCII bytes into frames separated by the pipe ascii character: "|".
import Network
fileprivate let pipe = Character("|").asciiValue!
class PipeFramer: NWProtocolFramerImplementation {
static let label = "Pipe framer"
static let definition = NWProtocolFramer.Definition(implementation: PipeFramer.self)
var minLengthUntilNextMessage = 1 {
didSet { print("client: minLength set to", minLengthUntilNextMessage) }
}
required init(framer: NWProtocolFramer.Instance) {}
func start(framer: NWProtocolFramer.Instance) -> NWProtocolFramer.StartResult { .ready }
func handleInput(framer: NWProtocolFramer.Instance) -> Int {
while true {
var delimiterPosition: Int?
_ = framer.parseInput(minimumIncompleteLength: minLengthUntilNextMessage, maximumLength: 65535) { buffer, endOfMessage in
if let buffer = buffer {
print("client: parsing buffer: \"\(String(bytes: buffer, encoding: .utf8) ?? buffer.debugDescription)\"")
if let indexOfDelimiter = buffer.firstIndex(of: pipe) {
minLengthUntilNextMessage = 1
delimiterPosition = indexOfDelimiter
} else {
minLengthUntilNextMessage = buffer.count + 1
}
} else {
print("client: no buffer")
}
return 0
}
if let length = delimiterPosition {
guard framer.deliverInputNoCopy(length: length, message: .init(instance: framer), isComplete: true) else {
return 0
}
_ = framer.parseInput(minimumIncompleteLength: 1, maximumLength: 65535) { _,_ in 1 }
} else {
return minLengthUntilNextMessage
}
}
}
func handleOutput(framer: NWProtocolFramer.Instance, message: NWProtocolFramer.Message, messageLength: Int, isComplete: Bool) {
try! framer.writeOutputNoCopy(length: messageLength)
framer.writeOutput(data: [pipe])
}
func wakeup(framer: NWProtocolFramer.Instance) {}
func stop(framer: NWProtocolFramer.Instance) -> Bool { return true }
func cleanup(framer: NWProtocolFramer.Instance) { }
}
The problem is that from the moment I get a chunk that does not end with "|", the framer gets stuck on that chunk. So the other chunks that come after this incomplete chunk never fully arrive in the framer.parseInput(...) call. Because it always parses chunks of minimumIncompleteLength and hence never arrives to the point where the next "|" is.
Here is a simple reproduction of this problem:
Create a TCP server
Setup the server so that it sends chunks of messages when a client connects.
Connect to the server (created in 1.) using the framer from above.
Start receiving messages.
Swift Code:
import Network
let client = DispatchQueue(label: "Server")
let server = DispatchQueue(label: "Client")
let networkParameters = NWParameters.tcp
networkParameters.defaultProtocolStack.applicationProtocols.insert(NWProtocolFramer.Options(definition: PipeFramer.definition), at: 0)
let server = try! NWListener(using: .tcp)
server.newConnectionHandler = { connection in
print("server: new connection from", connection.endpoint)
print("server (client \(connection.endpoint)): state", connection.state)
connection.viabilityUpdateHandler = { viable in
print("server (client \(connection.endpoint)): state", connection.state)
if viable {
print("server: sending")
connection.send(content: "A|Be||Sea".data(using: .utf8)!, isComplete: false, completion: .idempotent)
serverQueue.asyncAfter(deadline: .now() + 5) {
print("server: sending second part")
connection.send(content: " is longer than expected|0|".data(using: .utf8)!, isComplete: true, completion: .idempotent)
}
serverQueue.asyncAfter(deadline: .now() + 8) {
print("server: sending last part")
connection.send(content: "Done|".data(using: .utf8)!, isComplete: true, completion: .idempotent)
}
}
}
connection.start(queue: serverQueue)
}
server.stateUpdateHandler = { state in
print("server:", state)
if state == .ready, let port = server.port {
print("server: listening on", port)
}
}
server.start(queue: serverQueue)
let client = NWConnection(to: .hostPort(host: "localhost", port: server.port!), using: networkParameters)
func receiveNext() {
client.receiveMessage { (data, context, complete, error) in
let content: String
if let data = data {
content = String(data: data, encoding: .utf8) ?? data.description
} else {
content = data?.debugDescription ?? "<no data>"
}
print("client: received \"\(content)\"", context.debugDescription, complete, error?.localizedDescription ?? "No error")
receiveNext()
}
}
client.stateUpdateHandler = { state in
print("client:", state)
if state == .ready {
print("client: receiving")
receiveNext()
}
}
client.start(queue: clientQueue)
Results in:
server: waiting(POSIXErrorCode: Network is down)
server: ready
server: listening on 54894
client: preparing
client: ready
client: receiving
server: new connection from ::1.53179
server (client ::1.53179): state setup
server (client ::1.53179): state ready
server: sending
client: parsing buffer: "A|Be||Sea"
client: minLength set to 1
client: parsing buffer: "Be||Sea"
client: minLength set to 1
client: parsing buffer: "|Sea"
client: minLength set to 1
client: parsing buffer: "Sea"
client: minLength set to 4
client: parsing buffer: ""
client: minLength set to 1
client: received "A" Optional(Network.NWConnection.ContentContext) true No error
client: received "Be" Optional(Network.NWConnection.ContentContext) true No error
client: received "<no data>" Optional(Network.NWConnection.ContentContext) true No error
client: parsing buffer: "Sea"
client: minLength set to 4
server: sending second part
client: parsing buffer: "Sea "
client: minLength set to 5
client: parsing buffer: "Sea i"
client: minLength set to 6
server: sending last part
client: parsing buffer: "Sea is"
client: minLength set to 7
client: parsing buffer: "Sea is "
client: minLength set to 8
Notice that the fourth and fifth message are never received by the client. How should I write the Framer so that it receives messages after an incoming incomplete chunk?
References
A swift package containing the above code
Corresponding discussion on ο£Ώ Developer Forums
I had exactly the same problem... The network protocol that I was working with also had a simple delimiter that separated each 'message' and the protocol had no header that told me what to expect. Often at the end of the buffer, there was only a partial message with no delimiter and needed to read more bytes to get the remainder of the message. Something like this:
| PACKET A | PACKET B |
|<message>|<message>|<message><mess...age>|<message><message><message><m...essage>
1 2 4 5a 5b 6 7 8 9a 9b
Note:
delimiter = | - single character
lhsMessage = message 5a
rhsMessage = message 5b
Even after watching WWDC and looking at the other examples from Apple, I still do not completely understand how handleInput and parseInput are supposed to function.
I assumed I could simply return from handleInput with the (lhsMessage.count + 1) and it would keep the partial message in the current buffer AND add additional bytes into the buffer (ie from PACKET B) that parseInput could inspect.
However, it does appear to work that way. Instead I ended up storing the value of lhsMessage in a class var and then returned lhsMessage.count from parseInput, which I believe moves the β€˜cursor’ in the buffer to end and forces handleInput to get the new packet (ie packet B).
As part of parseInput, I then check if I have a lhsMessage and then assume if I find a delimiter that it is in fact rhsMessage. I then join LHS and RHS to create a completeMessage. At this point, I also return from parseInput the value of (rhsMessage.count + 1) to move the cursor along again.
Now to send this completeMessage I could not use deliverInputNoCopy as the bytes that make up completeMessage were no longer in the buffer :-)
Instead handleInput sent the message back using deliverInput.

Simple TCP listener on swift

I send messages to applications running on remote computers.
I send messages this ways.
c#:
void web_data_work()
{
try
{
TcpClient client = new TcpClient(address, port);
Byte[] data = Encoding.UTF8.GetBytes(string.Format("{0}", message));
NetworkStream stream = client.GetStream();
try
{
if (message != "")
{
stream.Write(data, 0, data.Length);
Byte[] readingData = new Byte[256];
String responseData = String.Empty;
StringBuilder completeMessage = new StringBuilder();
int numberOfBytesRead = 0;
do
{
numberOfBytesRead = stream.Read(readingData, 0, readingData.Length);
completeMessage.AppendFormat("{0}", Encoding.UTF8.GetString(readingData, 0, numberOfBytesRead));
}
while (stream.DataAvailable);
responseData = completeMessage.ToString();
this.Invoke((MethodInvoker)delegate ()
{
output_list.Items.Add(string.Format("Sended – {0}", responseData));
message = "";
});
}
}
finally
{
stream.Close();
client.Close();
}
}
catch
{
this.Invoke((MethodInvoker)delegate ()
{
output_list.Items.Add("Not sended");
message = "";
});
}
}
swift:
func web_data_work()
{
let address = address_box.stringValue
let port = port_box.intValue
let task = URLSession.shared.streamTask(withHostName: address, port: Int(port))
let data = message.data(using: .utf8)!
task.write(data as Data, timeout: 0)
{
error in
//om = "Not sended"
//self.output_message()
}
task.resume()
}
In c# i can read messages using TcpListener, how do i do it in swift?
Only two parameters are used:
"address" – ip address of the computer to which messages are sent and which is listened to by the TcpListener of that computer.
"port" – port of the sending and receiving between computers.
P.S. Preferably without additional libraries.
You can achieve this with just two classes: SocketPort & FileHandle.
Before you copy & paste the example below, please, read my comments at the end.
import Cocoa
class TcpEchoClient {
var readToEndOfFileCompletionHandler: (() -> Void)?
let fileHandle: FileHandle
var observer: NSObjectProtocol?
init(fileHandle: FileHandle) {
self.fileHandle = fileHandle
// Register observer for the data & EOF
self.observer = NotificationCenter.default.addObserver(forName: .NSFileHandleReadToEndOfFileCompletion,
object: fileHandle,
queue: OperationQueue.main) { [weak self] note in
self?.handleReadToEndOfFileCompletion(notification: note)
}
// Instruct the handle to read till the EOF & notify
fileHandle.readToEndOfFileInBackgroundAndNotify()
}
func handleReadToEndOfFileCompletion(notification note: Notification) {
defer {
// No matter what happens, call the completion handle by the end
readToEndOfFileCompletionHandler?()
}
// Is there an error?
if let errorCode = note.userInfo?["NSFileHandleError"] as? NSNumber {
print("Client \(fileHandle.fileDescriptor) error: File handle error \(errorCode.intValue)")
return
}
// No error, we should have data available
guard let data = note.userInfo?[NSFileHandleNotificationDataItem] as? Data else {
print("Client \(fileHandle.fileDescriptor) error: Unable to get data")
return
}
// Convert them to UTF-8 string
guard let text = String(data: data, encoding: .utf8) else {
print("Client \(fileHandle.fileDescriptor) error: Unable to convert data to UTF-8 string")
return
}
// Print the text
print("Client \(fileHandle.fileDescriptor) received: \(text)")
}
deinit {
// Remove observer for the data & EOF
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
}
// Close the handle
try? fileHandle.close()
}
}
class TcpServer {
let port: SocketPort
let fileHandle: FileHandle
var clients: Dictionary<Int32, TcpEchoClient>
var observer: NSObjectProtocol?
init?(tcpPort: UInt16) {
guard let socketPort = SocketPort(tcpPort: tcpPort) else {
return nil
}
// Keep the socket port around otherwise you'll get error 38
port = socketPort
// No clients for now
clients = [:]
// Create handle from the socket
fileHandle = FileHandle(fileDescriptor: port.socket)
// Register observer for the connection accepted
observer = NotificationCenter.default.addObserver(forName: .NSFileHandleConnectionAccepted,
object: fileHandle,
queue: OperationQueue.main) { [weak self] note in
if let handle = note.object as? FileHandle {
// Ask immediately for another accepted connection notification
handle.acceptConnectionInBackgroundAndNotify()
}
self?.handleConnectionAccepted(notification: note)
}
// Instruct the handle to accept connection & notify
fileHandle.acceptConnectionInBackgroundAndNotify()
}
func handleConnectionAccepted(notification note: Notification) {
// Is there an error?
if let errorCode = note.userInfo?["NSFileHandleError"] as? NSNumber {
print("Server error: File handle error \(errorCode.intValue)")
return
}
// No, we should have received the client file handle
guard let clientFileHandle = note.userInfo?[NSFileHandleNotificationFileHandleItem] as? FileHandle else {
print("Server error: Unable to get accepted connection file handle")
return
}
let fileDescriptor = clientFileHandle.fileDescriptor
// Create new client from the file handle
let client = TcpEchoClient(fileHandle: clientFileHandle)
// Once it finishes, remove it from the clients dictionary
client.readToEndOfFileCompletionHandler = { [weak self] in
guard let self = self else { return }
self.clients.removeValue(forKey: fileDescriptor)
print("Server: Client removed \(fileDescriptor) (total \(self.clients.count))")
}
// Store the client in the clients dictionary
clients[fileDescriptor] = client
print("Server: New client \(fileDescriptor) added (total \(self.clients.count))")
}
deinit {
// Remove all clients
clients.removeAll()
// Close the file handle
try? fileHandle.close()
// Invalidate the socket port
port.invalidate()
// Remove connection accepted observer
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
}
}
}
Then create a property (var server: TcpServer?) and initialize it (server = TcpServer(tcpPort: 8080)).
Test:
parallel -j 1 echo -n Foo '|' nc 127.0.0.1 8080 ::: {1..4}
Console output:
Server: New client 7 added (total 1)
Client 7 received: Foo
Server: Client removed 7 (total 0)
Server: New client 7 added (total 1)
Client 7 received: Foo
Server: Client removed 7 (total 0)
Server: New client 7 added (total 1)
Client 7 received: Foo
Server: Client removed 7 (total 0)
Server: New client 7 added (total 1)
Client 7 received: Foo
Server: Client removed 7 (total 0)
Comments:
This example is only a basic skeleton you can start with.
Don't be misleaded with the parallel test command.
I'm using it if I want to run multiple commands at once (same commands).
Everything in this example operates on the main queue.
Read documentation of all these classes, methods and notifications I do use. There're gotchas, you have to be familiar with run loops, etc.

NSLocalizedDescription=Writing is not permitted

I tried to make an app that sends messages from iPhone to Bluetooth LE module. But for some reason, it gives the following error:
NSLocalizedDescription=Writing is not permitted.
Even though the types of the blePeripheral and the blePeripheral!.write are CBCharacteristicWrite.withResponse, the error says that writing is not permitted. How come the following code does not work for me?
func writeValue(data: String) {
let valueString = (data as NSString).data(using: String.Encoding.utf8.rawValue)
//change the "data" to valueString
if let blePeripheral = blePeripheral {
if let txCharacteristic = txCharacteristic {
blePeripheral.writeValue(valueString!, for: txCharacteristic, type: CBCharacteristicWriteType.withResponse)
}
}
}
func writeCharacteristic(val: Int8) {
var val = val
let ns = NSData(bytes: &val, length: MemoryLayout<Int8>.size)
blePeripheral!.writeValue(ns as Data, for: txCharacteristic!, type: CBCharacteristicWriteType.withResponse)
}
The resource where I found the code is:
https://learn.adafruit.com/crack-the-code/communication

How i can return value from function use Alamofire

How i can return value from function used Alamofire . i try to print outside .responseJSON the value in ArrData is not set but i try print inside it work
this code:
func getDept()->NSMutableArray
{
var ArrData:NSMutableArray = []
let url = "http://www.xxxxxxxxxxxxx.com"
Alamofire.request(.GET, url).responseJSON { response in
let json = JSON(response.result.value!)
let count = json.count
for var index = 0; index < count;index++
{
ArrData.addObject(json[index]["dept"].stringValue)
}
}
return ArrData
}
it i good idea to check at least README.md of the framework which you are going to use in your code
Networking in Alamofire is done asynchronously. Asynchronous
programming may be a source of frustration to programmers unfamiliar
with the concept, but there are very good reasons for doing it this
way.
Rather than blocking execution to wait for a response from the server,
a callback is specified to handle the response once it's received. The
result of a request is only available inside the scope of a response
handler. Any execution contingent on the response or data received
from the server must be done within a handler.
Try to use a handler like this and a callback:
func getopt(callback:(array: [String]) -> void ){
func completion(request: NSURLRequest?, response:NSHTTPURLResponse?,result:Result<AnyObject>){
if let rdata = result.value{
let data = JSON(rdata)
print(data)
let myArray = [String]
let objects = data.array
for object in objects{
myArray.append(object)
}
callback(myArray)
}
}
let url = "http://www.xxxxxxxxxxxxx.com"
Alamofire.request(.GET,url),
encoding: .JSON).responseJSON(completionHandler: completion)
}
You pass the array to your callback So when you call getopt where you call it you can print the array. Some like this:
func something (){
getopt(callback)
}
func callback(array:[String]){
print array[0]
}

'NSInputStream' does not have a member named 'setDelegate'

I use NSStreamDelegate protocol in A UIViewController subclass,
And then send setDelegate message to a NSInputStream.
var input : NSInputStream?
var output: NSOutputStream?
func connectToSocket(host: String, port: Int) {
NSStream.getStreamsToHostWithName(host, port: port, inputStream: &(self.input), outputStream: &(self.output)
let str = "test"
let data = str.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
self.input?.setDelegate(self)
self.input?.open()
self.output?.open()
// ...
}
I got a 'NSInputStream' does not have a member named 'setDelegate' error message
Why can I use 'setDelegate'` like below document?
https://developer.apple.com/library/prerelease/iOS/documentation/Cocoa/Reference/Foundation/Classes/NSStream_Class/index.html
This should work:
self.input?.delegate = self
Looks like the documentation isn't quite up to date.