Home TWinSocketStream.Read(): Read error 6, The handle is invalid
Reply: 1

TWinSocketStream.Read(): Read error 6, The handle is invalid

Remy Lebeau
1#
Remy Lebeau Published in 2018-01-10 21:24:10Z

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
2#
Remy Lebeau Reply to 2018-01-11 00:23:35Z

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;
You need to login account before you can post.

About| Privacy statement| Terms of Service| Advertising| Contact us| Help| Sitemap|
Processed in 0.359837 second(s) , Gzip On .

© 2016 Powered by mzan.com design MATCHINFO