Дуплекс с именованным каналом висит на определенной записи - PullRequest
4 голосов
/ 09 ноября 2011

У меня есть серверное приложение C ++ и клиентское приложение C #, взаимодействующее через именованный канал Windows (дуплекс, режим сообщений, ожидание / блокировка в отдельном потоке чтения).

Все работает нормально (и отправка, и получение данных по каналу), пока я не попытаюсь записать в канал канал от клиента в ответ на событие формы textaged. Когда я делаю это, клиент зависает на вызове записи канала (или сбрасывает вызов, если автоматическая очистка отключена). Взлом приложения на сервере показывает, что он также ожидает вызова ReadFile и не возвращается. Я попытался запустить запись клиента в другом потоке - тот же результат.

Подозреваю что-то вроде тупика или состояния гонки, но не вижу где ... не думаю, что я пишу в канал одновременно.

Обновление1: пробные каналы в байтовом режиме вместо режима сообщений - та же самая блокировка.

Обновление2: странно, если (и только если) я перекачиваю много данных с сервера на клиент, это излечивает блокировку!?

Код сервера:

DWORD ReadMsg(char* aBuff, int aBuffLen, int& aBytesRead)
{
    DWORD byteCount;
    if (ReadFile(mPipe, aBuff, aBuffLen, &byteCount, NULL))
    {
        aBytesRead = (int)byteCount;
        aBuff[byteCount] = 0;
        return ERROR_SUCCESS;
    }

    return GetLastError();  
}

DWORD SendMsg(const char* aBuff, unsigned int aBuffLen)
{
    DWORD byteCount;
    if (WriteFile(mPipe, aBuff, aBuffLen, &byteCount, NULL))
    {
        return ERROR_SUCCESS;
    }

    mClientConnected = false;
    return GetLastError();  
}

DWORD CommsThread()
{
    while (1)
    {
        std::string fullPipeName = std::string("\\\\.\\pipe\\") + mPipeName;
        mPipe = CreateNamedPipeA(fullPipeName.c_str(),
                                PIPE_ACCESS_DUPLEX,
                                PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
                                PIPE_UNLIMITED_INSTANCES,
                                KTxBuffSize, // output buffer size
                                KRxBuffSize, // input buffer size
                                5000, // client time-out ms
                                NULL); // no security attribute 

        if (mPipe == INVALID_HANDLE_VALUE)
            return 1;

        mClientConnected = ConnectNamedPipe(mPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
        if (!mClientConnected)
            return 1;

        char rxBuff[KRxBuffSize+1];
        DWORD error=0;
        while (mClientConnected)
        {
            Sleep(1);

            int bytesRead = 0;
            error = ReadMsg(rxBuff, KRxBuffSize, bytesRead);
            if (error == ERROR_SUCCESS)
            {
                rxBuff[bytesRead] = 0;  // terminate string.
                if (mMsgCallback && bytesRead>0)
                    mMsgCallback(rxBuff, bytesRead, mCallbackContext);
            }
            else
            {
                mClientConnected = false;
            }
        }

        Close();
        Sleep(1000);
    }

    return 0;
}

код клиента:

public void Start(string aPipeName)
{
    mPipeName = aPipeName;

    mPipeStream = new NamedPipeClientStream(".", mPipeName, PipeDirection.InOut, PipeOptions.None);

    Console.Write("Attempting to connect to pipe...");
    mPipeStream.Connect();
    Console.WriteLine("Connected to pipe '{0}' ({1} server instances open)", mPipeName, mPipeStream.NumberOfServerInstances);

    mPipeStream.ReadMode = PipeTransmissionMode.Message;
    mPipeWriter = new StreamWriter(mPipeStream);
    mPipeWriter.AutoFlush = true;

    mReadThread = new Thread(new ThreadStart(ReadThread));
    mReadThread.IsBackground = true;
    mReadThread.Start();

    if (mConnectionEventCallback != null)
    {
        mConnectionEventCallback(true);
    }
}

private void ReadThread()
{
    byte[] buffer = new byte[1024 * 400];

    while (true)
    {
        int len = 0;
        do
        {
            len += mPipeStream.Read(buffer, len, buffer.Length);
        } while (len>0 && !mPipeStream.IsMessageComplete);

        if (len==0)
        {
            OnPipeBroken();
            return;
        }

        if (mMessageCallback != null)
        {
            mMessageCallback(buffer, len);
        }

        Thread.Sleep(1);
    }
}

public void Write(string aMsg)
{
    try
    {
        mPipeWriter.Write(aMsg);
        mPipeWriter.Flush();
    }
    catch (Exception)
    {
        OnPipeBroken();
    }
}

Ответы [ 3 ]

5 голосов
/ 25 сентября 2014

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

Я не тестировал перекрывающийся ввод-вывод, но он МОЖЕТ решить эту проблему. Однако, если вы решили использовать синхронные вызовы, то следующие модели могут помочь вам решить проблему.

Master / Slave

Вы можете реализовать модель «ведущий / ведомый», в которой клиент или сервер является ведущим, а другой конец отвечает только тем, что обычно вы найдете в примерах MSDN.

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

Writer / Reader

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

Тема

Если вы решили использовать синхронный API, вы можете использовать потоки с моделью master / slave, если вы не хотите, чтобы вас блокировали во время ожидания сообщения на стороне slave. Однако вы захотите заблокировать считыватель после того, как он прочитает сообщение (или встретит конец серии сообщений), затем напишет ответ (как должен ведомый) и, наконец, разблокирует считыватель. Вы можете заблокировать и разблокировать считыватель, используя блокирующие механизмы, которые переводят нить в спящий режим, поскольку они будут наиболее эффективными.

Проблема безопасности с TCP

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

0 голосов
/ 01 февраля 2014

Мне кажется, что то, что вы пытаетесь сделать, скорее всего, будет работать не так, как ожидалось.Некоторое время назад я пытался сделать что-то похожее на ваш код и получить аналогичные результаты, канал просто завис, и было трудно установить, что пошло не так.

Я бы скорее предложил использовать клиент очень простопуть:

  1. CreateFile
  2. Запрос на запись
  3. Чтение ответа
  4. Закрыть канал.

Если вы хотитеЕсли у вас есть двусторонняя связь с клиентами, которые также могут получать незапрошенные данные с сервера, вам лучше использовать два сервера.Это был обходной путь, который я использовал: здесь вы можете найти источники .

0 голосов
/ 10 ноября 2011

Я думаю, что вы можете столкнуться с проблемами в режиме сообщений именованных каналов. В этом режиме каждая запись в дескриптор канала ядра составляет сообщение. Это не обязательно соответствует тому, что ваше приложение рассматривает как «Сообщение», и сообщение может быть больше, чем ваш буфер чтения.

Это означает, что вашему коду чтения канала требуется два цикла: внутреннее чтение до тех пор, пока текущее сообщение [именованный канал] не будет полностью получено, и внешнее зацикливание до получения сообщения [уровень приложения].

Ваш клиентский код на C # имеет правильный внутренний цикл, снова считывая, если IsMessageComplete равно false:

do
{
    len += mPipeStream.Read(buffer, len, buffer.Length);
} while (len>0 && !mPipeStream.IsMessageComplete);

Ваш код сервера C ++ не имеет такого цикла - эквивалент на уровне Win32 API тестирует код возврата ERROR_MORE_DATA.

Я предполагаю, что каким-то образом это приводит к тому, что клиент ожидает, пока сервер прочитает один экземпляр канала, в то время как сервер ожидает, чтобы клиент записал другой экземпляр канала.

...