More threading --> Network: select & fd_set

General FreeBASIC programming questions.
badidea
Posts: 2591
Joined: May 24, 2007 22:10
Location: The Netherlands

More threading --> Network: select & fd_set

Post by badidea »

Hello all, I am trying to put network communication into a game. Which is new stuff for me. The plan is to have 1 sever and multiple clients.

Now the function NetRead (fbnet from D.J.Peters, but probably any implementation) waits for incoming data. But in the game loop I don't want to spend time on waiting, so I need to use threading I think. And the server needs multiple threads for receiving data from each client.

I made a drawing of how this should look like, I think. Anyone any comments on this?
Image
Last edited by badidea on Jan 06, 2022 21:56, edited 2 times in total.
1000101
Posts: 2556
Joined: Jun 13, 2005 23:14
Location: SK, Canada

Post by 1000101 »

The only thing you'll need to keep in mind is that with threading comes data protection which you have to implement. Best look do research on different data locking mechanisms (semaphores, mutexes, critical sections, read-write locks, etc).
pestery
Posts: 493
Joined: Jun 16, 2007 2:00
Location: Australia

Post by pestery »

I did some experimentation with this stuff a while ago and might be able to provide some tips at least. What type of game where you thinking of? First person, RTS, turn-based?

Also, with regard to a read-write lock thingy mentioned by 1000101, I made one of those. It'd be good to see if anyone thinks is good or not.

Code: Select all

Type control_lock
	Public:
	Declare Sub write_lock()
	Declare Sub write_unlock()
	Declare Sub read_lock()
	Declare Sub read_unlock()
	Declare Constructor
	Declare Destructor
	Private:
	As Integer readers
	As Integer writers
	As Integer readers_waiting
	As Integer writers_waiting
	As Any Ptr mutex
	As Any Ptr reader_cond
	As Any Ptr writer_cond
End Type
Sub control_lock.write_lock()
	MutexLock mutex
	If readers>0 Or writers>0 Then
		writers_waiting+=1
		While readers>0 Or writers>0
			CondWait writer_cond, mutex
		Wend
		writers_waiting-=1
	EndIf
	writers+=1
	MutexUnLock mutex
End Sub
Sub control_lock.write_unlock()
	MutexLock mutex
	writers-=1
	If writers<1 Then
		If writers_waiting>0 Then
			Condsignal writer_cond
		ElseIf readers_waiting>0 Then
			Condbroadcast reader_cond
		EndIf
		If writers<0 Then
			Print "Error in control_lock. Writers = " & writers
			writers=0
		EndIf
	EndIf
	MutexUnLock mutex
End Sub
Sub control_lock.read_lock()
	MutexLock mutex
	If writers>0 Or writers_waiting>0 Then
		readers_waiting+=1
		While writers>0 Or writers_waiting>0
			CondWait reader_cond, mutex
		Wend
		readers_waiting-=1
	EndIf
	readers+=1
	MutexUnLock mutex
End Sub
Sub control_lock.read_unlock()
	MutexLock mutex
	readers-=1
	If readers<1 Then
		If writers_waiting>0 Then Condsignal writer_cond
		If readers<0 Then
			Print "Error in control_lock. Readers = " & readers
			readers=0
		EndIf
	EndIf
	MutexUnLock mutex
End Sub
Constructor control_lock
	readers = 0
	writers = 0
	readers_waiting = 0
	writers_waiting = 0
	mutex = MutexCreate
	reader_cond = Condcreate
	writer_cond = Condcreate
End Constructor
Destructor control_lock
	If mutex Then MutexDestroy(mutex)
	If reader_cond Then CondDestroy(reader_cond)
	If writer_cond Then CondDestroy(writer_cond)
	mutex=0
	reader_cond=0
	writer_cond=0
End Destructor
badidea
Posts: 2591
Joined: May 24, 2007 22:10
Location: The Netherlands

Post by badidea »

Thanks, this will keep me busy for a while.
It will be a RTS-game.
D.J.Peters
Posts: 8586
Joined: May 28, 2005 3:28
Contact:

Post by D.J.Peters »

hello badidea
threads often not the right solution.

FD_SET's are a powerfull key.

you must create sockets and put in an FD_SET (an array of sockets)

now your server or your clients can ask with "select()" is any data aviable ?
(without to wait)

of course you can define an optional timeout value too.

Joshy

http://www.lowtek.com/sockets/select.html
badidea
Posts: 2591
Joined: May 24, 2007 22:10
Location: The Netherlands

Post by badidea »

And I was hoping that this would be easy.

Anyway, how about SDLNet_UDP_Recv?
"This is a non-blocking call, meaning if there’s no data ready to be received the function will return."

See also: http://www.freebasic.net/forum/viewtopic.php?t=3268

Code: Select all

' check if there is a packet waiting
sizerecv = SDLNet_UDP_Recv( data_socket, @packet)
' if data was received
If sizerecv = 1 Then
    ' process the received data
    HandleNetVarMessage(silentsignal)
End if
With this, it seems to me, I don't need threads and locking.
1000101
Posts: 2556
Joined: Jun 13, 2005 23:14
Location: SK, Canada

Post by 1000101 »

@pestery, that is an implementation however you are still using a global mutex which prevents multiple readers. The idea of a read-write lock (rwlock) is that a writer has complete exclusion to all others but readers can share the data at the same time. The following code does just this and for readers it is very fast to lock (important!) only waiting for a writer. Writers are much slower and will cause a thread to wait until all other locks are released, serializing the lock by preventing new locks after it's claim is made (all new locks must wait until the writers release the locks).

rwlock.bi

Code: Select all

#IfNDef _RW_LOCK_INCLUDE_
#Define _RW_LOCK_INCLUDE_

#Pragma Once




Extern	"C"




#Define		WRITER		1
#Define		READER		0




Type	rwlock_t
	
	#IfDef	__FB_WIN32__
		
		As Any Ptr		hReaderEvent
		As Any Ptr		hMutex
		As Any Ptr		hWriterMutex
		As Integer		iCounter
		
	#ElseIf	Defined( __FB_LINUX__ )
		
 		As Integer		__rw_readers
		As Any Ptr		__rw_writer
		As Any Ptr		__rw_read_waiting
		As Any Ptr		__rw_write_waiting
		As Integer		__rw_kind
		As Integer		__rw_pshared
		
	#Else
		
		#Error	Platform not supported!
		
	#EndIf
	
End	Type




Type	RWLock
	
	Declare		Function	Init					() As Integer
	Declare		Sub		Denit					()
	Declare		Sub		Claim					( ByVal Who As Integer = READER )
	Declare		Sub		Release					( ByVal Who As Integer = READER )
	
	
	Private:
	As rwlock_t	rwlock
	
	
End Type




End	Extern




#EndIf
rwlock.bas

Code: Select all

#Include Once "rwlock.bi"




#Ifdef __FB_WIN32__
	
	
	
	
	#Include Once "windows.bi"
	
	
	
	
	Extern	"C"
	
	
	
	
	Function		RWLock.Init		() As Integer
		Function		= 1
		
		rwlock.hReaderEvent	= CreateEvent( NULL, TRUE , FALSE, NULL )
		If( rwlock.hReaderEvent	= NULL )Then
			Denit()
			Exit Function
		End	If
		
		rwlock.hMutex		= CreateEvent( NULL, FALSE, TRUE , NULL )
		If( rwlock.hMutex	= NULL )Then
			Denit()
			Exit Function
		EndIf
		
		rwlock.hWriterMutex	= MutexCreate()
		If( rwlock.hWriterMutex	= NULL )Then
			Denit()
			Exit Function
		EndIf
		
		rwlock.iCounter		= -1
		Function		= 0
	End	Function
	
	
	
	
	Sub			RWLock.Denit		()
		
		If( rwlock.hReaderEvent	)Then CloseHandle ( rwlock.hReaderEvent	)
		If( rwlock.hMutex	)Then CloseHandle ( rwlock.hMutex	)
		If( rwlock.hWriterMutex	)Then MutexDestroy( rwlock.hWriterMutex	)
		
		rwlock.hReaderEvent	= NULL
		rwlock.hMutex		= NULL
		rwlock.hWriterMutex	= NULL
		
	End	Sub
	
	
	
	
	Sub			RWLock.Claim		( ByVal Who As Integer )
		
		If	( Who = WRITER )Then
			' Writer
			MutexLock( rwlock.hWriterMutex )
			WaitForSingleObject( rwlock.hMutex, INFINITE )
			
		ElseIf	( Who = READER )Then
			' Reader
			If( InterlockedIncrement( @rwlock.iCounter ) = 0 )Then
				WaitForSingleObject( rwlock.hMutex, INFINITE )
				SetEvent( rwlock.hReaderEvent )
			End	If
			WaitForSingleObject( rwlock.hReaderEvent, INFINITE )
			
		End	If
		
	End	Sub
	
	
	
	
	Sub			RWLock.Release		( ByVal Who As Integer )
		
		If	( Who = WRITER )Then
			' Writer
			SetEvent( rwlock.hMutex )
			MutexUnlock( rwlock.hWriterMutex )
			
		ElseIf	( Who = READER )Then
			' Reader
			If( InterlockedDecrement( @rwlock.iCounter ) < 0)Then
				ResetEvent( rwlock.hReaderEvent )
				SetEvent( rwlock.hMutex )
			End	If
			
		End	If
		
	End	Sub
	
	
	
	
	/'
		http://msdn.microsoft.com/en-us/library/ms810427.aspx
		
		
		#define WRITER 0
		#define READER 1
		
		
		BOOL CRWLock::Initialize(int i)
		{ // Note: We don't do any error checking here. Better do that in real life.
		  hReaderEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
		  hMutex = CreateEvent(NULL,FALSE,TRUE,NULL);
		  hWriterMutex = CreateMutex(NULL,FALSE,NULL);
		  iCounter = -1;
		  return (TRUE);
		};
		
		void CRWLock::Cleanup()
		{ CloseHandle(hReaderEvent);
		  CloseHandle(hMutex);
		  CloseHandle(hWriterMutex);
		};
		
		void CRWLock::Claim(int i)
		{ 
		 if (i== WRITER)
		 {  
		    WaitForSingleObject(hWriterMutex,INFINITE);
		   WaitForSingleObject(hMutex, INFINITE);
		 }
		 else
		 {   
		 if (InterlockedIncrement(&iCounter) == 0)
		      { WaitForSingleObject(hMutex, INFINITE);
		        SetEvent(hReaderEvent);
		      };
		 WaitForSingleObject(hReaderEvent,INFINITE);
		 };
		};
		
		void CRWLock::Release(int i)
		{ if (i == WRITER)
		  { 
		     SetEvent(hMutex);
		     ReleaseMutex(hWriterMutex);
		  }
		  else
		  if (InterlockedDecrement(&iCounter) < 0)
		     { ResetEvent(hReaderEvent);
		       SetEvent(hMutex);
		     };
		};
	'/
	
	
	
	
#ElseIf Defined( __FB_LINUX__ )
	
	
	
	
	Extern	"C"
	
	
	
	
	declare function pthread_rwlock_init cdecl alias "pthread_rwlock_init" _
		( _
			byval rwlock as pthread_rwlock_t ptr, _
			byval attr   as pthread_rwlockattr_t ptr _
		) as integer
	
	
	declare function pthread_rwlock_destroy cdecl alias "pthread_rwlock_destroy" _
		( _
			byval rwlock as pthread_rwlock_t ptr _
		) as integer
	
	
	declare function pthread_rwlock_rdlock cdecl alias "pthread_rwlock_rdlock" _
		( _
			byval rwlock as pthread_rwlock_t ptr _
		) as integer
	
	
	declare function pthread_rwlock_wrlock cdecl alias "pthread_rwlock_wrlock" _
		( _
			byval rwlock as pthread_rwlock_t ptr _
		) as integer
	
	
	declare function pthread_rwlock_unlock cdecl alias "pthread_rwlock_unlock" _
		( _
			byval rwlock as pthread_rwlock_t ptr _
		) as integer
	
	
	
	
	Function		RWLock.Init		() As Integer
		Function		= 1
		
		If( pthread_rwlock_init( @rwlock, NULL ) )Then
			Denit()
			Exit Function
		End	If
		
		Function		= 0
	End	Function
	
	
	
	
	Sub			RWLock.Denit		()
		
		pthread_rwlock_destroy( @rwlock )
		
		Clear @rwlock, 0, SizeOf( rwlock )
		
	End	Sub
	
	
	
	
	Sub			RWLock.Claim		( ByVal Who As Integer )
		
		If	( Who = WRITER )Then
			pthread_rwlock_wrlock( @rwlock )
			
		ElseIf	( Who = READER )Then
			pthread_rwlock_rdlock( @rwlock )
			
		End	If
		
	End	Sub
	
	
	
	
	Sub			RWLock.Release		( ByVal Who As Integer )
		
		pthread_rwlock_unlock( @rwlock )
		
	End	Sub
	
	
	
	
	/'
		http://linux.die.net/man/3/pthread_rwlock_rdlock
	'/
	
	
	
	
#Else
	
	
	#Error	Platform not supported!
	
	
#EndIf
 



End	Extern
No credit to myself for the code as it is all from web sources.
pestery
Posts: 493
Joined: Jun 16, 2007 2:00
Location: Australia

Post by pestery »

Hi 1000101. I had a look at your code to see if i could learn something from it but the windows API is beyond me. I did a speed test using counter.bas (http://www.freebasic.net/forum/viewtopic.php?t=4221) by simply locking and then unlocking the rwlock. With your method the writer cycle ran a bit faster than mine, but for the reader cycle it was the reverse. I should point out that this was only a quick test.

I also did a test to see how the two method behaved with several threads alternating between writers and readers. As it is my method give priority to threads that request a write lock which was the intention. Also, it does allow multiple reader threads to run simultaneously. Your method seemed to give priority to threads that request a read lock although because I don't know how the windows API work that's only an observation. The test program I used is:

Code: Select all

Dim Shared As Any Ptr print_mutex
Dim Shared As control_lock test_lock
#Macro test_write_lock(stage)
	MutexLock print_mutex
	Locate id
	Color 4
	Print "Thread " & id & " is waiting for a write lock. Stage " & stage
	MutexUnLock print_mutex
	test_lock.write_lock()
	MutexLock print_mutex
	Locate id
	Color 12
	Print "Thread " & id & " has a write lock.            Stage " & stage
	MutexUnLock print_mutex
	Sleep 500+(Rnd*1000)
	MutexLock print_mutex
	Locate id
	Color 7
	Print "Thread " & id & " is idle.                     Stage " & stage
	MutexUnLock print_mutex
	test_lock.write_unlock()
	Sleep 100*Rnd
#EndMacro
#Macro test_read_lock(stage)
	MutexLock print_mutex
	Locate id
	Color 2
	Print "Thread " & id & " is waiting for a read lock.  Stage " & stage
	MutexUnLock print_mutex
	test_lock.read_lock()
	MutexLock print_mutex
	Locate id
	Color 10
	Print "Thread " & id & " has a read lock.             Stage " & stage
	MutexUnLock print_mutex
	Sleep 500+(Rnd*1000)
	MutexLock print_mutex
	Locate id
	Color 7
	Print "Thread " & id & " is idle.                     Stage " & stage
	MutexUnLock print_mutex
	test_lock.read_unlock()
	Sleep 100*Rnd
#EndMacro
Sub thread_sub(ByVal param As Any Ptr)
	Dim As Integer id = Cast(Integer, param)
	test_write_lock(1)
	test_read_lock(2)
	test_read_lock(3)
	test_read_lock(4)
	test_write_lock(5)
	test_read_lock(6)
	test_read_lock(7)
End Sub
Dim As Any Ptr thread(1 To 9)
print_mutex = MutexCreate
screenres 640,480
For a As Integer = LBound(thread) To UBound(thread)
	thread(a) = ThreadCreate(@thread_sub, Cast(Any Ptr, a))
Next
For a As Integer = LBound(thread) To UBound(thread)
	ThreadWait thread(a)
Next
MutexDestroy print_mutex
I also tried to test both methods on Linux (Ubuntu 9.10) but I couldn't get your method to compile as there were complaints from fbc about "Illegal specification, at parameter xx" at the function aliases "declare function pthread_xxx". I tried both fbc-20.0b and fbc-21.1. Should there be an included library or something? With my method there were mixed results. With both versions of fbc the example compiled but when running the version compiled with fbc-21.1 there was a segment fault, whereas the version compiled with fbc-20.0b worked as expected. Until now I hadn't tested my method of Linux before and don't know why one version of fbc worked when the other didn't.

Anyway, back on topic.
And I was hoping that this would be easy.
Famous last words, hehe. Badidea, the two connection types you can use are TCP or UDP. You've probably read this already, but just in case. TCP is easier to use because more of the stuff is handled by the protocol and it should be suitable for an RTS (or turn based) game. For first person type games its better to use UDP because its faster and more of the control is left to the programmer, which also makes making it reliable more difficult. This is the one I was trying to use earlier in the year. As D.J.Peters said, have a look at the socket() command (in the FB libraries its alias is SelectSocket, and in fbnet its NetSocket). I haven't used any of the SDL libraries before so I can't help you there.
badidea
Posts: 2591
Joined: May 24, 2007 22:10
Location: The Netherlands

Post by badidea »

@pestery:

I never knew that the FB libraries included socket functions. I'm am going to try that instead of fbnet. In fbnet I only see the select() function in the windows header file, not in the linux version. And I want my program to work with both.

And since latency is important for the game, I'll go with UDP and accept that I have to do some extra work myself. Like giving the packets sequence numbers.
1000101
Posts: 2556
Joined: Jun 13, 2005 23:14
Location: SK, Canada

Post by 1000101 »

@pestery, I can't really say much about the speed of the code since, as I pointed out, none of it's mine. The win32 implementation is a direct port from the msdn and the pthreads code is completely untested and mostly a wrapper. If the linux version if giving warnings it's probably about the structure name (it's the same struct as the pthreads but I shanghai'd it for the use on both platforms and renamed it). However, I have found no problems with the [win32] speed and I use it for massively parallel code blocks.

See this example, specifically gt_thread_test. On my dual core (Amd @ 2.2GHz) the blitter tests run at less then 20ms per frame which are imposing 50 read locks per frame on resources shared by several threads. I feel this is quite an adequate speed for a reader considering you would never really be reading the exact same resource so many times a frame from multiple threads. Further, this frame time is slowed by the main thread imposing a read lock on the individual threads working buffers every frame of the main thread which forces the individual threads to wait for their write locks. As with everything though, there is always room for improvement.
pestery
Posts: 493
Joined: Jun 16, 2007 2:00
Location: Australia

Post by pestery »

Hi 1000101. I think my head will explode if I look at the gt_thread_test thingy anymore. Very cool.

@Badidea. The FB libraries that you'd want would probably be:

Code: Select all

#If Defined(__FB_WIN32__)
#Include Once "win/winsock2.bi"
#ElseIf Defined(__FB_LINUX__)
#Include Once "crt/netdb.bi"
#Include Once "crt/sys/socket.bi"
#Include Once "crt/netinet/in.bi"
#Include Once "crt/arpa/inet.bi"
#Include Once "crt/unistd.bi"
#Include Once "crt/sys/select.bi"
#Else
#Error Platform Not supported
#EndIf
You might also want to use the zlib.bi to compact data before you send it through the network. I understand that UDP messages should be kept under 1 MB in length or 0.5 MB or something for better reliability. I'm kinda reluctant to post what I did at the time because there are several iterations of the program and I can't remember which one was the most complete (or understandable) and they were all works in progress.
badidea
Posts: 2591
Joined: May 24, 2007 22:10
Location: The Netherlands

Post by badidea »

I haven't had much time to work on this subject, but I did made some simple UDP messaging. Uses the standard FB libraries, still only 1-way, and still blocking calls.
For windows, uncomment the statements with WSAsomething.

Sender:

Code: Select all

#If Defined(__FB_WIN32__)

  #Include Once "win/winsock2.bi"

#ElseIf Defined(__FB_LINUX__)

  #Include Once "crt/netdb.bi"

  #Include Once "crt/sys/socket.bi"

  #Include Once "crt/netinet/in.bi"

  #Include Once "crt/arpa/inet.bi"

  #Include Once "crt/unistd.bi"

  #Include Once "crt/sys/select.bi"

#Else

  #Error Platform Not supported

#EndIf



#include "fbgfx.bi"



#define NET_BUFLEN 512



function inet_addr_rev(ipAddress as in_addr) as string

  dim as ubyte ptr pChar = cptr(ubyte ptr, @ipAddress)

  return str(pChar[0]) & _
    "." & str(pChar[1]) & _
    "." & str(pChar[2]) & _
    "." & str(pChar[3])

end function



'--- SENDER ---



'dim as WSADATA wsaData

dim as SOCKET sendSocket

dim as sockaddr_in recvAddr

dim as integer port = 27015

dim as ubyte sendBuf(NET_BUFLEN-1)

dim as integer bufLen = NET_BUFLEN

dim as integer iResult, quit = 0



'iResult = WSAStartup(MAKEWORD(2,2), @wsaData)

'if (iResult <> 0) then

'  beep: print "Error: Winsock init"

'  sleep

'  end

'end if



sendSocket = socket_(AF_INET, SOCK_DGRAM, IPPROTO_UDP) 'IPv4, UDP

if (sendSocket < 0) then

  beep: print "Error: Net socket"

  'WSACleanup()

  end

end if



recvAddr.sin_family = AF_INET

recvAddr.sin_port = htons(port)

recvAddr.sin_addr.s_addr = inet_addr("127.0.0.1")



while (quit = 0)

  

  print "Trying: Net send"

  iResult = sendto(sendSocket, @sendBuf(0), bufLen, 0, cptr(sockaddr ptr, @recvAddr), sizeof(recvAddr))

  if (iResult < 0) then

    beep: print "Error: Net send"

    closesocket(sendSocket)

    'WSACleanup()

    sleep

    end

  else

    print "Bytes send:"; iResult; " to: "; inet_addr_rev(recvAddr.sin_addr)

  end if



  sleep 500, 0

  

  if multikey(FB.SC_ESCAPE) then

    quit = 1

  end if



wend



iResult = closeSocket(sendSocket)

if (iResult < 0) then

  beep: print "Error: Close socket"

end if

'WSACleanup()



print "End."

sleep

Receiver:

Code: Select all

#If Defined(__FB_WIN32__)

  #Include Once "win/winsock2.bi"

#ElseIf Defined(__FB_LINUX__)

  #Include Once "crt/netdb.bi"

  #Include Once "crt/sys/socket.bi"

  #Include Once "crt/netinet/in.bi"

  #Include Once "crt/arpa/inet.bi"

  #Include Once "crt/unistd.bi"

  #Include Once "crt/sys/select.bi"

#Else

  #Error Platform Not supported

#EndIf



#include "fbgfx.bi"



#define NET_BUFLEN 512



function inet_addr_rev(ipAddress as in_addr) as string

  dim as ubyte ptr pChar = cptr(ubyte ptr, @ipAddress)

  return str(pChar[0]) & _
    "." & str(pChar[1]) & _
    "." & str(pChar[2]) & _
    "." & str(pChar[3])

end function



'--- RECEIVER ---



'dim as WSADATA wsaData

dim as SOCKET recvSocket

dim as sockaddr_in recvAddr

dim as integer port = 27015

dim as ubyte recvBuf(NET_BUFLEN-1)

dim as integer bufLen = NET_BUFLEN

dim as sockaddr_in senderAddr

dim as integer senderAddrSize = sizeof(senderAddr)

dim as integer iResult, quit = 0



'iResult = WSAStartup(MAKEWORD(2,2), @wsaData)

'if (iResult <> 0) then

'  beep: print "Error: Winsock init"

'  sleep

'  end

'end if



recvSocket = socket_(AF_INET, SOCK_DGRAM, IPPROTO_UDP)

if (recvSocket < 0) then

  beep: print "Error: Net socket"

  'WSACleanup()

  end

end if



recvAddr.sin_family = AF_INET

recvAddr.sin_port = htons(port)

recvAddr.sin_addr.s_addr = htonl(INADDR_ANY)



iResult = bind(recvSocket, cptr(sockaddr ptr, @recvAddr), sizeof(recvAddr))

if (iResult < 0) then

  beep: print "Error: Net bind"

  'WSACleanup()

  sleep

  end

end if



while (quit = 0)

  

  print "Trying: Net recv"

  iResult = recvfrom(recvSocket, @recvBuf(0), bufLen, 0, cptr(sockaddr ptr, @senderAddr), @senderAddrSize)

  if (iResult > 0) then

    print "Bytes received:"; iResult; " from: "; inet_addr_rev(senderAddr.sin_addr)

  elseif (iResult = 0) then

    print "Connection closed"

    closesocket(recvSocket)

    'WSACleanup()

    sleep

    end

  else

    beep: print "Error: Net receive"

    closesocket(recvSocket) 

    'WSACleanup()

    sleep

    end 

  end if



  sleep 500, 0

  

  if multikey(FB.SC_ESCAPE) then

    quit = 1

  end if



wend



iResult = closesocket(recvSocket)

if (iResult < 0) then

  beep: print "Error: Close socket"

end if

'WSACleanup()



print "End."

sleep


inet_ntoa did not seem to work, so I made inet_addr_rev to display the ip-addres.

I have not yet tested the communication between 2 computers. Only locally.
<michael>
Posts: 24
Joined: Oct 29, 2005 10:14

Post by <michael> »

badidea wrote:And I was hoping that this would be easy.

Anyway, how about SDLNet_UDP_Recv?
"This is a non-blocking call, meaning if there’s no data ready to be received the function will return."
I would use Winsocks with fdsets, like D.J. Peters said.
It is not as difficult as you may think:

Code: Select all

function getdata(s_ as socket, byref buffer as string) As Integer
   dim blen as integer
   dim rbuffer as zstring*1024 'size of buffer: receive max. 1024 bytes at once
   dim fdset as FD_SET
   DIM tv AS timeval
   DIM retval AS INTEGER
   Dim temp As String

   tv.tv_Sec = 0  'timeout of function select in seconds
   tv.tv_Usec = 0 'and microseconds (0: don't wait)

   fd_set_(s_, @fdset)

   retval = select_(s_+1, @fdset, 0, 0, @tv)

   If (retval = -1) THEN
      return -1
   Else
      If (FD_ISSET(s_, @fdSet))<>0 then  'some data is available
         blen=recv(s_, strptr(rbuffer), buffersize, 0) 'receive data and save it to rbuffer
         If blen <= 0 then
            return -2
         End If
         
         rbuffer[blen] = 0

         temp=String(blen+1, " ") 'prepare a fb-string to copy contents to it
         memcpy(strptr(temp), strptr(rbuffer), blen)
         
         buffer = temp
      End If
   End If

  return blen
end function
Hope this helps.
pestery
Posts: 493
Joined: Jun 16, 2007 2:00
Location: Australia

Post by pestery »

Your code works perfectly computer to computer. I made a couple of modifications to the receiver bit to add in the SelectSocket command. I've tested it with (WinXP <-> WinXp) and (Ubuntu 9.10 <-> WinXP) and it works.

Code: Select all

#If Defined(__FB_WIN32__)
#Include Once "win/winsock2.bi"
#ElseIf Defined(__FB_LINUX__)
#Include Once "crt/netdb.bi"
#Include Once "crt/sys/socket.bi"
#Include Once "crt/netinet/in.bi"
#Include Once "crt/arpa/inet.bi"
#Include Once "crt/unistd.bi"
#Include Once "crt/sys/select.bi"
#Else
#Error Platform Not supported
#EndIf

#Include "fbgfx.bi"

#Define NET_BUFLEN 512

Function inet_addr_rev(ipAddress As in_addr) As String
	Dim As UByte Ptr pChar = CPtr(UByte Ptr, @ipAddress)
	Return Str(pChar[0]) & _
	"." & Str(pChar[1]) & _
	"." & Str(pChar[2]) & _
	"." & Str(pChar[3])
End Function

'--- RECEIVER ---

Dim As Socket recvSocket
Dim As sockaddr_in recvAddr
Dim As Integer port = 27015
Dim As UByte recvBuf(NET_BUFLEN-1)
Dim As Integer bufLen = NET_BUFLEN
Dim As sockaddr_in senderAddr
Dim As Integer senderAddrSize = SizeOf(senderAddr)
Dim As Integer iResult, quit = 0

#Ifdef __FB_WIN32__
Dim As WSADATA wsaData
iResult = WSAStartup(MAKEWORD(2,2), @wsaData)
If (iResult <> 0) Then
	Beep: Print "Error: Winsock init"
	Sleep
	End
End If
#EndIf

#Macro WSACleanup_()
#Ifdef __FB_WIN32__
WSACleanup()
#EndIf
#EndMacro

recvSocket = OpenSocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
If (recvSocket < 0) Then
	Beep: Print "Error: Net socket"
	WSACleanup_()
	End
End If

recvAddr.sin_family = AF_INET
recvAddr.sin_port = htons(port)
recvAddr.sin_addr.s_addr = htonl(INADDR_ANY)

iResult = bind(recvSocket, CPtr(sockaddr Ptr, @recvAddr), SizeOf(recvAddr))
If (iResult < 0) Then
	Beep: Print "Error: Net bind"
	WSACleanup_()
	Sleep
	End
End If


' Stuff for SelectSocket command
Dim As Single max_time_to_wait = 1.0 ' Seconds
Dim As timeval tv, tv0
Dim As fd_set SockList, SockList0
Dim As Socket SockHighest = recvSocket+1
Dim As Integer recv_count
tv0.tv_sec = Fix(max_time_to_wait)
tv0.tv_usec = Frac(max_time_to_wait)*1000
FD_ZERO(@SockList0)
FD_SET_(recvSocket, @SockList0)
'FD_SET_(recvSocket_2, @SockList0)
'FD_SET_(recvSocket_3, @SockList0)
'FD_SET_(recvSocket_4_etc, @SockList0)
'SockHighest = MAX(recvSocket, recvSocket_2, etc) + 1

While (quit = 0)

	' Wait for incoming data
	Print "Waiting for data"
	SockList = SockList0 ' Keep a backup because SelectSocket will change the data
	tv = tv0
	recv_count = SelectSocket(SockHighest, @SockList, 0, 0, @tv)

	' Check if there are any sockets with data waiting
	If recv_count = 0 Then
		Print "SelectSocket: time-out"
	ElseIf recv_count < 0 Then
		Print "SelectSocket: error"
	Else
		Print "SelectSocket: sockets with data " & recv_count
		
		' Check if the socket has data waiting (check and read all sockets separately if more than one)
		If FD_ISSET(recvSocket, @SockList) Then
			
			Print "Trying: Net recv"
			iResult = recvfrom(recvSocket, @recvBuf(0), bufLen, 0, CPtr(sockaddr Ptr, @senderAddr), @senderAddrSize)
			If (iResult > 0) Then
				Print "Bytes received:"; iResult; " from: "; inet_addr_rev(senderAddr.sin_addr)
			ElseIf (iResult = 0) Then
				Print "Connection closed"
				CloseSocket(recvSocket)
				WSACleanup_()
				Sleep
				End
			Else
				Beep: Print "Error: Net receive"
				CloseSocket(recvSocket)
				WSACleanup_()
				Sleep
				End
			End If
		EndIf
	EndIf

	If InKey = Chr(27) Then ' Escape key
		quit = 1
	End If

Wend

iResult = CloseSocket(recvSocket)
If (iResult < 0) Then
	Beep: Print "Error: Close socket"
End If
WSACleanup_()

Print "End."
Sleep
badidea
Posts: 2591
Joined: May 24, 2007 22:10
Location: The Netherlands

Post by badidea »

Thanks, I have the basics working.
Using fd_set and 2-way communications, multiple clients.
Works with WinXp and Ubuntu.

Client.bas

Code: Select all

#include "network.bi"
#include "fbgfx.bi"

#DEFINE SERVER_PORT 27015
#DEFINE SERVER_IP "127.0.0.1"

'--- CLIENT ---

dim as SOCKET socketId
dim as sockaddr_in serverAddr
dim as integer serverAddrSize = sizeof(serverAddr)
dim as integer iResult, quit, sendData, sendCount
dim as string key

dim as fd_set readfds, readfds0
dim as fd_set writefds, writefds0
dim as timeval timeout

dim as net_data netDataIn
dim as net_data netDataOut

iResult = init_socket_API()
if (iResult <> 0) then
  print "Winsock init: Error"
  sleep
  end
end if

socketId = socket_(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
if (socketId < 0) then
  print "Socket: Error"
  close_socket_API()
  sleep
  end
end if

serverAddr.sin_family = AF_INET
serverAddr.sin_port = htons(SERVER_PORT)
serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP)

FD_ZERO(@readfds0)
FD_ZERO(@writefds0)
FD_SET_(socketId, @readfds0)
FD_SET_(SocketId, @writefds0)

print "q = quit"
print "s = send"

while (quit = 0)

  quit = 0: sendData = 0
  key = lcase(inkey())
  if (key = "q") then quit = 1
  if (key = "s") then sendData = 1

 'Keep a backup because SelectSocket will change the data
  readfds = readfds0
  writefds = writefds0
  timeout = type(0, 0)
  
  iResult = selectSocket(FD_SETSIZE, @readfds, @writefds, NULL, @timeout)
  if (iResult = 0) then
    print "SelectSocket: Timeout"
  elseif (iResult < 0) then
    print "SelectSocket: Error"
  else
    'print "SelectSocket: Sockets ready: "; iResult
  end if

  if(sendData = 1) then
    netDataOut.seqNr = sendCount
    netDataOut.text = "Client says: Yes!"
    if(FD_ISSET(socketId, @writefds)) then
      print "Send: Trying..."
      iResult = sendto(socketId, cptr(ubyte ptr, @netDataOut), _
        sizeof(net_data), 0, cptr(sockaddr ptr, @serverAddr), serverAddrSize)
      if (iResult < 0) then
        print "Send: Error"
        closeSocket(socketId)
        close_socket_API()
        sleep
        end
      else
        print "Bytes send:"; iResult;
        print " to: "; inet_addr_rev(serverAddr.sin_addr)
      end if
    end if
    sendCount += 1
  end if

  if(FD_ISSET(socketId, @readfds)) then
    print "Receive: Trying..."
    iResult = recvfrom(socketId, cptr(ubyte ptr, @netDataIn), _
      sizeof(net_data), 0, cptr(sockaddr ptr, @serverAddr), @serverAddrSize)
    if (iResult > 0) then
      print "Bytes received:"; iResult;
      print " from: "; inet_addr_rev(serverAddr.sin_addr)
      print "SeqNr: "; netDataIn.seqNr;
      print " text: "; netDataIn.text
    elseif (iResult = 0) then
      print "Receive: Connection closed"
      closesocket(socketId)
      close_socket_API()
      sleep
      end
    else
        print "Receive: Error"
      closesocket(socketId) 
      close_socket_API()
      sleep
      end 
    end if
  end if

  sleep 1, 1

wend

iResult = closeSocket(socketId)
if (iResult < 0) then
  print "CloseSocket: Error"
end if
close_socket_API()

print "End."
sleep
Server.bas

Code: Select all

#include "network.bi"
#include "fbgfx.bi"

#DEFINE SERVER_PORT 27015
#DEFINE CLIENT_IP "127.0.0.1"

'--- SERVER ---

dim as SOCKET socketId
dim as sockaddr_in clientAddr
dim as sockaddr_in serverAddr
dim as integer serverAddrSize = sizeof(serverAddr)
dim as integer clientAddrSize = sizeof(clientAddr)
dim as integer iResult, quit, sendData, sendCount
dim as string key

dim as net_data netDataIn
dim as net_data netDataOut

dim as fd_set readfds, readfds0
dim as fd_set writefds, writefds0
dim as timeval timeout

iResult = init_socket_API()
if (iResult <> 0) then
  print "Winsock init: Error"
  sleep
  end
end if

socketId = socket_(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
if (socketId < 0) then
  print "Socket: Error"
  close_socket_API()
  end
end if

serverAddr.sin_family = AF_INET
serverAddr.sin_port = htons(SERVER_PORT)
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY)

iResult = bind(socketId, cptr(sockaddr ptr, @serverAddr), serverAddrSize)
if (iResult < 0) then
  print "Bind: Error"
  close_socket_API()
  sleep
  end
end if

clientAddr.sin_family = AF_INET
clientAddr.sin_port = htons(SERVER_PORT)
clientAddr.sin_addr.s_addr = inet_addr(CLIENT_IP)

FD_ZERO(@readfds0)
FD_ZERO(@writefds0)
FD_SET_(socketId, @readfds0)
FD_SET_(SocketId, @writefds0)

print "q = quit"
print "s = send"

while (quit = 0)
  
  quit = 0: sendData = 0
  key = lcase(inkey())
  if (key = "q") then quit = 1
  if (key = "s") then sendData = 1

 'Keep a backup because SelectSocket will change the data
  readfds = readfds0
  writefds = writefds0
  timeout = type(0, 0)

  iResult = selectSocket(FD_SETSIZE, @readfds, @writefds, NULL, @timeout)
  if (iResult = 0) then
    print "SelectSocket: Timeout"
  elseif (iResult < 0) then
    print "SelectSocket: Error"
  else
    'print "SelectSocket: Sockets ready: "; iResult
  end if
  
  if(sendData = 1) then
    netDataOut.seqNr = sendCount
    netDataOut.text = "Server says: No!"
    if(FD_ISSET(socketId, @writefds)) then
      print "Send: Trying..."
      iResult = sendto(socketId, cptr(ubyte ptr, @netDataOut), _
        sizeof(net_data), 0, cptr(sockaddr ptr, @clientAddr), clientAddrSize)
      if (iResult < 0) then
        print "Send: Error"
        closeSocket(socketId)
        close_socket_API()
        sleep
        end
      else
        print "Bytes send:"; iResult;
        print " to: "; inet_addr_rev(clientAddr.sin_addr)
      end if
    end if
    sendCount += 1
  end if
  
  if(FD_ISSET(socketId, @readfds)) then
    print "Receive: Trying..."
    iResult = recvfrom(socketId, cptr(ubyte ptr, @netDataIn), _
      sizeof(net_data), 0, cptr(sockaddr ptr, @clientAddr), @clientAddrSize)
    if (iResult > 0) then
      print "Bytes received:"; iResult;
      print " from: "; inet_addr_rev(clientAddr.sin_addr)
      print "SeqNr: "; netDataIn.seqNr;
      print " text: "; netDataIn.text
    elseif (iResult = 0) then
      print "Receive: Connection closed"
      closesocket(socketId)
      close_socket_API()
      sleep
      end
    else
      print "Receive: Error"
      closesocket(socketId) 
      close_socket_API()
      sleep
      end 
    end if
  end if
  
  sleep 1, 1
  
wend

iResult = closesocket(socketId)
if (iResult < 0) then
  print "CloseSocket: Error"
end if
close_socket_API()

print "End."
sleep
Network.bi

Code: Select all

#define __FD_SETSIZE 32

#If Defined(__FB_WIN32__)
  #Include Once "win/winsock2.bi"
#ElseIf Defined(__FB_LINUX__)
  #Include Once "crt/netdb.bi"
  #Include Once "crt/sys/socket.bi"
  #Include Once "crt/netinet/in.bi"
  #Include Once "crt/arpa/inet.bi"
  #Include Once "crt/unistd.bi"
  #Include Once "crt/sys/select.bi"
#Else
  #Error Platform Not supported
#EndIf

function init_socket_API() as integer
  #Ifdef __FB_WIN32__
    dim as WSADATA wsaData
    return WSAStartup(MAKEWORD(2,2), @wsaData)
  #Else
    return 0
  #EndIf
end function

sub close_socket_API()
  #Ifdef __FB_WIN32__
    WSACleanup()
  #Else
    'Nothing to do
  #EndIf
end sub

function inet_addr_rev(ipAddress as in_addr) as string
  dim as ubyte ptr pChar = cptr(ubyte ptr, @ipAddress)
  return str(pChar[0]) & _
    "." & str(pChar[1]) & _
    "." & str(pChar[2]) & _
    "." & str(pChar[3])
end function

type net_data
  as integer seqNr
  as string * 32 text
end type
Now, some management still to do before I can use it in my game.
Things like: Accepting new client, let clients time out, validate the UDP message, etc.
Post Reply