Delphi: ClientDataSet: EDatabaseError: Falta de datos de paquetes mediante la sinapsis
-
16-09-2019 - |
Pregunta
Desde el cliente Estoy enviando una cadena en el servidor de lo que me debe enviar de vuelta. Esta vez es un flujo creado por una ClientDataSet. Desafortunadamente recibir (o enviar ??) no funciona en este momento.
Nota: Estoy usando sinapsis con el bloqueo zócalos. El servidor es multiproceso.
El Servidor:
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;
El Cliente:
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;
Ahora, cada vez que inicio el servidor y haga clic en el botón de conexión en el cliente, es conveniente transferir el conjunto de datos al cliente y el TDBGrid debe venir a la vida.
Por desgracia, esto no sucede. En su lugar me sale el error como se indica en el título: Falta de datos o de datapackage. Sin embargo, el proveedor de datos se establece en DataSetProvider1 (en el inspector de objetos).
¿Cómo puedo hacer que funcione de que el cliente TDBGrid está lleno de datos?
Solución
Usted está creando una nueva instancia dentro de la variable ClientDataSet1, pero ninguno de los otros componentes en su formulario hará referencia a eso.
Eso no es la causa del "proveedor de datos faltantes o datapackage" mensaje de error: con el fin de descubrir eso usted debe enviar un caso reproducible
.La forma más fácil de conseguir ese caso reproducible ir sería:
- Ponga dos TClientDataSets en su servidor (ClientDataSet1 y ClientDataSet2)
- cargar los datos en que ClientDataSet1 en tiempo de diseño utilizando un proveedor y tal
- Guardar que los datos de un archivo a ClientDataSet1 .cds El archivo
- Cargar las .cds en ClientDataSet2 en tiempo de diseño
- Enviar más de ClientDataSet2 a su cliente
Si aún reproduce, a continuación, publicar que .pas / .dfm en alguna parte. Si no se reproduce, a continuación, empezar a cortar porciones hasta que se reproduce.
Buena suerte!
- Jeroen
Editar:. He descargado sus fuentes y de ellos trabajan en Delphi 2009
Contiene algunos problemas, y la biblioteca de la sinapsis contiene un problemas también.
La mayoría de los problemas se reducen a no siendo preciso. Yo ya tenía una vaga sensación de que, en función de su formato de código fuente, la mezcla de las convenciones de nomenclatura que se estaban utilizando para sus cosas, y la falta de desprendimiento de objetos asignados.
Antes de que me olvide: la primera cosa que hice fue asegurarse de que Habilité DCU uso de depuración y desactivar optimizaciones en las opciones del compilador. Esos son los ajustes de valor incalculable para investigar más profundamente en problemas. No añadí cualquier limpieza de su código existente, ya que quería que fuera lo más cerca posible. Pero yo he corrido un formateador de código fuente para que al menos los entonces bloquea estaban en líneas separadas.
Vamos a empezar con el código de envío.
Me deshice de su conexión a la base de datos, copiado el ClientDataSet1 del cliente al servidor , y refactorizado su sServer unidad para que tanto TTCPSocketDaemon y TTCPSocketThrd ahora tienen acceso a FClientDataSet: TClientDataSet
Eso me consiguió un tipo de caso reproducible, que pedí antes.
Entonces empezó a correr el cliente. No parecía que LogThis (strmReply.Size);. fue dar salida a 0 (! Cero), por lo que no recibía nada
Eso me hizo quedar en el servidor de nuevo. Parece que se estaban separando los argumentos de entrada utilizando texto delimitado, pero después de que estabas refiriendo a sl.Names [0] en lugar de si sl [0] . Eso falla en Delphi 2009, y probablemente en otras versiones de Delphi también. Además de eso, usted no estaba mirando para un vacío s , por lo que sería un error con un índice de lista fuera de límites después de cada tiempo de espera.
Luego he añadido algo de código de depuración: quería ver lo que realmente se está enviando. Esto me causó para transmitir el uso de XML en lugar de binario utilizando S_CLIENT.SaveToStream (STRM, dfXMLUTF8); y copiar el búfer en un archivo local, por lo que pude ver ese archivo. El archivo estaba bien: lo que parecía ser una representación XML válido de un ClientDataSet
.Por último, me di cuenta de que estaba utilizando búfer de envío en el servidor, y RecvStream en el cliente. Los que no coinciden: hay que elegir, así que o tampones de uso o arroyos en ambos lados de la conexión! Elegí para los flujos.
Así que el código se convierte en el envío de esto:
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;
Así que fui a revisar el código de recepción. Puesto que ya estaba entrando strmReply.Size , me preguntaba por qué no reacciona en el caso especial donde era 0 (cero), por lo que añade que: LogThis ( 'corriente vacío recibieron ')
Entonces empecé depuración, y descubrió que strmReply.Size seguía siendo0 (cero) cada vez. Así que empecé a cavar en el código de la sinapsis (Tomé la copia que ya tenía que está incluido en el Habari componentes) he estado usando últimamente. Allí descubrí dos cosas:
- momento de envío de la corriente, se codifica la longitud de la corriente en los primeros 4 bytes
- la codificación se realiza en un orden de bytes diferente que la decodificación
Este es sin duda un error en la sinapsis, así que no dude en informar de ello a los mismos. El resultado es que RecvStream no es la contrapartida de SendStream , pero RecvStreamIndy es.
Después de todos estos cambios, todavía no ha funcionado. La razón es que ClientDataSet1.LoadFromStream (strmReply); está empezando a leer desde la posición actual en la corriente (se puede comprobar que fuera usted mismo, la lógica de la base de carga está en TCustomClientDataSet.ReadDataPacket ). Por lo que parece que se olvidó de añadir strmReply.Seek (0, soBeginning);. , y después añadí que, de repente se trabajó
Así que el código de recepción es el siguiente:
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;
Al final, no fue difícil de resolver sus problemas. La parte más difícil fue encontrar a cabo sobre RecvStreamIndy , el resto fue una mera limpieza de su código y ser preciso en lo que sucede cuando: que tomó la mayor parte del tiempo. En este caso, el manejo de flujo que se requiere para ver sus posiciones corriente con cuidado.
Saludos,
- Jeroen