writing network servers can be endlessly frustrating when you don't actually know what the socket configuration is, and what works on loopback and Lan often doesn't work quite so well on Wan with higher latencies and that make is very hard to debug when you have no idea what the error is or where the bug is in your code and it get harder when it's threaded.
We ideally need to be able to set socket options, like keep alive, reuse and tcp no delay
we also need a network error function, or as I've done NetworkContinue which is simpler to use, you don't really need to know what the fatal error was, you only need to know if the error is recoverable, so you can delay and continue and that can all be wrapped in send and receive functions which either succeed or fail and return the error.
Currently the documentation causes some confusion as a socket returns either
0: connection was closed. (this isn't mentioned)
n bytes sent or received
-1 on error (which would have you bail out when 99% of the time it's actually recoverable)
And additionally since 6.20 we now have
#TLS_want_pollin = -2 (comes from the libre TLS sockets)
#TLS_want_ pollout = -3
both are recoverable meaning delay and try again
Thing is we can't really expect Fred to put on a pink tutu and wave a magic wand, it needs a community coordinated effort with real world tests not just over LAN and that takes time. Atomic web server for instance despite it's complexity runs flawlessly but it's taken a long time to get there and was the actual testbed for getting TLS introduced to PB via Hexors TLS wrapper along with infratec's help and myself debugging the TLS wrapper and then badgering Fred to add it natively.
These are the functions I'm using in atomic web server to do send and receives they are both used with the reverse proxy and cause no issues though I haven't tested the mutex branches as they're not needed due to the threading model of the server.
Code: Select all
CompilerIf #PB_Compiler_OS <> #PB_OS_Windows
#SOL_SOCKET = 1
#SO_KEEPALIVE = 8
#IPPROTO_TCP = 6
#TCP_NODELAY = 1
#SO_LINGER = 13
CompilerEndIf
Procedure KeepAlive(ID,set.l=#True)
Protected option.l,oplen.l=4
If setsockopt_(ID,#SOL_SOCKET,#SO_KEEPALIVE,@set,oplen) = 0
If getsockopt_(ID,#SOL_SOCKET,#SO_KEEPALIVE,@option,@oplen ) = 0
ProcedureReturn option
EndIf
ProcedureReturn -1
EndIf
EndProcedure
Procedure TCPNoDelay(ID,set.l=#True)
Protected option.l,oplen.l=4
If setsockopt_(ID,#IPPROTO_TCP,#TCP_NODELAY,@set,oplen) = 0
If getsockopt_(ID,#IPPROTO_TCP,#TCP_NODELAY,@option,@oplen ) = 0
ProcedureReturn option
EndIf
ProcedureReturn -1
EndIf
EndProcedure
;-Error codes
#PB_Network_Error_Fatal = -1
#PB_Network_Error_timeout = -2
#PB_Network_Error_Dropped = -3
#PB_Network_Error_Memory = -4
CompilerIf #PB_Compiler_OS = #PB_OS_Linux
ImportC "-lc"
__errno_location()
EndImport
CompilerEndIf
Procedure.i NetworkErrorContinue(ID.i, Val.i = 0)
Protected Ret.i, Error.l
#WSA_IO_INCOMPLETE = 996
#WSA_IO_PENDING = 997
CompilerSelect #PB_Compiler_OS
CompilerCase #PB_OS_Windows
#WSAEINTR = 10004
#WSAEMFILE = 10024
#WSAEWOULDBLOCK = 10035
#WSAEINPROGRESS = 10036
#WSAEALREADY = 10037
CompilerCase #PB_OS_Linux
#WSAEINTR = 4 ; EINTR
#WSAEMFILE = 17 ; ENOFILE
#WSAEWOULDBLOCK = 11 ; Eagain
#WSAEINPROGRESS = 115 ; EINPROGRESS
#WSAEALREADY = 114 ; EALREADY
CompilerCase #PB_OS_MacOS
#WSAEINTR = 4 ; EINTR
#WSAEMFILE = 24 ; EMFILE
#WSAEWOULDBLOCK = 35 ; EWOULDBLOCK = EAGAIN
#WSAEINPROGRESS = 36 ; EINPROGRESS
#WSAEALREADY = 37 ; EALREADY
CompilerEndSelect
#TLS_WANT_POLLIN= -2
#TLS_WANT_POLLOUT = -3
CompilerIf #PB_Compiler_OS = #PB_OS_Windows
Error = WSAGetLastError_()
CompilerElse
Error = PeekL(__errno_location())
CompilerEndIf
If Val = #TLS_WANT_POLLIN
ProcedureReturn #True
EndIf
If Val = #TLS_WANT_POLLOUT
ProcedureReturn #True
EndIf
Select Error
Case 0
Ret = 0
Case #WSAEWOULDBLOCK
Ret = 1
Case #WSAEINPROGRESS
Ret = 1
Case #WSAEALREADY
Ret = 1
Case #WSA_IO_INCOMPLETE
Ret =1
Case #WSA_IO_PENDING
Ret = 1
Case #WSAEMFILE
Ret =1
EndSelect
ProcedureReturn Ret
EndProcedure
;returns the buffer or 0 on error pass in the address of a error variable @error if you want to check it
Procedure Atomic_Server_ReceiveNetworkDataEx(clientId,len,timeout=15000,mutex=0,*error.Integer=0)
Protected result,recived,recvTimeout,tlen,bfirst=1
If len > 0
Protected *buffer = AllocateMemory(len)
If *buffer
recvTimeout=ElapsedMilliseconds()+timeout
Repeat
If result > 0
*buffer = ReAllocateMemory(*buffer, recived + len)
EndIf
If *buffer
If mutex
Repeat
If TryLockMutex(mutex)
Result = ReceiveNetworkData(clientId,*buffer+recived, len)
If result < 0
If Atomic_Server_NetworkErrorContinue(clientId,result)
Delay(10)
Else
UnlockMutex(mutex)
FreeMemory(*buffer)
If *error
*error\i = #PB_Network_Error_Fatal
EndIf
ProcedureReturn 0
EndIf
EndIf
UnlockMutex(mutex)
Break
Else
Delay(10)
EndIf
Until ElapsedMilliseconds() > recvTimeout
Else
Result = ReceiveNetworkData(clientId,*buffer+recived, len)
If result < 0
If Atomic_Server_NetworkErrorContinue(clientId,result)
Delay(10)
Continue
Else
FreeMemory(*buffer)
If *error
*error\i = #PB_Network_Error_Fatal
EndIf
Delay(10)
ProcedureReturn 0
EndIf
EndIf
EndIf
If result > 0
recived+result
recvTimeout = ElapsedMilliseconds() + timeout
ElseIf result = 0
FreeMemory(*buffer)
If *error
*error\i = #PB_Network_Error_Dropped
EndIf
ProcedureReturn 0
EndIf
Else
If *error
*error\i = #PB_Network_Error_Memory
EndIf
ProcedureReturn 0
EndIf
If ElapsedMilliseconds() > recvTimeout
FreeMemory(*buffer)
If *error
*error\i = #PB_Network_Error_timeout
EndIf
ProcedureReturn 0
EndIf
Delay(0)
Until result <> len
ProcedureReturn *buffer
EndIf
EndIf
EndProcedure
;returns the total sent or 0 on errror pass in the address of a varibale @error to get the error
Procedure Atomic_Server_SendNetworkDataEX(clientId,*buffer,len,timeout=15000,mutex=0,*error.Integer=0)
Protected totalSent,tryLen,sendLen,sendTimeout
sendTimeout = ElapsedMilliseconds() + timeout
Repeat
tryLen = len - totalSent
If tryLen > len
tryLen = len
EndIf
If mutex
Repeat
If TryLockMutex(mutex)
sendLen = SendNetworkData(clientId, *Buffer+totalSent,tryLen)
If sendLen < 0
If Atomic_Server_NetworkErrorContinue(clientId,sendLen)
Delay(10)
Else
If *error
*error\i = #PB_Network_Error_Fatal
EndIf
Debug Str(totalsent) + " " + Str(trylen) + " " + Str(len)
UnlockMutex(mutex)
ProcedureReturn 0
EndIf
EndIf
UnlockMutex(mutex)
Break
Else
Delay(10)
EndIf
Until ElapsedMilliseconds() > sendTimeout
Else
sendLen = SendNetworkData(clientId, *Buffer+totalSent,tryLen)
If sendLen < 0
If Atomic_Server_NetworkErrorContinue(clientId,sendLen)
Delay(10)
Else
If *error
*error\i = #PB_Network_Error_Fatal
EndIf
ProcedureReturn 0
EndIf
EndIf
EndIf
If sendLen > 0
totalSent + sendLen
sendLen = 0
sendTimeout = ElapsedMilliseconds() + timeout
ElseIf sendLen = 0
If *error
*error\i = #PB_Network_Error_Dropped
EndIf
ProcedureReturn 0
EndIf
If ElapsedMilliseconds() > sendTimeout
If *error
*error\i = #PB_Network_Error_timeout
EndIf
ProcedureReturn 0
EndIf
Delay(1)
Until totalSent >= len
ProcedureReturn totalSent
EndProcedure