印象中網絡程序都是sendBuffer和recvBuffer來發送數據和接收數據,本次Demo演示如何定義定義一個自己的對象,然後我們按照OO的思想直接進行對象的發送和接收,先上個流程圖。
下面是客戶端發送和接收的測試代碼。
下面我們來看看詳細的設計。
第一步(TMyObject):首先我們需要設計一個需要進行傳輸的對象.
type
TMyObject = class(TObject)
private
FDataString:String;
FOle:OleVariant;
public
property DataString:String read FDataString write FDataString;
property Ole:OleVariant read FOle write FOle;
end;
對象很簡單,一個DataString,和Ole。Ole可以存放各種數據。
第二步:編寫客戶端發送和接收過程
發送對象:
把對象變成可以傳遞的格式,然後用IdTcpClient進行發送,編碼的格式為:字符串長度+ole數據流長度 + 字符串數據 + Ole流數據,代碼很簡單如下。
class function TMyObjectCoderTools.Encode(pvSocket: TIdTcpClient;
pvObject: TObject): Integer;
var
lvMyObj:TMyObject;
lvOleStream:TMemoryStream;
lvOleLen, lvStringLen:Integer;
begin
lvMyObj := TMyObject(pvObject);
lvOleStream := TMemoryStream.Create;
try
WriteOleVariant(lvMyObj.Ole, lvOleStream);
lvOleLen := lvOleStream.Size;
lvOleStream.Position := 0;
//字符串長度+ole長度 + 字符串數據 + Ole數據
lvStringLen := Length(AnsiString(lvMyObj.DataString));
Result := 0;
Result := Result + sendBuffer(pvSocket,@lvStringLen,sizeOf(Integer));
Result := Result + sendBuffer(pvSocket,@lvOleLen,sizeOf(Integer));
Result := Result + sendBuffer(pvSocket,PAnsiChar(AnsiString(lvMyObj.DataString)), lvStringLen);
Result := Result + sendBuffer(pvSocket,lvOleStream.Memory, lvOleLen);
//result 發送長度
finally
lvOleStream.Free;
end;
end;
接收對象:
用IdTcpClient接收數據,把接收到的數據按照協議格式進行拆分,放入到對象的屬性中,依次讀取字符串長度+ole長度 + 字符串數據 + Ole數據,代碼如下
class function TMyObjectCoderTools.Decode(pvSocket: TIdTcpClient;
pvObject: TObject): Boolean;
var
lvStringLength, lvStreamLength:Integer;
lvData, lvTemp:AnsiString;
lvStream:TStream;
l, lvRemain:Integer;
lvBufData:PAnsiChar;
begin
Result := false;
lvStringLength := 0;
lvStreamLength := 0;
recvBuffer(pvSocket, @lvStringLength, SizeOf(Integer));
recvBuffer(pvSocket, @lvStreamLength, SizeOf(Integer));
if (lvStringLength = 0) and (lvStreamLength = 0) then exit;
//讀取json字符串
if lvStringLength > 0 then
begin
SetLength(lvData, lvStringLength);
l := recvBuffer(pvSocket, PAnsiChar(lvData), lvStringLength);
TMyObject(pvObject).DataString := lvData;
end;
//讀取Ole值
if lvStreamLength > 0 then
begin
GetMem(lvBufData, lvStreamLength);
try
recvBuffer(pvSocket, lvBufData, lvStreamLength);
lvStream := TMemoryStream.Create;
try
lvStream.WriteBuffer(lvBufData^, lvStreamLength);
lvStream.Position := 0;
TMyObject(pvObject).Ole := ReadOleVariant(lvStream);
finally
lvStream.Free;
end;
finally
FreeMem(lvBufData, lvStreamLength);
end;
end;
Result := true;
end;
第三步:服務端的接收和發送,服務端接收到數據後也需要解碼,返回數據也需要編碼。在服務端需要編寫編碼器,過程與客戶端的發送和接收類似。
接收的解碼器。
TMyObjectDecoder = class(TIOCPDecoder)
public
/// <summary>
/// 解碼收到的數據,如果有接收到數據,調用該方法,進行解碼
/// </summary>
/// <returns>
/// 返回解碼好的對象
/// </returns>
/// <param name="inBuf"> 接收到的流數據 </param>
function Decode(const inBuf: TBufferLink): TObject; override;
end;
function TMyObjectDecoder.Decode(const inBuf: TBufferLink): TObject;
var
lvStringLen, lvStreamLength:Integer;
lvData:AnsiString;
lvBuffer:array of Char;
lvBufData:PAnsiChar;
lvStream:TMemoryStream;
lvValidCount:Integer;
lvBytes:TIOCPBytes;
begin
Result := nil;
//如果緩存中的數據長度不夠包頭長度,解碼失敗<字符串長度,Ole流長度>
lvValidCount := inBuf.validCount;
if (lvValidCount < SizeOf(Integer) + SizeOf(Integer)) then
begin
Exit;
end;
//記錄讀取位置
inBuf.markReaderIndex;
inBuf.readBuffer(@lvStringLen, SizeOf(Integer));
inBuf.readBuffer(@lvStreamLength, SizeOf(Integer));
//如果緩存中的數據不夠json的長度和流長度<說明數據還沒有收取完畢>解碼失敗
lvValidCount := inBuf.validCount;
if lvValidCount < (lvStringLen + lvStreamLength) then
begin
//返回buf的讀取位置
inBuf.restoreReaderIndex;
exit;
end else if (lvStringLen + lvStreamLength) = 0 then
begin
//兩個都為0<兩個0>客戶端可以用來作為自動重連使用
TIOCPFileLogger.logDebugMessage('接收到一次[00]數據!');
Exit;
end;
//解碼成功
Result := TMyObject.Create;
//讀取json字符串
if lvStringLen > 0 then
begin
SetLength(lvData, lvStringLen);
inBuf.readBuffer(PAnsiChar(lvData), lvStringLen);
TMyObject(Result).DataString := lvData;
end;
//讀取Ole值
if lvStreamLength > 0 then
begin
GetMem(lvBufData, lvStreamLength);
try
inBuf.readBuffer(lvBufData, lvStreamLength);
lvStream := TMemoryStream.Create;
try
lvStream.WriteBuffer(lvBufData^, lvStreamLength);
lvStream.Position := 0;
TMyObject(Result).Ole := ReadOleVariant(lvStream);
finally
lvStream.Free;
end;
finally
FreeMem(lvBufData, lvStreamLength);
end;
end;
end;
發送的編碼器
TMyObjectEncoder = class(TIOCPEncoder)
public
/// <summary>
/// 編碼要發送的對象
/// </summary>
/// <param name="pvDataObject"> 要進行編碼的對象 </param>
/// <param name="ouBuf"> 編碼好的數據
/// 字符串長度+ole長度 + 字符串數據 + Ole數據
/// </param>
procedure Encode(pvDataObject:TObject; const ouBuf: TBufferLink); override;
end;
procedure TMyObjectEncoder.Encode(pvDataObject: TObject;
const ouBuf: TBufferLink);
var
lvMyObj:TMyObject;
lvOleStream:TMemoryStream;
lvOleLen, lvStringLen:Integer;
begin
lvMyObj := TMyObject(pvDataObject);
lvOleStream := TMemoryStream.Create;
try
WriteOleVariant(lvMyObj.Ole, lvOleStream);
lvOleLen := lvOleStream.Size;
lvOleStream.Position := 0;
//字符串長度+ole長度 + 字符串數據 + Ole數據
lvStringLen := Length(AnsiString(lvMyObj.DataString));
ouBuf.AddBuffer(@lvStringLen,sizeOf(Integer));
ouBuf.AddBuffer(@lvOleLen,sizeOf(Integer));
ouBuf.AddBuffer(PAnsiChar(AnsiString(lvMyObj.DataString)), lvStringLen);
ouBuf.AddBuffer(lvOleStream.Memory, lvOleLen);
finally
lvOleStream.Free;
end;
end;
然後在啟動IOCP的之前注冊編碼器和解碼器
FDecoder := TMyObjectDecoder.Create;
FEncoder := TMyObjectEncoder.Create;
//注冊解碼器
TIOCPContextFactory.instance.registerDecoder(FDecoder);
//注冊編碼器
TIOCPContextFactory.instance.registerEncoder(FEncoder);
服務端然後就可以在ClientContext中編寫相應的邏輯處理代碼就行了
procedure TClientContext.dataReceived(const pvDataObject:TObject);
var
lvMyObject:TMyObject;
begin
lvMyObject := TMyObject(pvDataObject);
try
//直接回傳
writeObject(lvMyObject);
except
on E:Exception do
begin
lvMyObject.DataString := E.Message;
writeObject(lvMyObject);
end;
end;
end;
本次DEMO使用XE5進行編寫,可以在D7-XE5中可以運行。
Demo在已經上傳在SVN中
>>>>>>DIOCP討論群:320641073
>>>>>>SVN源碼和DEMO下載:https://code.google.com/p/diocp/