0

This error occurs when I'm closing a connection with some client. The error is on this code line in the server:

Received := SocketStrm.Read(Data, SizeOf(Data));

And also, when the smartphone is rebooted (or, for example, when I close the client application), the data of the lost client not is being removed from the ListView in the server application.

Can someone help me fix these 2 errors, please?

Here is the code how I send data:

Client (Android):

 public class MainActivity extends AppCompatActivity {

        private Socket xclientSocket;

            class ClientThread implements Runnable {

                @Override
                public void run() {

                    try {

                        InetAddress serverAddr = InetAddress.getByName("192.168.15.12");

                        xclientSocket = new Socket(serverAddr, 101);

                        new Thread(new CMDThread()).start();

                    } catch (Exception e1) {
                        System.out.println(e1.toString());
                    }

                }
            }

            class CMDThread implements Runnable {

                @Override
                public void run() {

                    try {

                    while(xclientSocket.isConnected()){

                        BufferedReader xreader = new BufferedReader(new InputStreamReader(xclientSocket.getInputStream()));
                        DataOutputStream dOut = new DataOutputStream(xclientSocket.getOutputStream());

                        String xline;

                        if (xreader.ready()) {

                            while ((xline = xreader.readLine()) != null) {

                                System.out.println(xline);

                                if (xline != null && !xline.trim().isEmpty()) {

                                    if (xline.equalsIgnoreCase("info")) {

                                        DataOutputStream dOut = new DataOutputStream(xclientSocket.getOutputStream());
                                        dOut.writeChars("<|data|>" + myDeviceProduct.toUpperCase() + "<|>" + myDeviceModel + "<|>" + myVersion + "<|>" + SIM_OPNAME + "<|>" + SIM_NUMBER + "<<|");
                                        dOut.flush();

                                    }

                                    if (xline.equalsIgnoreCase("disconnect-client")) {

                                        break;

                                    }

                                }

                            }

                        }

                    }
                    System.out.println("Shutting down Socket!!");
                    xclientSocket.close();
                }
            catch (Exception e1) {
                    System.out.println(e1.toString());
                }
            }
            }

        ///////////////////////// USAGE /////////////////////////////

        @Override
        protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);

                new Thread(new ClientThread()).start();

                }
            }

Server (Delphi):

type
  TCMDSock_Thread = class(TServerClientThread)
  private
    function ArrayToString(const a: array of Char): string;
    procedure addClientToListView;
  protected
    procedure ClientExecute; override;
  end;

var
  FMain: TFMain;
  Manufacturer, Model, OsVersion, SIMOpName, SIMNumber: string;

implementation

{$R *.dfm}  

//==============================================================================================================

function TCMDSock_Thread.ArrayToString(const a: array of Char): string;
begin
  if Length(a)>0 then
    SetString(Result, PChar(@a[0]), Length(a))
  else
    Result := '';
end;

procedure TCMDSock_Thread.addClientToListView;
var
  Item: TListItem;
begin
  Item := FMain.ListView1.Items.Add;
  Item.Caption := IntToStr(ClientSocket.Handle);
  Item.SubItems.Add(ClientSocket.RemoteAddress);
  Item.SubItems.Add(ClientSocket.RemoteHost);
  Item.SubItems.Add(Manufacturer +' - '+Model+' - '+OsVersion);
  Item.SubItems.Add(SIMOpName+' - '+SIMNumber);
  Item.Data := ClientSocket.Data;
  FMain.ServerStatus.Panels.Items[1].Text := 'Connected';
end;

procedure TCMDSock_Thread.ClientExecute;
var
  Data: array [0 .. 1023] Of Char;
  SocketStrm: TWinSocketStream;
  ReceivedText: string;
  Received: Integer;
begin
  ClientSocket.SendText('info' + #13#10);
  SocketStrm := TWinSocketStream.Create(ClientSocket, 5000);
  try

    while ClientSocket.Connected do
    begin

      if not SocketStrm.WaitForData(100) then
        Continue;

      FillChar(Data, SizeOf(Data), #0);
      SocketStrm.Read(Data, SizeOf(Data));

      repeat
        try
          Received := SocketStrm.Read(Data, SizeOf(Data));
        except
          Break;
        end;
      until Received = 0;

      ReceivedText := ArrayToString(Data);

      if Pos('<|data|>', RecText) > 0 then
      begin

        Delete(ReceivedText, 1, Pos('<|data|>', ReceivedText) + 7);
        Manufacturer := Copy(ReceivedText, 1, Pos('<|>', ReceivedText) - 1);

        Delete(ReceivedText, 1, Pos('<|>', ReceivedText) + 2);
        Model := Copy(ReceivedText, 1, Pos('<|>', ReceivedText) - 1);

        Delete(ReceivedText, 1, Pos('<|>', ReceivedText) + 2);
        OsVersion := Copy(ReceivedText, 1, Pos('<|>', ReceivedText) - 1);

        Delete(ReceivedText, 1, Pos('<|>', ReceivedText) + 2);
        SIMOpName := Copy(ReceivedText, 1, Pos('<|>', ReceivedText) - 1);

        Delete(ReceivedText, 1, Pos('<|>', ReceivedText) + 2);
        SIMNumber := Copy(ReceivedText, 1, Pos('<<|', ReceivedText) - 1);

        Synchronize(addClientToListView);

      end;
    end;
  finally
    SocketStrm.Free;
  end;
end;

//================================================================================================================

procedure TFMain.ServerSocket1GetThread(Sender: TObject;
  ClientSocket: TServerClientWinSocket; var SocketThread: TServerClientThread);
begin
  SocketThread := TCMDSock_Thread.Create(False, ClientSocket);
end;

procedure TFMain.Button2Click(Sender: TObject); // <= PopupMenu
var
  Index: Integer;
begin
  Index := ListView1.Selected.Index;
  if Index = -1 then
    Exit;
  ServerSocket1.Socket.Connections[Index].SendText('disconnect-client' + #13#10);

  ServerSocket1.Active := False;
end;

procedure TFMain.ServerSocket1ClientDisconnect(Sender: TObject;
  Socket: TCustomWinSocket);
var
  Item: TListItem;
begin
  Item := FMain.ListView1.FindCaption(0, IntToStr(Socket.Handle), False, True, False);
  if Item <> nil then
    Item.Delete;
  FMain.StatusBar1.Panels.Items[1].Text := 'Desconnected';
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • This thread already is in other `TServerSocket`. I left the first `TServerSocket` only to receive screenshot. –  Jan 10 '18 at 21:50
  • But also on [**screenshot code**](https://stackoverflow.com/a/48031088/9200623) happens this same error like described above, in others words, when i try disconnect of two android sockets at same time, are two exception of this error in `TWinSocketStream.Read()` on server. –  Jan 10 '18 at 22:03

1 Answers1

13

The "The handle is invalid" error happens if the ClientSocket's socket handle gets closed before you free the TWinSocketStream that uses it. For instance, that can happen when you deactivate the server, as it loops through its internal list of active threads closing their sockets. This is normal behavior. Just ignore the error in your thread and exit from ClientExecute().

That being said, you are not using TWinSocketStream.Read() correctly. In this situation, you need to call Read() in a loop (your first call before the loop is erroneous, get rid of it), append received data to a growing buffer, and scan the buffer for complete messages before processing them. Repeat until disconnected.

Received will not be set to 0 until the client disconnects, but your client is setup to (potentially) send many messages to the server before it disconnects. You are not accounting for that possibility, so you shouldn't read endlessly until disconnect. Stop reading when you get a complete message, process it, and then go back to reading.

As for your ListView, as I already told you earlier, the OnClientDisconnect event is not fired in thread-blocking mode, so you need to remove the ListView item before ClientExecute() exits. For that matter, since the client is setup to (potentially) send many messages, your code would add each one to the ListView, so you need to make sure you remove all of them, not just the first one that is found. Otherwise, make sure you don't add more than one ListView item per client.

Also, your server can (potentially) accept multiple clients, but you are assuming there is only ever 1 client connected at a time. Your worker thread is accessing global variables that really should be local to the thread instead, so multiple clients do not overwrite each other's data.

Also, you have a small race condition. You have the worker thread sending an info command to the client, and the main thread sending the disconnect-client command. You are not synchronizing the commands, so there is a small window of opportunity where they could overlap, corrupting your communications.

Lastly, in Java, DataOutputStream.writeChars() writes out Unicode strings in UTF-16 format. In Delphi, Char is AnsiChar in D2007 and earlier, and is WideChar in D2009 and later. Network communications should use UTF-8 for text, as it is portable between platforms (no endian issues), and generally takes up less bandwidth, especially for Latin-based languages. You should write the Android and Delphi code to both force UTF-8 over the connection.

With that said, try something more like this:

public class MainActivity extends AppCompatActivity {

    private Socket xclientSocket;

    class ClientThread implements Runnable {

        @Override
        public void run() {
            try {
                xclientSocket = new Socket("192.168.15.12", 101);
                new Thread(new CMDThread()).start();
            }
            catch (Exception e1) {
                System.out.println(e1.toString());
            }
        }
    }

    class CMDThread implements Runnable {

        @Override
        public void run() {
            try {
                BufferedReader xreader = new BufferedReader(new InputStreamReader(xclientSocket.getInputStream(), StandardCharsets.UTF_8));
                DataOutputStream dOut = new DataOutputStream(new BufferedOutputStream(xclientSocket.getOutputStream()));

                while ((xline = xreader.readLine()) != null) {
                    System.out.println(xline);
                    xline = xline.trim();

                    if (xline.equalsIgnoreCase("info")) {
                        byte[] data = ("<|data|>" + myDeviceProduct.toUpperCase() + "<|>" + myDeviceModel + "<|>" + myVersion + "<|>" + SIM_OPNAME + "<|>" + SIM_NUMBER + "<<|").getBytes(StandardCharsets.UTF_8);
                        dOut.writeInt(data.length);
                        dOut.write(data, 0, data.length);
                        dOut.flush();
                    }

                    if (xline.equalsIgnoreCase("disconnect")) {
                        break;
                    }
                }

                System.out.println("Shutting down Socket!!");
                xclientSocket.close();
            }
            catch (Exception e1) {
                System.out.println(e1.toString());
            }
        }
    }

    ///////////////////////// USAGE /////////////////////////////

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new Thread(new ClientThread()).start();
    }
}

var
  FMain: TFMain;

implementation

{$R *.dfm}  

//================================================================================================================

type
  TCMDSock_Thread = class(TServerClientThread)
  private
    Manufacturer, Model, OsVersion, SIMOpName, SIMNumber: string;
    procedure addClientToListView;
    procedure removeClientFromListView;
  protected
    procedure ClientExecute; override;
  end;

procedure TCMDSock_Thread.addClientToListView;
var
  Item: TListItem;
begin
  Item := FMain.ListView1.FindData(0, ClientSocket, True, False);
  if Item = nil then
  begin
    Item := FMain.ListView1.Items.Add;
    Item.Data := ClientSocket;
  end;
  Item.Caption := IntToStr(ClientSocket.Handle);
  Item.SubItems.Add(ClientSocket.RemoteAddress);
  Item.SubItems.Add(ClientSocket.RemoteHost);
  Item.SubItems.Add(Manufacturer + ' - ' + Model + ' - ' + OsVersion);
  Item.SubItems.Add(SIMOpName + ' - ' + SIMNumber);
  FMain.ServerStatus.Panels.Items[1].Text := 'Connected';
end;

procedure TCMDSock_Thread.removeClientFromListView;
var
  Item: TListItem;
begin
  Item := FMain.ListView1.FindData(0, ClientSocket, True, False);
  if Item <> nil then
    Item.Delete;
  FMain.StatusBar1.Panels.Items[1].Text := 'Disconnected';
end;

procedure TCMDSock_Thread.ClientExecute;
var
  SocketStrm: TWinSocketStream;
  ReceivedText: string;

  procedure readRaw(var Data; Size: Integer);
  var
    P: PByte;
    Received: Integer;
  begin
    P := PByte(@Data);
    while Size > 0 do
    begin
      Received := SocketStrm.Read(P^, Size);
      if Received <= 0 then SysUtils.Abort;
      Inc(P, Received);
      Dec(Size, Received);
    end;
  end;

  procedure writeRaw(const Data; Size: Integer);
  var
    P: PByte;
    Sent: Integer;
  begin
    P := PByte(@Data);
    while Size > 0 do
    begin
      Sent := SocketStrm.Write(P^, Size);
      if Sent <= 0 then SysUtils.Abort;
      Inc(P, Sent);
      Dec(Size, Sent);
    end;
  end;

  function readMessage: boolean;
  var
    Tmp: UTF8String;
    Len: Integer;
  begin
    Result := SocketStrm.WaitForData(100);
    if not Result then Exit;

    readRaw(Len, sizeof(Len));
    Len := ntohl(Len);

    SetLength(Tmp, Len);
    readRaw(PAnsiChar(Tmp)^, Len);

    ReceivedText := string(Tmp);
  end;

  procedure writeString(const S: string);
  var
    Tmp: UTF8String;
  begin
    Tmp := UTF8String(S);
    writeRaw(PAnsiChar(Tmp)^, Length(Tmp));
  end;

  procedure writeLine(const S: string);
  begin
    writeString(S + #13#10);
  end;

  function splitData: boolean;
  var
    Idx: Integer;
  begin
    Result := StartsText('<|data|>', ReceivedText);
    if not Result then Exit;

    Delete(ReceivedText, 1, 8);
    Idx := Pos('<|>', ReceivedText);
    Manufacturer := Copy(ReceivedText, 1, Idx-1);

    Delete(ReceivedText, 1, Idx+2);
    Idx := Pos('<|>', ReceivedText);
    Model := Copy(ReceivedText, 1, Idx-1);

    Delete(ReceivedText, 1, Idx+2);
    Idx := Pos('<|>', ReceivedText);
    OsVersion := Copy(ReceivedText, 1, Idx-1);

    Delete(ReceivedText, 1, Idx+2);
    Idx := Pos('<|>', ReceivedText);
    SIMOpName := Copy(ReceivedText, 1, Idx-1);

    Delete(ReceivedText, 1, Idx+2);
    Idx := Pos('<<|', ReceivedText);
    SIMNumber := Copy(ReceivedText, 1, Idx-1);
  end;

begin
  try
    SocketStrm := TWinSocketStream.Create(ClientSocket, 5000);
    try
      writeLine('info');
      try
        while (not Terminated) and ClientSocket.Connected do
        begin
          if readMessage then
          begin
            if splitData then
              Synchronize(addClientToListView);
          end;
        end;
      finally
        writeLine('disconnect');
      end;
    finally
      SocketStrm.Free;
    end;
  finally
    Synchronize(removeClientFromListView);
  end;
end;

//================================================================================================================

procedure TFMain.ServerSocket1GetThread(Sender: TObject;
  ClientSocket: TServerClientWinSocket; var SocketThread: TServerClientThread);
begin
  SocketThread := TCMDSock_Thread.Create(False, ClientSocket);
end;

procedure TFMain.Button2Click(Sender: TObject);
begin
  ServerSocket1.Active := False;
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • I have a doubt about `TWinSocketStream.Read()`. Really is necessary make a loop around this process? only in first call not is possible obtain all content received? –  Jan 10 '18 at 23:06
  • in answer to this: `"You should write the Android and Delphi code to both force UTF-8 over the connection."` I will use [**this approach**](https://stackoverflow.com/a/9119261/9200623) and i'm using **Delphi XE5** => `"In Delphi, Char is AnsiChar in D2007 and earlier, and is WideChar in D2009 and later."` –  Jan 10 '18 at 23:36
  • @JeffersonFarias: "*I have a doubt about `TWinSocketStream.Read()`. Really is necessary make a loop around this process?*" - As I told you before (and I'm getting really tired of repeating myself to you), the loop is necessary. A read operation may return fewer bytes than requested. And TCP is a stream, it has no concept of messages. So no, you cannot "obtain all content" in a single read (if you want that, switch to Indy). You need a loop that reads, parses what you have read so far, and read again, repeating as needed until you have read what you are expecting. Same with sending, actually – Remy Lebeau Jan 10 '18 at 23:52
  • ok understood. Then i thinked wrong about this function :-( –  Jan 10 '18 at 23:58
  • @JeffersonFarias: "*I will use this approach*" - I chose not to use `writeUTF()` because it writes the string data in [**modified UTF-8**](https://docs.oracle.com/javase/7/docs/api/java/io/DataInput.html#modified-utf-8) format, and also because it is limited to 65535 characters max. But, I didn't notice that it also writes the UTF byte length, which is a good thing (it makes the server code simpler), so I have adjusted the code to do something similar using standard UTF-8 instead. – Remy Lebeau Jan 11 '18 at 00:18
  • 1
    the error that i described on question [**still comes**](https://image.prntscr.com/image/SIyxTMTUQpWZIhxhHUfYcw.png). `"This is normal behavior. Just ignore the error in your thread and exit from ClientExecute()."` **Try**, **Except** and **Break** again? or exists something more specific? –  Jan 11 '18 at 01:51
  • And about disconnect only a specific client, i can continue using my initial approach or is wrong? –  Jan 11 '18 at 03:34