最近,我怎么也连不上大富翁,本来五一就写好了这个贴子,直到现在才贴上来。
这两天,过节有了充裕时间来研究这个问题,我又仔细地分析了twinsocketstream.write
函数的源代码,发现它的代码与twinsocketstream.read函数的代相似,但是write与read
相比在GetOverlappedResult(FSocket.SocketHandle, Overlapped, Integer(Result), false);语
句后少了一个fevent.resetevent;语句。这个语句是用来把时间event设为不发信号,阻止
与此事件有关的线程(即writefile函数)运行,据我所知,winsocket自己不负责处理
多线程之间的重入,这必须由程序员自己解决,如果write函数没有fevent.resetevent ,
那么当我的程序继续对socket进行操作,例如读socket时,writefile函数也同时在对
socket进行写操作,这就发生了重入,从而使得winsocket崩溃,即使能关闭有问题的
程序,winsocket也无法使用了,除非重启。
这种分析能够解释我在编程中碰到的现象。
我把fevent.resetevent加到write函数中去,用我编的email程序发送几个长达1M的附件,
发现在发送一个附件时,程序能够正常运行,当我的程序创建了四个线程,同时发送四
个附件时,老问题又出来了。看来真是好事多磨,我发现
if FEvent.WaitFor(FTimeOut) <> wrSignaled then
Result := 0;
也有上述的问题,我在程序中设定的ftimeout是2000,即2秒,当我同时发送多个附
件时,socket有些繁忙,2秒过后可能writefile还没有完成操作,这个判断就成立了,
但是这里面少了fevent.resetevent,于是用样的问题就又出来了。解决的办法是,加入
fevent.resetevent。不过如果想让程序正常运行必须加大ftimeout值,这个值在编程时应
预留接口,让使用者根据网络情况自己设定。
综上所述,delphi3/cs在此有bug ,不知delphi4.0是否加以了改进。 另外,我认为这里
虽然使用了writefile的异步功能,但是并没有充分利用异步特性,而只是简单地有waitfor
进行等待,这与twinsocketstream本身是阻塞的socket有关,这里使用了异步写操作唯
一的作用是引入了timeout, 避免因网络阻塞writefile长时间无法返回,给程序一个撤
销写操作的机会。
最后我把修改后的write 与read函数附在这里,请大家参考。
谈了这么多,不知道自己对不对,请众位高手指教。
function TWinSocketStream.Read(var Buffer; Count: Longint): Longint;
var
Overlapped: TOverlapped;
ErrorCode: Integer;
begin
FSocket.Lock;
try
FillChar(OVerlapped, SizeOf(Overlapped), 0);
Overlapped.hEvent := FEvent.Handle;
if not ReadFile(FSocket.SocketHandle, Buffer, Count, Integer(Result),
@Overlapped) and (GetLastError <> ERROR_IO_PENDING) then
begin
ErrorCode := GetLastError;
raise ESocketError.CreateFmt(sSocketIOError, [sSocketRead, ErrorCode,
SysErrorMessage(ErrorCode)]);
end;
if FEvent.WaitFor(FTimeOut) <> wrSignaled then
begin
Result := 0;
FEvent.ResetEvent;
end
else
begin
GetOverlappedResult(FSocket.SocketHandle, Overlapped, Integer(Result), False);
FEvent.ResetEvent;
end;
finally
FSocket.Unlock;
end;
end;
function TWinSocketStream.Write(const Buffer; Count: Longint): Longint;
var
Overlapped: TOverlapped;
ErrorCode: Integer;
begin
FSocket.Lock;
try
FillChar(OVerlapped, SizeOf(Overlapped), 0);
Overlapped.hEvent := FEvent.Handle;
if not WriteFile(FSocket.SocketHandle, Buffer, Count, Integer(Result),
@Overlapped) and (GetLastError <> ERROR_IO_PENDING) then
begin
ErrorCode := GetLastError;
raise ESocketError.CreateFmt(sSocketIOError, [sSocketWrite, ErrorCode,
SysErrorMessage(ErrorCode)]);
end;
if FEvent.WaitFor(FTimeOut) <> wrSignaled then
begin
Result := 0;
fevent.resetevent;
end
else
begin
GetOverlappedResult(FSocket.SocketHandle, Overlapped, Integer(Result),false);
FEvent.ResetEvent;
end;
finally
FSocket.Unlock;
end;
end;