Подтвердить что ты не робот

WCF и потоковые запросы и ответы

Правильно ли, что в WCF я не могу писать службу в поток, полученный клиентом?

Потоки поддерживаются в WCF для запросов, ответов или обоих. Я хотел бы поддержать сценарий, в котором генератор данных (либо клиент в случае потокового запроса, либо сервер в случае потокового ответа) может писать в потоке. Поддерживается ли это?

Аналогом является Response.OutputStream из запроса ASP.NET. В ASPNET любая страница может вызывать запись в выходном потоке, а контент получен клиентом. Могу ли я сделать что-то подобное в службе WCF - вызвать Write в потоке, полученном клиентом?

Позвольте мне объяснить с помощью иллюстрации WCF. Простейший пример Streaming в 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)

Ясно, да? Сервер возвращает Stream клиенту, и клиент вызывает Read на нем.

Возможно ли, чтобы клиент предоставил Stream, а сервер для вызова Write на нем?
Другими словами, вместо того, чтобы тянуть модель, где клиент извлекает данные с сервера, это модель push, где клиент предоставляет поток "потоковый" и сервер записывает в него. Код на стороне сервера может выглядеть примерно так:

[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, и если да, то как? Каковы настройки конфигурации, необходимые для привязки, интерфейса и т.д.? Что такое терминология?

Может, это сработает? (Я не пробовал)

Спасибо.

4b9b3361

Ответ 1

Я абсолютно уверен, что нет никакой комбинации привязок WCF, которая позволит вам буквально писать в поток клиента. Кажется логичным, что должно быть, учитывая, что где-то под поверхностью определенно будет NetworkStream, но как только вы начнете добавлять шифрование и все эти забавные вещи, WCF должен будет знать, как обернуть этот поток, чтобы его перевернуть в "реальное" сообщение, которое я не думаю, что оно делает.

Однако сценарий I-need-to-return-a-stream-but-component-X-want-to-write-to-one является обычным, и у меня есть решение для всех, которое я использую для этого сценария, который должен создать двухсторонний поток и порождать рабочий поток для записи на него. Самый простой и безопасный способ сделать это (под которым я подразумеваю, способ, который включает в себя запись наименьшего кода и, следовательно, наименее вероятный багги), заключается в использовании анонимных каналов.

Вот пример метода, который возвращает 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 немедленно вернется и создаст условие гонки. (Если это вызывает беспокойство, вы можете сделать код более защищенным, чтобы избежать этого, но это усложняет ситуацию для случая редкого края.)

Надеюсь, это поможет в вашем конкретном случае.

Ответ 2

Потоковая передача WCF работает в обоих направлениях - ваши клиенты могут загружать файлы в поток, но ваша служба WCF также может отправлять вещи в поток. Вы можете определенно написать службу, которая передает большие файлы на основе их имени файла или некоторого идентификатора или чего-то - абсолютно.

Если вы хотите отправить данные обратно из службы, вам нужно сделать это в возвращаемом значении вашего метода обслуживания, который должен быть типа Stream - вы не можете (насколько мне известно) "поставлять", поток для записи - служба WCF создаст поток ответов и отправит их обратно.

Вы проверили MSDN Передача потокового сообщения или Дэвид Вуд сообщение в блоге или сообщение в блоге Kjell-Sverre о потоке WCF? Все они прекрасно показывают, какие настройки конфигурации вам нужны (в основном настройка TransferMode в вашей конфигурации привязки на Streamed, StreamedRequest или StreamedResponse).