TCP-Client for iOS

Hello,
I’m new to Oxygen and was introduced by Jens. He tought me a lot about the commonly used construct of a project written in this language. But first and foremost he brought me back from Delphi XE6 on the right track.

Now I have to write a small TCP-Client for iOS. I found the following code in Objective-C:
How to open a TCP socket in Objective-C
Then I translated the code to Oxygen, so it looks like this Client.pas (3.2 KB)
If I try to send some data to my server (written in D7) I will notice a connection but no data will be received.
The server hangs up at “Server.Socket.ReadLn;”

The problem may be the following part:

method ServerConnector.writeOut(s: NSString);
begin
  var buf:= Byte(s.UTF8String); // originally: uint8_t *buf = (uint8_t *)[s UTF8String];
  fOutputStream.&write(@buf) maxLength(Char(buf).stringValue.length); // originally: [outputStream write:buf maxLength:strlen((char *)buf)];

end;

I think I have done some mistakes while translating.

I would be glad if you could help me.

Best regards
Benny

whats wrong with just strlen(buf) for the last parameter?

You’re casting the pointer to a char, calling stringValue on that (which gives you a new NSString of just that one Char ± not actually even the first char of the string, but the poiter, reinterpreted as Char), and then calling length (which gives you back “1”)

strlen expects a ^AnsiChar and If I try to cast this

OutputStream.&write(@buf) maxLength(strlen(^AnsiChar(buf)));

I ll get a BAD_ACCESS exception

Come to think, there’s more wrong with this. s.UTF8String returns a char pointer (think pChar), but you cast it to a byte. not to a byte pointer, to a plain byte. That’s all but the lowest 8 bits gone, right there.

Then you pass the address of that byte to write() maxLength(). There’s at most 1 valid byte to find at that address (the lowest 8 bits truncated from the original char’s address.

You’ll want something like:

var buf:= s.UTF8String; // no cast, this will be an ^AnsiChar
fOutputStream.&write(buf) maxLength(strlen(but))

ad if that deposit like passing an AnsiChar^ to write() maxLength(), you might need to add a case to ^Bute, such as

fOutputStream.&write(^Byte(buf)) maxLength(strlen(but))

But what you can’t to is just cast from poster types to simply types, and back.

Hope this helps,
marc

1 Like

Now I understand. Thank you for that!
But the server still hangs up at

Server.Socket.ReadLn;

It seems that the stream didn’t get flush. But also after closing the app (from the dock) my server doesn’t receive any data.

I’d probably need to see more to comment on that new part.

Here is the whole class:

interface

uses UIKit;

type
  Communicator = public class(INSStreamDelegate) 
  private
    fReadStream: CFReadStreamRef;
    fWriteStream: CFWriteStreamRef;
    fInputStream: NSInputStream;
    fOutputStream: NSOutputStream;

    method setup;
    method open;
    method close;
    method stream(stream: NSStream) handleEvent(&event: NSStreamEvent);
    method readIn(s: NSString);
    method writeOut(s: NSString);
  public
    constructor; 
  end;

implementation

constructor Communicator;
begin
  setup;
  writeOut('Test\n');
end;

method Communicator.setup;
begin
  CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, 'xx.xxx.xxx.xxx', 8080, @fReadStream, @fWriteStream);
  if not CFWriteStreamOpen(fWriteStream) then
  begin
    NSLog('Error, WriteStream not open');
    exit;
  end;
  self.open();
  NSLog('Status of OutputStream: %i', fOutputStream.streamStatus());
  exit;
end;

method Communicator.open;
begin
  NSLog('Opening streams.');
  fInputStream := bridge<NSInputStream>(fReadStream);
  fOutputStream := bridge<NSOutputStream>(fWriteStream);

  fInputStream.setDelegate(self);
  fOutputStream.setDelegate(self);
  fInputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop()) forMode(NSDefaultRunLoopMode);
  fOutputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop()) forMode(NSDefaultRunLoopMode);
  fInputStream.open();
  fOutputStream.open();
end;

method Communicator.close;
begin
  NSLog('Closing streams.');
  fInputStream.close();
  fOutputStream.close();
  fInputStream.removeFromRunLoop(NSRunLoop.currentRunLoop()) forMode(NSDefaultRunLoopMode);
  fOutputStream.removeFromRunLoop(NSRunLoop.currentRunLoop()) forMode(NSDefaultRunLoopMode);
  fInputStream.setDelegate(nil);
  fOutputStream.setDelegate(nil);

  fInputStream := nil;
  fOutputStream := nil;
end;

method Communicator.stream(stream: NSStream) handleEvent(&event: NSStreamEvent);
begin
  NSLog('Stream triggered.');
  case &event of
    NSStreamEvent.NSStreamEventHasSpaceAvailable: begin
      if stream = fOutputStream then
      begin
        NSLog('OutputStream is ready.');
      end;
    end;
    NSStreamEvent.NSStreamEventHasBytesAvailable: begin
      if stream = fInputStream then
      begin
        NSLog('InputStream is ready.');
        var buf: array[1024] of uint8_t;
        var len: Integer := 0;
        len := fInputStream.&read(buf) maxLength(1024);
        if len > 0 then
        begin
          var data: NSMutableData := NSMutableData.alloc().initWithLength(0);
          data.appendBytes(@buf) length(len); // originally:     [data appendBytes: (const void *)buf length:len];
          var s: NSString := NSString.alloc().initWithData(data) encoding(NSStringEncoding.NSASCIIStringEncoding);
          self.readIn(s);          
        end;
      end;
    end;
    else begin
      NSLog('Stream is sending an Event: %i', &event);
    end;
  end;
end;

method Communicator.readIn(s: NSString);
begin
  NSLog('Reading in the following:');
  NSLog('%@', s);
end;

method Communicator.writeOut(s: NSString);
begin
  var buf:= s.UTF8String();
  fOutputStream.&write(^Byte(buf)) maxLength(strlen(buf));
  
  NSLog('Writing out the following:');
  NSLog('%@', s);
end;

end.

My server uses the TIdTCPServer (Indy 10) and has the following method:

procedure TServerEventHandler.OnServerExecute(AContext: TIdContext);
var StreamSize: Int64;
    Cmd: String;
    Response: TMemoryStream;
    RequestHandler: TRequestHandler;
begin     
 Cmd:= Trim(AContext.Connection.IOHandler.ReadLn);
 WriteLn(Format('[%s, %s] Requested: [%s]',            // doesn't get reached
                 [DateTimeToStr(now),
                  AContext.Connection.Socket.Binding.IP,
                  Cmd]));
 Response:= TMemoryStream.Create;
 RequestHandler:= TRequestHandler.Create(Response);    
 try
  RequestHandler.GetResponse(Cmd);
  Response.Seek(0, soFromBeginning);
  StreamSize:= Response.Size;
  AContext.Connection.IOHandler.Write(StreamSize);
  AContext.Connection.IOHandler.Write(Response);
  AContext.Connection.DisconnectNotifyPeer;
 finally
  RequestHandler.Free;
  Response.Free;
 end;
end;