好心人,快来拿分吧! (100分)

  • 主题发起人 主题发起人 zheng
  • 开始时间 开始时间
Z

zheng

Unregistered / Unconfirmed
GUEST, unregistred user!
请看以下代码:(非阻赛模式,问题出在传送超大量数据连续触发Read时间,在取数据叠加时,出现怪问题,每触发一次数据总量不但没有增加,反而减少了)

服务端:
Procedure SendScreen(aStream: TMemoryStream); //aStream很大,大概有64K大小。
var Count, Remain: Integer;
Buffer: PChar;
begin
Count := aStream.Size;
GetMem(Buffer, Count + 1);
try
aStream.ReadBuffer(Buffer^, Count);
Remain := Count;
repeat
if Remain > MaxBufSize then //MaxBufSize=4096
if Send(CliSocket, Buffer[Count-Remain], MaxBufSize, 0) = SOCKET_ERROR then Continue
else
if Send(CliSocket, Buffer[Count-Remain], Remain, 0) = SOCKET_ERROR then Continue;
Remain := Remain - MaxBufSize;
until Remain <= 0;
finally
aStream.Clear;
FreeMem(Buffer);
end;
end;
注:原先我是用设置“SetSockOpt(SrvSocket, SOL_SOCKET, SO_SNDBUF, @BufSize, Sizeof(Integer))”(BufSize=64K)
然后一次性发送,但出现上述问题;我以为是一次发送数据量太大的原因,因此改成上面的分次发送,谁知问题依然存在!

付套接字设置(客户端也一样):
SrvSocket := Socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
SrvAddr.SIn_Family := AF_INET;
SrvAddr.SIn_Port := htons(TCP_PORT);
SrvAddr.SIn_Addr.S_Addr := INADDR_ANY;
Bind(SrvSocket, @SrvAddr, Sizeof(SrvAddr));
Listen(SrvSocket, 5);
WSAAsyncSelect(SrvSocket, Handle, WM_SOCK, FD_READ or FD_ACCEPT or FD_CLOSE);

客户端:
procedure TForm1.RecvData(var Message: TMessage); {message WM_SOCK}
var Event: Word;
begin
Event := WSAGetSelectEvent(Message.LParam);
case Event of
FD_CONNECT:
begin
HasConnected := True;
end;
FD_READ:
ReadData(); //*************接收数据****************//
FD_CLOSE:
begin
HasConnected := False;
Messagebox(0, 'Server Closed!', AppName, + MB_ICONINFORMATION + MB_OK);
end;
end;
end;

procedure TForm1.ReadData; //FBuffer: PChar;
var Buf: array [0..MaxBufSize-1] of Char;
Len: Integer;
begin
Buf := '';
Len := Recv(FCliSocket, Buf, MaxBufSize, 0); //******* Recv() Socket ********//
//Buf[Len] := #0;
if Command = 'SDSR' then //假如收到“SDSR”标记,则说明已经接收完所有发送的数据。
begin
FZipSize := StrToInt(Copy(Buf, CommandBit + 1, DataLenBit)); //这些字符串都能够正确取得,不要怀疑这里出错。
Messagebox(0, PChar(IntToStr(FZipSize)), AppName, + MB_ICONINFORMATION + MB_OK);
FRcvLen := Len;
GetMem(FBuffer, FZipSize);
FBuffer := Buf;
end else //没有收到“SDSR”标记,则说明正在接收发送的数据。
begin
FRcvLen := FRcvLen + Len;
Messagebox(0, PChar(IntToStr(FRcvLen)), AppName, + MB_ICONINFORMATION + MB_OK); //问题1:FRcvLen值越来越小,每次触发就减少一些,好象是没有规律地减少。
//if FRcvLen > FZipSize then raise Exception.Create();
StrCat(FBuffer, Buf); //将PChar:Buf添加到PChar:FBuffer中
Messagebox(0, PChar(IntToStr(StrLen(FBuffer))), AppName, + MB_ICONINFORMATION + MB_OK); //问题2:FBuffer内容也不断减少。
if FRcvLen = FZipSize then //接收完毕
begin
try
Dosomething; //***************//
finally
FZipSize := 0;
FRcvLen := 0;
FreeMem(FBuffer);
end;
end;
end;
end;

我已经搞了很多天都搞不定,试了很多方法都不行,我猜肯定是对非阻塞模式的原理不了解造成,请务必为我清楚解释一下。最好附上几行关键代码。
 
小弟不才,有几点疑问:
Count := StrToInt(Copy(Len, 1, 4); //取得传送过来的整个字符串的长度,被插在流的最前面。这一句可能有问题吧,语法也不通的,因为Len是整型。

一般而言,你说的这种情况是通过判断发送“结束符”来判断的,很少有通过长度来判断的,比如http协议的命令的结束是发送一个空行,建议你也通过此方法来尝试。或者发送eof。
 
还有分在http://www.delphibbs.com/delphibbs/modifyl.asp?lid=2253709
 
if Remain > MaxBufSize then //MaxBufSize=4096
if Send(CliSocket, Buffer[Count-Remain], MaxBufSize, 0) = SOCKET_ERROR then Continue //其实你要用Send以后的值来判断,因为其值真实为实际发送字符数量。

我改了一下:
{.............}
Var SendSize: Integer;
{.............}
if Remain > MaxBufSize then //MaxBufSize=4096
begin
SendSize:= Send(CliSocket, Buffer[Count-Remain], MaxBufSize, 0);
if SendSize = SOCKET_ERROR then Continue
end
else
begin
SendSize:= Send(CliSocket, Buffer[Count-Remain], Remain, 0);
if SendSize = SOCKET_ERROR then Continue;
end;
Remain := Remain - SendSize;
{...............}
稍稍改了一点,希望有所帮助,
呵呵~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
to Netsoft:
我看不出有什么不一样。另外我的问题不在这,是在客户端接收的数据不能汇总的问题。
如上面的注释(问题1、问题2)。
 
(1)FRcvLen的初始值是多少?是否为0?没找到初始化的语句;
(2)你在数据收完后才给FBuffer分配内存,而在之前StrCat(FBuffer, Buf)时FBuffer实际上未定义的;
(3)FBuffer := Buf不会将Buf的内容送到FBuffer里,而是一次指针赋值而已,这样前面的分配内存实际上没意义。
解决的办法是先发送和接收数据的长度,分配内存,然后接受数据。呵呵,记得处理完后释放内存哦!如果不注意,局部变量和指针未定义就使用的错误是很容易发生,而且难以发现,如果你的程序出现随机的结果,十有八九是这方面的原因。另外,我觉得你的程序编译时应该有警告的,是不是你把警告的级别设置得太低了?
 
在Create方法中已有初始化所有变量,但问题不在这里,主要是超大数据量传输出现问题,
超过最大接收缓冲区,多次触发FD_READ时间,而在该消息里读数据并累计却发现错误,不能正确接收到所有数据,看过别人的代码好象加了IOCtlSocket(),但我用了没用,主要还是不理解整个机制。
 
这错误和网络无关,你的错误是我上贴的(2),(1)只是一个潜在的错误!请仔细理解(2)和(3)的意思!
 
现在问题解决了一半,能够正确接收到超大数据,但必须在FD_READ时间的接收处理函数(或服务端发送完数据时),使用Messagebox之类的对话框,
才能接收到所有数据,否则只能接收到最后一个数据包,我在测试时用了Messagebox来检验接收的正确性,结果
得到正确的数据,我以为完全成功,随着真正用的时候去掉了Messagebox却出现错误,我以为是传输的延迟,但
用了Sleep(6000)和Application.HandleMessage都不行,我又用了一个写磁盘文件的语句确又能正确接收全部,这是什么怪问题?望赐教!
 
估计又是程序逻辑上的问题,请把代码贴上来看看。
 
好,等等!记得给我回复!
 
占贵宝地一用
http://www.delphibbs.com/delphibbs/dispq.asp?lid=2293137
 
Server端代码:

const
TCP_PORT = 6677;
WM_SOCK = WM_USER + 1; //自定义windows消息
MaxRecvBuf = 4096;
MaxSendBuf = 64 * 1024;
MaxDestSize = 64 * 1024;
MaxSourSize = 999424;
DataLenBit = 5; //服务端发送信息内容长度占用的位数
CommandBit = 4; //命令占用的位数
MsgHeadBit = CommandBit + DataLenBit; //客户端信息头位数,为命令位数+客户端发送的信息内容长度位数(4+5)

procedure GetScreen(DrawCur: Boolean); //取得本机屏幕
var
CursorX, CursorY: Integer;
DC: HDC;
MyCan: TCanvas;
R: TRect;
DrawPos: TPoint;
MyCursor: TIcon;
Hld: HWnd;
Threadld: Dword;
MP: TPoint;
IconInfo: TIconInfo;
begin
aBmp := TBitmap.Create; //建立BMPMAP
MyCan := TCanvas.Create; //屏幕截取
DC := GetWindowDC(0);
try
MyCan.Handle := DC;
R := Rect(0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
aBmp.PixelFormat := PF8bit;
aBmp.Width := R.Right;
aBmp.Height := R.Bottom;
aBmp.Canvas.CopyRect(R, MyCan, R);
finally
ReleaseDC(0, DC);
end;
MyCan.Handle := 0;
MyCan.Free;
if DrawCur then //画上鼠标图象
begin
GetCursorPos(DrawPos);
MyCursor := TIcon.Create;
GetCursorPos(MP);
Hld := WindowFromPoint(MP);
Threadld := GetWindowThreadProcessId(Hld, nil);
AttachThreadInput(GetCurrentThreadId, Threadld, True);
MyCursor.Handle := Getcursor();
AttachThreadInput(GetCurrentThreadId, Threadld, False);
GetIconInfo(MyCursor.Handle, IconInfo);
CursorX := DrawPos.X - Round(IconInfo.XHotsPot);
CursorY := DrawPos.Y - Round(IconInfo.YHotsPot);
aBmp.Canvas.Draw(CursorX, Cursory, MyCursor); //画上鼠标
DeleteObject(IconInfo.HbmColor); //GetIconInfo使用时创建了两个bitmap对象. 需要手工释放这两个对象
DeleteObject(IconInfo.HbmMask); //否则,调用他后,他会创建一个bitmap,多次调用会产生多个,直至资源耗尽
MyCursor.ReleaseHandle; //释放数组内存
MyCursor.Free; //释放鼠标指针
end;
end;

function MakeStream: Boolean;
const
BigDataLenBit = DataLenBit + 1; //BMP原流比压缩后传送给客户端的压缩流应该大1位(6Bit=百K)。
NewMsgHeadBit = CommandBit + BigDataLenBit + DataLenBit;
var
TempStream: TCompressionStream;
DestStream: TMemoryStream;
MsgHead: string;
SourLen, DestLen: Integer;
begin
Result := False;
try
//MessageBox(0, PChar(IntToStr(Stream.Size)), AppName, MB_ICONINFORMATION + MB_OK);
DestStream := TMemoryStream.Create;
TempStream := TCompressionStream.Create(clDefault, DestStream);
aBmp.SaveToStream(aStream);
SourLen := aStream.Size; //BMP流的原长度(一般为7位),在客户端解压时要用到.
//Messagebox(0, PChar(IntToStr(SourLen)), 'PServer:源', + MB_ICONINFORMATION + MB_OK);
aStream.SaveToStream(TempStream);
TempStream.Free;
aStream.Clear;
DestLen := NewMsgHeadBit + DestStream.Size; //压缩流总长度(一般为5位; 包括信息头位), 用于客户端的校验。
//Messagebox(0, PChar(IntToStr(DestLen)), 'PServer:压缩', + MB_ICONINFORMATION + MB_OK);
SetLength(MsgHead, NewMsgHeadBit); //由于BMP流较大,BigMsgHeadBit=7+4Bit=11Bit;
MsgHead := 'SDSR' + IntToStr(SourLen) + IntToStr(DestLen); //填充命令及源长度位数
aStream.WriteBuffer(MsgHead[1], Length(MsgHead));
//==========================================//
aStream.CopyFrom(DestStream, 0); //Stream.CopyFrom(DestStream)这里一个怪问题,必须在流的拷贝后到发送到客户端之前使用Messagebox,否则将只能传送部分数据,不知是什么原因(Sleep(5000)都没用)。
//==========================================//
Result := (DestLen < MaxDestSize) and (SourLen < MaxSourSize); //超过定义的最大流尺寸(5位和7位), 则取消发送(Result=False不发送).
//选择1:将此注释去掉可成功发送全部数据;Messagebox(0, PChar(IntToStr(aStream.Size)), 'PServer:压缩', + MB_ICONINFORMATION + MB_OK);
//==========================================//
finally
aBmp.ReleaseHandle;
DestStream.Free;
end;
end;

procedure SendScreen;
var Count, SndLen: Integer;
Buffer: PChar;
begin
GetScreen(True);
if not MakeStream then //*****生成屏幕流*****//
begin
aStream.Free;
aBmp.Free;
Exit;
end;
aStream.Position := 0;
Count := aStream.Size;
GetMem(Buffer, Count + 1);
try
aStream.ReadBuffer(Buffer^, Count);
SndLen := Send(CliSocket, Buffer^, Count, 0);
if (SndLen = SOCKET_ERROR) or (SndLen < Count) then
MessageBox(0, 'SendScreen Error!', AppName, MB_ICONINFORMATION + MB_OK);
finally
aStream.Clear;
FreeMem(Buffer);
end;
end;

procedure DispSockMessage(lParam: LPARAM); //响应自定义的“WM_SOCK”套接字消息。
var Event: Word;
AddrLen: Integer;
begin
Event := WSAGetSelectEvent(lParam);
case Event of
FD_ACCEPT:
begin
New(PCliAddr);
AddrLen := Sizeof(PCliAddr^);
CliSocket := Accept(SrvSocket, PCliAddr, @AddrLen); //第四步,等待接收,这一步与上三步有所不同,每次客户端请求连接都必须执行一次。
end;
FD_READ:
RecvData; //*****************//
FD_CLOSE:
CloseSocket(CliSocket); //Messagebox(0, 'FD_CLOSE', AppName, + MB_ICONINFORMATION + MB_OK);
end;
end;

procedure RecvData; //*************专门用来接收和发送数据的过程*************//
var Buf: array[0..MaxRecvBuf-1] of Char;
Command, Data: string;
CliSendLen, RecvLen: Integer;
begin
RecvLen := Recv(CliSocket, Buf, MaxRecvBuf, 0); //***********************//
Buf[RecvLen] := #0;
if RecvLen <= 0 then Exit;
CliSendLen := StrToInt(Copy(Buf, 1, DataLenBit)); //客户发送信息的总长度,这与服务端发往客户端的相反,数据位是在命令位前。
if CliSendLen <> RecvLen then Exit; //客户发送的数据长度(在客户端定义)不等于服务端本次接收(一次)的长度, 则退出。
Command := Copy(Buf, (DataLenBit + 1), CommandBit); //客户端发送何种命令
Data := Copy(Buf, (MsgHeadBit + 1), Length(Buf)) ; //前9个字符为客户端发送数据的信息头; 或: Data := Buf, Delete(Data, 1, MsgHeadBit).
DoSomething(Command, Data);
end;

function DoSomething(var Command: string; Data: string): Boolean;
begin
Result := False;
.......
if (Command = 'GTDK') then
SendScreen;
end;

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Client端代码:

const
TCP_PORT = 6677;
Version = 514;
WM_SOCK = WM_USER + 1; //自定义windows消息
AppName = 'Client';
MaxBufSize = 64 * 1024;
MaxMapSize = 64 * 1024; //允许的最大屏幕流尺寸
DataLenBit = 5; //发送到服务端的数据的长度占用的位数
CommandBit = 4; //命令占用的位数
BmpDataLenBit = DataLenBit + 1;
MsgHeadBit = CommandBit + DataLenBit; //服务端信息头位数,为命令位数+客户端发送的信息内容长度位数(4+5)
TempSendAddr = '127.0.0.1'; // '192.168.16.3';

procedure TForm1.RecvData(var Message: TMessage); //message WM_SOCK;
var Event: Word;
Len: LongWord;
Socket: TSocket;
begin
Event := WSAGetSelectEvent(Message.lParam);
case Event of
FD_CONNECT:
begin
.....
end;
FD_READ:
begin
Socket := Message.wParam;
while True do
begin
if IoctlSocket(Socket, FIONREAD, Len) = SOCKET_ERROR then
begin
MessageBox(0, 'IoctlSocket Error', AppName, MB_ICONINFORMATION + MB_OK);
Exit;
end;
if Len = 0 then Break;
ReadData(Socket); //*************接收数据****************//
end;
end;
FD_CLOSE:
begin
Messagebox(0, 'Server Closed!', AppName, + MB_ICONINFORMATION + MB_OK);
end;
end;
end;

procedure TForm1.ReadData(Socket: TSocket); //使用Message.wParam作为接收套接字,而不用Connect获得的套接字。
var Buf: array [0..MaxBufSize-1] of Char;
Len: Integer;
Command: string;
begin
Len := Recv(Socket, Buf, MaxBufSize, 0); //******* Recv(...) Socket ********//
Buf[Len] := #0; //不使用这行,则要在Recv之前加 Buf := ''.
//MessageBox(Handle, Buf, AppName, MB_ICONINFORMATION + MB_OK);
Command := Copy(Buf, 1, CommandBit); //命令都定义为4位
if Command = 'OPEN' then
...........
else begin
FStream.WriteBuffer(Buf, Len); //************* (替换测试) *************//
if Command = 'SDSR' then //根据先入后出(缓冲区)的原则,当接收到命令,即说明完成了一次完成的接收。
begin
//FStream.SaveToFile('C:/Dest.txt');
FBmpSize := StrToInt(Copy(Buf, CommandBit + 1, BmpDataLenBit));
//==========================================//
//选择2:将此注释去掉可成功发送全部数据; Messagebox(0, PChar(IntToStr(FBmpSize)), 'PClient:源', + MB_ICONINFORMATION + MB_OK);
FZipSize := StrToInt(Copy(Buf, CommandBit + BmpDataLenBit + 1, DataLenBit));
//Messagebox(0, PChar(IntToStr(FZipSize)), 'PClient:压缩', + MB_ICONINFORMATION + MB_OK);
//==========================================//
ResumeStream; //能够否接收到完整数据跟以下这两个函数无关,他们只是对存放着压缩的屏幕流FStream进行解压。
LoadScreen;
end;
end;
end;

(完)
######################################################################################
只要选择1或选择2的注释去掉,即使用Messagebox去显示一个对话框就能完整收到(从FStream中读取)所有数据,
否则只能收到好像是最后4380个字符。看表面好象是Messagebox延迟了一些时间,但用Sleep(60000)和Application.HandleMessage却都没有用。
不过将ReadData过程中的“替换测试”一行代码改成以下三行:
times := times+1;
FStream.WriteBuffer(Buf, Len);
FStream.SaveToFile('C:/' + inttostr(times)+'.txt');
却又能完整收到所有数据,这是否是写磁盘时延迟了一些时间,所以能完整的接收到所有数据。
 
我拿下去先看看,大家共同研究。
 
你程序里的问题主要是对指针的应用有误,具体如下:
(1) SndLen := Send(CliSocket, Buffer^, Count, 0);这一句发送的根本不是你需要的数据,应改为 SndLen := Send(CliSocket, Buffer, Count, 0);
(2)if IoctlSocket(Socket, FIONREAD, Len) = SOCKET_ERROR then...这一句也不对,没非法操作算你运气,应该为if IoctlSocket(Socket, FIONREAD, @Len) = SOCKET_ERROR then...
Len := Recv(Socket, Buf, MaxBufSize, 0); 错误和以上一样,应为:Len := Recv(Socket, @Buf, MaxBufSize, 0);
(3)if Command = 'SDSR' then //根据先入后出(缓冲区)的原则,当接收到命令,即说明完成了一次完成的接收。 ???谁规定的先入后出???
MsgHead := 'SDSR' + IntToStr(SourLen) + IntToStr(DestLen); //填充命令及源长度位数
aStream.WriteBuffer(MsgHead[1], Length(MsgHead));
aStream.CopyFrom(DestStream, 0);
很明显信息头在前面,那么先收到的也是信息头,此时数据才刚开始!

你在做远程控制或监视之类的东西吧?程序里有很多可以简化的地方,干嘛搞得那么复杂?

 
后退
顶部