Page 2 of 2

Re: Creando Servicios con Harbour y xHarbour

Posted: Wed Sep 18, 2024 3:54 pm
by leandro
Antonio gracias como siempre por todo

Esto ya lo podemos usar en xharbour? o solo es para harbour? no entendí bien el mensaje?, la traducción en google no es muy clara.

Re: Creando Servicios con Harbour y xHarbour

Posted: Wed Sep 18, 2024 4:08 pm
by wilsongamboa
buenos dias con todos magino que la funcion
TrabajoServicio() va a reempalzar a la entrada o main de nuestra aplicacion solo una pregunta esto funciona para los programas multihilo ?? como hbnetio o como hbhttpd ?
gracias por todo Masters !!
saludos

Re: Creando Servicios con Harbour y xHarbour

Posted: Wed Sep 18, 2024 4:13 pm
by Antonio Linares
leandro wrote:Antonio gracias como siempre por todo

Esto ya lo podemos usar en xharbour? o solo es para harbour? no entendí bien el mensaje?, la traducción en google no es muy clara.
Funciona correctamente con Harbour y xHarbour

Re: Creando Servicios con Harbour y xHarbour

Posted: Wed Sep 18, 2024 4:15 pm
by Antonio Linares
wilsongamboa wrote:buenos dias con todos magino que la funcion
TrabajoServicio() va a reempalzar a la entrada o main de nuestra aplicacion solo una pregunta esto funciona para los programas multihilo ?? como hbnetio o como hbhttpd ?
gracias por todo Masters !!
saludos
la gran limitación es que no se puede usar NADA de GUI. Ni siquiera ni un MsgInfo() ó MsgBeep()

Re: Creando Servicios con Harbour y xHarbour

Posted: Wed Sep 18, 2024 4:34 pm
by wilsongamboa
gracias antonio por contestar
pero sirve para programas multihilos ? que no tengan salida a pantalla claro
saludos

Re: Creando Servicios con Harbour y xHarbour

Posted: Wed Sep 18, 2024 4:43 pm
by Antonio Linares
Wilson,

habria que probarlo pero supongo que si, que debe funcionar multihilo correctamente

Re: Creando Servicios con Harbour y xHarbour

Posted: Sat Oct 12, 2024 8:37 am
by VictorCasajuana
Antonio Linares wrote:
wilsongamboa wrote:buenos dias con todos magino que la funcion
TrabajoServicio() va a reempalzar a la entrada o main de nuestra aplicacion solo una pregunta esto funciona para los programas multihilo ?? como hbnetio o como hbhttpd ?
gracias por todo Masters !!
saludos
la gran limitación es que no se puede usar NADA de GUI. Ni siquiera ni un MsgInfo() ó MsgBeep()
Hola Antonio. Partiendo de tener un servicio realizado con FWH, con las ventajas que supone pero la limitación de no poder interactuar con él de primeras. Cuál sería la forma más correcta para comunicarse con el servicio mediante otro programa de FWH? me refiero a montar otro programa que sí ejecuta el usuario cuando abre sesión y desde dicho programa, poder interactuar con el servicio para lanzar acciones, ver su estado, etc... Se me ocurre utilizar sockets?

Gracias y salud!

Re: Creando Servicios con Harbour y xHarbour

Posted: Sat Oct 12, 2024 10:17 am
by Antonio Linares
Victor,

Si, posiblemente usar sockets funcione. No lo he probado

Re: Creando Servicios con Harbour y xHarbour

Posted: Sat Oct 12, 2024 4:28 pm
by VictorCasajuana
Antonio Linares wrote:Victor,

Si, posiblemente usar sockets funcione. No lo he probado
Hola Antonio.
Montando un ejemplo funcional con sockets, me ha funcionado. La conexión la realizo con postman para hacer las pruebas iniciales, luego si soluciono lo que te comento, haré un cliente para las pruebas básicas de interacción con el servicio. El problema es cuando activo el multithread para que después de aceptar una conexión, se quede escuchando de nuevo. Esto lo hago con algunas utilidades que tengo montadas con sockets y mt y me funciona perfectamente, pero en este caso no me funciona, no sé si es porque hay que adaptar la función que has realizado para el servicio en C a multithread, ahí me pierdo.

Con el siguiente código:

Code: Select all | Expand

// Once you build the EXE open a CMD window as Administrator, then:
// sc create servicio binPath=c:\fwh\samples\servicio.exe
// sc start servicio
// when you want to stop it: sc stop servicio
// when you want to remove it: sc delete servicio
// DON'T USE ANY GUI FROM IT !!!

#include "FiveWin.ch"
#include "hbsocket.ch"


function Main()
    
    Local nPuerto := 8500

    public hListen := Nil
    public log := "c:\servicio.log"

    fErase( log )
    hb_MemoWrit( log, hb_MemoRead( log) + hb_Eol() + 'iniciando socketserver' )
    hListen := hb_socketOpen( HB_SOCKET_AF_INET, HB_SOCKET_PT_STREAM, HB_SOCKET_IPPROTO_TCP )
    hb_socketBind( hListen, { HB_SOCKET_AF_INET, '0.0.0.0', nPUerto } )
    hb_socketListen( hListen )
    hb_MemoWrit( log, hb_MemoRead( log) + hb_Eol() + 'socketserver iniciado en puerto ' + Str(nPUerto) )

    Servicio()

return nil

Function Listen()

    Local hSocket := Nil

    hb_MemoWrit( log, hb_MemoRead( log) + hb_Eol() + Time() +'Escuchando puerto' )

    hSocket := hb_socketAccept( hListen,, 1000  )

    if Empty( hSocket )

        if hb_socketGetError() == HB_SOCKET_ERR_TIMEOUT

                // No se hace nada, simplemente a superado el tiempo de espera para recibir una petición de conexión

        Else

            hb_MemoWrit( log, hb_MemoRead( log) + hb_Eol() + 'accept error ' + hb_ntos( hb_socketGetError() ) )

        endif

    Else

        hb_MemoWrit( log, hb_MemoRead( log) + hb_Eol() + 'conexión aceptada' ) 
        hb_threadDetach( hb_threadStart( @ServeClient(), hSocket, Self ) )

    endif

Return ( Nil )

Function ServeClient()

    hb_MemoWrit( log, hb_MemoRead( log) + hb_Eol() + 'ejecutando thread del cliente' ) 

Return ( Nil )

#pragma BEGINDUMP

#include <windows.h>
#include <tchar.h>
#include <hbvm.h>

SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE hStatus;

void ServiceMain(int argc, char** argv);
void ControlHandler(DWORD request);
int InitService( void );

HB_FUNC( SERVICIO )
{
    SERVICE_TABLE_ENTRY ServiceTable[2];
    ServiceTable[0].lpServiceName = _T("Servicio");
    ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
    ServiceTable[1].lpServiceName = NULL;
    ServiceTable[1].lpServiceProc = NULL;

    StartServiceCtrlDispatcher(ServiceTable);
}

#pragma warn -8057

void ServiceMain(int argc, char** argv )
{
    ServiceStatus.dwServiceType        = SERVICE_WIN32;
    ServiceStatus.dwCurrentState       = SERVICE_START_PENDING;
    ServiceStatus.dwControlsAccepted   = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
    ServiceStatus.dwWin32ExitCode      = 0;
    ServiceStatus.dwServiceSpecificExitCode = 0;
    ServiceStatus.dwCheckPoint         = 0;
    ServiceStatus.dwWaitHint           = 0;

    hStatus = RegisterServiceCtrlHandler(_T("Servicio"), (LPHANDLER_FUNCTION)ControlHandler);

    if (InitService() == 0)
    {
        ServiceStatus.dwCurrentState = SERVICE_RUNNING;
        SetServiceStatus(hStatus, &ServiceStatus);

        // Aquí va el bucle principal del servicio
        while (ServiceStatus.dwCurrentState == SERVICE_RUNNING)
        {
            // Realiza las tareas del servicio
            hb_vmPushSymbol( hb_dynsymSymbol( hb_dynsymFindName( "LISTEN" ) ) );
            hb_vmPushNil();
            hb_vmDo( 0 );

            // Sleep(1000); ya lo hago en el hb_socketaccept
        }
    }

    ServiceStatus.dwCurrentState = SERVICE_STOPPED;
    SetServiceStatus(hStatus, &ServiceStatus);
}

void ControlHandler(DWORD request)
{
    switch(request)
    {
        case SERVICE_CONTROL_STOP:
            ServiceStatus.dwWin32ExitCode = 0;
            ServiceStatus.dwCurrentState = SERVICE_STOPPED;
            SetServiceStatus(hStatus, &ServiceStatus);
            MessageBox( 0, "service stopped", "ok", 0 );
            return;
        case SERVICE_CONTROL_SHUTDOWN:
            ServiceStatus.dwWin32ExitCode = 0;
            ServiceStatus.dwCurrentState = SERVICE_STOPPED;
            SetServiceStatus(hStatus, &ServiceStatus);
            return;
        default:
            break;
    }
    SetServiceStatus(hStatus, &ServiceStatus);
}

int InitService()
{
    // Inicialización del servicio
    return 0;
}

#pragma ENDDUMP
El resultado siguiente es compilado con buildh.bat, funciona correctamente pero al recibir la conexión se para la escucha:
Image

En cambio si lo compilo con buildhmt.bat, no llega a ejecutar la función Listen(), se queda ahí:
Image

puede ser algo de la función del servicio?

Gracias!!

Re: Creando Servicios con Harbour y xHarbour

Posted: Sat Oct 12, 2024 4:32 pm
by VictorCasajuana
Algunas cosas que me he encontrado jugando con los servicios por si alguien llega por aquí y le pueden ser útiles:
sc config servicio5 start= auto configura el servicio para que se ejecute automáticamente al iniciar windows.

A veces te vuelve loco el tema del servicio, puede que lo tengas parado pero cuando compilas te indica el compilador que se está ejecutando el programa y a veces te deja compilar con el servicio en marcha y el servicio te ejecuta el último exe que has compilado automáticamente. Supongo que es porque los servicios de windows van con algún delay interno

Cuando se borra un servicio, en ocasioners no lo hace inmediato, a veces hay que reiniciar

El binPath ha de ser con la ruta completa, de lo contrario lo crea pero luego cuando lo inicias no encuentra el .exe

Salud!

Re: Creando Servicios con Harbour y xHarbour

Posted: Sat Oct 12, 2024 7:35 pm
by Antonio Linares
Victor,

Lo suyo sería construirlo solo con Harbour y no usar FWH porque si hay un error el GUI intentará mostrarse y ahi quedará bloqueado

Re: Creando Servicios con Harbour y xHarbour

Posted: Sun Oct 13, 2024 4:39 pm
by cnavarro
Un ejemplo sacado de hbwin/tests

Code: Select all | Expand

#define _SERVICE_NAME "Harbour_Test_Service"

PROCEDURE Main( cMode )

   LOCAL nError
   LOCAL cMsg

   hb_default( @cMode, "S" ) /* NOTE: Must be the default action */

   SWITCH Upper( cMode )
   CASE "I"

      IF win_serviceInstall( _SERVICE_NAME, "Harbour Windows Test Service" )
         ? "Service has been successfully installed"
      ELSE
         nError := wapi_GetLastError()
         cMsg := Space( 128 )
         wapi_FormatMessage( ,,,, @cMsg )
         ? "Error installing service: " + hb_ntos( nError ) + " " + cMsg
      ENDIF
      EXIT

   CASE "U"

      IF win_serviceDelete( _SERVICE_NAME )
         ? "Service has been deleted"
      ELSE
         nError := wapi_GetLastError()
         cMsg := Space( 128 )
         wapi_FormatMessage( ,,,, @cMsg )
         ? "Error deleting service: " + hb_ntos( nError ) + " " + cMsg
      ENDIF
      EXIT

   CASE "S"

      /* NOTE: Used when starting up as service.
               Do not invoke the executable manually with this option */

      IF win_serviceStart( _SERVICE_NAME, @SrvMain() )
         ? "Service has started OK"
      ELSE
         nError := wapi_GetLastError()
         cMsg := Space( 128 )
         wapi_FormatMessage( ,,,, @cMsg )
         ? "Service has had some problems: " + hb_ntos( nError ) + " " + cMsg
      ENDIF
      EXIT

   ENDSWITCH

   RETURN

#include "fileio.ch"

PROCEDURE SrvMain( cParam1, cParam2 )

   LOCAL n := 0
   LOCAL fhnd := hb_FCreate( hb_FNameExtSet( hb_ProgName(), ".out" ), FC_NORMAL, FO_DENYNONE + FO_WRITE )
   LOCAL cParam

   hb_default( @cParam1, "" )
   hb_default( @cParam2, "" )

   FWrite( fhnd, "Startup" + hb_eol() )
   FWrite( fhnd, "|" + hb_CmdLine() + "|" + hb_eol() )
   FWrite( fhnd, "|" + cParam1 + "|" + cParam2 + "|" + hb_eol() )

   FOR EACH cParam IN hb_AParams()
      FWrite( fhnd, "Parameter " + hb_ntos( cParam:__enumIndex() ) + " >" + cParam + "<" + hb_eol() )
   NEXT

   DO WHILE win_serviceGetStatus() == WIN_SERVICE_RUNNING
      FWrite( fhnd, "Work in progress " + hb_ntos( ++n ) + hb_eol() )
      hb_idleSleep( 0.5 )
   ENDDO

   FWrite( fhnd, "Exiting..." + hb_eol() )
   FClose( fhnd )

   win_serviceSetExitCode( 0 )
   win_serviceStop()

   RETURN


 

Re: Creando Servicios con Harbour y xHarbour

Posted: Mon Oct 14, 2024 5:56 pm
by VictorCasajuana
partiendo del repositorio :

Code: Select all | Expand

https://github.com/FiveTechSoft/wsserver
y aplicado como servicio, pongo un pequeño ejemplo funcional:
es un servicio que incluye un servidor de sockets con multithread para procesar multiples conexiones simultáneas que permiten interactuar con el servicio.

Code: Select all | Expand

//TODO: Definir el path del hb_out.log

#include 'hbwin.ch'
#include "hbsocket.ch"

#DEFINE SERVICE_NAME 'aaa'
#DEFINE HOST '0.0.0.0'
#DEFINE PUERTO 8500
#DEFINE LISTEN_TIMEOUT 1000
#DEFINE LOG 'c:\servicio_hb.log'
#define FILEHEADER "data:application/octet-stream;base64,"
#define JSONHEADER "data:application/json;base64,"
#define HTMLHEADER "data:text/html;base64,"

Function Main()

    fErase( LOG )
    win_serviceStart( SERVICE_NAME, @Servicio() )

Return ( Nil )

Function Servicio()

    Local hListen := Nil
    lOCAL hSocket := Nil

    hListen := hb_socketOpen( HB_SOCKET_AF_INET, HB_SOCKET_PT_STREAM, HB_SOCKET_IPPROTO_TCP )
    hb_socketBind( hListen, { HB_SOCKET_AF_INET, HOST, PUERTO } )
    hb_socketListen( hListen )

    if .Not. hb_mtvm()

        LogServer( 'Multithread no disponible. El servicio no se iniciará'  )
        win_serviceSetExitCode( 1 )
        win_serviceStop()
        Return ( Nil )

    Else

        LogServer( 'Multithread disponible'  )

    Endif


    LogServer( 'socketserver iniciado en puerto ' + Str(PUERTO) )

    While win_serviceGetStatus() == WIN_SERVICE_RUNNING

        LogServer( Time() +' Escuchando puerto' + Str( PUERTO ) )

        hSocket := hb_socketAccept( hListen,, LISTEN_TIMEOUT  )
    
        if Empty( hSocket )
    
            if hb_socketGetError() == HB_SOCKET_ERR_TIMEOUT
    
                    // No se hace nada, simplemente a superado el tiempo de espera para recibir una petición de conexión
    
            Else
    
                LogServer( 'accept error ' + hb_ntos( hb_socketGetError() ) )
    
            endif
    
        Else
    
            LogServer( 'conexión aceptada' )
            hb_threadDetach( hb_threadStart( @ServeClient(), hSocket ) )
    
        endif

    Enddo

    win_serviceSetExitCode( 0 )
    win_serviceStop()

Return ( Nil )

Function ServeClient( hSocket )

    Local cBuffer := Space( 4096 )
    Local lConnectionClosed := .F.
    Local nLen := 0
    Local cRequest := ''
    Local nOpcode := 0

    hb_socketRecv( hSocket, @cBuffer,,, 1024 )
    HandShaking( RTrim( cBuffer ), hSocket )

    while .Not. lConnectionClosed

        LogServer( ThreadId() + ' Esperando petición de cliente' )

        cRequest := ''
        nLen := 1

        while nLen > 0

            cBuffer := Space( 4096 )

            if ( nLen := hb_socketRecv( hSocket, @cBuffer,,, LISTEN_TIMEOUT ) ) > 0  

                cRequest += Left( cBuffer, nLen )

            else

                if nLen == -1 .and. hb_socketGetError() == HB_SOCKET_ERR_TIMEOUT

                    nLen = 0

                endif

            endif

        Enddo

        If !Empty( cRequest )

            cRequest:= UnMask( cRequest, @nOpcode )

            LogServer( ThreadId() + ' Respuesta:' + cRequest )

            switch cRequest

                case 'ACCION1'

                    LogServer( ThreadId() + ' Ejecutando la acción 1' )
                    hb_idleSleep( 1 )
                    LogServer( ThreadId() + ' Fin acción 1' )
                    
                exit

                case 'ACCION2'

                    LogServer( ThreadId() + ' Ejecutando la acción 2' )
                    hb_idleSleep( 1 )
                    LogServer( ThreadId() + ' Fin acción 2' )
                    
                exit

                case 'EXIT'

                    lConnectionClosed := .T.
                    LogServer( ThreadId() + ' Se solicita desconexión' )

                exit

                otherwise

                    LogServer( ThreadId() + ' Petición desconocida ' + cRequest )

                exit

            endswitch

        Else 

            //LogServer( 'Se ha recibido una petición vacia ' + cRequest )

        Endif

    Enddo

    LogServer( 'Desconectando Socket' )
    hb_socketShutdown( hSocket )
    LogServer( 'Cerrando Socket' )
    hb_socketClose( hSocket )

Return ( Nil )

Static Function HandShaking( cHeaders, hSocket )

    local aHeaders as Array := hb_ATokens( cHeaders, hb_Eol() )
    local hHeaders as Hash := {=>}
    Local cLine  as Character := ''
    local cAnswer as Character := ''
     
    for each cLine in aHeaders

        hHeaders[ SubStr( cLine, 1, At( ":", cLine ) - 1 ) ] = SubStr( cLine, At( ":", cLine ) + 2 )

    next
     
    cAnswer = "HTTP/1.1 101 Web Socket Protocol Handshake" + Hb_Eol() + ;
              "Upgrade: websocket" + Hb_Eol() + ;
              "Connection: Upgrade" + Hb_Eol() + ;
              "WebSocket-Origin: " + HOST + Hb_Eol() + ;
              "WebSocket-Location: ws://" + HOST + ":" + hb_ntos( PUERTO ) + Hb_Eol() + ;
              "Sec-WebSocket-Accept: " + ;
              hb_Base64Encode( hb_SHA1( hHeaders[ "Sec-WebSocket-Key" ] + ;
                               "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", .T. ) ) + Hb_Eol() + Hb_Eol()
    
    LogServer( 'Enviando HandShaking' )
    hb_socketSend( hSocket, cAnswer ) 
     
Return ( Nil )

Static function Unmask( cBytes, nOpcode )
   
    local lComplete := hb_bitTest( hb_bPeek( cBytes, 1 ), 7 )
    local nFrameLen := hb_bitAnd( hb_bPeek( cBytes, 2 ), 127 ) 
    local nLength, cMask, cData, cChar, cHeader := ""
 
    nOpcode := hb_bitAnd( hb_bPeek( cBytes, 1 ), 15 )
 
    do case
       case nFrameLen <= 125
          nLength = nFrameLen
          cMask = SubStr( cBytes, 3, 4 )
          cData = SubStr( cBytes, 7 )
 
       case nFrameLen = 126
          nLength = ( hb_bPeek( cBytes, 3 ) * 256 ) + hb_bPeek( cBytes, 4 )
          cMask   = SubStr( cBytes, 5, 4 )
          cData   = SubStr( cBytes, 9 )
 
       case nFrameLen = 127  
          nLength = NetworkBin2ULL( SubStr( cBytes, 3, 8 ) )  
          cMask   = SubStr( cBytes, 11, 4 )
          cData   = SubStr( cBytes, 15 )
    endcase 
 
    cBytes = ""

    for each cChar in cData
       cBytes += Chr( hb_bitXor( Asc( cChar ),;
                      hb_bPeek( cMask, ( ( cChar:__enumIndex() - 1 ) % 4 ) + 1 ) ) ) 
    next   
 
    do case
       case Left( cBytes, Len( FILEHEADER ) ) == FILEHEADER
          cBytes = hb_base64Decode( SubStr( cBytes, Len( FILEHEADER ) + 1 ) )
          cHeader = FILEHEADER 
 
       case Left( cBytes, Len( JSONHEADER ) ) == JSONHEADER
          cBytes = hb_base64Decode( SubStr( cBytes, Len( JSONHEADER ) + 1 ) )
          cHeader = JSONHEADER
 
       case Left( cBytes, Len( HTMLHEADER ) ) == HTMLHEADER
          cBytes = hb_base64Decode( SubStr( cBytes, Len( HTMLHEADER ) + 1 ) )
          cheader = HTMLHEADER
    endcase
    
 return cBytes 

  Static function NetworkBin2ULL( cBytes )

    local cByte, n := 0
    
    for each cByte in cBytes

       n += hb_BitShift( Asc( cByte ), 64 - cByte:__enumIndex() * 8 )

    next
    
 return n

 function NetworkULL2Bin( n )

    local nBytesLeft := 64
    local cBytes := ""
 
    while nBytesLeft > 0

       nBytesLeft -= 8
       cBytes += Chr( hb_BitAnd( hb_BitShift( n, -nBytesLeft ), 0xFF ) )

    end
 
 return cBytes
 
Static Function LogServer( cMessage )

    hb_MemoWrit( LOG, hb_MemoRead( LOG ) + hb_Eol() + cMessage )

Return ( Nil )

Static Function ThreadId()
Return ( '[' + hb_ntos( hb_threadId( hb_threadSelf( ) ) ) + ']......' )
Aquí una pequeña demo donde se ve el estado inicial del servicio, como se inicia, dos peticiones simultáneas por postman ( se pueden hacer mediante un cliente ce HB, javascript, o lo que uno quiera ) lo que se ejecuta dentro de cada petición lleva entre corchetes el thread correspondiente, y todo reflejado en un log, sin GUI para que no haya problemas de interrupciones del servicio por errores. Por supuesto, al ser un servicio ha de llevar un control de errores de cada acción que se hace, pero esto es solo una pequeña demo funcional que he montado por si a alguien le sirve.

Image

Gracias Antonio por tu código y consejos y también al resto por ayudarme.

Salud!

Re: Creando Servicios con Harbour y xHarbour

Posted: Mon Oct 14, 2024 7:59 pm
by wilsongamboa
Felicitaciones Victor excelente tu trabajo
podrias poner un cliente con harbour para los que no tenemos mucha idea
que envie datos a ese server y recoja la respuesta

gracias
Wilson