2

クライアントからサーバーに文字列を送信しています。今回は ClientDataSet によって作成されたストリームです。残念ながら、現時点では受信 (または送信??) は機能しません。

注: ソケットをブロックする Synapse を使用しています。サーバーはマルチスレッドです。

サーバー:

procedure TTCPSocketThrd.Execute;
var s: String;
    strm: TMemoryStream;
    ADO_QUERY: TADOQuery;
    DS_PROV: TDataSetProvider;
    DS_CLIENT: TClientDataSet;
begin
    CoInitialize(nil);
    Sock := TTCPBlockSocket.Create;
  try
    Sock.Socket := CSock;
    Sock.GetSins;
    with Sock do
        begin
        repeat
        if terminated then break;
          //if within 60s no data is input, close connection.
          s := RecvTerminated(60000,'|');
          if s = 'getBENUds' then
            begin
              //ini ADO_QUERY
            ADO_QUERY := TADOQuery.Create(Form1);                    
                ADO_QUERY.ConnectionString := 'private :)';
                ADO_QUERY.SQL.Clear;
                ADO_QUERY.SQL.Add('sql_query');
                ADO_QUERY.Open;
              //ini DS_PROV
                DS_PROV := TDataSetProvider.Create(ADO_QUERY);
                DS_PROV.DataSet := ADO_QUERY;
              //ini DS_CLIENT
                DS_CLIENT := TClientDataSet.Create(ADO_QUERY);
                DS_CLIENT.ProviderName := 'DS_PROV';
                DS_CLIENT.SetProvider(DS_PROV);
              //DSCLIENTDATASET bauen
                strm := TMemoryStream.Create;;
                DS_CLIENT.Open;

                DS_CLIENT.SaveToStream(strm);
                SendStream(strm);
             end;

クライアント:

procedure TForm1.btnConnectClick(Sender: TObject);
begin
  CSocket := TTCPBlockSocket.Create;
  strmReply := TMemoryStream.Create;
  ClientDataSet1 := TClientDataSet.Create(Form1);
    try
    CSocket.Connect('ip', 'port');
    if CSocket.LastError = 0 then
    begin
      //Sending to the server I want data
      CSocket.SendString('getBENUds|');
      if CSocket.LastError = 0 then
        begin
            //Waiting for data
            //Receiving the Stream in strmReply, 5000ms timeout
            CSocket.RecvStream(strmReply, 5000);
            //Loading the data into ClientDataSet
            ClientDataSet1.LoadFromStream(strmReply);
            ClientDataSet1.Open;
        end;
    end;
  except
    on E:Exception do
        ShowMessage(E.Message);
    end;
  CSocket.Free;
end;

これで、サーバーを起動してクライアントの接続ボタンをクリックするたびに、DataSet がクライアントに転送され、TDBGrid が起動する必要があります。

残念ながら、これは起こりません。代わりに、タイトルに記載されているエラーが表示されます: Missing data provider or datapackage. ただし、データ プロバイダーは DataSetProvider1 に設定されています (オブジェクト インスペクターで)。

クライアントの TDBGrid にデータが入力されるようにするにはどうすればよいですか?

4

1 に答える 1

4

ClientDataSet1変数内に新しいインスタンスを作成していますが、フォーム上の他のコンポーネントはどれもそれを参照していません。

これは、「データプロバイダーまたはデータパッケージがありません」というエラーメッセージの原因ではありません。それを見つけるには、再現可能なケースを投稿する必要があります。

その再現可能なケースを実行する最も簡単な方法は、次のとおりです。

  1. サーバーに2つのTClientDataSetを配置します(ClientDataSet1とClientDataSet2)
  2. プロバイダーなどを使用して、設計時にそのClientDataSet1にデータをロードします。
  3. そのデータをClientDataSet1から.CDSファイルに保存します
  4. 設計時に.CDSファイルをClientDataSet2にロードします
  5. ClientDataSet2をクライアントに送信します

それでも再現される場合は、その.pas/.dfmをどこかに投稿してください。再現されない場合は、再現されるまで部分を切り取り始めます。

幸運を!

--jeroen

編集:私はあなたのソースをダウンロードし、Delphi2009で動作させました。

いくつかの問題が含まれており、Synapseライブラリにも問題が含まれています。

あなたの問題の大部分は正確ではないことに帰着します。ソースコードのフォーマット、使用している命名規則の組み合わせ、割り当てられたオブジェクトの解放の欠如に基づいて、私はすでにそれについて漠然とした感覚を持っていました。

忘れる前に:私が最初にしたことは、debug dcusの使用を有効にし、コンパイラオプションの最適化を無効にすることでした。これらは、問題をより深く掘り下げるための非常に貴重な設定です。できるだけ近くにしたかったので、既存のコードにクリーンアップを追加しませんでした。しかし、少なくともthenブロックが別々の行にあるように、ソースコードフォーマッターを実行しました。

送信コードから始めましょう。

データベース接続を削除し、ClientDataSet1クライアントからサーバーにコピーし、 SServerユニットをリファクタリングして、 TTCPSocketDaemonTTCPSocketThrdの両方がFClientDataSetにアクセスできるようにしました。TClientDataSet

それは私が以前に求めた一種の再現可能なケースを私に与えました。

次に、クライアントの実行を開始しました。LogThis(strmReply.Size);が表示されました。0(ゼロ!)を出力していたので、何も受信していませんでした。

それで私はサーバーをもう一度調べました。区切りテキストを使用して着信引数を分割しているようですが、その後は、ifsl[0]ではなくsl.Names[0 ]を参照していました。これはDelphi2009で失敗し、おそらく他のDelphiバージョンでも失敗します。それに加えて、空のsをチェックしていなかったため、タイムアウトするたびにリストインデックスが範囲外で失敗していました。

次に、デバッグコードを追加しました。実際に送信されているものを確認したかったのです。これにより、S_CLIENT.SaveToStream(strm、dfXMLUTF8);を使用して、バイナリではなくXMLを使用してストリーミングしました。バッファをローカルのファイルにコピーして、そのファイルを監視できるようにします。ファイルはOKでした:ClientDataSetの有効なXML表現であるように見えました。

最後に、サーバーでSendBufferを使用し、クライアントでRecvStreamを使用していることに気付きました。それらは一致しません。選択する必要があるため、接続の両側でバッファーまたはストリームを使用してください。私はストリームを選びました。

したがって、送信コードは次のようになります。

procedure TTCPSocketThrd.Execute;
var
  s: string;
  strm: TMemoryStream;
  ADO_QUERY: TADOQuery;
  DS_PROV: TDataSetProvider;
  DS_CLIENT: TClientDataSet;
  sl: TStringList;
  adoconnstr: string;
  CDSStream: TFileStream;
begin
  CoInitialize(nil);
  Sock := TTCPBlockSocket.Create;
  adoconnstr := 'POST YOUR CONNECTION STRING HERE, IF TOO LONG adoconnstr := adoconnstr + ''nwestring''';
  try
    Sock.Socket := CSock;
    sl := TStringList.Create;
    Sock.GetSins;
    with Sock do
    begin
      repeat
        if terminated then
          break;
        s := RecvTerminated(60000, '|');

        if s = '' then
          Exit;

        //Den Text mit Leerzeichen splitten zur besseren Verarbeitung
        sl.Delimiter := ' ';
        sl.DelimitedText := s;

        LogThis(sl.Names[0]);
        if sl[0] = 'getBENUds' then // jpl: not sl.Names[0] !!!!
        begin
          //          //ini ADO_QUERY
          //          ADO_QUERY := TADOQuery.Create(Form1);
          //          ADO_QUERY.ConnectionString := adoconnstr;
          //          ADO_QUERY.SQL.Clear;
          //          ADO_QUERY.SQL.Add('SELECT * FROM BENU');
          //          ADO_QUERY.Open;
          //          LogThis('ADO_QUERY fertig');
          //          //ini DS_PROV
          //          DS_PROV := TDataSetProvider.Create(ADO_QUERY);
          //          DS_PROV.DataSet := ADO_QUERY;
          //          LogThis('DS_DSPROV fertig');
          //          //ini DS_CLIENT
          //          DS_CLIENT := TClientDataSet.Create(ADO_QUERY);
          //          DS_CLIENT.ProviderName := 'DS_PROV';
          //          DS_CLIENT.SetProvider(DS_PROV);
          DS_CLIENT := FClientDataSet;
          LogThis('DS_CLIENT fertig');
          //DSCLIENTDATASET bauen
          strm := TMemoryStream.Create;
          DS_CLIENT.Open;
          //DS_CLIENT.Open;
          DS_CLIENT.SaveToStream(strm, dfXMLUTF8); // jpl: easier debugging than binary
          strm.Seek(0, soBeginning);
          SendStream(strm); // jpl: not SendBuffer(strm.Memory, strm.Size);  !!!

          strm.Seek(0, soBeginning);
          CDSStream := TFileStream.Create('Server.cds', fmCreate);
          CDSStream.CopyFrom(strm, strm.Size);
          CDSStream.Free();

          LogThis('gesendet');
        end
        else if sl[0] = 'validuser' then // jpl: not sl.Names[0] !!!!
        begin
          //do stuff
        end;
        if lastError <> 0 then
          break;
      until false;
      Form1.Memo1.Lines.Add('down');
    end;
  finally
    Sock.Free;
  end;

end;

そこで、受信コードを再検討しました。すでにstrmReply.Sizeをログに記録しているので、0(ゼロ)の特殊なケースに反応しなかったのはなぜだろうと思ったので、次のように追加しました。LogThis('空のストリームを受信しました')

次に、デバッグを開始しましたが、strmReply.Sizeが毎回0(ゼロ)のままであることがわかりました。そこで、最近使用しているSynapseコード( Habariコンポーネントに含まれているすでに持っているコピーを取得しました)を掘り下げ始めました。そこで私は2つのことを発見しました:

  1. ストリームを送信すると、最初の4バイトでストリームの長さをエンコードしていました
  2. エンコードは、デコードとは異なるバイト順序で実行されました

これは間違いなくSynapseのバグですので、遠慮なく報告してください。その結果、RecvStreamはSendStreamに対応しませんが、 RecvStreamIndyは対応します。

これらすべての変更の後、それはまだ機能しませんでした。その理由は、ClientDataSet1.LoadFromStream(strmReply); はストリーム内の現在の位置から読み取りを開始しています(自分で確認できます。コアの読み込みロジックはTCustomClientDataSet.ReadDataPacketにあります)。したがって、strmReply.Seek(0、soBeginning);を追加するのを忘れたようです。、そしてそれを追加した後、それは突然機能しました。

したがって、受信コードは次のとおりです。

procedure TForm1.btnConnectClick(Sender: TObject);
var
  CDSStream: TFileStream;
begin
  CSocket := TTCPBlockSocket.Create;
  strmReply := TMemoryStream.Create;
  ClientDataSet1 := TClientDataSet.Create(Form1);
  try
    //      CSocket.Connect('10.100.105.174', '12345');
    CSocket.Connect('127.0.0.1', '12345');
    if CSocket.LastError = 0 then
    begin
      LogThis('verbunden');
      CSocket.SendString('getBENUds|');
      LogThis('''getBENUds|'' gesendet');
      if CSocket.LastError = 0 then
      begin
        CSocket.RecvStreamIndy(strmReply, 50000); // jpl: this fails, because SendStream defaults to Indy: CSocket.RecvStream(strmReply, 50000);
        LogThis(strmReply.Size);

        if strmReply.Size = 0 then
          LogThis('empty stream received')
        else
        begin
          strmReply.Seek(0, soBeginning);
          CDSStream := TFileStream.Create('Client.cds', fmCreate);
          CDSStream.CopyFrom(strmReply, strmReply.Size);
          CDSStream.Free();

          strmReply.Seek(0, soBeginning); // jpl: always make sure that the stream position is right!
          ClientDataSet1.LoadFromStream(strmReply);
          ClientDataSet1.Open;
        end;
      end;
    end;
  except
    on E: Exception do
      ShowMessage(E.Message);
  end;
  CSocket.Free;
end;

結局、あなたの問題を解決することは難しくありませんでした。最も難しかったのはRecvStreamIndyについて知ることでした。残りはコードの単なるクリーンアップであり、いつ何が起こるかを正確に把握することでした。これにはほとんどの時間がかかりました。この場合、ストリーム処理では、ストリームの位置を注意深く監視する必要があります。

よろしく、

--jeroen

于 2009-08-14T10:46:58.263 に答える