【问题标题】:TWinSocketStream.Read(): Read error 6, The handle is invalid [closed]TWinSocketStream.Read():读取错误6,句柄无效[关闭]
【发布时间】:2018-06-20 03:32:23
【问题描述】:

当我关闭与某些客户端的连接时会发生此错误。错误在服务器的这个代码行上:

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

而且,当智能手机重新启动时(或者,例如,当我关闭客户端应用程序时),丢失的客户端数据不会从服务器应用程序的ListView 中删除。

有人可以帮我解决这两个错误吗?

这是我如何发送数据的代码:

客户端(安卓):

 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();

                }
            }

服务器(德尔福):

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;

【问题讨论】:

  • 这个话题已经在其他TServerSocket。我留下第一个TServerSocket 只是为了接收截图。
  • 但在screenshot code 上也会发生与上述相同的错误,换句话说,当我尝试同时断开两个android 套接字时,服务器上TWinSocketStream.Read() 中的此错误有两个例外.

标签: android multithreading sockets delphi delphi-xe5


【解决方案1】:

如果ClientSocket 的套接字句柄在您释放使用它的TWinSocketStream 之前关闭,则会发生“句柄无效”错误。例如,当您停用服务器时,可能会发生这种情况,因为它会循环通过其关闭其套接字的活动线程的内部列表。这是正常行为。只需忽略线程中的错误并退出ClientExecute()

话虽如此,您没有正确使用TWinSocketStream.Read()。在这种情况下,您需要在循环中调用Read()(您在循环之前的第一次调用是错误的,摆脱它),将接收到的数据附加到不断增长的缓冲区,并在处理它们之前扫描缓冲区以获取完整的消息。重复直到断开。

Received 在客户端断开连接之前不会设置为 0,但是您的客户端设置为(可能)在断开连接之前向服务器发送许多消息。你没有考虑到这种可能性,所以在断开连接之前你不应该无休止地阅读。当您收到完整的消息时停止阅读,对其进行处理,然后再继续阅读。

至于您的 ListView,正如我之前已经告诉您的,OnClientDisconnect 事件不会在线程阻塞模式下触发,因此您需要在 ClientExecute() 退出之前删除 ListView 项。就此而言,由于客户端设置为(可能)发送许多消息,因此您的代码会将每个消息添加到 ListView,因此您需要确保删除所有消息,而不仅仅是找到的第一个消息。否则,请确保每个客户端添加的 ListView 项不超过一个。

此外,您的服务器可以(可能)接受多个客户端,但您假设一次只有 1 个客户端连接。您的工作线程正在访问真正应该是线程本地的全局变量,因此多个客户端不会覆盖彼此的数据。

另外,你有一个小的竞争条件。您让工作线程向客户端发送info 命令,而主线程发送disconnect-client 命令。您没有同步命令,因此它们可能重叠的机会很小,从而破坏了您的通信。

最后,在 Java 中,DataOutputStream.writeChars() 以 UTF-16 格式写出 Unicode 字符串。在 Delphi 中,Char 在 D2007 及更早版本中为 AnsiChar,在 D2009 及更高版本中为 WideChar。网络通信应该对文本使用 UTF-8,因为它可以在平台之间移植(没有字节序问题),并且通常占用更少的带宽,尤其是对于基于拉丁语的语言。您应该编写 Android 和 Delphi 代码来强制 UTF-8 通过连接。

话虽如此,请尝试更多类似的东西:

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;

【讨论】:

  • 我对@9​​87654339@ 有疑问。真的有必要围绕这个过程进行循环吗?只有在第一次通话中才能获得所有收到的内容?
  • 回答这个问题:"You should write the Android and Delphi code to both force UTF-8 over the connection." 我将使用this approach,我正在使用 Delphi XE5 => "In Delphi, Char is AnsiChar in D2007 and earlier, and is WideChar in D2009 and later."
  • @JeffersonFarias:“我对@9​​87654342@ 有疑问。真的有必要围绕这个过程进行循环吗?” - 正如我之前告诉过你的那样(我是真的厌倦了向你重复自己),循环是必要的。读取操作返回的字节数可能少于请求的字节数。而TCP是一个流,它没有消息的概念。所以不,你不能在一次阅读中“获得所有内容”(如果你想要,切换到 Indy)。您需要一个循环来读取、解析您目前已阅读的内容并再次阅读,并根据需要重复,直到您阅读了您所期望的内容。实际上与发送相同
  • 好的,明白了。然后我想错了这个功能:-(
  • 我在问题still comes 中描述的错误。 "This is normal behavior. Just ignore the error in your thread and exit from ClientExecute()." 尝试除外中断?还是存在更具体的东西?
猜你喜欢
  • 2013-08-01
  • 2019-10-16
  • 1970-01-01
  • 2021-11-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-18
相关资源
最近更新 更多