我写的一个客户端收发主线程。刚开始线程可以正常收发,
但在有的电脑上运行一段时间后只能发不能收。
服务端是使用完成端口完成的,也可以正常给客户端发送数据。
以下是我的主线程,代码,请高手指点指点。
拜。Unit u_MainThread;interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, SyncObjs, ExtCtrls,shareunit,IOCPComp, ScktComp,WinSock2;type
TRecMsgEvent = procedure(aRecStr: string) of object;
TClientThread=class(TThread)
private
FHost: string;
FAddMsg: TRecMsgEvent;
FSocket: TClientWinSocket;
LastRecvTest: TDateTime; //最后收到测试字符串的时间
protected
procedure Execute; override;
public
constructor Create(AHost: string; AAddMsg: TRecMsgEvent);
destructor Destroy; override;
end;
{ TClient }
implementation
uses u_wy108ClientMain;
constructor TClientThread.Create;
begin
FreeOnTerminate := True;
inherited Create(False);
LastRecvTest := now();
FHost := AHost;
FAddMsg := AAddMsg;
FSocket := TClientWinSocket.Create(Integer(not(0)));
//FSocket.ClientType := ctNonBlocking;
//采用阻塞模式
FSocket.ClientType := ctBlocking;
end;destructor TClientThread.Destroy;
begin
FSocket.Free;
inherited Destroy;
end;procedure TClientThread.Execute;
const
SizeInt = SizeOf(Integer);
SizeBlock = SizeOf(TDataBlock);
Data: TDataBlock = (len: 14; Content: 'Testpiaoxuesky');
//检查网络异常过程
function IsClose(socket, event: Cardinal): Boolean;
var
Network: TWSANetworkEvents;
begin
Result := True;
FillChar(Network, SizeOf(Network), 0);
if WSAEnumNetworkEvents(FSocket.SocketHandle, Event, @Network) = -1 then Exit;
{ Close 消息 }
Result := ((Network.lNetworkEvents and FD_CLOSE) = FD_CLOSE) and
(Network.iErrorCode[FD_CLOSE_BIT] <> 0);
end;
var
msg: TMsg; //线程消息
D: TDataBlock; //接收数组
TimeOut, RetLen: Integer; //超时设置 接收数组大小
Event: THandle; //事件句柄
Conn: TDataBlock ; //发送数组
HadSendConn: boolean; //是否已经登陆
aSendMsg: string; //发送字串
Count: integer; //发送数组大小
begin
try
FSocket.Open(FHost, FHost, '', 8309); //连接服务端
Timeout := 2000; //2秒超时
HadSendConn := false;
setsockopt(FSocket.SocketHandle, SOL_SOCKET, SO_RCVTIMEO, @Timeout, SizeOf(Timeout));
except
Exit;
end;
PeekMessage(msg, 0, 0, 0, PM_NOREMOVE);
Event := WSACreateEvent; //创建事件句柄
try
WSAEventSelect(FSocket.SocketHandle, Event, FD_READ or FD_CLOSE);
while (not Terminated) and (FSocket.Connected=True) do
case MsgWaitForMultipleObjects(1, Event, False, 500, QS_ALLINPUT) of
//接收网络消息部分
WAIT_OBJECT_0:
begin
if IsClose(FSocket.SocketHandle, Event) then
begin
{ 'server close' ; }
Break;
end;
//初始化接收数组
FillChar(D, SizeBlock, 0);
RetLen := FSocket.ReceiveBuf(D.Len, SizeInt);
if RetLen = 0 then
Break;
if RetLen <> SizeInt then
begin
Continue;
end;
RetLen := FSocket.ReceiveBuf(D.Content, D.Len);
if RetLen <> D.Len then
begin
Continue;
end;
//不合规格接收字串丢失
if (Copy(D.Content,1,4)<>'Succ') and (Copy(D.Content,1,4)<>'News')
and (Copy(D.Content,1,4)<>'HQSX') and (Copy(D.Content,1,4)<>'GPSX')
and (Copy(D.Content,1,4)<>'MMSX') and (Copy(D.Content,1,4)<>'GDGG')
and (Copy(D.Content,1,4)<>'Succ') and (Copy(D.Content,1,4)<>'LScc')
and (Copy(D.Content,1,4)<>'LDis') and (Copy(D.Content,1,4)<>'LHaL')
and (Copy(D.Content,1,4)<>'OOut') and (Copy(D.Content,1,4)<>'Trys')
and (Copy(D.Content,1,4)<>'Upda') and (Copy(D.Content,1,4)<>'Test')
and (Copy(D.Content,1,4)<>'Hell')
then
begin
Continue;
end;
//测试字串
if (Copy(D.Content,1,4)='Test') or (Copy(D.Content,1,4)='Hell') then
begin
//记录最后一个接收到的测试字串事件
LastRecvTest := now();
//WriteErrorLog('Last Rec Test Message->'+D.Content);
Continue;
end;
EnterCriticalSection(Critical1);//进入临界段
//给一个全局变量赋值,因为在主界面接收到WM_GETMES消息后需要读取ClientRecvMsg内容。
//所以我在这里做了同步操作。
ClientRecvMsg := '';
ClientRecvMsg := D.Content;
LeaveCriticalSection(Critical1);//退出临界段
//发送消息刷新界面元素
PostMessage(frm_wyClientMain.Handle, WM_GETMES, 0, 0);
//重置事件句柄
WSAResetEvent(Event);
end;
WAIT_OBJECT_0 + 1:
begin
//发送网络消息部分
if PeekMessage(msg, 0, 0, 0, PM_REMOVE) then
begin
case msg.message of
WM_USER:
begin
//初始化发送消息字串
aSendMsg := ClientSendMsg;
FillChar(Conn, SizeOf(Conn), 0);
Conn.Len := Length(aSendMsg);
StrPCopy(Conn.Content, aSendMsg);
Count := SizeOf(Integer) + Length(aSendMsg);
//发送自定义消息 如 登陆,注销等
FSocket.SendBuf((@Conn)^, Count);
end;
WM_CLOSE:
begin
//发送客户端正常关闭消息
aSendMsg := 'OOut$%^&';
FillChar(Conn, SizeOf(Conn), 0);
Conn.Len := Length(aSendMsg);
StrPCopy(Conn.Content, aSendMsg);
Count := SizeOf(Integer) + Length(aSendMsg);
//正常退出,服务端会对数据库做相应操作
FSocket.SendBuf((@Conn)^,Count);
//退出线程
break;
end;
end;
end;
end;
WAIT_TIMEOUT:
begin
//线程启动时发送连接消息
if HadSendConn = false then
begin
FillChar(Conn, SizeOf(Conn), 0);
Conn.Len := Length(ConnStr);
StrPCopy(Conn.Content, ConnStr);
Count := SizeOf(Integer) + Length(ConnStr);
FSocket.SendBuf((@Conn)^,Count);
//置已连接标志为真
HadSendConn := true;
end;
ss := ForMatDateTime('hh:mm:ss',(Now()-LastRecvTest));
if (Now()-LastRecvTest)>6/24*60 then
begin
Break;
end;
end;
else
begin
WSAResetEvent(Event);
//如果当前事件减去最后接收到测试消息的时间超过6分钟则线程退出
if (Now()-LastRecvTest)>6/24*60 then
begin
Break;
end;
end;
end;
finally
WSACloseEvent(Event);
try
FSocket.Close;
except
on e:Exception do
WriteErrorLog('Close Sokcet Error->'+e.Message);
end;
end;
end;
end.
但在有的电脑上运行一段时间后只能发不能收。
服务端是使用完成端口完成的,也可以正常给客户端发送数据。
以下是我的主线程,代码,请高手指点指点。
拜。Unit u_MainThread;interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, SyncObjs, ExtCtrls,shareunit,IOCPComp, ScktComp,WinSock2;type
TRecMsgEvent = procedure(aRecStr: string) of object;
TClientThread=class(TThread)
private
FHost: string;
FAddMsg: TRecMsgEvent;
FSocket: TClientWinSocket;
LastRecvTest: TDateTime; //最后收到测试字符串的时间
protected
procedure Execute; override;
public
constructor Create(AHost: string; AAddMsg: TRecMsgEvent);
destructor Destroy; override;
end;
{ TClient }
implementation
uses u_wy108ClientMain;
constructor TClientThread.Create;
begin
FreeOnTerminate := True;
inherited Create(False);
LastRecvTest := now();
FHost := AHost;
FAddMsg := AAddMsg;
FSocket := TClientWinSocket.Create(Integer(not(0)));
//FSocket.ClientType := ctNonBlocking;
//采用阻塞模式
FSocket.ClientType := ctBlocking;
end;destructor TClientThread.Destroy;
begin
FSocket.Free;
inherited Destroy;
end;procedure TClientThread.Execute;
const
SizeInt = SizeOf(Integer);
SizeBlock = SizeOf(TDataBlock);
Data: TDataBlock = (len: 14; Content: 'Testpiaoxuesky');
//检查网络异常过程
function IsClose(socket, event: Cardinal): Boolean;
var
Network: TWSANetworkEvents;
begin
Result := True;
FillChar(Network, SizeOf(Network), 0);
if WSAEnumNetworkEvents(FSocket.SocketHandle, Event, @Network) = -1 then Exit;
{ Close 消息 }
Result := ((Network.lNetworkEvents and FD_CLOSE) = FD_CLOSE) and
(Network.iErrorCode[FD_CLOSE_BIT] <> 0);
end;
var
msg: TMsg; //线程消息
D: TDataBlock; //接收数组
TimeOut, RetLen: Integer; //超时设置 接收数组大小
Event: THandle; //事件句柄
Conn: TDataBlock ; //发送数组
HadSendConn: boolean; //是否已经登陆
aSendMsg: string; //发送字串
Count: integer; //发送数组大小
begin
try
FSocket.Open(FHost, FHost, '', 8309); //连接服务端
Timeout := 2000; //2秒超时
HadSendConn := false;
setsockopt(FSocket.SocketHandle, SOL_SOCKET, SO_RCVTIMEO, @Timeout, SizeOf(Timeout));
except
Exit;
end;
PeekMessage(msg, 0, 0, 0, PM_NOREMOVE);
Event := WSACreateEvent; //创建事件句柄
try
WSAEventSelect(FSocket.SocketHandle, Event, FD_READ or FD_CLOSE);
while (not Terminated) and (FSocket.Connected=True) do
case MsgWaitForMultipleObjects(1, Event, False, 500, QS_ALLINPUT) of
//接收网络消息部分
WAIT_OBJECT_0:
begin
if IsClose(FSocket.SocketHandle, Event) then
begin
{ 'server close' ; }
Break;
end;
//初始化接收数组
FillChar(D, SizeBlock, 0);
RetLen := FSocket.ReceiveBuf(D.Len, SizeInt);
if RetLen = 0 then
Break;
if RetLen <> SizeInt then
begin
Continue;
end;
RetLen := FSocket.ReceiveBuf(D.Content, D.Len);
if RetLen <> D.Len then
begin
Continue;
end;
//不合规格接收字串丢失
if (Copy(D.Content,1,4)<>'Succ') and (Copy(D.Content,1,4)<>'News')
and (Copy(D.Content,1,4)<>'HQSX') and (Copy(D.Content,1,4)<>'GPSX')
and (Copy(D.Content,1,4)<>'MMSX') and (Copy(D.Content,1,4)<>'GDGG')
and (Copy(D.Content,1,4)<>'Succ') and (Copy(D.Content,1,4)<>'LScc')
and (Copy(D.Content,1,4)<>'LDis') and (Copy(D.Content,1,4)<>'LHaL')
and (Copy(D.Content,1,4)<>'OOut') and (Copy(D.Content,1,4)<>'Trys')
and (Copy(D.Content,1,4)<>'Upda') and (Copy(D.Content,1,4)<>'Test')
and (Copy(D.Content,1,4)<>'Hell')
then
begin
Continue;
end;
//测试字串
if (Copy(D.Content,1,4)='Test') or (Copy(D.Content,1,4)='Hell') then
begin
//记录最后一个接收到的测试字串事件
LastRecvTest := now();
//WriteErrorLog('Last Rec Test Message->'+D.Content);
Continue;
end;
EnterCriticalSection(Critical1);//进入临界段
//给一个全局变量赋值,因为在主界面接收到WM_GETMES消息后需要读取ClientRecvMsg内容。
//所以我在这里做了同步操作。
ClientRecvMsg := '';
ClientRecvMsg := D.Content;
LeaveCriticalSection(Critical1);//退出临界段
//发送消息刷新界面元素
PostMessage(frm_wyClientMain.Handle, WM_GETMES, 0, 0);
//重置事件句柄
WSAResetEvent(Event);
end;
WAIT_OBJECT_0 + 1:
begin
//发送网络消息部分
if PeekMessage(msg, 0, 0, 0, PM_REMOVE) then
begin
case msg.message of
WM_USER:
begin
//初始化发送消息字串
aSendMsg := ClientSendMsg;
FillChar(Conn, SizeOf(Conn), 0);
Conn.Len := Length(aSendMsg);
StrPCopy(Conn.Content, aSendMsg);
Count := SizeOf(Integer) + Length(aSendMsg);
//发送自定义消息 如 登陆,注销等
FSocket.SendBuf((@Conn)^, Count);
end;
WM_CLOSE:
begin
//发送客户端正常关闭消息
aSendMsg := 'OOut$%^&';
FillChar(Conn, SizeOf(Conn), 0);
Conn.Len := Length(aSendMsg);
StrPCopy(Conn.Content, aSendMsg);
Count := SizeOf(Integer) + Length(aSendMsg);
//正常退出,服务端会对数据库做相应操作
FSocket.SendBuf((@Conn)^,Count);
//退出线程
break;
end;
end;
end;
end;
WAIT_TIMEOUT:
begin
//线程启动时发送连接消息
if HadSendConn = false then
begin
FillChar(Conn, SizeOf(Conn), 0);
Conn.Len := Length(ConnStr);
StrPCopy(Conn.Content, ConnStr);
Count := SizeOf(Integer) + Length(ConnStr);
FSocket.SendBuf((@Conn)^,Count);
//置已连接标志为真
HadSendConn := true;
end;
ss := ForMatDateTime('hh:mm:ss',(Now()-LastRecvTest));
if (Now()-LastRecvTest)>6/24*60 then
begin
Break;
end;
end;
else
begin
WSAResetEvent(Event);
//如果当前事件减去最后接收到测试消息的时间超过6分钟则线程退出
if (Now()-LastRecvTest)>6/24*60 then
begin
Break;
end;
end;
end;
finally
WSACloseEvent(Event);
try
FSocket.Close;
except
on e:Exception do
WriteErrorLog('Close Sokcet Error->'+e.Message);
end;
end;
end;
end.
楼主大部分都用API实现,那为什么还要用TClientWinSocket?这样子很难确实两者会不会产生冲突。造成事件或者状态的丢失。
我的界面操作几乎没有。只有一个链接,登录和注销的动作。其余没有操作的。
还有ClientRecvMsg使用我想不是很好。不过我不知道用什么办法把线程接受到的字符串往外传。
或许我只能在线程内部做分析,然后给主界面发一个刷新消息。请师兄们再指点指点。拜。