partiendo del repositorio :
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.
//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.
Gracias Antonio por tu código y consejos y también al resto por ayudarme.
Salud!