200分求教,网络通讯的缓冲是怎么做的?(100分)

  • 主题发起人 主题发起人 jeefong
  • 开始时间 开始时间
J

jeefong

Unregistered / Unconfirmed
GUEST, unregistred user!
我没什么TCP的经验,只写过很简单的通讯程序.
现在有一个任务,要跟设备服务器端通讯并控制服务器设备的相关动作.
通讯协议如下:
协议结构:包头+指令类型+操作指令长度+操作指令
1.TCP连接,用户名和密码登录.
2.连接成功后,服务器端发送设备信息(包括可操作设备的数量,名称,地址码,当前状态.....等)
3.根据控制协议,选择设备并进行控制
4.服务器返回控制成功与否.
5.设备状态刷新时,会马上通知所有客户端

就是说,客户的操作界面,要根据服务器端返回设备信息,重置客户端上面显示的状态,同时客户端操作时,向服务器发送相关操作指令.

大概的流程清楚,现在就是不知道怎样处理接收区的缓冲和处理.
我的思路是:服务器发过来的N多信息,接收到后,放到缓冲区,然后从缓冲区读出数据,进行处理.
现在有几个问题不知道怎么实现:
1.缓冲区是不是用数组来实现(我用VB写的就是用数组)?
2.怎样在读缓冲的同时,又可以同时接收来自服务器数据,并压入缓冲区?
3.怎样清掉缓冲区中已经读过的数据?(这个问题很重要)
4.怎样从缓冲区中,完整地取出一个包? (服务器有可能一次发多个包,也有可能一个包分几次发,因为有些包太大)

请各位高手最好给出简单的代码加以说明,谢谢!
 
这里有一个方案可参考: http://www.delphibbs.com/delphibbs/dispq.asp?lid=554437
 
1、2、3、最好用控件;
4、根据协议可以算出一个包的长度,报头定位弄出包来;
 
creation-zy兄:
能否给个源码参详参详.
atin98@163.com
 
type
TTCPBag = class
private
FSocket: TCustomSocket;
FIdentity: Integer; //Socket标识为接收封包用 jfyes 2006-05-22 modified;
FCurrentLength: Integer;
FTotalLength: Integer;
FLastRecvTime: Cardinal;
FRecvText: string;
FStrList: TStrings; //接受数据时保存数据的
private
procedure ClearCurrentTotalLength(const ATotalLength: Integer = 0);
public
constructor Create(ASocket: TCustomSocket);
destructor Destroy; override;
//接收一个数据包
function RecvABag(var AText: string; var ACurrentLength: Integer): Boolean;
//得到合并后的字符串
function UnitRecvStr: string;
property Identity: Integer read FIdentity write FIdentity; //为接收封包用 jfyes 2006-05-22 modified;
property LastRecvTime: Cardinal read FLastRecvTime write FLastRecvTime;
property RecvText: string read FRecvText write FRecvText;
end;



//TCP 检查接收包是否完整
function TCustomSocket.TCPCheckRecvFinish(ASocket: TCustomSocket; ABuffer: PChar;
var TotalLength: Integer; var AMsg: string): Boolean;
var
bFind: Boolean;
i: Integer;
AFCUDPBag: TTCPBag;
TempTotalLen: Integer;
begin
bFind := False;
Result := False;

///这里要重新修正缓存包,将数据队列放到列表中去,并在头端读取相应的长度就行。
EnterCriticalSection(FSliceBagLock);
try
try
for I := FSliceBagList.Count - 1 downto 0 do
begin
AFCUDPBag := TTCPBag(FSliceBagList);
//如果收了一半没有发剩下的,不就死掉了, 还是要为一个包的建立超时
//不是删除掉,而是清空它
//超过6s的,因为TCP数据包是按排 队的发送不可有错误包顺序,所以7延时就不要
if ((GetTickCount - AFCUDPBag.LastRecvTime) > 20000) // 20秒
{ and (AFCUDPBag.FCurrentLength > 0) and (AFCUDPBag.FTotalLength > 0)}then
begin
AFCUDPBag.ClearCurrentTotalLength; //清空上一次的包
if AFCUDPBag.Identity <> ASocket.Identity then
Continue;
end;

//找到了该包, 这里是为每个客户端建立一个缓存包
if AFCUDPBag.Identity = ASocket.Identity then begin
bFind := True;
AFCUDPBag.LastRecvTime := GetTickCount;
if AFCUDPBag.RecvABag(AMsg, TotalLength) then
begin
AMsg := AFCUDPBag.RecvText; //返回结果AMsg 给用户
Result := True;
Break;
end
else begin //还没接受完毕,等待下一次接受
Exit;
end;
end // if AFCUDPBag.Identity = AIdentity then begin
end; //end for

if not bFind then begin //没找到,则新增一条进去
//注意第一次接收可能会是完整的包。
TempTotalLen := PInteger(ABuffer)^;
if TotalLength = TempTotalLen then
begin
Result := True;
//是个完整的数据包
Exit;
end;

AFCUDPBag := TTCPBag.Create(ASocket);
AFCUDPBag.LastRecvTime := GetTickCount;
AFCUDPBag.FIdentity := ASocket.Identity;
if AFCUDPBag.RecvABag(AMsg, TotalLength) then
begin
AMsg := AFCUDPBag.RecvText;
FreeAndNil(AFCUDPBag);
Exit;
end;
FSliceBagList.Add(AFCUDPBag);
end;
except on E: Exception do begin
Result := False;
ASocket.DoException(ASocket, 'TCPCheckRecvFinish: ' + E.Message);
end;
end;
finally
LeaveCriticalSection(FSliceBagLock);
end;
end;

{ TTCPBag }

procedure TTCPBag.ClearCurrentTotalLength(const ATotalLength: Integer);
begin
FCurrentLength := 0;
FTotalLength := 0;
FLastRecvTime := GetTickCount;
FStrList.Clear;
end;

constructor TTCPBag.Create(ASocket: TCustomSocket);
begin
inherited Create;
FSocket := ASocket;
FTotalLength := 0;
FCurrentLength := 0;
FIdentity := -1;
Identity := -1;
FLastRecvTime := GetTickCount;
FRecvText := '';
FStrList := TStringList.Create;
FStrList.Clear;
end;

destructor TTCPBag.Destroy;
begin
FTotalLength := 0;
FCurrentLength := 0;
FIdentity := -1;
Identity := -1;

FRecvText := '';
FStrList.Free;
inherited;
end;

function TTCPBag.RecvABag(var AText: string; var ACurrentLength: Integer): Boolean;
var
i_Len: Integer;
ATempLen: Integer;
Temp: string;
AFcur: Integer;
begin
Result := False;
try
if (FTotalLength < 0) or (FTotalLength > 10000000) then
begin
FSocket.DoException(FSocket,
Format('TTCPBag.FTotalLength: ABB TotalLen: %d, ATextLen: %d, CurLen: %d',
[FTotalLength, ACurrentLength, FCurrentLength]));
ClearCurrentTotalLength; //清除内存中的
Exit;
end;

//如果初发始化或清空就取前4字节为总长度
if (FTotalLength = 0) then
begin
Move(AText[1], FTotalLength, SizeOf(Integer));
end;

//这里为什么会有包分析出错,大部分原因是数据包处理不及时,发生冗长的原因,
//在客户端进行Sleep(20)后发送就正常,但总觉得不是个好办法。
if (FTotalLength < 0) or (FTotalLength > 10000000) then
begin
if FSocket is TServerClientSocket then
FSocket.DoException(FSocket,
Format('TTCPBag.FTotalLength: ThreadCount: %d, BufferCount: %d',
[TServerClientSocket(FSocket).FServerSocket.FDealThreads.Count,
TServerClientSocket(FSocket).FServerSocket.FBufferQueue.Count]));
ClearCurrentTotalLength; //清除内存中的
Exit;
end;


//如果等于就正好
if ACurrentLength = FTotalLength then begin
FRecvText := AText;
Result := True;
ClearCurrentTotalLength;
Exit;
end;

i_Len := FTotalLength - FCurrentLength;

AFcur := FCurrentLength;
//累计当前长度和总长度对比
FCurrentLength := FCurrentLength + ACurrentLength;

//如果等于就正好 ,这里是对的,看来下面的大于长度还是有点问题,但是经过测试,好
//像是藕合问题,有时会有问题,有时又没有,为什么呢?又在如何长度的情况下会这样呢?
if (FCurrentLength = FTotalLength) then begin

// FSocket.DoException(FSocket, Format('TTCPBag.ToatlLen = %d, OldAFcurLen: %d, FCurLen: %d AtextLen: %d , ListTextLen: %d',
// [FTotalLength, AFcur, FCurrentLength, ACurrentLength, Length(FStrList.Strings[0])]));
ACurrentLength := FTotalLength;
FRecvText := '';
FRecvText := UnitRecvStr; //先取得前面的部分
FRecvText := FRecvText + AText; //取得后面部分
Result := True;

ClearCurrentTotalLength;
Exit;
end;


//这里计算,如果大于,就取大于的部分,剩下的部分在放进去。
if FCurrentLength > FTotalLength then begin
FRecvText := UnitRecvStr; //先取得前面的部分
// 剩下的部分的长度
ATempLen := ACurrentLength;

FRecvText := FRecvText + Copy(AText, 1, i_Len); //取大于部分
ACurrentLength := FTotalLength; //返回总长度
ClearCurrentTotalLength; //清除内存中的

//新的当前等于来的总长减去上一次剩于的部分长度就是了。
FCurrentLength := ATempLen - i_Len;

Temp := Copy(AText, i_Len + 1, FCurrentLength);
Move(Temp[1], FTotalLength, SizeOf(Integer));
//FSocket.DoException(FSocket,
//Format('TTCPBag. > FTotalLength: A %d, OldAFcurLen: %d,, FCurLen: %d TextLen: %d',
// [FTotalLength, AFcur, FCurrentLength, ATempLen]));
if (FTotalLength < 0) or (FTotalLength > 100000) then
begin
FSocket.DoException(FSocket, Format('TTCPBag.FTotalLength: B ReadLen Error: %d, AText Len: %d, CurLen: %d',
[FTotalLength, ATempLen, FCurrentLength]));
ClearCurrentTotalLength; //清除内存中的
Exit;
end;

FStrList.Add(Temp);
//Temp := '';
Result := True;
Exit;
end;

//否则就是小于了,直接添加进去
FStrList.Add(AText);
except on E: Exception do begin
Result := False;
FSocket.DoException(FSocket, 'TTCPBag.RecvABag: ' + E.Message);
end;
end;
end;

function TTCPBag.UnitRecvStr: string;
var
i: Integer;
begin
Result := '';
for i := 0 to FStrList.Count - 1 do
begin
Result := Result + FStrList;
end;
end;
 
多人接受答案了。
 
后退
顶部