WCF и потоковые запросы и ответы
Правильно ли, что в WCF у меня не может быть записи службы в поток, полученный клиентом?
Потоковая передача поддерживается в WCF для запросов, ответов или обоих. Я хотел бы поддержать сценарий, в котором генератор данных (либо клиент в случае потокового запроса, либо сервер в случае потокового ответа) может выполнять запись в поток. Это поддерживается?
Аналогия - Response.OutputStream из запроса ASP.NET. В ASPNET любая страница может вызывать запись в выходной поток, и содержимое получает клиент. Могу ли я сделать нечто подобное в службе WCF - вызвать запись в поток, полученный клиентом?
Позвольте мне объяснить с иллюстрацией WCF. Простейшим примером потоковой передачи в WCF является служба, возвращающая FileStream клиенту. Это потоковый ответ. Код сервера для реализации этого выглядит так:
[ServiceContract]
public interface IStreamService
{
[OperationContract]
Stream GetData(string fileName);
}
public class StreamService : IStreamService
{
public Stream GetData(string filename)
{
return new FileStream(filename, FileMode.Open)
}
}
И код клиента выглядит так:
StreamDemo.StreamServiceClient client =
new WcfStreamDemoClient.StreamDemo.StreamServiceClient();
Stream str = client.GetData(@"c:\path\on\server\myfile.dat");
do {
b = str.ReadByte(); //read next byte from stream
...
} while (b != -1);
(пример взят из http://blog.joachim.at/?p=33)
Ясно, верно? Сервер возвращает поток клиенту, а клиент вызывает для него чтение.
Может ли клиент предоставить поток, а сервер вызвать запись в него?
Другими словами, это не модель извлечения, когда клиент извлекает данные с сервера, а модель проталкивания, когда клиент предоставляет поток "приемника", а сервер записывает в него данные. Код на стороне сервера может выглядеть примерно так:
[ServiceContract]
public interface IStreamWriterService
{
[OperationContract]
void SendData(Stream clientProvidedWritableStream);
}
public class DataService : IStreamWriterService
{
public void GetData(Stream s)
{
do {
byte[] chunk = GetNextChunk();
s.Write(chunk,0, chunk.Length);
} while (chunk.Length > 0);
}
}
Возможно ли это в WCF, и если да, то как? Какие параметры конфигурации требуются для привязки, интерфейса и т. Д.? Какая терминология?
Может это просто сработает? (Я не пробовал это)
Благодарю.
2 ответа
Я совершенно уверен, что нет никакой комбинации привязок WCF, которая позволила бы вам буквально записывать в поток клиента. Кажется логичным, что это должно быть, учитывая, что где-то под поверхностью определенно будет NetworkStream
, но как только вы начнете добавлять шифрование и все эти забавные вещи, WCF придется знать, как обернуть этот поток, чтобы превратить его в "реальное" сообщение, что я не думаю, что это делает.
Тем не менее, сценарий "Мне нужно вернуть поток, но компонент X-хочет-записать-один" является распространенным, и у меня есть универсальное решение, которое я использую для этого сценария., который должен создать двусторонний поток и создать рабочий поток для записи в него. Самый простой и безопасный способ сделать это (я имею в виду способ, который предполагает наименьшее количество кода и, следовательно, с наименьшей вероятностью глючит), - это использовать анонимные каналы.
Вот пример метода, который возвращает AnonymousPipeClientStream
это может использоваться в сообщениях WCF, результатах MVC и т. д. - везде, где вы хотите изменить направление:
static Stream GetPipedStream(Action<Stream> writeAction)
{
AnonymousPipeServerStream pipeServer = new AnonymousPipeServerStream();
ThreadPool.QueueUserWorkItem(s =>
{
using (pipeServer)
{
writeAction(pipeServer);
pipeServer.WaitForPipeDrain();
}
});
return new AnonymousPipeClientStream(PipeDirection.In, pipeServer.ClientSafePipeHandle);
}
Пример использования этого будет:
static Stream GetTestStream()
{
string data = "The quick brown fox jumps over the lazy dog.";
return GetPipedStream(s =>
{
StreamWriter writer = new StreamWriter(s);
writer.AutoFlush = true;
for (int i = 0; i < 50; i++)
{
Thread.Sleep(50);
writer.WriteLine(data);
}
});
}
Всего два замечания об этом подходе:
Это не удастся, если
writeAction
делает что-нибудь, чтобы избавиться от потока (вот почему метод теста на самом деле неStreamWriter
- потому что это избавит от основного потока);Может произойти сбой, если
writeAction
на самом деле не пишет никаких данных, потому чтоWaitForPipeDrain
вернется немедленно и создаст условия гонки. (Если это проблема, вы можете написать немного более осторожно, чтобы избежать этого, но это очень усложняет ситуацию в редких случаях.)
Надеюсь, это поможет в вашем конкретном случае.
Потоковая передача WCF работает в обоих направлениях - ваши клиенты могут загружать данные в потоке, но ваша служба WCF также может отправлять данные в потоке. Вы можете определенно написать сервис, который возвращает потоковые файлы большого размера на основе их имени, идентификатора или чего-то еще - абсолютно.
Если вы хотите отправить данные обратно из сервиса, вам нужно сделать это в возвращаемом значении вашего метода сервиса, который должен иметь тип Stream
- вы не можете (насколько мне известно) "предоставить" поток для записи - служба WCF создаст поток ответа и отправит его обратно.
Вы проверили MSDN Streaming Message Transfer или сообщение в блоге Дэвида Вуда или сообщение в блоге Kjell-Sverre о потоковой передаче WCF? Все они хорошо показывают, какие настройки конфигурации вам нужны (в основном TransferMode
в вашей конфигурации привязки к Streamed
, StreamedRequest
или же StreamedResponse
).