Delphi 通过套接字发送动态数组(记录内部)?
我正在尝试直接使用.SendBuf将记录从服务器传输到客户端 然而,这个记录有一个成员,它是一个动态数组,我在SOF的某个地方读到,当发送记录时,成员必须是静态固定长度的,但问题是。。。我无法确定将来会发送多少个参数 我怎样才能解决这个问题Delphi 通过套接字发送动态数组(记录内部)?,delphi,sockets,winsock,Delphi,Sockets,Winsock,我正在尝试直接使用.SendBuf将记录从服务器传输到客户端 然而,这个记录有一个成员,它是一个动态数组,我在SOF的某个地方读到,当发送记录时,成员必须是静态固定长度的,但问题是。。。我无法确定将来会发送多少个参数 我怎样才能解决这个问题 procedure TServerClass.SendBufToSocket(const vmName: TVMNames; const vmArgs: Array of TValue); var // this record is sent to cl
procedure TServerClass.SendBufToSocket(const vmName: TVMNames; const vmArgs: Array of TValue);
var
// this record is sent to client
// vmName = method to be called [in]
// vmArgs = Argument for the method [in, optional]
BufRec: packed record
vmName: array[0..49] of char;
vmArgs: Array of TValue;
end;
s: string;
i: integer;
begin
// convert enum method name to string
s:= GetEnumName(TypeInfo(TVMNames), Integer(vmName));
// copy method name to record
lstrcpy(BufRec.vmName, pChar(s));
// copy arg array to record
SetLength(BufRec.vmArgs, length(vmArgs));
for i:=0 to high(vmArgs)
do BufRec.vmArgs[i] := vmArgs[i];
// send record
ServerSocket.Socket.Connections[idxSocket].SendBuf(PByte(@BufRec)^, SizeOf(BufRec));
end;
我从哪里读到的,这里:
您将无法按原样发送记录,因此实际上您甚至不需要使用记录。您必须将数据序列化为适合通过网络传输的平面格式。例如,发送字符串时,在发送字符串数据之前先发送字符串长度。同样,在发送数组时,在发送数组项之前先发送数组长度。至于项目本身,因为TValue是动态的,所以您还必须将其序列化为平面格式 在发送端尝试以下操作:
procedure TServerClass.SendBufToSocket(const vmName: TVMNames; const vmArgs: Array of TValue);
var
I: integer;
procedure SendRaw(Data: Pointer; DataLen: Integer);
var
DataPtr: PByte;
Socket: TCustomWinSocket;
Sent, Err: Integer;
begin
DataPtr := PByte(Data);
Socket := ServerSocket.Socket.Connections[idxSocket];
while DataLen > 0 do
begin
Sent := Socket.SendBuf(DataPtr^, DataLen);
if Sent > 0 then
begin
Inc(DataPtr, Sent);
Dec(DataLen, Sent)
end else
begin
Err := WSAGetLastError();
if Err <> WSAEWOULDBLOCK then
raise Exception.CreateFmt('Unable to sent data. Error: %d', [Err]);
Sleep(10);
end;
end;
end;
procedure SendInteger(Value: Integer);
begin
Value := htonl(Value);
SendRaw(@Value, SizeOf(Value));
end;
procedure SendString(const Value: String);
var
S: UTF8string;
Len: Integer;
begin
S := Value;
Len := Length(S);
SendInteger(Len);
SendRaw(PAnsiChar(S), Len);
end;
begin
SendString(GetEnumName(TypeInfo(TVMNames), Integer(vmName)));
SendInteger(Length(vmArgs));
for I := Low(vmArgs) to High(vmArgs) do
SendString(vmArgs[I].ToString);
end;
然后在接收端:
type
TValueArray := array of TValue;
procedure TServerClass.ReadBufFromSocket(var vmName: TVMNames; var vmArgs: TValueArray);
var
Cnt, I: integer;
Tmp: String;
procedure ReadRaw(Data: Pointer; DataLen: Integer);
var
DataPtr: PByte;
Socket: TCustomWinSocket;
Read, Err: Integer;
begin
DataPtr := PByte(Data);
Socket := ClientSocket.Socket;
while DataLen > 0 do
begin
Read := Socket.ReceiveBuf(DataPtr^, DataLen);
if Read > 0 then
begin
Inc(DataPtr, Read);
Dec(DataLen, Read);
end
else if Read = 0 then
begin
raise Exception.Create('Disconnected');
end else
begin
Err := WSAGetLastError();
if Err <> WSAEWOULDBLOCK then
raise Exception.CreateFmt('Unable to read data. Error: %d', [Err]);
Sleep(10);
end;
end;
end;
function ReadInteger: Integer;
begin
ReadRaw(@Result, SizeOf(Result));
Result := ntohl(Result);
end;
function ReadString: String;
var
S: UTF8String;
Len: Integer;
begin
Len := ReadInteger;
SetLength(S, Len);
ReadRaw(PAnsiChar(S), Len);
Result := S;
end;
begin
vmName := TVMNames(GetEnumValue(TypeInfo(TVMNames), ReadString));
Cnt := ReadInteger;
SetLength(vmArgs, Cnt);
for I := 0 to Cnt-1 do
begin
Tmp := ReadString;
// convert to TValue as needed...
vmArgs[I] := ...;
end;
end;
procedure TServerClass.ClientConnect(Sender: TObject; Socket: TCustomWinSocket);
begin
Socket.Data := TMemoryStream.Create;
end;
procedure TServerClass.ClientDisconnect(Sender: TObject; Socket: TCustomWinSocket);
begin
TMemoryStream(Socket.Data).Free;
Socket.Data := nil;
end;
procedure TServerClass.ClientRead(Sender: TObject; Socket: TCustomWinSocket);
var
InBuffer: TMemoryStream;
Ptr: PByte;
OldSize, Pos, Read: Integer;
function HasAvailable(DataLen: Integer): Boolean;
being
Result := (InBuffer.Size - InBuffer.Position) >= DataLen;
end;
function ReadInteger(var Value: Integer);
begin
Result := False;
if HasAvailable(SizeOf(Integer)) then
begin
InBuffer.ReadBuffer(Value, SizeOf(Integer));
Value := ntohl(Value);
Result := True;
end;
end;
function ReadString(var Value: String);
var
S: UTF8String;
Len: Integer;
begin
Result := False;
if not ReadInteger(Len) then Exit;
if not HasAvailable(Len) then Exit;
SetLength(S, Len);
InBuffer.ReadBuffer(PAnsiChar(S)^, Len);
Value := S;
Result := True;
end;
function ReadNames: Boolean;
var
S: String;
vmName: TVMNames;
vmArgs: TValueArray;
begin
Result := False;
if not ReadString(S) then Exit;
vmName := TVMNames(GetEnumValue(TypeInfo(TVMNames), S));
if not ReadInteger(Cnt) then Exit;
SetLength(vmArgs, Cnt);
for I := 0 to Cnt-1 do
begin
if not ReadString(S) then Exit;
// convert to TValue as needed...
vmArgs[I] := ...;
end;
// use vmArgs as needed...
Result := True;
end;
begin
InBuffer := TMemoryStream(Socket.Data);
Read := Socket.ReceiveLength;
if Read <= 0 then Exit;
OldSize := InBuffer.Size;
InBuffer.Size := OldSize + Read;
try
Ptr := PByte(InBuffer.Memory);
Inc(Ptr, OldSize);
Read := Socket.ReceiveBuf(Ptr^, Read);
except
Read := -1;
end;
if Read < 0 then Read := 0;
InBuffer.Size := OldSize + Read;
if Read = 0 then Exit;
InBuffer.Position := 0;
repeat
Pos := InBuffer.Position;
until not ReadNames;
InBuffer.Position := Pos;
Read := InBuffer.Size - InBuffer.Position;
if Read < 1 then
InBuffer.Clear
else
begin
Ptr := PByte(InBuffer.Memory);
Inc(Ptr, InBuffer.Position);
Move(Ptr^, InBuffer.Memory^, Read);
InBuffer.Size := Read;
end;
end;
您将无法按原样发送记录,因此实际上您甚至不需要使用记录。您必须将数据序列化为适合通过网络传输的平面格式。例如,发送字符串时,在发送字符串数据之前先发送字符串长度。同样,在发送数组时,在发送数组项之前先发送数组长度。至于项目本身,因为TValue是动态的,所以您还必须将其序列化为平面格式 在发送端尝试以下操作:
procedure TServerClass.SendBufToSocket(const vmName: TVMNames; const vmArgs: Array of TValue);
var
I: integer;
procedure SendRaw(Data: Pointer; DataLen: Integer);
var
DataPtr: PByte;
Socket: TCustomWinSocket;
Sent, Err: Integer;
begin
DataPtr := PByte(Data);
Socket := ServerSocket.Socket.Connections[idxSocket];
while DataLen > 0 do
begin
Sent := Socket.SendBuf(DataPtr^, DataLen);
if Sent > 0 then
begin
Inc(DataPtr, Sent);
Dec(DataLen, Sent)
end else
begin
Err := WSAGetLastError();
if Err <> WSAEWOULDBLOCK then
raise Exception.CreateFmt('Unable to sent data. Error: %d', [Err]);
Sleep(10);
end;
end;
end;
procedure SendInteger(Value: Integer);
begin
Value := htonl(Value);
SendRaw(@Value, SizeOf(Value));
end;
procedure SendString(const Value: String);
var
S: UTF8string;
Len: Integer;
begin
S := Value;
Len := Length(S);
SendInteger(Len);
SendRaw(PAnsiChar(S), Len);
end;
begin
SendString(GetEnumName(TypeInfo(TVMNames), Integer(vmName)));
SendInteger(Length(vmArgs));
for I := Low(vmArgs) to High(vmArgs) do
SendString(vmArgs[I].ToString);
end;
然后在接收端:
type
TValueArray := array of TValue;
procedure TServerClass.ReadBufFromSocket(var vmName: TVMNames; var vmArgs: TValueArray);
var
Cnt, I: integer;
Tmp: String;
procedure ReadRaw(Data: Pointer; DataLen: Integer);
var
DataPtr: PByte;
Socket: TCustomWinSocket;
Read, Err: Integer;
begin
DataPtr := PByte(Data);
Socket := ClientSocket.Socket;
while DataLen > 0 do
begin
Read := Socket.ReceiveBuf(DataPtr^, DataLen);
if Read > 0 then
begin
Inc(DataPtr, Read);
Dec(DataLen, Read);
end
else if Read = 0 then
begin
raise Exception.Create('Disconnected');
end else
begin
Err := WSAGetLastError();
if Err <> WSAEWOULDBLOCK then
raise Exception.CreateFmt('Unable to read data. Error: %d', [Err]);
Sleep(10);
end;
end;
end;
function ReadInteger: Integer;
begin
ReadRaw(@Result, SizeOf(Result));
Result := ntohl(Result);
end;
function ReadString: String;
var
S: UTF8String;
Len: Integer;
begin
Len := ReadInteger;
SetLength(S, Len);
ReadRaw(PAnsiChar(S), Len);
Result := S;
end;
begin
vmName := TVMNames(GetEnumValue(TypeInfo(TVMNames), ReadString));
Cnt := ReadInteger;
SetLength(vmArgs, Cnt);
for I := 0 to Cnt-1 do
begin
Tmp := ReadString;
// convert to TValue as needed...
vmArgs[I] := ...;
end;
end;
procedure TServerClass.ClientConnect(Sender: TObject; Socket: TCustomWinSocket);
begin
Socket.Data := TMemoryStream.Create;
end;
procedure TServerClass.ClientDisconnect(Sender: TObject; Socket: TCustomWinSocket);
begin
TMemoryStream(Socket.Data).Free;
Socket.Data := nil;
end;
procedure TServerClass.ClientRead(Sender: TObject; Socket: TCustomWinSocket);
var
InBuffer: TMemoryStream;
Ptr: PByte;
OldSize, Pos, Read: Integer;
function HasAvailable(DataLen: Integer): Boolean;
being
Result := (InBuffer.Size - InBuffer.Position) >= DataLen;
end;
function ReadInteger(var Value: Integer);
begin
Result := False;
if HasAvailable(SizeOf(Integer)) then
begin
InBuffer.ReadBuffer(Value, SizeOf(Integer));
Value := ntohl(Value);
Result := True;
end;
end;
function ReadString(var Value: String);
var
S: UTF8String;
Len: Integer;
begin
Result := False;
if not ReadInteger(Len) then Exit;
if not HasAvailable(Len) then Exit;
SetLength(S, Len);
InBuffer.ReadBuffer(PAnsiChar(S)^, Len);
Value := S;
Result := True;
end;
function ReadNames: Boolean;
var
S: String;
vmName: TVMNames;
vmArgs: TValueArray;
begin
Result := False;
if not ReadString(S) then Exit;
vmName := TVMNames(GetEnumValue(TypeInfo(TVMNames), S));
if not ReadInteger(Cnt) then Exit;
SetLength(vmArgs, Cnt);
for I := 0 to Cnt-1 do
begin
if not ReadString(S) then Exit;
// convert to TValue as needed...
vmArgs[I] := ...;
end;
// use vmArgs as needed...
Result := True;
end;
begin
InBuffer := TMemoryStream(Socket.Data);
Read := Socket.ReceiveLength;
if Read <= 0 then Exit;
OldSize := InBuffer.Size;
InBuffer.Size := OldSize + Read;
try
Ptr := PByte(InBuffer.Memory);
Inc(Ptr, OldSize);
Read := Socket.ReceiveBuf(Ptr^, Read);
except
Read := -1;
end;
if Read < 0 then Read := 0;
InBuffer.Size := OldSize + Read;
if Read = 0 then Exit;
InBuffer.Position := 0;
repeat
Pos := InBuffer.Position;
until not ReadNames;
InBuffer.Position := Pos;
Read := InBuffer.Size - InBuffer.Position;
if Read < 1 then
InBuffer.Clear
else
begin
Ptr := PByte(InBuffer.Memory);
Inc(Ptr, InBuffer.Position);
Move(Ptr^, InBuffer.Memory^, Read);
InBuffer.Size := Read;
end;
end;
正如一些评论中提到的,将记录序列化为流,然后通过网络发送流内容。我用在我的一些项目中,效果非常好。 您可以在记录中使用任何动态类型,如字符串、数组 小例子:
type
TMyRecord = record
str : string;
end;
procedure Test;
var
FStream : TMemoryStream;
MYrecord : TMyRecord;
MYrecord1 : TMyRecord;
begin
FStream := TMemoryStream.Create;
try
MyRecord.Str := 'hello world';
// save record to stream
TKBDynamic.WriteTo(FStream, MyRecord, TypeInfo(TMyRecord));
FStream.Position := 0;
// read record from stream
TKBDynamic.ReadFrom(FStream, MyRecord1, TypeInfo(TMyRecord));
If MyRecord1.Str <> MyRecord.Str then
ShowMessage('this should not happen!');
finally
FStream.Free;
end;
end;
正如一些评论中提到的,将记录序列化为流,然后通过网络发送流内容。我用在我的一些项目中,效果非常好。 您可以在记录中使用任何动态类型,如字符串、数组 小例子:
type
TMyRecord = record
str : string;
end;
procedure Test;
var
FStream : TMemoryStream;
MYrecord : TMyRecord;
MYrecord1 : TMyRecord;
begin
FStream := TMemoryStream.Create;
try
MyRecord.Str := 'hello world';
// save record to stream
TKBDynamic.WriteTo(FStream, MyRecord, TypeInfo(TMyRecord));
FStream.Position := 0;
// read record from stream
TKBDynamic.ReadFrom(FStream, MyRecord1, TypeInfo(TMyRecord));
If MyRecord1.Str <> MyRecord.Str then
ShowMessage('this should not happen!');
finally
FStream.Free;
end;
end;
我知道我没有按原样发送记录,我在服务器/客户机上都有相同的记录,我所做的是第一个成员是一个字节类型,它保存记录ID,告诉客户机操作的位置,我在两侧都有相同的记录,所以假设我将记录发送给客户机,我只填充了其中的第二个成员,在第一个成员中,即ID,首先读取,然后我知道如何处理该记录。您的问题中没有显示任何这些。下次,请解释一下当你请求帮助时你实际上在做什么。我还想说,TValue本身不是一个简单的类型,有时也是指向真实数据的第二个指针。所以,@0x90,请进行序列化。例如,另一个例子是JSON,TCP是一个字节流。发送和接收之间没有1:1的关系,就像UDP一样。recv返回的字节数少于请求的字节数。您必须在循环中调用它,直到您读取了所需的所有字节。使用非阻塞套接字时,当套接字有数据要读取时,会发生OnRead事件。如果您完成了对可用内容的读取,并且需要更多字节,则必须等待OnRead事件再次发生。每次读取时,如果您有一条基于您正在实施的协议的完整消息-在本例中为HTTP,则必须查找HTTP头和正文之间的换行符,然后分析标题以了解要读取的额外字节数,然后对其进行处理并保存剩余的字节,以便它们可以与以后的字节一起用于处理下一条消息。我知道我没有按原样发送记录,我在服务器/客户端上都有相同的记录,我所做的是,第一个成员是一个字节类型,它保存记录ID,告诉客户端操作的位置,我两边都有相同的记录,所以假设我将记录发送给客户端,我只填充了其中的第二个成员,在第一个成员中,即ID,首先读取,然后我知道如何处理该记录。您的问题中没有显示任何这些。下次,请解释一下当你请求帮助时你实际上在做什么。我还想说,TValue本身不是一个简单的类型,有时也是指向真实数据的第二个指针。所以,@0x90,请进行序列化。例如,另一个例子是JSON,TCP是一个字节流。发送和接收之间没有1:1的关系,就像UDP一样。recv返回的字节数少于请求的字节数。您必须在循环中调用它,直到您读取了所需的所有字节。使用非阻塞套接字时,当套接字有数据要读取时,会发生OnRead事件。如果您完成读取可用的内容并需要更多字节,则必须等待OnRead事件再次发生。每次执行读取操作时,如果您有基于协议的完整消息,您将执行以下操作:
menting—在本例中是HTTP,因此您必须查找HTTP头和正文之间的换行符,然后解析头以了解要读取的额外字节数,然后对其进行处理并保存剩余的字节,以便它们可以与以后的字节一起用于处理下一条消息。滚动您自己的RPC是一项非常困难的任务。您最好使用已知良好的RPC。@DavidHeffernan实际上,到目前为止,我已经有了很好的结果;],它工作得很好..滚动您自己的RPC是一项非常困难的任务。您最好使用已知良好的RPC。@DavidHeffernan实际上,到目前为止,我已经有了很好的结果;],它工作得很好。这就是我试图避免的,使用第三方组件。它不是一个真正的组件,它是一个单元。。。如果你真的想推出自己的,我建议看看这个库,因为它公开了所有需要的信息…我找到了这个链接,在那里他展示了如何将JSON反序列化回TValue,太棒了。。。这就是我试图避免的,使用第三方组件。它不是一个真正的组件,它是一个单元。。。如果你真的想推出自己的,我建议看看这个库,因为它公开了所有需要的信息…我找到了这个链接,在那里他展示了如何将JSON反序列化回TValue,太棒了。。。