FiveTech Support Forums

FiveWin / Harbour / xBase community
Board index FiveWin para Harbour/xHarbour SOCKETS - Hay que reiniciar ordenador para volver a conectar
Posts: 563
Joined: Sun Oct 09, 2005 07:23 PM
SOCKETS - Hay que reiniciar ordenador para volver a conectar
Posted: Sun May 23, 2021 08:46 PM
Me estoy encontrando con el siguiente problema:
Tengo desarrollado un cliente sockets para conectar con un ordenador y un programa externos que hacen de servidor.
Todo va bien pero si salgo de mi programa y vuelvo a tratar de conectar el otro ordenador (servidor) parece no devolver nunca connect, esto solo se soluciona reiniciando el ordenador que hace de cliente.
Es como si algo se quedara pillado con la tarjeta de red o con el socket, de manera que al salir del programa lo dejara bloqueado.
He tratado de solucionarlo incluyendo un shutdown() en el m茅todo end de la clase Tsocket pero sigue igual.

Copio el c贸digo incluido en la clase Tsocket para tratar de solucionarlo aunque no ha funcionado:

Code (fw): Select all Collapse
#define SD_RECEIVE 聽 聽 聽 聽 0
#define SD_SEND 聽 聽 聽 聽 聽 聽1
#define SD_BOTH 聽 聽 聽 聽 聽 聽2

...

METHOD End() CLASS TSocket

聽 聽local nAt := AScan( ::aSockets, { | oSocket | oSocket:nSocket == ::nSocket 聽} )

聽 聽while ::lSending
聽 聽 聽 SysRefresh()
聽 聽end
聽
聽 聽//A帽adido el 23/05/2021 para ver si al salir del programa y volver a entrar no hay problema para volver a conectar con el servidor, pero no lo arregla.
聽 聽if shutdown( ::nSocket, SD_BOTH ) <> 0
聽 聽 聽 聽msgwait('ERROR: ShutDown(), WSAGetLastError = '+alltrim(str(WSAGetLastError())),"oSocket:SendData",2)
聽 聽 聽 else
聽 聽 聽 聽msgwait('OK, ShutDown() == 0',"oSocket:Close()",2)
聽 聽endif

聽 聽if nAt != 0
聽 聽 聽 ADel( ::aSockets, nAt )
聽 聽 聽 ASize( ::aSockets, Len( ::aSockets ) - 1 )
聽 聽 聽 if Len( ::aSockets ) == 0
聽 聽 聽 聽 聽WSACleanUp()
聽 聽 聽 endif
聽 聽endif
聽 聽
聽 聽if ! Empty( ::nSocket )
聽 聽 聽 CloseSocket( ::nSocket )
聽 聽 聽 ::nSocket = 0
聽 聽endif

return nil

...

DLL32 STATIC FUNCTION shutdown( dwReserved AS LONG, lpdwReserved AS LONG);
聽 聽 聽 AS LONG PASCAL FROM "shutdown" LIB "ws2_32.dll" 聽// "wsock32.dll" 聽tambi茅n vale
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: SOCKETS - Hay que reiniciar ordenador para volver a conectar
Posted: Mon May 24, 2021 02:16 PM
Pon esta traza y comprueba que pase por ahi:

Code (fw): Select all Collapse
   if nAt != 0
      ADel( ::aSockets, nAt )
      ASize( ::aSockets, Len( ::aSockets ) - 1 )
      if Len( ::aSockets ) == 0
         MsgInfo( "si" )
         WSACleanUp()
      endif
   endif
regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 563
Joined: Sun Oct 09, 2005 07:23 PM
Re: SOCKETS - Hay que reiniciar ordenador para volver a conectar
Posted: Mon May 24, 2021 04:18 PM

Hola Antonio.
Lo he comprobado y no pasa por WSACleanUp().
La matriz ::aSockets contiene m谩s de un socket. Y este es el problema, porque al hacer end() si queda alguno distinto del que ha creado el programa, como la condici贸n para pasar por WSACleanUp() es que no quede ninguno, esto impide que pase por WSACleanUp().

驴C贸mo se puede arreglar? porque el otro socket lo est谩 creando otro programa que corre en el ordenador y no se si al hacer WSACleanUp() voy a interferir en el otro programa.

Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: SOCKETS - Hay que reiniciar ordenador para volver a conectar
Posted: Mon May 24, 2021 04:37 PM

Cambia MsgInfo( "si" ) por MsgInfo( Len( ::aSockets ) ) 贸 mejor por XBrowser( ::aSockets ) para averiguar que sockets hay en el array

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 563
Joined: Sun Oct 09, 2005 07:23 PM
Re: SOCKETS - Hay que reiniciar ordenador para volver a conectar
Posted: Mon May 24, 2021 05:02 PM

Me aparecen dos sockets en el array, pero mi programa solo est谩 creando uno.
Si salgo del otro programa que usa sokets en ese mismo ordenador y arranco el m铆o solamente, entonces me sale solo uno.
Es como si la librer铆a de windows llevara la cuenta de todos los sockets abiertos en el ordenador y por esto aSockets contiene dos el de mi programa y el del otro programa.

Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: SOCKETS - Hay que reiniciar ordenador para volver a conectar
Posted: Mon May 24, 2021 06:32 PM

Puedes proporcionar un ejemplo completo para probarlo aqui ?

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 563
Joined: Sun Oct 09, 2005 07:23 PM
Re: SOCKETS - Hay que reiniciar ordenador para volver a conectar
Posted: Tue May 25, 2021 10:57 AM
Buenas tardes Antonio.
Paso la clase TSockets con algunas modificaciones que le he hecho para:
Evitar en el m茅todo SendData entrar en un bucle sin fin si falla el env铆o.
SendData en vez de los byte totales a enviar devuelve los enviados porque cuando el valor es -1 significa que ha habido fallos.
Le a帽ado una m铆nima descripci贸n de errores.
Tambi茅n incorporo la funci贸n shutdown() porque he le铆do en la informaci贸n que ofrece Microsoft que conviene ejecutarla antes de CloseSocket().
Y una funci贸n para imprimir en un log la descripci贸n de los errores.

Code (fw): Select all Collapse
// FiveWin WinSocket.dll support !!!

/* 25/05/2021
   MODIFICACION DE LA CLASE ORIGINAL TSOCKET:
     El problema radicaba en que la clase TSocket original de FiveWin no gestiona los errores 
     cuando trata de enviar datos, EL FALLO CONSISTE en que deja al m茅todo SendData en un bucle
     infinito. He modificado TSockets para gestionarlo.

  
   Referencia de sockets propios de harbour (No usados en esta clase).
   https://github.com/Petewg/harbour-core/wiki/Harbour-Socket-API
*/

#include "FiveWin.ch"

#define AF_INET            2
#define SOCK_STREAM        1

#define IPPROTO_IP         0
#define SOL_SOCKET        -1

#define FD_READ            1
#define FD_WRITE           2
#define FD_OOB             4
#define FD_ACCEPT          8
#define FD_CONNECT        16
#define FD_CLOSE          32

#define SD_RECEIVE         0
#define SD_SEND            1
#define SD_BOTH            2

#define SO_REUSEADDR       4

#define FILE_BLOCK     30000

/*  ERRORES DE CONEXION SOCKETS DE WINSOCK2   :
    <!-- m --><a class="postlink" href="https://docs.microsoft.com/es-es/windows/win32/winsock/windows-sockets-error-codes-2">https://docs.microsoft.com/es-es/window ... or-codes-2</a><!-- m -->     */

#define WSAEWOULDBLOCK 10035  // El buffer de env铆o est谩 lleno.
/* WSAEWOULDBLOCK is not really an error but simply tells you that your send buffers are full. 
   This can happen if you saturate the network or if the other side simply doesn't acknowledge the received data.*/

#define WSAECONNRESET  10054  //El host remoto cort贸 la conexi贸n.

#define WSAENOTCONN    10057  /*  Socket is not connected.
                                  A request to send or receive data was disallowed because the socket is not connected 
                                  and (when sending on a datagram socket using sendto) no address was supplied. 
                                  Any other type of operation might also return this error鈥攆or example, 
                                  setsockopt setting SO_KEEPALIVE if the connection has been reset.   */

#ifdef __XPP__
   #define New _New
#endif

//----------------------------------------------------------------------------//

CLASS GSocket

   DATA    nPort    AS NUMERIC INIT  0      // socket port number
   DATA    cIPAddr  AS String  INIT ""      // socket IP address
   DATA    nTimeOut AS NUMERIC INIT 30
   DATA    nBackLog AS NUMERIC INIT  5
   DATA    nSocket  AS NUMERIC INIT -1
   DATA    hFile    AS NUMERIC INIT  0

   DATA    bAccept, bRead, bWrite, bClose, bConnect, bOOB
   DATA    lDebug
   DATA    cLogFile

   DATA    cMsg, nRetCode, Cargo
   DATA    aBuffer                         // data sending buffer
   DATA    lSending                        // sending in progress

   CLASSDATA aSockets INIT {}

   METHOD  New( nPort, oWnd )  CONSTRUCTOR

   MESSAGE Accept METHOD _Accept( nSocket )

   METHOD  End()

   METHOD  HandleEvent( nSocket, nOperation, nErrorCode )

   METHOD  GetData()

   METHOD  SendBin( pMemory, nSize ) INLINE SendBinary( pMemory, nSize )

   METHOD  SendChunk( nBlockSize )
   METHOD  SendFile( cFileName, nBlockSize )

   METHOD SendData( cData )
   
   MESSAGE Listen METHOD _Listen()

   METHOD  Close()

   METHOD  Connect( cIPAddr, nPort ) INLINE ;
         ConnectTo( ::nSocket, If( nPort != nil, nPort, ::nPort ), cIPAddr )

   METHOD  Refresh() INLINE SocketSelect( ::nSocket )

   METHOD  OnAccept()  INLINE If( ::bAccept  != nil, Eval( ::bAccept,  Self ),)
   METHOD  OnRead()    INLINE If( ::bRead    != nil, Eval( ::bRead,    Self ),)
   METHOD  OnWrite()   INLINE If( ::bWrite   != nil, Eval( ::bWrite,   Self ),)
   METHOD  OnClose()   INLINE If( ::bClose   != nil, Eval( ::bClose,   Self ),)
   METHOD  OnConnect( nErrorCode ) INLINE If( ::bConnect != nil, Eval( ::bConnect, Self, nErrorCode ),)
   METHOD  OnOOB()     INLINE If( ::bOOB     != nil, Eval( ::bOOB,     Self ),)

   METHOD  ClientIP()  INLINE GetPeerName( ::nSocket )

ENDCLASS

//----------------------------------------------------------------------------//

METHOD New( nPort, oWnd ) CLASS GSocket

   DEFAULT oWnd := WndMain(), ::aSockets := {}

   if Len( ::aSockets ) == 0
      if WSAStartup() != 0
         MsgAlert( "WSAStartup error" )
      endif
   endif

   if ( ::nSocket := Socket( AF_INET, SOCK_STREAM, IPPROTO_IP ) ) == 0
      MsgAlert( "Socket creation error: " + Str( WsaGetLastError() ) )
   endif
   //msginfo(::nSocket,"wintpv: Nuevo socket creado")
   
   ::cIPAddr  = GetHostByName( GetHostName() )  //"127.1.1.1"
   ::aBuffer  = {}
   ::lSending = .f.
   ::lDebug   = .f.

   if nPort != nil
      ::nPort = nPort
      BindToPort( ::nSocket, nPort )  // Bind is not needed for connect sockets
   endif

   AAdd( ::aSockets, Self )
   //msginfo(Len( ::aSockets ),"Sockets totales creados con este nuevo:")
   if oWnd != nil
      oWnd:bSocket = { | nSocket, nLParam | ::HandleEvent( nSocket,;
                         nLoWord( nLParam ), nHiWord( nLParam ) ) }

      WSAAsyncSelect( ::nSocket, oWnd:hWnd, WM_ASYNCSELECT,;
            nOr( FD_ACCEPT, FD_OOB, FD_READ, FD_CLOSE, FD_CONNECT, FD_WRITE ) )
   else
      MsgAlert( "You must create a main window in order to use a GSocket object" )
   endif

return Self

//----------------------------------------------------------------------------//

METHOD _Accept( nSocket ) CLASS GSocket

   ::nSocket  = Accept( nSocket )
   ::aBuffer  = {}
   ::lSending = .f.
   ::lDebug   = .f.

   AAdd( ::aSockets, Self )

   WSAAsyncSelect( ::nSocket, WndMain():hWnd, WM_ASYNCSELECT,;
      nOr( FD_ACCEPT, FD_OOB, FD_READ, FD_CLOSE, FD_CONNECT, FD_WRITE ) )

return Self

//----------------------------------------------------------------------------//

METHOD GetData() CLASS GSocket

   local cData := ""

   ::nRetCode = Recv( ::nSocket, @cData )

   if ::lDebug .AND. ! Empty( ::cLogFile )
      LogFile( ::cLogFile, { cData } )
   endif

return cData

//----------------------------------------------------------------------------//

METHOD _Listen() CLASS GSocket

   local nRetCode := Listen( ::nSocket, ::nBackLog )

return ( nRetCode == 0 )

//----------------------------------------------------------------------------//

METHOD End() CLASS GSocket

   local nAt := AScan( ::aSockets, { | oSocket | oSocket:nSocket == ::nSocket  } )
   local nShutdown:=0

   while ::lSending
      SysRefresh()
   end
 
/*   if nAt != 0
      ADel( ::aSockets, nAt )
      ASize( ::aSockets, Len( ::aSockets ) - 1 )
      if Len( ::aSockets ) == 0
         msginfo("antes de WSACleanUp()")
         WSACleanUp()
      endif
   endif */
   
   //A帽adido el 23/05/2021 para ver si al salir del programa y volver a entrar no hay problema 
   // para volver a conectar con el servidor.
   if (nShutdown:=shutdown( ::nSocket, SD_BOTH )) <> 0
       //msginfo( nShutdown, "Shutdown()" )
       //msgwait('ERROR: ShutDown(), WSAGetLastError = '+alltrim(str(WSAGetLastError())),"oSocket:ShutDown()",3)
       EscribeEnFichTxt(dtos(Date())+' '+time()+;
                      ' ERROR: ShutDown('+alltrim(str(::nSocket))+'), WSAGetLastError = '+alltrim(str(WSAGetLastError())),;
                      'LOG_SOCKETS.TXT',.T.,2)
      //else
       //msgwait('OK, ShutDown() == '+str(nShutdown),"oSocket:ShutDown()",2)
   endif
      
   if ! Empty( ::nSocket )
      CloseSocket( ::nSocket )
      ::nSocket = 0
   endif

   if nAt != 0
      ADel( ::aSockets, nAt )
      ASize( ::aSockets, Len( ::aSockets ) - 1 )
      if Len( ::aSockets ) == 0
         //msginfo("antes de WSACleanUp()")
         //EscribeEnFichTxt(dtos(Date())+" "+time()+" WSACleanUp() OK",'LOG_SOCKETS.TXT',.T.,2)
         WSACleanUp()
      endif
   endif 

return nil

//----------------------------------------------------------------------------//

METHOD Close() CLASS GSocket
  
   while ::lSending
      SysRefresh()
   end

return CloseSocket( ::nSocket )

//----------------------------------------------------------------------------//

METHOD HandleEvent( nSocket, nOperation, nErrorCode ) CLASS GSocket

   local nAt := AScan( ::aSockets, { | oSocket | oSocket:nSocket == nSocket } )
   local oSocket

   if nAt != 0
      oSocket = ::aSockets[ nAt ]

      do case
         case nOperation == FD_ACCEPT
              if ::lDebug .AND. ! Empty( ::cLogFile )
                 LogFile( ::cLogFile, { "Accept",;
                          "Socket handle:" + Str( nSocket ) } )
              endif
              oSocket:OnAccept()

         case nOperation == FD_READ
              if ::lDebug .AND. ! Empty( ::cLogFile )
                 LogFile( ::cLogFile, { "Read",;
                          "Socket handle:" + Str( nSocket ) } )
              endif
              oSocket:OnRead()

         case nOperation == FD_WRITE
              if ::lDebug .AND. ! Empty( ::cLogFile )
                 LogFile( ::cLogFile, { "Write",;
                          "Socket handle:" + Str( nSocket ) } )
              endif
              oSocket:OnWrite()

         case nOperation == FD_CLOSE
              if ::lDebug .AND. ! Empty( ::cLogFile )
                 LogFile( ::cLogFile, { "Close",;
                          "Socket handle:" + Str( nSocket ) } )
              endif
              oSocket:OnClose()

         case nOperation == FD_CONNECT
              if ::lDebug .AND. ! Empty( ::cLogFile )
                 LogFile( ::cLogFile, { "Connect",;
                          "Socket handle:" + Str( nSocket ) } )
              endif
              oSocket:OnConnect( nErrorCode )

         case nOperation == FD_OOB
              if ::lDebug .AND. ! Empty( ::cLogFile )
                 LogFile( ::cLogFile, { "OOB",;
                          "Socket handle:" + Str( nSocket ) } )
              endif
              oSocket:OnOOB()

         otherwise
              if ::lDebug .AND. ! Empty( ::cLogFile )
                 LogFile( ::cLogFile, { "nOperation not recognized",;
                          Str( nOperation ) } )
              endif
      endcase
   endif

return nil

//----------------------------------------------------------------------------//

METHOD SendChunk( nBlockSize ) CLASS GSocket

   local cBuffer, nBytes := 0

   DEFAULT nBlockSize := FILE_BLOCK

   cBuffer = Space( nBlockSize )

   if ::hFile != 0
      nBytes = FRead( ::hFile, @cBuffer, nBlockSize )
      if nBytes < nBlockSize
         cBuffer = SubStr( cBuffer, 1, nBytes )
         FClose( ::hFile )
         ::hFile = 0
      endif

      ::SendData( cBuffer )
   end

return nBytes

//----------------------------------------------------------------------------//

METHOD SendFile( cFileName, nBlockSize ) CLASS GSocket

   DEFAULT nBlockSize := FILE_BLOCK

   if ! Empty( cFileName ) .AND. File( cFileName )
      If( ( ::hFile := FOpen( cFileName ) ) != -1 )
         while ::SendChunk( nBlockSize ) == nBlockSize
         end
      endif
   endif

return nil


METHOD SendData( cData ) CLASS GSocket
   local nSize := Len( cData )
   local nLen  := nSize
   local nSent := 0
   local nIntentos := 3
   
   local nErrorWSA := 0, cErrDesc:=""

   if ! ::lSending
      ::lSending = .t.
    else
      aadd( ::aBuffer, cData )
      return nSize
   endif

   while ( nLen > 0 .AND. ;
           ( nSent := SocketSend( ::nSocket, cData ) ) < nLen ) .OR. ;
         len( ::aBuffer ) > 0
      
      // Check for buffered packets to send
      if nLen == 0 .AND. Len( ::aBuffer ) > 0
         cData = ::aBuffer[ 1 ]
         ADel( ::aBuffer, 1 )
         ASize( ::aBuffer, len( ::aBuffer ) - 1 )
      endif
      if nSent != -1  //No hay error en el env铆o.
         cData = SubStr( cData, nSent + 1 )
         nLen  = len( cData )
        else          //Ha habido error en el env铆o.
         nErrorWSA = WSAGetLastError()
         if nErrorWSA != WSAEWOULDBLOCK  //Buffer lleno
            exit
           else  //WSAEWOULDBLOCK => Buffer lleno. Reintenta el env铆o hasta nIntentos veces.
            if nIntentos > 0
               nIntentos = nIntentos - 1
               RetardoSecs(1)
               sysrefresh()
               loop
             else
               exit  // 14/08/2018   Sale para no quedar en un bucle sin fin si hay errores WINSOCK2.
            endif
         endif
      endif
      sysrefresh()
   enddo

   if nSent == -1
     //Descripci贸n del Error:
     do CASE
        CASE nErrorWSA == WSAENOTCONN   //Socket is not connected.
             cErrDesc:="Socket is not connected."
             // Tiene que volver a conectar el Socket porque se ha desconectado.
        
        CASE nErrorWSA == WSAECONNRESET //El host remoto cort贸 la conexi贸n.
             cErrDesc:="El host remoto cort贸 la conexi贸n."
             
        CASE nErrorWSA != WSAEWOULDBLOCK
             cErrDesc:="Buffer Send lleno."

     ENDCASE

     //msgwait('ERROR: SocketSend(), WSAGetLastError = '+alltrim(str(nErrorWSA)),"oSocket:SendData",2)
     EscribeEnFichTxt(dtos(Date())+' '+time()+;
                      ' ERROR: SocketSend(), WSAGetLastError = '+alltrim(str(nErrorWSA))+;
                      ' FROM '+iif(empty(::cIPAddr),"ip ???",::cIPAddr)+":"+iif(empty(::nPort),"port ???",alltrim(str(::nPort)))+" = "+cErrDesc,'LOG_SOCKETS.TXT',.T.,2)
     
   endif

   // if ::lDebug .AND. ! Empty( ::cLogFile )
   //    LogFile( ::cLogFile, { cData } )
   // endif

   ::lSending = .f.

return nSent // Propongo nSent en vez de nSize porque Si nSent = -1 es que la instrucci贸n no ha ido bien, hay errores. //nSize


//----------------------------------------------------------------------------//

function GShowIP()
 
   local oSocket := GSocket():New( 2000 )
   local cIp := oSocket:cIPAddr 
   
   oSocket:End() 
   
return cIp

//----------------------------------------------------------------------------//

/* Escribe en un fichero txt.
   A帽ade la l铆nea si lAppend:=.t. o NIL
   Devuelve: .F. si no tuvo exito al abrir el fichero.
             .T. si tuvo exito.
*/
function EscribeEnFichTxt(cMensaje,cFich,lAppend,Intentos,lAvisoError,lBorraFichExistente,lAnadeCRLFfinal)
local lValRet:=.f.
local nLongFichero:=0
local nLongRec:=len(cMensaje)
local nManejador:=-1

default cFich    := "LOG.TXT"
default lAppend  := .T.
default Intentos := 1
default lAvisoError:=.t.
default lBorraFichExistente:=.F.
default lAnadeCRLFfinal:=.T.

if lBorraFichExistente
   ferase(cFich)
endif

while lValRet=.f. .AND. intentos > 0
 intentos=intentos-1
 nManejador := iif(FILE(cFich)                        ,;
                   FOPEN(cfich,FO_READWRITE+FO_SHARED),;
                   FCREATE(cFich, FC_NORMAL))
  
 if FERROR() = 0
   //Longitud del fichero y se sit煤a al final del fichero.
   nLongFichero:=FSEEK(nManejador,0,FS_END)
  
   //Devuelve a la posici贸n inicial si lAppend=.f.
   iif( lAppend, NIL, FSEEK(nManejador,0) )
  
   // Escribe el mensaje
   iif( FWRITE(nManejador, cMensaje+iif(lAnadeCRLFfinal,CRLF,"")) < nLongRec, lValRet:=.f., lValRet:=.t. )

   FCLOSE(nManejador)
  else
   if lAvisoError
      msgwait('ERROR AL ABRIR FICHERO:'+CRLF+cFich,'EscribeFich',1)
   endif
 endif
enddo
return lValRet

DLL32 STATIC FUNCTION shutdown( dwReserved AS LONG, lpdwReserved AS LONG);
      AS LONG PASCAL FROM "shutdown" LIB "ws2_32.dll"  // "wsock32.dll"  tambi茅n vale


Como el servidor lo he desarrollado a partir del c贸digo que aparece en sockserv.prg que viene con FiveWin, observo que con cada bAccept que recibe el servidor el sistema crea un nuevo socket, sucediendo que cuando se "mata" el socket correspondiente al servidor NO se matan sin embargo a sus clientes asociados creados en el c贸digo bAccept.
Por esto hay que tener la precauci贸n de ir matando todos los sockets de los clientes creados para evitar que se queden vivos y que el c贸digo del m茅todo end() de TSocket no llegue nunca a ejecutar WSACleanUp().

Code (fw): Select all Collapse
//C贸digo para crear el servidor de sockets:
static oServerSocket    //Socket SERVIDOR
static oClientSocket     //Socket para atender al cliente que se conecta con el servidor que se crea en bAccept del socket servidor (oServerSocket).

. . .

function CreaSocketServidor()
   oServerSocket := GSocket():New( nPuerto )

   oServerSocket:bAccept = { | oServerSocket | oClientSocket := GSocket():Accept( oServerSocket:nSocket ),;
                       oClientSocket:Cargo  := NIL,;
                       oClientSocket:bRead  := { | oServerSocket | SocketOnRead( oServerSocket ) },;
                       oClientSocket:bClose := { | oServerSocket | SocketOnClose( oServerSocket ) } }

   lServerSocketActivo = .T.
   
   oServerSocket:Listen()
return NIL

function SocketOnClose( oSocket )

    lServerSocketActivo = .F.

    oClientSocket:END()  //Necesario porque el cliente cuando se conecta crea un nuevo socket
                                   // que hay que eliminar para que GSocket, en el m茅todo end(), ejecute WSACleanUp().
return nil


Espero haberme explicado bien...
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: SOCKETS - Hay que reiniciar ordenador para volver a conectar
Posted: Tue May 25, 2021 05:09 PM

Parece que static oClientSocket se sobreescribir铆a en una siguiente petici贸n

Deberias usar un aClientSockets := {} e ir a帽adiendo cada uno de los clientes a 茅l

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 563
Joined: Sun Oct 09, 2005 07:23 PM
Re: SOCKETS - Hay que reiniciar ordenador para volver a conectar
Posted: Tue May 25, 2021 06:37 PM

Lo hab铆a pensado, el problema es que cuando el servidor recibe un close desde el cliente no encuentro la manera de identificar cual ha sido el cliente que lo ha mandado. Con lo cual no se que cliente asociado es el que tengo que "matar".

Posts: 8515
Joined: Tue Dec 20, 2005 07:36 PM
Re: SOCKETS - Hay que reiniciar ordenador para volver a conectar
Posted: Tue May 25, 2021 06:52 PM
Complete, porfa:

Code (fw): Select all Collapse
// FiveWin WinSocket.dll support !!!

/* 25/05/2021
   MODIFICACION DE LA CLASE ORIGINAL TSOCKET:
     El problema radicaba en que la clase TSocket original de FiveWin no gestiona los errores
     cuando trata de enviar datos, EL FALLO CONSISTE en que deja al m茅todo SendData en un bucle
     infinito. He modificado TSockets para gestionarlo.


   Referencia de sockets propios de harbour (No usados en esta clase).
   https://github.com/Petewg/harbour-core/wiki/Harbour-Socket-API
*/

#include "FiveWin.ch"
#include "Fileio.ch"

#define AF_INET            2
#define SOCK_STREAM        1

#define IPPROTO_IP         0
#define SOL_SOCKET        -1

#define FD_READ            1
#define FD_WRITE           2
#define FD_OOB             4
#define FD_ACCEPT          8
#define FD_CONNECT        16
#define FD_CLOSE          32

#define SD_RECEIVE         0
#define SD_SEND            1
#define SD_BOTH            2

#define SO_REUSEADDR       4

#define FILE_BLOCK     30000

/*  ERRORES DE CONEXION SOCKETS DE WINSOCK2   :
    <!-- m --><a class="postlink" href="https://docs.microsoft.com/es-es/window">https://docs.microsoft.com/es-es/window</a><!-- m --> ... or-codes-2     */

#define WSAEWOULDBLOCK 10035  // El buffer de env铆o est谩 lleno.
/* WSAEWOULDBLOCK is not really an error but simply tells you that your send buffers are full.
   This can happen if you saturate the network or if the other side simply doesn't acknowledge the received data.*/

#define WSAECONNRESET  10054  // El host remoto cort贸 la conexi贸n.

#define WSAENOTCONN    10057  /*  Socket is not connected.
                                  A request to send or receive data was disallowed because the socket is not connected
                                  and (when sending on a datagram socket using sendto) no address was supplied.
                                  Any other type of operation might also return this error鈥攆or example,
                                  setsockopt setting SO_KEEPALIVE if the connection has been reset.   */

#ifdef __XPP__
#define New _New
#endif

// ----------------------------------------------------------------------------//

CLASS GSocket

   DATA    nPort    AS NUMERIC INIT  0      // socket port number
   DATA    cIPAddr  AS String  INIT ""      // socket IP address
   DATA    nTimeOut AS NUMERIC INIT 30
   DATA    nBackLog AS NUMERIC INIT  5
   DATA    nSocket  AS NUMERIC INIT -1
   DATA    hFile    AS NUMERIC INIT  0

   DATA    bAccept, bRead, bWrite, bClose, bConnect, bOOB
   DATA    lDebug
   DATA    cLogFile

   DATA    cMsg, nRetCode, Cargo
   DATA    aBuffer                         // data sending buffer
   DATA    lSending                        // sending in progress

   CLASSDATA aSockets INIT {}

   METHOD  New( nPort, oWnd )  CONSTRUCTOR

   MESSAGE ACCEPT METHOD _Accept( nSocket )

   METHOD  End()

   METHOD  HandleEvent( nSocket, nOperation, nErrorCode )

   METHOD  GetData()

   METHOD  SendBin( pMemory, nSize ) INLINE SendBinary( pMemory, nSize )

   METHOD  SendChunk( nBlockSize )
   METHOD  SendFile( cFileName, nBlockSize )

   METHOD SendData( cData )

   MESSAGE Listen METHOD _Listen()

   METHOD  Close()

   METHOD  Connect( cIPAddr, nPort ) INLINE ;
      ConnectTo( ::nSocket, If( nPort != NIL, nPort, ::nPort ), cIPAddr )

   METHOD  Refresh() INLINE SocketSelect( ::nSocket )

   METHOD  OnAccept()  INLINE If( ::bAccept  != NIL, Eval( ::bAccept,  Self ), )
   METHOD  OnRead()    INLINE If( ::bRead    != NIL, Eval( ::bRead,    Self ), )
   METHOD  OnWrite()   INLINE If( ::bWrite   != NIL, Eval( ::bWrite,   Self ), )
   METHOD  OnClose()   INLINE If( ::bClose   != NIL, Eval( ::bClose,   Self ), )
   METHOD  OnConnect( nErrorCode ) INLINE If( ::bConnect != NIL, Eval( ::bConnect, Self, nErrorCode ), )
   METHOD  OnOOB()     INLINE If( ::bOOB     != NIL, Eval( ::bOOB,     Self ), )

   METHOD  ClientIP()  INLINE GetPeerName( ::nSocket )

ENDCLASS

// ----------------------------------------------------------------------------//

METHOD New( nPort, oWnd ) CLASS GSocket

   DEFAULT oWnd := WndMain(), ::aSockets := {}

   IF Len( ::aSockets ) == 0
      IF WSAStartup() != 0
         MsgAlert( "WSAStartup error" )
      ENDIF
   ENDIF

   IF ( ::nSocket := Socket( AF_INET, SOCK_STREAM, IPPROTO_IP ) ) == 0
      MsgAlert( "Socket creation error: " + Str( WsaGetLastError() ) )
   ENDIF
   // msginfo(::nSocket,"wintpv: Nuevo socket creado")

   ::cIPAddr  = GetHostByName( GetHostName() )  // "127.1.1.1"
   ::aBuffer  = {}
   ::lSending = .F.
   ::lDebug   = .F.

   IF nPort != nil
      ::nPort = nPort
      BindToPort( ::nSocket, nPort )  // Bind is not needed for connect sockets
   ENDIF

   AAdd( ::aSockets, Self )
   // msginfo(Len( ::aSockets ),"Sockets totales creados con este nuevo:")
   IF oWnd != nil
      oWnd:bSocket = {| nSocket, nLParam | ::HandleEvent( nSocket, ;
         nLoWord( nLParam ), nHiWord( nLParam ) ) }

      WSAAsyncSelect( ::nSocket, oWnd:hWnd, WM_ASYNCSELECT, ;
         nOr( FD_ACCEPT, FD_OOB, FD_READ, FD_CLOSE, FD_CONNECT, FD_WRITE ) )
   ELSE

      MsgAlert( "You must create a main window in order to use a GSocket object" )

   ENDIF

   RETURN Self

// ----------------------------------------------------------------------------//

METHOD _Accept( nSocket ) CLASS GSocket

   ::nSocket  = Accept( nSocket )
   ::aBuffer  = {}
   ::lSending = .F.
   ::lDebug   = .F.

   AAdd( ::aSockets, Self )

   WSAAsyncSelect( ::nSocket, WndMain():hWnd, WM_ASYNCSELECT, ;
      nOr( FD_ACCEPT, FD_OOB, FD_READ, FD_CLOSE, FD_CONNECT, FD_WRITE ) )

   RETURN Self

// ----------------------------------------------------------------------------//

METHOD GetData() CLASS GSocket

   LOCAL cData := ""

   ::nRetCode = Recv( ::nSocket, @cData )

   IF ::lDebug .AND. ! Empty( ::cLogFile )
      LogFile( ::cLogFile, { cData } )
   ENDIF

   RETURN cData

// ----------------------------------------------------------------------------//

METHOD _Listen() CLASS GSocket

   LOCAL nRetCode := Listen( ::nSocket, ::nBackLog )

   RETURN ( nRetCode == 0 )

// ----------------------------------------------------------------------------//

METHOD End() CLASS GSocket

   LOCAL nAt := AScan( ::aSockets, {| oSocket | oSocket:nSocket == ::nSocket  } )
   LOCAL nShutdown := 0

   WHILE ::lSending
      SysRefresh()
   END

/*   if nAt != 0
      ADel( ::aSockets, nAt )
      ASize( ::aSockets, Len( ::aSockets ) - 1 )
      if Len( ::aSockets ) == 0
         msginfo("antes de WSACleanUp()")
         WSACleanUp()
      endif
   endif */

   // A帽adido el 23/05/2021 para ver si al salir del programa y volver a entrar no hay problema
   // para volver a conectar con el servidor.
   IF ( nShutdown := shutdown( ::nSocket, SD_BOTH ) ) <> 0
      // msginfo( nShutdown, "Shutdown()" )
      // msgwait('ERROR: ShutDown(), WSAGetLastError = '+alltrim(str(WSAGetLastError())),"oSocket:ShutDown()",3)
      EscribeEnFichTxt( DToS( Date() ) + ' ' + Time() + ;
         ' ERROR: ShutDown(' + AllTrim( Str( ::nSocket ) ) + '), WSAGetLastError = ' + AllTrim( Str( WSAGetLastError() ) ), ;
         'LOG_SOCKETS.TXT', .T., 2 )
      // else
      // msgwait('OK, ShutDown() == '+str(nShutdown),"oSocket:ShutDown()",2)
   ENDIF

   IF ! Empty( ::nSocket )
      CloseSocket( ::nSocket )
      ::nSocket = 0
   ENDIF

   IF nAt != 0
      ADel( ::aSockets, nAt )
      ASize( ::aSockets, Len( ::aSockets ) - 1 )
      IF Len( ::aSockets ) == 0
         // msginfo("antes de WSACleanUp()")
         // EscribeEnFichTxt(dtos(Date())+" "+time()+" WSACleanUp() OK",'LOG_SOCKETS.TXT',.T.,2)
         WSACleanUp()
      ENDIF
   ENDIF

   RETURN NIL

// ----------------------------------------------------------------------------//

METHOD Close() CLASS GSocket

   WHILE ::lSending
      SysRefresh()
   END

   RETURN CloseSocket( ::nSocket )

// ----------------------------------------------------------------------------//

METHOD HandleEvent( nSocket, nOperation, nErrorCode ) CLASS GSocket

   LOCAL nAt := AScan( ::aSockets, {| oSocket | oSocket:nSocket == nSocket } )
   LOCAL oSocket

   IF nAt != 0
      oSocket = ::aSockets[ nAt ]

      DO CASE
      CASE nOperation == FD_ACCEPT
         IF ::lDebug .AND. ! Empty( ::cLogFile )
            LogFile( ::cLogFile, { "Accept", ;
               "Socket handle:" + Str( nSocket ) } )
         ENDIF
         oSocket:OnAccept()

      CASE nOperation == FD_READ
         IF ::lDebug .AND. ! Empty( ::cLogFile )
            LogFile( ::cLogFile, { "Read", ;
               "Socket handle:" + Str( nSocket ) } )
         ENDIF
         oSocket:OnRead()

      CASE nOperation == FD_WRITE
         IF ::lDebug .AND. ! Empty( ::cLogFile )
            LogFile( ::cLogFile, { "Write", ;
               "Socket handle:" + Str( nSocket ) } )
         ENDIF
         oSocket:OnWrite()

      CASE nOperation == FD_CLOSE
         IF ::lDebug .AND. ! Empty( ::cLogFile )
            LogFile( ::cLogFile, { "Close", ;
               "Socket handle:" + Str( nSocket ) } )
         ENDIF
         oSocket:OnClose()

      CASE nOperation == FD_CONNECT
         IF ::lDebug .AND. ! Empty( ::cLogFile )
            LogFile( ::cLogFile, { "Connect", ;
               "Socket handle:" + Str( nSocket ) } )
         ENDIF
         oSocket:OnConnect( nErrorCode )

      CASE nOperation == FD_OOB
         IF ::lDebug .AND. ! Empty( ::cLogFile )
            LogFile( ::cLogFile, { "OOB", ;
               "Socket handle:" + Str( nSocket ) } )
         ENDIF
         oSocket:OnOOB()

      OTHERWISE
         IF ::lDebug .AND. ! Empty( ::cLogFile )
            LogFile( ::cLogFile, { "nOperation not recognized", ;
               Str( nOperation ) } )
         ENDIF
      ENDCASE
   ENDIF

   RETURN NIL

// ----------------------------------------------------------------------------//

METHOD SendChunk( nBlockSize ) CLASS GSocket

   LOCAL cBuffer, nBytes := 0

   DEFAULT nBlockSize := FILE_BLOCK

   cBuffer = Space( nBlockSize )

   IF ::hFile != 0
      nBytes = FRead( ::hFile, @cBuffer, nBlockSize )
      IF nBytes < nBlockSize
         cBuffer = SubStr( cBuffer, 1, nBytes )
         FClose( ::hFile )
         ::hFile = 0
      ENDIF

      ::SendData( cBuffer )
   END

   RETURN nBytes

// ----------------------------------------------------------------------------//

METHOD SendFile( cFileName, nBlockSize ) CLASS GSocket

   DEFAULT nBlockSize := FILE_BLOCK

   IF ! Empty( cFileName ) .AND. File( cFileName )
      If( ( ::hFile := FOpen( cFileName ) ) != -1 )
         WHILE ::SendChunk( nBlockSize ) == nBlockSize
         END
      ENDIF
   ENDIF

   RETURN NIL


METHOD SendData( cData ) CLASS GSocket

   LOCAL nSize := Len( cData )
   LOCAL nLen  := nSize
   LOCAL nSent := 0
   LOCAL nIntentos := 3

   LOCAL nErrorWSA := 0, cErrDesc := ""

   IF ! ::lSending
      ::lSending = .T.
   ELSE
      AAdd( ::aBuffer, cData )
      RETURN nSize
   ENDIF

   WHILE ( nLen > 0 .AND. ;
         ( nSent := SocketSend( ::nSocket, cData ) ) < nLen ) .OR. ;
         Len( ::aBuffer ) > 0

      SYSREFRESH()

      // Check for buffered packets to send
      IF nLen == 0 .AND. Len( ::aBuffer ) > 0
         cData = ::aBuffer[ 1 ]
         ADel( ::aBuffer, 1 )
         ASize( ::aBuffer, Len( ::aBuffer ) - 1 )
      ENDIF
      IF nSent != -1  // No hay error en el env铆o.
         cData = SubStr( cData, nSent + 1 )
         nLen  = Len( cData )
      ELSE          // Ha habido error en el env铆o.
         nErrorWSA = WSAGetLastError()
         IF nErrorWSA != WSAEWOULDBLOCK  // Buffer lleno
            EXIT
         ELSE  // WSAEWOULDBLOCK => Buffer lleno. Reintenta el env铆o hasta nIntentos veces.
            IF nIntentos > 0
               nIntentos = nIntentos - 1
               // RetardoSecs(1)
               RetardoSecs()

               LOOP
            ELSE
               EXIT  // 14/08/2018   Sale para no quedar en un bucle sin fin si hay errores WINSOCK2.
            ENDIF
         ENDIF
      ENDIF

   ENDDO

   IF nSent == -1
      // Descripci贸n del Error:
      DO CASE
      CASE nErrorWSA == WSAENOTCONN   // Socket is not connected.
         cErrDesc := "Socket is not connected."
         // Tiene que volver a conectar el Socket porque se ha desconectado.

      CASE nErrorWSA == WSAECONNRESET // El host remoto cort贸 la conexi贸n.
         cErrDesc := "El host remoto cort贸 la conexi贸n."

      CASE nErrorWSA != WSAEWOULDBLOCK
         cErrDesc := "Buffer Send lleno."

      ENDCASE

      // msgwait('ERROR: SocketSend(), WSAGetLastError = '+alltrim(str(nErrorWSA)),"oSocket:SendData",2)
      EscribeEnFichTxt( DToS( Date() ) + ' ' + Time() + ;
         ' ERROR: SocketSend(), WSAGetLastError = ' + AllTrim( Str( nErrorWSA ) ) + ;
         ' FROM ' + iif( Empty( ::cIPAddr ), "ip ???", ::cIPAddr ) + ":" + iif( Empty( ::nPort ), "port ???", AllTrim( Str( ::nPort ) ) ) + " = " + cErrDesc, 'LOG_SOCKETS.TXT', .T., 2 )

   ENDIF

   // if ::lDebug .AND. ! Empty( ::cLogFile )
   // LogFile( ::cLogFile, { cData } )
   // endif

   ::lSending = .F.

   RETURN nSent // Propongo nSent en vez de nSize porque Si nSent = -1 es que la instrucci贸n no ha ido bien, hay errores. //nSize


// ----------------------------------------------------------------------------//

FUNCTION GShowIP()

   LOCAL oSocket := GSocket():New( 2000 )
   LOCAL cIp := oSocket:cIPAddr

   oSocket:End()

   RETURN cIp

// ----------------------------------------------------------------------------//

/* Escribe en un fichero txt.
   A帽ade la l铆nea si lAppend:=.t. o NIL
   Devuelve: .F. si no tuvo exito al abrir el fichero.
             .T. si tuvo exito.
*/
FUNCTION EscribeEnFichTxt( cMensaje, cFich, lAppend, Intentos, lAvisoError, lBorraFichExistente, lAnadeCRLFfinal )

   LOCAL lValRet := .F.
   LOCAL nLongFichero := 0
   LOCAL nLongRec := Len( cMensaje )
   LOCAL nManejador := -1

   DEFAULT cFich    := "LOG.TXT"
   DEFAULT lAppend  := .T.
   DEFAULT Intentos := 1
   DEFAULT lAvisoError := .T.
   DEFAULT lBorraFichExistente := .F.
   DEFAULT lAnadeCRLFfinal := .T.

   IF lBorraFichExistente
      FErase( cFich )
   ENDIF

   WHILE lValRet = .F. .AND. intentos > 0

      SYSREFRESH()

      intentos = intentos - 1
      nManejador := iif( File( cFich ), ;
         FOpen( cfich, FO_READWRITE + FO_SHARED ), ;
         FCreate( cFich, FC_NORMAL ) )

      IF FError() = 0
         // Longitud del fichero y se sit煤a al final del fichero.
         nLongFichero := FSeek( nManejador, 0, FS_END )

         // Devuelve a la posici贸n inicial si lAppend=.f.
         iif( lAppend, NIL, FSeek( nManejador, 0 ) )

         // Escribe el mensaje
         iif( FWrite( nManejador, cMensaje + iif( lAnadeCRLFfinal, CRLF, "" ) ) < nLongRec, lValRet := .F., lValRet := .T. )

         FClose( nManejador )
      ELSE
         IF lAvisoError
            msgwait( 'ERROR AL ABRIR FICHERO:' + CRLF + cFich, 'EscribeFich', 1 )
         ENDIF
      ENDIF

   ENDDO

   RETURN lValRet

FUNCTION RetardoSecs() // Falta esta FUNCTION. ??

   Syswait( .5 )

   RETURN( .T. )

DLL32 STATIC FUNCTION shutdown( dwReserved AS LONG, lpdwReserved AS LONG );
      AS LONG PASCAL FROM "shutdown" LIB "ws2_32.dll"  // "wsock32.dll"  tambi茅n vale



Saludos.
Jo茫o Santos - S茫o Paulo - Brasil - Phone: +55(11)95150-7341
Posts: 563
Joined: Sun Oct 09, 2005 07:23 PM
Re: SOCKETS - Hay que reiniciar ordenador para volver a conectar
Posted: Tue May 25, 2021 06:56 PM
Code (fw): Select all Collapse
function RetardoSecs(nSecs, lVerMsgProceso, cMensaje)
  local nSecIni:=Secs(Time())
  local nElapsed:=0
  
  local oDlg, oSay
  
  default cMensaje:='Sistema procesando, espere...'
  default lVerMsgProceso:=.f.

  if lVerMsgProceso
      DEFINE DIALOG oDlg TITLE 'PROCESO EN CURSO' FROM 6,10 TO 15,60 ;
         FONT oFont
         @ 1,2 SAY oSay VAR cMensaje FONT oFont OF oDlg
       ACTIVATE DIALOG oDlg NOWAIT
      sysrefresh()
  endif
  
  while nElapsed < nSecs
     nElapsed:=Secs(Time())-nSecIni
     if nElapsed < 0
        nElapsed:=nElapsed+86400
     endif
  enddo
  
  if lVerMsgProceso
     oDlg:end()
     sysrefresh()
  endif
return nil
Posts: 8515
Joined: Tue Dec 20, 2005 07:36 PM
Re: SOCKETS - Hay que reiniciar ordenador para volver a conectar
Posted: Tue May 25, 2021 08:22 PM
No me hace nada... ????

Code (fw): Select all Collapse
// \SAMPLES\SOCKET1.PRG

// FiveWin WinSocket.dll support !!!

/* 25/05/2021
   MODIFICACION DE LA CLASE ORIGINAL TSOCKET:
     El problema radicaba en que la clase TSocket original de FiveWin no gestiona los errores
     cuando trata de enviar datos, EL FALLO CONSISTE en que deja al m茅todo SendData en un bucle
     infinito. He modificado TSockets para gestionarlo.


   Referencia de sockets propios de harbour (No usados en esta clase).
   https://github.com/Petewg/harbour-core/wiki/Harbour-Socket-API
*/

#include "FiveWin.ch"
#include "Fileio.ch"

#define AF_INET            2
#define SOCK_STREAM        1

#define IPPROTO_IP         0
#define SOL_SOCKET        -1

#define FD_READ            1
#define FD_WRITE           2
#define FD_OOB             4
#define FD_ACCEPT          8
#define FD_CONNECT        16
#define FD_CLOSE          32

#define SD_RECEIVE         0
#define SD_SEND            1
#define SD_BOTH            2

#define SO_REUSEADDR       4

#define FILE_BLOCK     30000

/*  ERRORES DE CONEXION SOCKETS DE WINSOCK2   :
    <!-- m --><a class="postlink" href="https://docs.microsoft.com/es-es/window">https://docs.microsoft.com/es-es/window</a><!-- m --> ... or-codes-2     */

#define WSAEWOULDBLOCK 10035  // El buffer de env铆o est谩 lleno.
/* WSAEWOULDBLOCK is not really an error but simply tells you that your send buffers are full.
   This can happen if you saturate the network or if the other side simply doesn't acknowledge the received data.*/

#define WSAECONNRESET  10054  // El host remoto cort贸 la conexi贸n.

#define WSAENOTCONN    10057  /*  Socket is not connected.
                                  A request to send or receive data was disallowed because the socket is not connected
                                  and (when sending on a datagram socket using sendto) no address was supplied.
                                  Any other type of operation might also return this error鈥攆or example,
                                  setsockopt setting SO_KEEPALIVE if the connection has been reset.   */

#ifdef __XPP__
#define New _New
#endif

// ----------------------------------------------------------------------------//

CLASS GSocket

   DATA    nPort    AS NUMERIC INIT  0      // socket port number
   DATA    cIPAddr  AS String  INIT ""      // socket IP address
   DATA    nTimeOut AS NUMERIC INIT 30
   DATA    nBackLog AS NUMERIC INIT  5
   DATA    nSocket  AS NUMERIC INIT -1
   DATA    hFile    AS NUMERIC INIT  0

   DATA    bAccept, bRead, bWrite, bClose, bConnect, bOOB
   DATA    lDebug
   DATA    cLogFile

   DATA    cMsg, nRetCode, Cargo
   DATA    aBuffer                         // data sending buffer
   DATA    lSending                        // sending in progress

   CLASSDATA aSockets INIT {}

   METHOD  New( nPort, oWnd )  CONSTRUCTOR

   MESSAGE ACCEPT METHOD _Accept( nSocket )

   METHOD  End()

   METHOD  HandleEvent( nSocket, nOperation, nErrorCode )

   METHOD  GetData()

   METHOD  SendBin( pMemory, nSize ) INLINE SendBinary( pMemory, nSize )

   METHOD  SendChunk( nBlockSize )
   METHOD  SendFile( cFileName, nBlockSize )

   METHOD SendData( cData )

   MESSAGE Listen METHOD _Listen()

   METHOD  Close()

   METHOD  Connect( cIPAddr, nPort ) INLINE ;
      ConnectTo( ::nSocket, If( nPort != NIL, nPort, ::nPort ), cIPAddr )

   METHOD  Refresh() INLINE SocketSelect( ::nSocket )

   METHOD  OnAccept()  INLINE If( ::bAccept  != NIL, Eval( ::bAccept,  Self ), )
   METHOD  OnRead()    INLINE If( ::bRead    != NIL, Eval( ::bRead,    Self ), )
   METHOD  OnWrite()   INLINE If( ::bWrite   != NIL, Eval( ::bWrite,   Self ), )
   METHOD  OnClose()   INLINE If( ::bClose   != NIL, Eval( ::bClose,   Self ), )
   METHOD  OnConnect( nErrorCode ) INLINE If( ::bConnect != NIL, Eval( ::bConnect, Self, nErrorCode ), )
   METHOD  OnOOB()     INLINE If( ::bOOB     != NIL, Eval( ::bOOB,     Self ), )

   METHOD  ClientIP()  INLINE GetPeerName( ::nSocket )

ENDCLASS

// ----------------------------------------------------------------------------//

METHOD New( nPort, oWnd ) CLASS GSocket

   DEFAULT oWnd := WndMain(), ::aSockets := {}

   IF Len( ::aSockets ) == 0
      IF WSAStartup() != 0
         MsgAlert( "WSAStartup error" )
      ENDIF
   ENDIF

   IF ( ::nSocket := Socket( AF_INET, SOCK_STREAM, IPPROTO_IP ) ) == 0
      MsgAlert( "Socket creation error: " + Str( WsaGetLastError() ) )
   ENDIF
   // msginfo(::nSocket,"wintpv: Nuevo socket creado")

   ::cIPAddr  = GetHostByName( GetHostName() )  // "127.1.1.1"
   ::aBuffer  = {}
   ::lSending = .F.
   ::lDebug   = .F.

   IF nPort != nil
      ::nPort = nPort
      BindToPort( ::nSocket, nPort )  // Bind is not needed for connect sockets
   ENDIF

   AAdd( ::aSockets, Self )
   // msginfo(Len( ::aSockets ),"Sockets totales creados con este nuevo:")
   IF oWnd != nil
      oWnd:bSocket = {| nSocket, nLParam | ::HandleEvent( nSocket, ;
         nLoWord( nLParam ), nHiWord( nLParam ) ) }

      WSAAsyncSelect( ::nSocket, oWnd:hWnd, WM_ASYNCSELECT, ;
         nOr( FD_ACCEPT, FD_OOB, FD_READ, FD_CLOSE, FD_CONNECT, FD_WRITE ) )
   ELSE

      MsgAlert( "You must create a main window in order to use a GSocket object" )

   ENDIF

   RETURN Self

// ----------------------------------------------------------------------------//

METHOD _Accept( nSocket ) CLASS GSocket

   ::nSocket  = Accept( nSocket )
   ::aBuffer  = {}
   ::lSending = .F.
   ::lDebug   = .F.

   AAdd( ::aSockets, Self )

   WSAAsyncSelect( ::nSocket, WndMain():hWnd, WM_ASYNCSELECT, ;
      nOr( FD_ACCEPT, FD_OOB, FD_READ, FD_CLOSE, FD_CONNECT, FD_WRITE ) )

   RETURN Self

// ----------------------------------------------------------------------------//

METHOD GetData() CLASS GSocket

   LOCAL cData := ""

   ::nRetCode = Recv( ::nSocket, @cData )

   IF ::lDebug .AND. ! Empty( ::cLogFile )
      LogFile( ::cLogFile, { cData } )
   ENDIF

   RETURN cData

// ----------------------------------------------------------------------------//

METHOD _Listen() CLASS GSocket

   LOCAL nRetCode := Listen( ::nSocket, ::nBackLog )

   RETURN ( nRetCode == 0 )

// ----------------------------------------------------------------------------//

METHOD End() CLASS GSocket

   LOCAL nAt := AScan( ::aSockets, {| oSocket | oSocket:nSocket == ::nSocket  } )
   LOCAL nShutdown := 0

   WHILE ::lSending
      SysRefresh()
   END

/*   if nAt != 0
      ADel( ::aSockets, nAt )
      ASize( ::aSockets, Len( ::aSockets ) - 1 )
      if Len( ::aSockets ) == 0
         msginfo("antes de WSACleanUp()")
         WSACleanUp()
      endif
   endif */

   // A帽adido el 23/05/2021 para ver si al salir del programa y volver a entrar no hay problema
   // para volver a conectar con el servidor.
   IF ( nShutdown := shutdown( ::nSocket, SD_BOTH ) ) <> 0
      // msginfo( nShutdown, "Shutdown()" )
      // msgwait('ERROR: ShutDown(), WSAGetLastError = '+alltrim(str(WSAGetLastError())),"oSocket:ShutDown()",3)
      EscribeEnFichTxt( DToS( Date() ) + ' ' + Time() + ;
         ' ERROR: ShutDown(' + AllTrim( Str( ::nSocket ) ) + '), WSAGetLastError = ' + AllTrim( Str( WSAGetLastError() ) ), ;
         'LOG_SOCKETS.TXT', .T., 2 )
      // else
      // msgwait('OK, ShutDown() == '+str(nShutdown),"oSocket:ShutDown()",2)
   ENDIF

   IF ! Empty( ::nSocket )
      CloseSocket( ::nSocket )
      ::nSocket = 0
   ENDIF

   IF nAt != 0
      ADel( ::aSockets, nAt )
      ASize( ::aSockets, Len( ::aSockets ) - 1 )
      IF Len( ::aSockets ) == 0
         // msginfo("antes de WSACleanUp()")
         // EscribeEnFichTxt(dtos(Date())+" "+time()+" WSACleanUp() OK",'LOG_SOCKETS.TXT',.T.,2)
         WSACleanUp()
      ENDIF
   ENDIF

   RETURN NIL

// ----------------------------------------------------------------------------//

METHOD Close() CLASS GSocket

   WHILE ::lSending
      SysRefresh()
   END

   RETURN CloseSocket( ::nSocket )

// ----------------------------------------------------------------------------//

METHOD HandleEvent( nSocket, nOperation, nErrorCode ) CLASS GSocket

   LOCAL nAt := AScan( ::aSockets, {| oSocket | oSocket:nSocket == nSocket } )
   LOCAL oSocket

   IF nAt != 0
      oSocket = ::aSockets[ nAt ]

      DO CASE
      CASE nOperation == FD_ACCEPT
         IF ::lDebug .AND. ! Empty( ::cLogFile )
            LogFile( ::cLogFile, { "Accept", ;
               "Socket handle:" + Str( nSocket ) } )
         ENDIF
         oSocket:OnAccept()

      CASE nOperation == FD_READ
         IF ::lDebug .AND. ! Empty( ::cLogFile )
            LogFile( ::cLogFile, { "Read", ;
               "Socket handle:" + Str( nSocket ) } )
         ENDIF
         oSocket:OnRead()

      CASE nOperation == FD_WRITE
         IF ::lDebug .AND. ! Empty( ::cLogFile )
            LogFile( ::cLogFile, { "Write", ;
               "Socket handle:" + Str( nSocket ) } )
         ENDIF
         oSocket:OnWrite()

      CASE nOperation == FD_CLOSE
         IF ::lDebug .AND. ! Empty( ::cLogFile )
            LogFile( ::cLogFile, { "Close", ;
               "Socket handle:" + Str( nSocket ) } )
         ENDIF
         oSocket:OnClose()

      CASE nOperation == FD_CONNECT
         IF ::lDebug .AND. ! Empty( ::cLogFile )
            LogFile( ::cLogFile, { "Connect", ;
               "Socket handle:" + Str( nSocket ) } )
         ENDIF
         oSocket:OnConnect( nErrorCode )

      CASE nOperation == FD_OOB
         IF ::lDebug .AND. ! Empty( ::cLogFile )
            LogFile( ::cLogFile, { "OOB", ;
               "Socket handle:" + Str( nSocket ) } )
         ENDIF
         oSocket:OnOOB()

      OTHERWISE
         IF ::lDebug .AND. ! Empty( ::cLogFile )
            LogFile( ::cLogFile, { "nOperation not recognized", ;
               Str( nOperation ) } )
         ENDIF
      ENDCASE
   ENDIF

   RETURN NIL

// ----------------------------------------------------------------------------//

METHOD SendChunk( nBlockSize ) CLASS GSocket

   LOCAL cBuffer, nBytes := 0

   DEFAULT nBlockSize := FILE_BLOCK

   cBuffer = Space( nBlockSize )

   IF ::hFile != 0
      nBytes = FRead( ::hFile, @cBuffer, nBlockSize )
      IF nBytes < nBlockSize
         cBuffer = SubStr( cBuffer, 1, nBytes )
         FClose( ::hFile )
         ::hFile = 0
      ENDIF

      ::SendData( cBuffer )
   END

   RETURN nBytes

// ----------------------------------------------------------------------------//

METHOD SendFile( cFileName, nBlockSize ) CLASS GSocket

   DEFAULT nBlockSize := FILE_BLOCK

   IF ! Empty( cFileName ) .AND. File( cFileName )
      If( ( ::hFile := FOpen( cFileName ) ) != -1 )
         WHILE ::SendChunk( nBlockSize ) == nBlockSize
         END
      ENDIF
   ENDIF

   RETURN NIL


METHOD SendData( cData ) CLASS GSocket

   LOCAL nSize := Len( cData )
   LOCAL nLen  := nSize
   LOCAL nSent := 0
   LOCAL nIntentos := 3

   LOCAL nErrorWSA := 0, cErrDesc := ""

   IF ! ::lSending
      ::lSending = .T.
   ELSE
      AAdd( ::aBuffer, cData )
      RETURN nSize
   ENDIF

   WHILE ( nLen > 0 .AND. ;
         ( nSent := SocketSend( ::nSocket, cData ) ) < nLen ) .OR. ;
         Len( ::aBuffer ) > 0

      SYSREFRESH()

      // Check for buffered packets to send
      IF nLen == 0 .AND. Len( ::aBuffer ) > 0
         cData = ::aBuffer[ 1 ]
         ADel( ::aBuffer, 1 )
         ASize( ::aBuffer, Len( ::aBuffer ) - 1 )
      ENDIF
      IF nSent != -1  // No hay error en el env铆o.
         cData = SubStr( cData, nSent + 1 )
         nLen  = Len( cData )
      ELSE          // Ha habido error en el env铆o.
         nErrorWSA = WSAGetLastError()
         IF nErrorWSA != WSAEWOULDBLOCK  // Buffer lleno
            EXIT
         ELSE  // WSAEWOULDBLOCK => Buffer lleno. Reintenta el env铆o hasta nIntentos veces.
            IF nIntentos > 0

               nIntentos = nIntentos - 1

               RetardoSecs(1)

               LOOP

            ELSE
               EXIT  // 14/08/2018   Sale para no quedar en un bucle sin fin si hay errores WINSOCK2.
            ENDIF
         ENDIF
      ENDIF

   ENDDO

   IF nSent == -1
      // Descripci贸n del Error:
      DO CASE
      CASE nErrorWSA == WSAENOTCONN   // Socket is not connected.
         cErrDesc := "Socket is not connected."
         // Tiene que volver a conectar el Socket porque se ha desconectado.

      CASE nErrorWSA == WSAECONNRESET // El host remoto cort贸 la conexi贸n.
         cErrDesc := "El host remoto cort贸 la conexi贸n."

      CASE nErrorWSA != WSAEWOULDBLOCK
         cErrDesc := "Buffer Send lleno."

      ENDCASE

      // msgwait('ERROR: SocketSend(), WSAGetLastError = '+alltrim(str(nErrorWSA)),"oSocket:SendData",2)
      EscribeEnFichTxt( DToS( Date() ) + ' ' + Time() + ;
         ' ERROR: SocketSend(), WSAGetLastError = ' + AllTrim( Str( nErrorWSA ) ) + ;
         ' FROM ' + iif( Empty( ::cIPAddr ), "ip ???", ::cIPAddr ) + ":" + iif( Empty( ::nPort ), "port ???", AllTrim( Str( ::nPort ) ) ) + " = " + cErrDesc, 'LOG_SOCKETS.TXT', .T., 2 )

   ENDIF

   // if ::lDebug .AND. ! Empty( ::cLogFile )
   // LogFile( ::cLogFile, { cData } )
   // endif

   ::lSending = .F.

   RETURN nSent // Propongo nSent en vez de nSize porque Si nSent = -1 es que la instrucci贸n no ha ido bien, hay errores. //nSize


// ----------------------------------------------------------------------------//

FUNCTION GShowIP()

   LOCAL oSocket := GSocket():New( 2000 )
   LOCAL cIp := oSocket:cIPAddr

   oSocket:End()

   RETURN cIp

// ----------------------------------------------------------------------------//

/* Escribe en un fichero txt.
   A帽ade la l铆nea si lAppend:=.t. o NIL
   Devuelve: .F. si no tuvo exito al abrir el fichero.
             .T. si tuvo exito.
*/
FUNCTION EscribeEnFichTxt( cMensaje, cFich, lAppend, Intentos, lAvisoError, lBorraFichExistente, lAnadeCRLFfinal )

   LOCAL lValRet := .F.
   LOCAL nLongFichero := 0
   LOCAL nLongRec := Len( cMensaje )
   LOCAL nManejador := -1

   DEFAULT cFich    := "LOG.TXT"
   DEFAULT lAppend  := .T.
   DEFAULT Intentos := 1
   DEFAULT lAvisoError := .T.
   DEFAULT lBorraFichExistente := .F.
   DEFAULT lAnadeCRLFfinal := .T.

   IF lBorraFichExistente
      FErase( cFich )
   ENDIF

   WHILE lValRet = .F. .AND. intentos > 0

      SYSREFRESH()

      intentos = intentos - 1
      nManejador := iif( File( cFich ), ;
         FOpen( cfich, FO_READWRITE + FO_SHARED ), ;
         FCreate( cFich, FC_NORMAL ) )

      IF FError() = 0
         // Longitud del fichero y se sit煤a al final del fichero.
         nLongFichero := FSeek( nManejador, 0, FS_END )

         // Devuelve a la posici贸n inicial si lAppend=.f.
         iif( lAppend, NIL, FSeek( nManejador, 0 ) )

         // Escribe el mensaje
         iif( FWrite( nManejador, cMensaje + iif( lAnadeCRLFfinal, CRLF, "" ) ) < nLongRec, lValRet := .F., lValRet := .T. )

         FClose( nManejador )
      ELSE
         IF lAvisoError
            msgwait( 'ERROR AL ABRIR FICHERO:' + CRLF + cFich, 'EscribeFich', 1 )
         ENDIF
      ENDIF

   ENDDO

   RETURN lValRet

FUNCTION RetardoSecs(nSecs, lVerMsgProceso, cMensaje)

   local nSecIni:=Secs(Time())
   local nElapsed:=0
   local oDlg, oSay, oFont
 
   default cMensaje:='Sistema procesando, espere...'
   default lVerMsgProceso:=.f.

   if lVerMsgProceso

      DEFINE DIALOG oDlg TITLE 'PROCESO EN CURSO' FROM 6,10 TO 15,60 ;
         FONT oFont

         @ 1,2 SAY oSay VAR cMensaje FONT oFont OF oDlg

       ACTIVATE DIALOG oDlg NOWAIT

      //sysrefresh()

   endif
 
   while nElapsed < nSecs

     nElapsed:=Secs(Time())-nSecIni

     if nElapsed < 0
        nElapsed:=nElapsed+86400
     endif

  enddo
 
  if lVerMsgProceso

     oDlg:end()

     //sysrefresh()

  endif

RETURN NIL

DLL32 STATIC FUNCTION shutdown( dwReserved AS LONG, lpdwReserved AS LONG );
      AS LONG PASCAL FROM "shutdown" LIB "ws2_32.dll"  // "wsock32.dll"  tambi茅n vale

// FIN


Sorry, saludos.
Jo茫o Santos - S茫o Paulo - Brasil - Phone: +55(11)95150-7341

Continue the discussion