在TServerSokcet中调用SendBuf发送4000的数据包,在TClientSocket的ClientSocketRead事件中用Socket.ReceiveBuf接收数据包,然后用日志记录,在日志中看到发送的记录与接收的记录不一致,会有几个数据包接收不到,我用的是TCP方式,不应该会经常丢失(功能 :从一台电脑读文件到别一台电脑),请高手指教....

解决方案 »

  1.   

    我也遇到同样的问题,不知道是怎么回事,感觉TClientSocket的ClientSocketRead不怎么可靠
      

  2.   

    bds已经没有这控件了,可能是它自己也觉得不可靠吧
      

  3.   

    ReceiveBuf并不一定能一次性接收完整的数据。
    在调用该函数前,先通过Socket.ReceiveLength获取数据长度,然后检查ReceiveBuf函数的返回值,这个返回值表示该函数实际接收了的数据。如果返回值小于Socket.ReceiveLength获取的数据长度,则需要再次调用ReceiveBuf函数,直至所有数据全部获取。例如:
    procedure TForm1.SocketRead(Sender: TObject; Socket: TCustomWinSocket);
    var
      iLength   : Integer;
      iReceived : Integer;
      bBuffer   : array of Byte;
    begin
      iLength := Socket.ReceiveLength;
      while iLength > 0 do
      begin
        SetLength (bBuffer, iLength);
        iReceived := Socket.ReceiveBuf(bBuffer[0], iLength);
        //处理数据
        ......
        iLength := iLength - iReceived;
      end;
      bBuffer := nil;
    end;
      

  4.   

    接收到的数据保存在缓冲区里,ReceiveLength就是缓冲区里接收到的数据的长度
      

  5.   

    jadeluo(秀峰)的解答很详细,同意
      

  6.   

    你要确认你使用的是blocking方式还是其他。
    一般服务器为threadblocking,客户端为blocking。
      

  7.   

    提点不同意见
    jadeluo(秀峰)的回答我感觉不是最佳的,因为ReceiveLength有时候并不准确,建议你最好先把文件的大小传过来,或者把每次发送的数据包大小先传过来。这样才能保证完整的收到发送的数据。
      

  8.   

    我的代码并不是用来解决“如何接收一个完整的应用层通讯协议包“的。
    这段代码解决的问题是“ReceiveBuf并不一定能一次性接收完整的数据”,代码所给出的方法能够正确、完整地接收并处理已经到达的数据(不是通讯协议包)。
    再把代码补充得更详细些:
    procedure TForm1.SocketRead(Sender: TObject; Socket: TCustomWinSocket);
    var
      iLength   : Integer;
      iReceived : Integer;
      bBuffer   : array of Byte;
      iLoop     : Integer;
    begin
      iLength := Socket.ReceiveLength;
      while iLength > 0 do
      begin
        SetLength (bBuffer, iLength);
        iReceived := Socket.ReceiveBuf(bBuffer[0], iLength);
        //处理数据(下面的代码需要按照通讯协议来对已经接收的数据进行处理)
        for iLoop := 0 to iReceived - 1 do
        begin
          ......
        end;
        ......
        iLength := iLength - iReceived;
      end;
      bBuffer := nil;
    end;
      

  9.   

    到达的数据并不一定就是完整的发送的数据。
    我无意与你争论,只是想说明这个问题。如果想完整的接收到另一端发送的数据,用ReceiveLength是不可靠的
      

  10.   

    我也遇到ServerSocket, ClientSocket快速发送丢包的情况。
      

  11.   

    用阻塞模式传送,局域网速度达到10M左右发送端:
    unit UniClient;interfaceuses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, ScktComp, StdCtrls, shlobj, ActiveX;type
      TForm1 = class(TForm)
        Button1: TButton;
        ClientSocket1: TClientSocket;
        OpenDialog1: TOpenDialog;
        Edit1: TEdit;
        CSocket_Cmd: TClientSocket;
        Memo1: TMemo;
        Button2: TButton;
        Memo2: TMemo;
        procedure Button1Click(Sender: TObject);
        procedure Button2Click(Sender: TObject);
        procedure FormCreate(Sender: TObject);
        procedure FormClose(Sender: TObject; var Action: TCloseAction);
      private
        { Private declarations }
      public
        { Public declarations }
      end;var
      Form1: TForm1;
      Path: string; //起始路径
      IsClose: Boolean;
    implementation{$R *.dfm}function BrowseCallbackProc(hwnd: HWND; uMsg: UINT; lParam: Cardinal; lpData: Cardinal): integer; stdcall;
    begin
      if uMsg = BFFM_INITIALIZED then
        result := SendMessage(Hwnd, BFFM_SETSELECTION, Ord(TRUE), Longint(PChar(Path)))
      else
        result := 1
    end;function SelDir(const Caption: string; const Root: WideString; out Directory: string): Boolean;
    var
      WindowList: Pointer;
      BrowseInfo: TBrowseInfo;
      Buffer: PChar;
      RootItemIDList, ItemIDList: PItemIDList;
      ShellMalloc: IMalloc;
      IDesktopFolder: IShellFolder;
      Eaten, Flags: LongWord;
    begin
      Result := False;
      Directory := '';
      FillChar(BrowseInfo, SizeOf(BrowseInfo), 0);
      if (ShGetMalloc(ShellMalloc) = S_OK) and (ShellMalloc <> nil) then
      begin
        Buffer := ShellMalloc.Alloc(MAX_PATH);
        try
          RootItemIDList := nil;
          if Root <> '' then begin
            SHGetDesktopFolder(IDesktopFolder);
            IDesktopFolder.ParseDisplayName(Application.Handle, nil, POleStr(Root), Eaten, RootItemIDList, Flags);
          end;
          with BrowseInfo do begin
            hwndOwner := Application.Handle;
            pidlRoot := RootItemIDList;
            pszDisplayName := Buffer;
            lpszTitle := PChar(Caption);
            ulFlags := BIF_RETURNONLYFSDIRS;
            lpfn := @BrowseCallbackProc;
            lParam := BFFM_INITIALIZED;
          end;
          WindowList := DisableTaskWindows(0);
          try
            ItemIDList := ShBrowseForFolder(BrowseInfo);
          finally
            EnableTaskWindows(WindowList);
          end;
          Result := ItemIDList <> nil;
          if Result then begin
            ShGetPathFromIDList(ItemIDList, Buffer);
            ShellMalloc.Free(ItemIDList);
            Directory := Buffer;
          end;
        finally
          ShellMalloc.Free(Buffer);
        end;
      end;
    end;procedure Delay(dwMilliseconds: Longint);
    var
      iStart, iStop: DWORD;
    begin
      iStart := GetTickCount;
      repeat
        iStop := GetTickCount;
        Application.ProcessMessages;
      until (iStop - iStart) >= dwMilliseconds;
    end;function SubStr(Str: string; FindStr: string; Index: Integer): string;
    var
      i, x: Integer;
    begin
      Result := '';
      i := Pos(FindStr, Str);
      x := 0;
      while i > 0 do
      begin
        Inc(x);
        if x = Index then
        begin
          Result := Trim(Copy(Str, 1, i - 1));
          Exit;
        end;
        Delete(str, 1, i + length(FindStr) - 1);
        i := Pos(FindStr, Str);
      end;
      if (x + 1 = Index) and (str <> '') then
        Result := Trim(Str);end;function GetFileSize(const FileName: string): LongInt;
    var
      SearchRec: TSearchRec;
    begin
      try
        if FindFirst(ExpandFileName(FileName), faAnyFile, SearchRec) = 0 then
          Result := SearchRec.Size
        else Result := -1;
      finally
        SysUtils.FindClose(SearchRec);
      end;
    end;procedure GetAllFile(FileList: TStrings; const Path: string; FileEx: string = '*.*');
    var
      f: TSearchRec;
      Ret: Integer;
      p: string;
    begin
      FileEx := UpperCase(FileEx);
      Ret := FindFirst(Path + '\*.*', faAnyFile, f);
      while Ret = 0 do
      begin
        if f.Attr = faDirectory then
        begin
          if (f.Name <> '.') and (f.Name <> '..') then
            GetAllFile(FileList, Path + '\' + f.Name)
        end
        else
          if (FileEx = '*.*') or (UpperCase(ExtractFileExt(f.Name)) = FileEx) then
          begin
            p := Path + '\' + f.Name;
            FileList.Append(IntToStr(GetFileSize(p)) + '|' + p);
          end;
        Ret := FindNext(f)
      end;
      FindClose(f)
    end;function ReadReceiveText(Socket: TCustomWinSocket; TimeOut: Longint = 60000): string;
    var
      StartTick, CurrentTick: Integer;
    begin
      StartTick := GetTickCount;
      CurrentTick := GetTickCount;
      while (CurrentTick - StartTick < TimeOut) and
        (not IsClose) do
      begin
        Result := Socket.ReceiveText;
        if Result <> '' then
          break
        else
        begin
          Delay(100);
          CurrentTick := GetTickCount;
        end;
      end;end;procedure TForm1.Button1Click(Sender: TObject);
    var
      s: TWinSocketStream;
      f: TFileStream;
      i, x: integer;
      m: TMemoryStream;
      fileName, ReceiveText, str, str2: string;
      FileList: TStringList;
    begin
    //  if not OpenDialog1.Execute then Exit;
    //去掉无用的路径,用于传送到接收端
      FileList := TStringList.Create;
      FileList.Assign(Memo1.Lines);
      x := Length(IncludeTrailingBackslash(Path));
      for i := 0 to FileList.Count - 1 do
      begin
        str := SubStr(FileList[i], '|', 1);
        str2 := SubStr(FileList[i], '|', 2);
        Delete(str2, 1, x);
        FileList[i] := str + '|' + str2;
      end;
      ClientSocket1.Address := Edit1.Text;
      ClientSocket1.Port := 1234;
      ClientSocket1.Open;
      s := TWinSocketStream.Create(ClientSocket1.Socket, 60000);
      try
        m := TMemoryStream.Create;
        FileList.SaveToStream(m);
        s.CopyFrom(m, 0);
        str := #123#124'filelist_send_over'#123#124;
        s.WriteBuffer(str[1], Length(str));
    //    sleep(2000);
        ReceiveText := ReadReceiveText(ClientSocket1.Socket);
    //    showmessage(ReceiveText);//开始发送文件
        FileList.Assign(Memo1.Lines);
        i := 0;
        while True do
    //    for i := 0 to Memo1.Lines.Count - 1 do
        begin      fileName := SubStr(FileList[i], '|', 2);
          f := TFileStream.Create(FileName, fmShareDenyWrite);
          s.CopyFrom(f, 0);
          f.Free;
          Memo2.Lines.Add(FileList[i]);
          ReceiveText := ReadReceiveText(ClientSocket1.Socket);
          inc(i);
          if i >= FileList.Count then
            Break;
        end;  finally
        s.Free;
    //    f.Free;
        ClientSocket1.Close;
      end;
    end;procedure TForm1.Button2Click(Sender: TObject);
    var
      Path1: string;
    begin
      Path := Edit1.Text;
      if SelDir('SelectDirectory Sample', '', Path) then
      begin
        GetAllFile(Memo1.Lines, Path);
      end;
    end;procedure TForm1.FormCreate(Sender: TObject);
    begin
      IsClose := False;
    end;procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
    begin
      IsClose := True;
    end;end.
      

  12.   

    接收端:
    unit Uniserver;interfaceuses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, ScktComp;type
      TClientThread = class(TServerClientThread)
      private
      public
        procedure ClientExecute; override;
      end;
    type
      TForm1 = class(TForm)
        ServerSocket1: TServerSocket;
        procedure ServerSocket1GetThread(Sender: TObject;
          ClientSocket: TServerClientWinSocket;
          var SocketThread: TServerClientThread);
        procedure FormCreate(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
      end;var
      Form1: TForm1;implementation{$R *.dfm}{ TClientThread }function SubStr(Str: string; FindStr: string; Index: Integer): string;
    var
      i, x: Integer;
    begin
      Result := '';
      i := Pos(FindStr, Str);
      x := 0;
      while i > 0 do
      begin
        Inc(x);
        if x = Index then
        begin
          Result := Trim(Copy(Str, 1, i - 1));
          Exit;
        end;
        Delete(str, 1, i + length(FindStr) - 1);
        i := Pos(FindStr, Str);
      end;
      if (x + 1 = Index) and (str <> '') then
        Result := Trim(Str);end;procedure TClientThread.ClientExecute;
    type
      TFileInf = record
        Name: string;
        Size: Longint;
      end;
    const
      filelist_send_over = #123#124'filelist_send_over'#123#124;var
      ReceiveBuffer: array[0..4095] of Char;
      SocketStream: TWinSocketStream;
      BytesRead: Integer;
      F: TFileStream;
      FileList: TStringList;
      i: integer;
      CurrentFile: TFileInf;
      Path, CurrentPath, Str: string;
    begin
      while not Terminated and ClientSocket.Connected do
      begin
        try
          SocketStream := TWinSocketStream.Create(ClientSocket, 60000);
          FileList := TStringList.Create;//      F := TFileStream.Create('c:\afile.exe', fmCreate);
          try
            FillChar(ReceiveBuffer, 10, 0);
            if SocketStream.WaitForData(5000) then
              repeat
                BytesRead := SocketStream.Read(ReceiveBuffer, SizeOf(ReceiveBuffer));
                if BytesRead = 0 then
                begin
    //              ClientSocket.Close
                  break;
                end
                else
                begin
                  FileList.Text := FileList.Text + ReceiveBuffer;
                end;
              until not SocketStream.WaitForData(2000);        i := Pos(filelist_send_over, FileList.Text);
            if i > 0 then
            begin
              ClientSocket.SendText('ok');
              Str := FileList.Text;
              Delete(Str, i, MaxInt);
              FileList.Text := Str;
    //          break;
            end;        Path := 'c:\test\';
            i := 0;        while true do
            begin
            //当前文件的信息
              CurrentFile.Name := Path + SubStr(FileList[i], '|', 2);
              CurrentFile.Size := StrToInt64(SubStr(FileList[i], '|', 1));
            //检查文件路径
              CurrentPath := ExtractFilePath(CurrentFile.Name);
              if not DirectoryExists(CurrentPath) then
                ForceDirectories(CurrentPath);          F := TFileStream.Create(CurrentFile.Name, fmCreate);
              if SocketStream.WaitForData(5000) then
                repeat
                  BytesRead := SocketStream.Read(ReceiveBuffer, SizeOf(ReceiveBuffer));
                  if BytesRead = 0 then
                  begin
                    ClientSocket.Close;
    //                  break;
                  end
                  else
                  begin
                    F.WriteBuffer(ReceiveBuffer, BytesRead);
                    if f.Size = CurrentFile.Size then
                    begin
                      ClientSocket.SendText('ok');
                      f.Free;
                      break;
                    end;
                  end;
                until not SocketStream.WaitForData(2000);
              inc(i);
              if i >= FileList.Count then
                Break;
            end;      finally
            SocketStream.Free;
            ClientSocket.Close;
    //        f.Free;
          end;
        except
        end;
      end;
    end;
    procedure TForm1.ServerSocket1GetThread(Sender: TObject;
      ClientSocket: TServerClientWinSocket;
      var SocketThread: TServerClientThread);
    begin
      SocketThread := TClientThread.Create(False, ClientSocket);end;procedure TForm1.FormCreate(Sender: TObject);
    begin
      ServerSocket1.Port := 1234;
      ServerSocket1.Open;
    end;end.支持多文件同线程传送!!
      

  13.   

    jadeluo(秀峰)的解答很详细,同意   另外,发送的数据报越大.丢失的情况越严重.
       最好一次性发送较小的数据量,2000以下最好
      

  14.   

    在DELPHI帮助里面,也着重说了不宜取ReceiveLength后根据长度再RECV读出数据.
    不过象秀峰那样,把这个数值作为参考来分配空间读数据也行,
    关键只在判断ReceiveBuf的返回值把握住...楼主丢数据失误关键就是这儿处理得不妥吧.