您将无法按原样发送记录,因此实际上您甚至根本不需要使用记录。您必须将数据序列化为适合通过网络传输的平面格式。例如发送字符串时,先发送字符串长度,再发送字符串数据。同样,发送数组时,请在发送数组项之前发送数组长度。至于项目本身,由于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;
话虽如此,请注意套接字编程比这个简单示例显示的更复杂。您必须进行适当的错误处理。您必须考虑部分数据的发送和接收。如果您使用的是非阻塞套接字,如果套接字进入阻塞状态,那么您必须等待它再次进入可读/可写状态,然后才能尝试读取/写入仍然挂起的数据。你还没有这样做。你需要给自己一本关于有效套接字编程的好书。
更新:如果您尝试使用套接字组件的OnRead
和OnWrite
事件,则必须采用不同的方法:
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.ClientWrite(Sender: TObject; Socket: TCustomWinSocket);
var
OutBuffer: TMemoryStream;
Ptr: PByte;
Sent, Len: Integer;
begin
OutBufer := TMemoryStream(Socket.Data);
if OutBuffer.Size = 0 then Exit;
OutBuffer.Position := 0;
Ptr := PByte(OutBuffer.Memory);
Len := OutBuffer.Size - OutBuffer.Position;
while Len > 0 do
begin
Sent := Socket.SendBuf(Ptr^, Len);
if Sent <= 0 then Break;
Inc(Ptr, Sent);
Dec(Len, Sent)
end;
if OutBuffer.Position > 0 then
begin
if OutBuffer.Position >= OutBuffer.Size then
OutBuffer.Clear
else
begin
Move(Ptr^, OutBuffer.Memory^, Len);
OutBuffer.Size := Len;
end;
end;
end;
procedure TServerClass.SendBufToSocket(const vmName: TVMNames; const vmArgs: Array of TValue);
var
I: integer;
Socket: TCustomWinSocket;
OutBuffer: TMemoryStream;
procedure SendRaw(Data: Pointer; DataLen: Integer);
var
DataPtr: PByte;
Sent: Integer;
begin
if DataLen < 1 then Exit;
DataPtr := PByte(Data);
if OutBuffer.Size = 0 then
begin
repeat
Sent := Socket.SendBuf(DataPtr^, DataLen);
if Sent < 1 then Break;
Inc(DataPtr, Sent);
Dec(DataLen, Sent)
until DataLen < 1;
end;
if DataLen > 0 then
begin
OutBuffer.Seek(0, soEnd);
OutBuffer.WriteBuffer(DataPtr^, DataLen);
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
Socket := ServerSocket.Socket.Connections[idxSocket];
OutBuffer := TMemoryStream(Socket.Data);
SendString(GetEnumName(TypeInfo(TVMNames), Integer(vmName)));
SendInteger(Length(vmArgs));
for I := Low(vmArgs) to High(vmArgs) do
SendString(vmArgs[I].ToString);
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;