在套接字上发送正确的记录大小

时间:2016-12-13 22:14:38

标签: delphi

我有这个记录:

type
  TSocks5_Packet = record
    Socks_ID: String[6];
    Socks_Packet: array of byte;
  end;

我把字符串[6],因为我确定这个字符串总是有6个字符(并尝试方便我的工作,但还不够)。

我尝试在此处发送此记录:

procedure TForm1.SocksServerClientRead(Sender: TObject;
  Socket: TCustomWinSocket);
var
  Socks5_Request: TSocks5_Packet;
begin
  SetLength(Socks5_Request.Socks_Packet, Socket.ReceiveLength);
  Socket.ReceiveBuf(Socks5_Request.Socks_Packet[0], Length(Socks5_Request.Socks_Packet));
  Socks5_Request.Socks_ID:= PSocket_Identity(Socket.Data).Sock_ID;
  TunnelClient.SendBuf(Socks5_Request, Length(Socks5_Request.Socks_Packet + Length(Socks5_Request.Socks_ID)));
end;

我很确定问题出在SendBuf第二个参数上,其中我指定了要发送的字节数。什么是正确的方法,以及我应该如何学习它?

1 个答案:

答案 0 :(得分:5)

您的代码存在一些问题:

  1. 您希望String[6]为6个字节,但实际上是7个字节。您声明ShortString的最大长度为6 AnsiChar个字符,ShortString包含长度的前导字节。

  2. 您的记录不是packed,因此需要进行对齐填充。所以不要试图按原样发送它。您正在处理可变长度数据,因此您应该序列化记录的内容。

  3. array of byte是一个动态数组。动态数组是指向在内存中其他位置分配的数据的指针。数组变量本身的字节大小为SizeOf(Pointer),在32位为4,在64位为8。您必须取消引用指针才能访问分配的内存块。你在接收时就这样做了,但是你的发送没有这样做。

  4. TCP是一种流媒体传输,根本没有消息概念。 Socket.ReceiveLength报告当前在该特定时刻的套接字内部接收缓冲区中的未读字节数。这些字节是任意的,缓冲区中可能只有1个字节。在查询长度之后和执行实际读取之前,可能会收到更多字节。

  5. 发送数据时,不保证发送您请求的字节数,可能会发送更少的字节。 SendBuf()的返回值表示接受发送的实际字节数,因此您可能需要多次调用SendBuf()来发送特定的数据。因此,循环发送直到数据耗尽。

  6. 由于您尝试隧道传输任意数据,因此需要指定实际隧道传输的字节数。

  7. 话虽如此,尝试更像这样的事情:

    function TForm1.SendData(Socket: TCustomWinSocket; const Data; DataLen: Integer): Boolean;
    var
      PData: PByte;
      NumSent: Integer;
    begin
      Result := False;
      PData := PByte(@Data);
      while DataLen > 0 do
      begin
        // SendBuf() returns -1 on error. If that error is WSAEWOULDBLOCK
        // then retry the send again.  Otherwise, TCustomWinSocket disconnects
        // itself, and if its OnError event handler does not set the ErrorCode
        // to 0 then it raises an ESocketError exception...
        //
        NumSent := Socket.SendBuf(PData^, DataLen);
        if NumSent = -1 then
        begin
          if WSAGetLastError() <> WSAEWOULDBLOCK then
            Exit;
        end else
        begin
          Inc(PData, NumSent);
          Dec(DataLen, NumSent);
        end;
      end;
      Result := True;
    end;
    
    function TForm1.SendInteger(Socket: TCustomWinSocket; Value: Integer): Boolean;
    begin
      Value := htonl(Value);
      Result := SendData(Socket, Value, SizeOf(Value));
    end;
    
    function TForm1.SendString(Socket: TCustomWinSocket; const Value: String): Boolean;
    var
      S: AnsiString; // or UTF8String
    begin
      S := AnsiString(Value);
      // or: S := UTF8Encode(Value);
      Result := SendInteger(Socket, Length(S));
      if Result then
        Result := SendData(Socket, PAnsiChar(S)^, Length(S));
    end;
    
    function TForm1.SendBytes(Socket: TCustomWinSocket; const Data: array of Byte): Boolean;
    begin
      Result := SendInteger(Socket, Length(Data));
      if Result then
        Result := SendData(Socket, PByte(Data)^, Length(Data));
    end;
    
    procedure TForm1.SocksServerClientRead(Sender: TObject; Socket: TCustomWinSocket);
    var
      Packet: array of byte;
    begin
      Len := Socket.ReceiveLength;
      if Len <= 0 the Exit;
    
      SetLength(Packet, Len);
      Len := Socket.ReceiveBuf(PByte(Packet)^, Len);
      if Len <= 0 the Exit;
      if Len < Length(Packet) then
        SetLength(Packet, Len);
    
      if SendString(TunnelClient, PSocket_Identity(Socket.Data).Sock_ID) then
        SendBytes(TunnelClient, Packet);
    end;
    

    然后相应地调整隧道接收器以根据需要对值进行反序列化(读取Integer字节计数,使用ntohl()将其转换为主机字节顺序,然后读取指定的字节数)