通过套接字交换数据

时间:2012-11-13 12:33:17

标签: delphi sockets winsock delphi-xe

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

例如,Integer(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;

2 个答案:

答案 0 :(得分:4)

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

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

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

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

InvokeRec.ArgTypeInf := MethodArg.TypeInfo^;

此代码已被破坏,因为它正在分配给TTypeInfo,正如我上面所述,无法通过简单的赋值完成。

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

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

答案 1 :(得分:0)

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