我写的一个客户端收发主线程。刚开始线程可以正常收发,
但在有的电脑上运行一段时间后只能发不能收。
服务端是使用完成端口完成的,也可以正常给客户端发送数据。
以下是我的主线程,代码,请高手指点指点。
拜。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.

解决方案 »

  1.   

    请使用代码格式化,这样看着很累的。
    楼主大部分都用API实现,那为什么还要用TClientWinSocket?这样子很难确实两者会不会产生冲突。造成事件或者状态的丢失。
      

  2.   

    另外ClientRecvMsg的使用是有问题的。很可能造成前一个没有处理,后一个又过来覆盖了,假如网络通畅的时候,而界面上操作比较多,到达主线的消息比较多。
      

  3.   

    TThread的同步函数,可以用来显示。
      

  4.   

    谢谢僵哥。。
    我的界面操作几乎没有。只有一个链接,登录和注销的动作。其余没有操作的。
    还有ClientRecvMsg使用我想不是很好。不过我不知道用什么办法把线程接受到的字符串往外传。
    或许我只能在线程内部做分析,然后给主界面发一个刷新消息。请师兄们再指点指点。拜。
      

  5.   

    线程类添加一个私有(private)成员MessageString,再添加一个没有参数的私有方法,里面就只是执行显示界面的代码,信息从MessageString取,不需要临界区,只需要在线程当中调用该方法时使用Synchronize.比如该方法叫做SyncMessage,调用代码就是Synchronize(SyncMessage).