Windows CE 5.0 HTTPD <-> .NET-приложение - PullRequest
4 голосов
/ 24 июня 2011

Какой самый практичный способ подключения веб-сервера HTTPD устройства Windows CE 5.0 к приложению .NET, которое выполняется на том же устройстве Windows CE?

Моей первой идеей было создание расширения ISAPI, которое будет пересылать входящие HTTP-запросы в приложение .NET. Не уверен, как это сделать! Может быть общая память, COM, сокеты TCP / IP?

Другим способом может быть внедрение автономного HTTP-сервера в самом приложении .NET и избегание использования HTTPD.

Есть опыт или идеи?

Спасибо Chris

Ответы [ 2 ]

3 голосов
/ 07 августа 2015

Ключом к запуску кода .NET на веб-сервере CE является загрузка dll сервера в процесс .NET.Несколько лет назад я проверил концепцию концепции, чтобы продемонстрировать это.

На первый взгляд дизайн может показаться немного запутанным, но имеет несколько преимуществ:

  • .NET-код и неуправляемыйРасширения ISAPI могут работать бок о бок на веб-сервере
  • Функции веб-сервера, такие как шифрование и аутентификация, все еще работают
  • Ресурсы по-прежнему управляются веб-сервером, включая пул потоков
  • По производительности, вероятно, превосходит любое решение, основанное на отдельных процессах и IPC

Во-первых, нам нужно запретить Windows CE автоматически запускать веб-сервер.Добавьте это в реестр:

[HKEY_LOCAL_MACHINE\Services\HTTPD]
    "Flags"=dword:4  ; DEVFLAGS_NOLOAD

Пока мы это делаем, добавьте еще один ключ для сопоставления '/ dotnet' в наш пользовательский обработчик ISAPI:

[HKEY_LOCAL_MACHINE\Comm\HTTPD\VROOTS\/dotnet]
    @="\\Windows\\HttpdHostUnmanaged.dll"

Теперь создайте.NET exe называется HttpdHostProc.exe из следующего исходного кода:

using System;
using System.Runtime.InteropServices;
using System.Text;

class HttpdHostProc
{
    static void Main(string[] args)
    {
        GetExtensionVersionDelegate pInit =
            new GetExtensionVersionDelegate(GetExtensionVersion);
        TerminateExtensionDelegate pDeinit
            = new TerminateExtensionDelegate(TerminateExtension);
        HttpExtensionProcDelegate pProc =
            new HttpExtensionProcDelegate(HttpExtensionProc);

        Init(pInit, pDeinit, pProc);

        int state = SERVICE_INIT_STOPPED | SERVICE_NET_ADDR_CHANGE_THREAD;
        int context = HTP_Init(state);

        HTP_IOControl(context, IOCTL_SERVICE_REGISTER_SOCKADDR,
             IntPtr.Zero, 0, IntPtr.Zero, 0, IntPtr.Zero);

        HTP_IOControl(context, IOCTL_SERVICE_STARTED,
             IntPtr.Zero, 0, IntPtr.Zero, 0, IntPtr.Zero);

        RunHttpd(context, 80);
    }

    static int GetExtensionVersion(IntPtr pVer)
    {
        OutputDebugString("GetExtensionVersion from .NET\r\n");
        return 1;
    }

    static int TerminateExtension(int dwFlags)
    {
        OutputDebugString("TerminateExtension from .NET\r\n");
        return 1;
    }

    static int HttpExtensionProc(IntPtr pECB)
    {
        OutputDebugString("HttpExtensionProc from .NET\r\n");

        var response = "<html><head></head><body><p>Hello .NET!</p></body></html>";
        var bytes = Encoding.UTF8.GetBytes(response);
        var length = bytes.Length;

        var unmanagedbuffer = Marshal.AllocHGlobal(length);
        Marshal.Copy(bytes, 0, unmanagedbuffer, length);

        var retval = WriteClient(pECB, unmanagedbuffer, ref length);

        Marshal.FreeHGlobal(unmanagedbuffer);

        return retval;
    }

    delegate int GetExtensionVersionDelegate(IntPtr pVer);
    delegate int TerminateExtensionDelegate(int dwFlags);
    delegate int HttpExtensionProcDelegate(IntPtr pECB);

    [DllImport("HttpdHostUnmanaged.dll", SetLastError = true)]
    extern static void Init(
        GetExtensionVersionDelegate pInit,
        TerminateExtensionDelegate pDeinit,
        HttpExtensionProcDelegate pProc
    );

    [DllImport("HttpdHostUnmanaged.dll", SetLastError = true)]
    extern static int RunHttpd(int context, int port);

    [DllImport("HttpdHostUnmanaged.dll", SetLastError = true)]
    extern static int WriteClient(IntPtr pECB, IntPtr Buffer, ref int lpdwBytes);

    [DllImport("coredll.dll")]
    extern static void OutputDebugString(string msg);

    [DllImport("httpd.dll", SetLastError = true)]
    extern static int HTP_Init(int dwData);

    [DllImport("httpd.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    extern static bool HTP_IOControl(int dwData, int dwCode, IntPtr pBufIn,
        int dwLenIn, IntPtr pBufOut, int dwLenOut, IntPtr pdwActualOut);

    const int IOCTL_SERVICE_STARTED = 0x01040038;
    const int IOCTL_SERVICE_REGISTER_SOCKADDR = 0x0104002c;

    const int SERVICE_INIT_STOPPED = 0x00000001;
    const int SERVICE_NET_ADDR_CHANGE_THREAD = 0x00000008;
}

Несколько комментариев:

  • Функция Main загружает и инициализирует нашу неуправляемую dll, которая будет действовать как пошаговыйкамень между управляемым и неуправляемым кодом
  • Затем он инициализирует и запускает веб-сервер, вызывая его функцию HTP_Init, а затем пару ioctls
  • Наконец, он вызывает RunHttpd в нашем неуправляемом dll, который будетпринимать входящие запросы и пересылать их на веб-сервер.

Три функции далее - GetExtensionVersion, TerminateExtension, HttpExtensionProc - должны выглядеть знакомо, если вы когда-либо занимались программированием ISAPI.Если нет, все, что вам действительно нужно знать, это то, что входящие запросы обрабатываются HttpExtensionProc.

Переход к неуправляемой dll, HttpdHostUnmanaged.dll:

#include <winsock2.h>
#include <httpext.h>

typedef BOOL (* pfHTP_IOControl)(DWORD dwData, DWORD dwCode, PBYTE pBufIn,
    DWORD dwLenIn, PBYTE pBufOut, DWORD dwLenOut, PDWORD pdwActualOut);

typedef BOOL (* PFN_WRITE_CLIENT)(HCONN ConnID, LPVOID Buffer,
    LPDWORD lpdwBytes, DWORD dwReserved);

static PFN_GETEXTENSIONVERSION g_pInit;
static PFN_TERMINATEEXTENSION g_pDeinit;
static PFN_HTTPEXTENSIONPROC g_pProc;

BOOL APIENTRY DllMain(HANDLE, DWORD, LPVOID)
{
    return TRUE;
}

extern "C" void Init(
    PFN_GETEXTENSIONVERSION pInit,
    PFN_TERMINATEEXTENSION pDeinit,
    PFN_HTTPEXTENSIONPROC pProc)
{
    // Store pointers to .NET delegates
    g_pInit = pInit;
    g_pDeinit = pDeinit;
    g_pProc = pProc;
}

extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer)
{
    pVer->dwExtensionVersion = HSE_VERSION;
    strncpy(pVer->lpszExtensionDesc, "HttpdHostUnmanaged",
        HSE_MAX_EXT_DLL_NAME_LEN);

    // Call .NET GetExtensionVersion
    return g_pInit(pVer);
}

extern "C" BOOL WINAPI TerminateExtension(DWORD dwFlags)
{
    // Call .NET TerminateExtension
    return g_pDeinit(dwFlags);
}

extern "C" DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB)
{
    // Call .NET HttpExtensionProc
    return g_pProc(pECB);
}

extern "C" DWORD WINAPI WriteClient(EXTENSION_CONTROL_BLOCK *pECB,
    LPVOID Buffer, LPDWORD lpdwBytes)
{
    return pECB->WriteClient(pECB->ConnID, Buffer, lpdwBytes, 0);
}

extern "C" int WINAPI RunHttpd(DWORD context, int port)
{
    // Load web server and start accepting connections.
    // When a connection comes in,
    // pass it to httpd using IOCTL_SERVICE_CONNECTION.

    HMODULE hDll = LoadLibrary(L"httpd.dll");
    if(!hDll)
    {
        return -1;
    }

    pfHTP_IOControl Ioctl =
        (pfHTP_IOControl)GetProcAddress(hDll, L"HTP_IOControl");
    if(!Ioctl)
    {
        FreeLibrary(hDll);
        return -2;
    }

    WSADATA Data;
    int status = WSAStartup(MAKEWORD(1, 1), &Data);
    if(status != 0)
    {
        FreeLibrary(hDll);
        return status;
    }

    SOCKET s = socket(PF_INET, SOCK_STREAM, 0);
    if(s == INVALID_SOCKET)
    {
        status = WSAGetLastError();
        goto exit;
    }

    SOCKADDR_IN sAddr;
    memset(&sAddr, 0, sizeof(sAddr));
    sAddr.sin_port = htons(port);
    sAddr.sin_family = AF_INET;
    sAddr.sin_addr.s_addr = htonl(INADDR_ANY);

    if(bind(s, (LPSOCKADDR)&sAddr, sizeof(sAddr)) == SOCKET_ERROR)
    {
        status = WSAGetLastError();
        goto exit;
    }

    if(listen(s, SOMAXCONN) == SOCKET_ERROR)
    {
        status = WSAGetLastError();
        goto exit;
    }

    for(;;)
    {
        SOCKADDR_IN addr;
        int cbAddr = sizeof(addr);

        SOCKET conn = accept(s, (LPSOCKADDR)&addr, &cbAddr);

        if(conn == INVALID_SOCKET)
        {
            status = WSAGetLastError();
            goto exit;
        }

        DWORD IOCTL_SERVICE_CONNECTION = 0x01040034;
        Ioctl(context, IOCTL_SERVICE_CONNECTION,
            (PBYTE)&conn, sizeof(conn), NULL, 0, NULL);
    }

exit:
    FreeLibrary(hDll);

    if(s != INVALID_SOCKET)
    {
        closesocket(s);
    }

    WSACleanup();
    return status;
}

Есть ряд неТам очень интересные функции, которые пересылают вызовы в .NET.

Как упоминалось выше, функция RunHttpd просто принимает входящие соединения и передает их на веб-сервер для обработки с помощью другого ioctl.

Чтобы проверить все это, запустите HttpdHostProc.exe на устройстве, затем откройте http://<ipaddr>/dotnet в браузере.Устройство CE должно ответить небольшим HTML-кодом, содержащим сообщение «Hello .NET!».

Этот код работает в Windows Embedded Compact 7.0 с .NET Compact Framework 3.5, но, вероятно, будет работать и в других версиях..

Я собрал неуправляемую dll для Pocket PC 2003 SDK, так как я установил именно это.Вероятно, подойдет любой Windows CE SDK, но вам, возможно, придется изменить настройки компилятора, например, мне пришлось собирать с / GS- (проверки безопасности буфера отключены) для PPC2003.

Соблазнительно реализовать функцию RunHttpdв .NET, но имейте в виду, что есть несколько потенциальных проблем:

  • В моем тестировании свойство Handle в сокете .NET CF возвратило своего рода псевдо-дескриптор, которыйне работал с собственными API сокетов
  • Время жизни сокета будет управляться средой выполнения .NET, что особенно затруднит передачу владения сокетом веб-серверу

Если вы не возражаете против компиляции с / unsafe, производительность, возможно, можно немного улучшить, передав фиксированные буферы в WriteClient, а не копируя все данные ответов в неуправляемый буфер в HttpExtensionProc.

Структура EXTENSION_CONTROL_BLOCK содержит числополезных полей и функций, которые, очевидно, должны быть включеныг в полной реализации.

РЕДАКТИРОВАТЬ

Просто чтобы уточнить, как обрабатываются запросы:

  1. Входящие запросы принимаются в RunHttpd, которыйперенаправляет их на веб-сервер с помощью ioctl
  2. В соответствии с записью vroot в реестре, которую мы настроили ранее, веб-сервер вызывает HttpdHostUnmanaged.dll для обработки запросов на / dotnet
  3. Если это первый запрос для / dotnet, веб-сервер запускается с вызова неуправляемой версии GetExtensionVersion в HttpdHostUnmanaged.dll. Неуправляемый GetExtensionVersion вызывает обратно .NET-версию GetExtensionVersion. GetExtensionVersion - это удобное место для инициализации любых необходимых ресурсов, так как он вызывается только один раз (соответствующая функция очистки - TerminateExtension, которая вызывается, когда / если веб-сервер решает выгрузить HttpdHostUnmanaged.dll)
  4. Затем веб-сервер вызывает неуправляемый HttpExtensionProc
  5. Неуправляемый HttpExtensionProc вызывает обратно .NET-версию HttpExtensionProc
  6. Управляемый HttpExtensionProc генерирует ответ и вызывает неуправляемый WriteClient для доставки его клиенту
2 голосов
/ 24 июня 2011

По моему мнению, основанное на попытке использовать встроенный HTTPD-сервер в прошлом, этот встроенный сервер абсолютно не подходит для попыток сделать что-нибудь полезное.Это большая головная боль для отладки чего-либо, и взаимодействие с любым оборудованием / системой устройства является болезненным.

Поскольку в CF отсутствует поддержка хостинга EE, веб-сервер не может загружать управляемые сборки (из ISAPI или чего-либо еще).Это означает, что ваш управляемый код должен быть в отдельном процессе, и для связи вы должны использовать IPC - что-то вроде P2PMessageQueue , MemoryMappedFile , сокет и т. Д.

Запись собственного HTTPD-сервера также возможна, но это не тривиальная задача.Звучит просто, но когда вы погрузитесь в это, многое будет вовлечено.Я знаю, что, поскольку мы приняли это решение несколько лет назад по проекту, и сервер, который мы в итоге создали, поддерживал подмножество ASP.NET, и мы фактически превратили его в коммерческий продукт , потому что а) это было действительнополезно и б) потому что на самом деле потребовалось много работы, чтобы написать.Это, однако, дает то преимущество, что хостинг управляемого кода и возможность отладки в Studio вместо printf, как ожидает управляемый разработчик.

...