WCF и потоковые запросы и ответы - PullRequest
13 голосов
/ 28 апреля 2010

Правильно ли, что в 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 ]

9 голосов
/ 28 апреля 2010

Я довольно уверен, что нет никакой комбинации привязок 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);
        }
    });
}

Всего два замечания об этом подходе:

  1. Произойдет сбой, если writeAction сделает что-нибудь для удаления потока (вот почему метод теста на самом деле не располагает StreamWriter - потому что это приведет к удалению основного потока);

  2. Может произойти сбой, если writeAction на самом деле не записывает никаких данных, потому что WaitForPipeDrain немедленно вернется и создаст условие гонки. (Если это вызывает озабоченность, вы можете написать немного более осторожно, чтобы избежать этого, но это сильно усложняет ситуацию в редких случаях.)

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

2 голосов
/ 28 апреля 2010

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...