【问题标题】:Exchanging data over sockets通过套接字交换数据
【发布时间】:2012-11-13 12:33:16
【问题描述】:

当我向客户端发送一个字符串 (tkString) 时,客户端在尝试重建 TValue 时告诉我(在 WMClientRecv 内部)访问冲突,为什么? -_-" 与其他类型的数据一起工作得很好......

例如,整数 (tkEnum)

这里是服务器代码(向客户端发送调用数据)

procedure TServerClass.InvokeMethod(const InvokableMethod: TInvokeableMethod; const MethodArg: TValue);
var
  InvokeRec : packed record
    Method: Array[0..19] of Char;
    ArgRawSize: Integer;
    ArgTypeInf: TTypeInfo;
    ArgRawData: Array[0..255] of Byte; 
  end;
begin
  // copy method name to record
  lstrcpy(InvokeRec.Method, PChar(InvokableMethod));

  // copy arg array to record
  InvokeRec.ArgRawSize := MethodArg.DataSize;

  if (MethodArg.DataSize <> 0) then
  begin
    MethodArg.ExtractRawData(PByte(@InvokeRec.ArgRawData[0]));

    InvokeRec.ArgTypeInf := MethodArg.TypeInfo^;
  end; 

  // send record
  ServerSocket.Socket.Connections[idxSocket].SendBuf(PByte(@InvokeRec)^, SizeOf(InvokeRec));
end;

客户端将其读取为:

procedure TClientClass.ClientSocketRead(Sender: TObject; Socket: TCustomWinSocket);
var
  Buffer: PByte;
  BufLen: Integer;

  InvokeRec: TInvokeRec;
begin
  BufLen := Socket.ReceiveLength;

  Memo1.Lines.Append(Format('%d: Received %d bytes', [Memo1.Lines.Count, BufLen]));

  // dump keep-alive from recv buffer
  if (BufLen = 64) then
  begin
    GetMem(Buffer, BufLen);
    try
      Socket.ReceiveBuf(Buffer^, BufLen);
    finally
      FreeMem(Buffer, BufLen);
    end;
    Exit;
  end;

  Socket.ReceiveBuf(PByte(@InvokeRec)^, BufLen);
  SendMessage(Handle, WM_ClientRecv, 0, LPARAM(@InvokeRec));
end;

WMClientRecv 的例程:

procedure TClientClass.WMClientRecv(var Msg: TMessage);
var
  // we send to server
  ReadRec: TSharedRec;

  // we recv from server
  InvokeRecPtr: PInvokeRec;

  Value: TValue;
begin
  InvokeRecPtr := PInvokeRec(Msg.LParam);

  case InvokeRecPtr.ArgRawSize of
  0:
    Value.Empty;
  else
    TValue.Make(PByte(@InvokeRecPtr.ArgRawData)^, PTypeInfo(@InvokeRecPtr.ArgTypeInf), Value);
  end;

  InvokableSubclass.ExecMethod(InvokeRecPtr.Method, Value);
end;

还有ExceMethod:

procedure TInvokableSubclass.ExecMethod(const vmName: string; const Arg0: TValue);
var
  LContext: TRttiContext;
begin
  //
  FSendMode := TextMode;

  //
  if (Arg0.DataSize = 0) then
  begin
    LContext.GetType(TInvokableSubclass).GetMethod(vmName).Invoke(Self, []);
    Exit;
  end;

  LContext.GetType(TInvokableSubclass).GetMethod(vmName).Invoke(Self, Arg0);
end;

【问题讨论】:

    标签: delphi sockets winsock delphi-xe


    【解决方案1】:

    您正在尝试序列化TTypeInfo 类型的变量。看看它的声明:

    TTypeInfo = record
      Kind: TTypeKind;
      Name: ShortString;
     {TypeData: TTypeData}
    end;
    

    您的代码将序列化前两个字段,但无法序列化作为实现细节的一部分的隐藏字段。这是一种比较特殊的类型。您不能简单地将一个TTypeInfo 变量分配给另一个变量。你应该传递PTypeInfo 变量。

    实际上,考虑发送类型信息的代码:

    InvokeRec.ArgTypeInf := MethodArg.TypeInfo^;
    

    这个代码已经被破坏了,因为它被分配给TTypeInfo,正如我上面所说的,这不能通过简单的分配来完成。

    现在,您显然不能简单地序列化 PTypeInfo,因为这仅在拥有它的进程的地址空间中有意义。而且您不能轻易调用GetTypeData() 并序列化返回的PTypeData。这是一个复杂的结构,其中还包含难以序列化的指针。

    我怀疑最简单的方法是推出您自己的类型信息序列化机制。也许发送类型的名称然后让接收者使用该名称查找它就足够了。

    【讨论】:

    • @0x90,有关序列化对象,请参阅Delphi JSON library for XE2 available for object serialization
    • 我还在这里添加了一个样板序列化单元,包括大多数 TValue 类型,Convert Record to Serialized Form Data for sending via HTTP
    • 首先,感谢您的回复;] 现在第二,当我调试客户端时,它确实发送了 TTypeInfo 记录(正如我所说,TTypeKind 显示 tkEnun(用于整数)和名称显示'整数')。当我使用布尔值调用方法时(也可以),它显示在 Kind (TTypeInfo) 'tkEnumeration' 中,在名称中显示'Boolean'。只是字符串(Kind = tkUString)我有问题..
    • 我不知道如何使用那个 JSON,我可以举个例子吗?我从来没用过-.-”。
    • 它不会发送整个类型数据。因为 Delphi 声明是关于记录中的内容。该注释掉的字段表明实际上在Name 字段之后存储了更多信息。由于编译器不知道它,它无法复制它。看看TypInfo.GetTypeData的实现。
    【解决方案2】:

    正如我刚刚评论您的other questionTTypeInfo 是可变长度并包含指向其他数据的指针,因此您不能按原样传输它并让它在接收端有意义。您必须传输TValue.TypeInfo.Name 字符串,然后您可以在接收端使用TRttiContext.FindType(Name).Handle 以获取适当的TTypeInfo 以传递给TValue.Make()

    【讨论】:

    • 给我访问冲突,或者我做错了。 procedure TClientClass.WMClientRecv(var Msg: TMessage); var // we send to server ReadRec: TSharedRec; // we recv from server InvokeRecPtr: PInvokeRec; Value: TValue; LContext: TRttiContext; begin InvokeRecPtr := PInvokeRec(Msg.LParam); case InvokeRecPtr.ArgRawSize of 0: Value.Empty; else TValue.Make(PByte(@InvokeRecPtr.ArgRawData)^, LContext.FindType(InvokeRecPtr.ArgTypeInf).Handle, Value); end;
    • var InvokeRec : 打包记录方法: Array[0..19] of Char; ArgRawSize:整数; ArgTypeInf: Array[0..19] of Char; ArgRawData: Array[0..255] of Byte;结尾; S:字符串;
    • @0x90 您无法在 cmets 中格式化大块代码。不止一个班轮需要有问题。
    • 您似乎正试图通过套接字连接执行类方法调用。我强烈建议您为此类事情使用已建立的协议,例如 COM 或 RPC。如果必须使用套接字连接,请考虑使用 SOAP、XML-RPC 或 JSON-RPC。
    猜你喜欢
    • 2022-08-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-11
    • 2014-12-28
    • 2012-12-13
    • 2016-04-06
    相关资源
    最近更新 更多