[crossplatform] Disconnect Event for Client

Share your advanced PureBasic knowledge/code with the community.
DarkPlayer
Enthusiast
Enthusiast
Posts: 107
Joined: Thu May 06, 2010 11:36 pm

[crossplatform] Disconnect Event for Client

Post by DarkPlayer »

Hello,

i wrote a code which detects the Disconnect Event at the client. This may be useful for stuff like HTTP 1.0, in which the server tells the client that all data is transmitted by closing the connection. It works under Linux, Windows and Mac OS (tested!).

This code is deprecated and should be only used for old PB versions - new PureBasic versions already have #PB_NetworkEvent_Disconnect implemented, but currently still contain a bug (they for example trigger the disconnect event too early or don't detect disconnect events at all). I recommend using my new version which can be found here: http://www.purebasic.fr/english/viewtop ... 12&t=54302.


Changelog:
EDIT 2010-06-13: Improved the MacOS and Linux version, added some checks to prevent crashing in case of incorrect usage
EDIT 2013-04-11: Fixed bug in TIMEVAL structure on Linux/MacOS 64 bit systems

Code: Select all

; Client Event4 support by DarkPlayer, PureFan

;EnableExplicit

CompilerIf #PB_Compiler_OS = #PB_OS_Linux ;{
  #FIONREAD     = $541B
  
  #__FD_SETSIZE = 1024
  #__NFDBITS    = 8 * SizeOf(LONG)
  
  Macro __FDELT(d)
    ((d) / #__NFDBITS)
  EndMacro
  
  Macro __FDMASK(d)
    (1 << ((d) % #__NFDBITS))
  EndMacro
  
  Structure FD_SET
    fds_bits.l[#__FD_SETSIZE / #__NFDBITS]
  EndStructure
  
  Procedure.i __FD_SET(d.i, *set.FD_SET)
    If d >= 0 And d < #__FD_SETSIZE
      *set\fds_bits[__FDELT(d)] | __FDMASK(d)
    EndIf
  EndProcedure
  
  Procedure.i __FD_ISSET(d.i, *set.FD_SET)
    If d >= 0 And d < #__FD_SETSIZE
      ProcedureReturn *set\fds_bits[__FDELT(d)] & __FDMASK(d)
    EndIf
  EndProcedure
  
  Procedure.i __FD_ZERO(*set.FD_SET)
    FillMemory(*set, SizeOf(FD_SET), 0, #PB_Byte)
  EndProcedure
  
  
  #FD_SETSIZE = #__FD_SETSIZE
  #NFDBITS    = #__NFDBITS
  
  Macro FD_SET(fd, fdsetp)
    __FD_SET(fd, fdsetp)
  EndMacro
  
  Macro FD_ISSET(fd, fdsetp)
    __FD_ISSET(fd, fdsetp)
  EndMacro
  
  Macro FD_ZERO(fdsetp)
    __FD_ZERO(fdsetp)
  EndMacro 
  
  ; Returns the minimum value for NFDS
  Procedure.i _NFDS(*set.FD_SET)
    Protected I.i, J.i
    
    For I = SizeOf(FD_SET)/SizeOf(LONG) - 1 To 0 Step -1
      If *set\fds_bits[I]
        
        For J = (#__NFDBITS - 1) To 0 Step -1
          If *set\fds_bits[I] & (1 << J)
            ProcedureReturn I * #__NFDBITS + J + 1
          EndIf
        Next
        
      EndIf
    Next
    
    ProcedureReturn 0
  EndProcedure
  ;}
CompilerEndIf

CompilerIf #PB_Compiler_OS = #PB_OS_MacOS ;{
  #IOC_OUT  = $40000000 ;(__uint32_t)
  Macro _IOR(g,n,t)
    _IOC(#IOC_OUT, (g), (n), (t))
  EndMacro
  #IOCPARM_MASK = $1fff
  Macro _IOC(inout,group,num,len)
    ((inout) | (((len) & #IOCPARM_MASK) << 16) | ((group) << 8) | (num))
  EndMacro
  #FIONREAD = _IOR('f', 127, SizeOf(LONG))
  
  #__DARWIN_FD_SETSIZE = 1024
  #__DARWIN_NBBY       = 8
  #__DARWIN_NFDBITS    = SizeOf(LONG) * #__DARWIN_NBBY
  
  Structure FD_SET
    fds_bits.l[ (#__DARWIN_FD_SETSIZE + #__DARWIN_NFDBITS - 1) / #__DARWIN_NFDBITS ]
  EndStructure
  
  Procedure.i __DARWIN_FD_SET(fd.i, *p.FD_SET)
    If fd >= 0 And fd < #__DARWIN_FD_SETSIZE
      *p\fds_bits[fd / #__DARWIN_NFDBITS] | (1 << (fd % #__DARWIN_NFDBITS))
    EndIf
  EndProcedure
  
  Procedure.i __DARWIN_FD_ISSET(fd.i, *p.FD_SET)
    If fd >= 0 And fd < #__DARWIN_FD_SETSIZE
      ProcedureReturn *p\fds_bits[fd / #__DARWIN_NFDBITS] & (1 << (fd % #__DARWIN_NFDBITS))
    EndIf
  EndProcedure
  
  Procedure.i __DARWIN_FD_ZERO(*p.FD_SET)
    FillMemory(*p, SizeOf(FD_SET), 0, #PB_Byte)
  EndProcedure
  
  #FD_SETSIZE = #__DARWIN_FD_SETSIZE
  
  Macro FD_SET(n, p)
    __DARWIN_FD_SET(n, p)
  EndMacro
  
  Macro FD_ISSET(n, p)
    __DARWIN_FD_ISSET(n, p)
  EndMacro
  
  Macro FD_ZERO(p)
    __DARWIN_FD_ZERO(p)
  EndMacro
  
  ; Returns the minimum value for NFDS
  Procedure.i _NFDS(*p.FD_SET)
    Protected I.i, J.i
    
    For I = SizeOf(FD_SET)/SizeOf(LONG) - 1 To 0 Step -1
      If *p\fds_bits[I]
        
        For J = (#__DARWIN_NFDBITS - 1) To 0 Step -1
          If *p\fds_bits[I] & (1 << J)
            ProcedureReturn I * #__DARWIN_NFDBITS + J + 1
          EndIf
        Next
        
      EndIf
    Next
    
    ProcedureReturn 0
  EndProcedure
  ;}
CompilerEndIf

CompilerIf #PB_Compiler_OS = #PB_OS_Windows ;{
  ; #FIONREAD is already defined
  ; FD_SET is already defined
  
  Macro FD_ZERO(set)
    set\fd_count = 0
  EndMacro
  
  Procedure.i FD_SET(fd.i, *set.FD_SET)
    If *set\fd_count < #FD_SETSIZE
      *set\fd_array[ *set\fd_count ] = fd
      *set\fd_count + 1
    EndIf
  EndProcedure
  
  Procedure.i FD_ISSET(fd.i, *set.FD_SET)
    Protected I.i
    For I = *set\fd_count - 1 To 0 Step -1
      If *set\fd_array[I] = fd
        ProcedureReturn #True
      EndIf
    Next
    
    ProcedureReturn #False
  EndProcedure
  
  Procedure.i _NFDS(*set.FD_SET)
    ProcedureReturn *set\fd_count
  EndProcedure
  ;}
CompilerEndIf
  
  
CompilerIf Defined(TIMEVAL, #PB_Structure) = #False ;{
  Structure TIMEVAL
    tv_sec.l
    CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
      padding.l
   CompilerEndIf
    tv_usec.l
  EndStructure ;}
CompilerEndIf

Procedure.i Hook_NetworkClientEvent(Connection.i)
  Protected Event.i = NetworkClientEvent(Connection)
  If Event
    ProcedureReturn Event
  EndIf
  
  Protected hSocket.i = ConnectionID(Connection)
  Protected tv.timeval, readfds.fd_set, RetVal.i, Length.i
  tv\tv_sec  = 0 ; Dont even wait, just query status
  tv\tv_usec = 0
  
  FD_ZERO(readfds)
  FD_SET(hSocket, readfds)
  
  ; Check if there is something new
  RetVal = select_(_NFDS(readfds), @readfds, #Null, #Null, @tv)
  If RetVal < 0 ; Seems to be an error
    ProcedureReturn #PB_NetworkEvent_Disconnect
  ElseIf RetVal = 0 Or Not FD_ISSET(hSocket, readfds) ; No data available
    ProcedureReturn 0
  EndIf
  
  ; Check if data is available?
  CompilerIf #PB_Compiler_OS = #PB_OS_Windows
    RetVal = ioctlsocket_(hSocket, #FIONREAD, @Length)
  CompilerElse
    RetVal = ioctl_(hSocket, #FIONREAD, @Length)
  CompilerEndIf
  If RetVal Or Length = 0 ; Not successful to query for data available OR no data available ? This seems to be an error!
    ProcedureReturn #PB_NetworkEvent_Disconnect
  EndIf
  
  ProcedureReturn 0
EndProcedure

Macro NetworkClientEvent(Connection)
  Hook_NetworkClientEvent(Connection)
EndMacro
Just Include this Code and NetworkClientEvent() will return #PB_NetworkEvent_Disconnect when the connection got closed by the remote host.

I think Fred should make a native implementation for this, or what do you think?

DarkPlayer
Last edited by DarkPlayer on Thu Apr 11, 2013 6:46 pm, edited 3 times in total.
User avatar
luis
Addict
Addict
Posts: 3893
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: [crossplatform] Disconnect Event for Client

Post by luis »

I haven't tried it yet but this is something could have simplified my life while I was writing my ip2c (ip to country) program.

So thank you very much for sharing this.
"Have you tried turning it off and on again ?"
A little PureBasic review
User avatar
Joakim Christiansen
Addict
Addict
Posts: 2452
Joined: Wed Dec 22, 2004 4:12 pm
Location: Norway
Contact:

Re: [crossplatform] Disconnect Event for Client

Post by Joakim Christiansen »

Sweet! Respect to you!
I will try this later, but it should really come in handy.
I like logic, hence I dislike humans but love computers.
moogle
Enthusiast
Enthusiast
Posts: 372
Joined: Tue Feb 14, 2006 9:27 pm
Location: London, UK

Re: [crossplatform] Disconnect Event for Client

Post by moogle »

Seems to work great. This sort of thing should be implemented in PB. Would make the Network part of it much better.
Image
User avatar
Joakim Christiansen
Addict
Addict
Posts: 2452
Joined: Wed Dec 22, 2004 4:12 pm
Location: Norway
Contact:

Re: [crossplatform] Disconnect Event for Client

Post by Joakim Christiansen »

moogle wrote:Seems to work great. This sort of thing should be implemented in PB. Would make the Network part of it much better.
+1
I like logic, hence I dislike humans but love computers.
User avatar
Alireza
Enthusiast
Enthusiast
Posts: 143
Joined: Sat Aug 16, 2008 2:02 pm
Location: Iran

Re: [crossplatform] Disconnect Event for Client

Post by Alireza »

a stupid question!
please make a example for connection parameter :?:
thanks
PB v 5.6 :D
moogle
Enthusiast
Enthusiast
Posts: 372
Joined: Tue Feb 14, 2006 9:27 pm
Location: London, UK

Re: [crossplatform] Disconnect Event for Client

Post by moogle »

Alireza wrote:a stupid question!
please make a example for connection parameter :?:
thanks

Code: Select all

Connection = OpenNetworkConnection("127.0.0.1", 80) 
Repeat
    NEvent = NetworkClientEvent(Connection)
    Select NEvent
        Case #PB_NetworkEvent_Data
            ;Data Here
        Case #PB_NetworkEvent_Disconnect
            ;Disconnect Here
        Default
    EndSelect
    Delay(1)
ForEver

That's a good example :)
Image
DarkPlayer
Enthusiast
Enthusiast
Posts: 107
Joined: Thu May 06, 2010 11:36 pm

Re: [crossplatform] Disconnect Event for Client

Post by DarkPlayer »

Here you got another example:

Code: Select all

#CRLF = Chr(13) + Chr(10)

Define *Buffer = AllocateMemory(4096)

If *Buffer
  
  If InitNetwork()
  
    Define Connection.i = OpenNetworkConnection("google.com", 80)
    
    If Connection
      
      Debug "Connected to server, sending request"
      
      SendNetworkString(Connection, "GET / HTTP/1.0" + #CRLF + #CRLF)
      
      Define Event.i
      
      Repeat
        
        Event = NetworkClientEvent(Connection)
        
        Select Event
        
          Case #PB_NetworkEvent_Data:
            Debug "Received data"
            ReceiveNetworkData(Connection, *Buffer, 4096)
                 
          Case #PB_NetworkEvent_Disconnect   ; Without including the disconnect code first we wouldn't get this event!
            Debug "Sever closed connection"
        
        EndSelect
      
      Until Event = #PB_NetworkEvent_Disconnect
      
    EndIf
    
  EndIf
  
  FreeMemory(*Buffer)
  
EndIf
This example sends a HTTP 1.0 request to google.com
In HTTP 1.0 the server signals the end of data by closing the connection. Without the disconnect event code you would be unable to detect if all data was send by the server.

Dark
DarkDragon
Addict
Addict
Posts: 2344
Joined: Mon Jun 02, 2003 9:16 am
Location: Germany
Contact:

Re: [crossplatform] Disconnect Event for Client

Post by DarkDragon »

DarkPlayer wrote:This example sends a HTTP 1.0 request to google.com
In HTTP 1.0 the server signals the end of data by closing the connection. Without the disconnect event code you would be unable to detect if all data was send by the server.
And that's mainly the reason why the PureBasic commands are so slow for HTTP: they mostly wait for a timeout.
bye,
Daniel
kinglestat
Enthusiast
Enthusiast
Posts: 746
Joined: Fri Jul 14, 2006 8:53 pm
Location: Malta
Contact:

Re: [crossplatform] Disconnect Event for Client

Post by kinglestat »

thanks for this
truly a big help
I may not help with your coding
Just ask about mental issues!

http://www.lulu.com/spotlight/kingwolf
http://www.sen3.net
kinglestat
Enthusiast
Enthusiast
Posts: 746
Joined: Fri Jul 14, 2006 8:53 pm
Location: Malta
Contact:

Re: [crossplatform] Disconnect Event for Client

Post by kinglestat »

Seems there is a bug; when using UDP instead of TCP it keeps returning disconnect, probably as UDP is a stateless protocol
I may not help with your coding
Just ask about mental issues!

http://www.lulu.com/spotlight/kingwolf
http://www.sen3.net
DarkPlayer
Enthusiast
Enthusiast
Posts: 107
Joined: Thu May 06, 2010 11:36 pm

Re: [crossplatform] Disconnect Event for Client

Post by DarkPlayer »

Hi,

this is not a bug, because UDP does not have any states like connected. You can not use this code for UDP, you have to create your own keepalive packets to detect something like a disconnect.

If you use UDP and TCP in the same project you can simply ignore the disconnect event for the UDP connection. You can also add this UNTESTED code to prevent the disconnect event on UDP connections:

Code: Select all

  Protected hSocket.i = ConnectionID(Connection)
  
  Protected Type.l
  Protected Size.l = SizeOf(LONG)
  If (getsockopt_(hSocket,  #SOL_SOCKET, #SO_TYPE, @Type, @Size) <> 0) Or (Type <> #SOCK_STREAM)
    ProcedureReturn 0
  EndIf
  
  Protected tv.timeval, readfds.fd_set, RetVal.i, Length.i
This should work for Windows. For Linux/MacOS these are most probaly integers instead of longs and you may need to look up the values of the constants. I do not guarantee that this code will work as i dont have the time to test on Windows x64, Linux/MacOS.

DarkPlayer
kinglestat
Enthusiast
Enthusiast
Posts: 746
Joined: Fri Jul 14, 2006 8:53 pm
Location: Malta
Contact:

Re: [crossplatform] Disconnect Event for Client

Post by kinglestat »

thanks
I may not help with your coding
Just ask about mental issues!

http://www.lulu.com/spotlight/kingwolf
http://www.sen3.net
User avatar
thyphoon
Enthusiast
Enthusiast
Posts: 345
Joined: Sat Dec 25, 2004 2:37 pm

Re: [crossplatform] Disconnect Event for Client

Post by thyphoon »

thanks for this great code ! :D
auser
Enthusiast
Enthusiast
Posts: 195
Joined: Wed Sep 06, 2006 6:59 am

Re: [crossplatform] Disconnect Event for Client

Post by auser »

Nice code snipped. But I'm afraid there was done a failure for linux regarding the NFDBITS size. Current value is always 32 because sizeof(LONG) on PB is always 4. But sizeof of long differs on C in linux depending if you are on 32 or 64 bit. So it should be sizeof(INTEGER) (on PB) not sizeof(LONG).

If we would use just some C snipped and don't reinvent the wheel yet again but just do some quick, easy and save "include"?

Code: Select all

#include <stdio.h>
#include <sys/select.h>

int main()
{ printf("Size of __NFDBITS is %d on this machine.\n",__NFDBITS); }
If you compile that with gcc on 32 bit machine you should get "32" if I compile on a 64 bit machine you should get "64".

This is one futher example where I feel a bit like an alien on linux with PB (compared to PB on windows and compoared to using C). Lot of sturctures that are there in PB windows version are not there in PB on linux (e.g. fd_set, sockaddr, sockaddr_in, in_addr, hostent, timeval). Compared to the big C(++) world you could not just do some quick "include" but have to reinvent the wheel and that is often a bit dangerous because there are traps like PBs "long" and "int" which work a bit turned on it's head compared to C.
Post Reply