Trying to create a network

Blitz3D Forums/Blitz3D Programming/Trying to create a network

PowerPC603(Posted 2010) [#1]
Hi guys,

I'm trying to get networking to work, but it seems to fail.
I've modified the default examples which come with B3D (the example provided with the CreateTCPServer command):

Server:
Global Server = CreateTCPServer(5000) 

If Server <> 0 Then 
	Print "Server started successfully." 
Else 
	Print "Server failed to start." 
	End 
End If 

While Not KeyHit(1) 
	strStream = AcceptTCPStream(Server)
	Cnt = Cnt + 1

	If strStream Then 
		Print ReadString$(strStream) 
	Else
		Print "No stream was received: " + Str$(Cnt)
		Delay 100
	End If 
Wend 

End


Client:
Global strGame = OpenTCPStream("127.0.0.1", 5000)

If strGame <> 0 Then
	Print "Client Connected successfully."
Else
	Print "Server failed to connect."
	WaitKey()
	End
End If

While Not KeyHit(1)
	strStream$ = Str$(Rand(0, 1000))
	WriteString strGame, strStream$
	Print strStream$
	Delay 500
Wend

End


This should send random number to the server every frame, but the server only receives the first number, while the client window happily continues to generate random numbers and prints them to the screen.
The server only lists the first number that was sent and continues to print "No stream was received: xxx" (where xxx increases every frame).

What am I doing wrong here?
I'm running the server.exe first, then I run the Client.exe (on the same machine).


GfK(Posted 2010) [#2]
	strStream = AcceptTCPStream(Server)

Shouldn't that be outside of the While/Wend loop? And shouldn't you be checking for received packets with ReadAvail instead of a conditional check on StrStream?


PowerPC603(Posted 2010) [#3]
In the docs, AcceptTCPStream is also within the loop.

But I'll try to modify it.

EDIT: If I try this code, I get MAV on the ReadAvail line (stream does not exist):
Global Server = CreateTCPServer(5000) 

If Server <> 0 Then 
	Print "Server started successfully." 
Else 
	Print "Server failed to start." 
	End 
End If 

Global strStream = AcceptTCPStream(Server)

While Not KeyHit(1)
	Cnt = Cnt + 1

	If ReadAvail(strStream) Then 
		Print ReadString$(strStream) 
	Else
		Print "No stream was received: " + Str$(Cnt)
		Delay 100
	End If 
Wend 

End



GfK(Posted 2010) [#4]
If its from the example then its probably right and I'm wrong - its been years since I used Blitz3D. It just didn't make sense that you should have to use AcceptTCPStream once per loop.


PowerPC603(Posted 2010) [#5]
After a bit of testing, I came up with this:

Server:
Global Server = CreateTCPServer(5000)

If Server <> 0 Then
	Print "Server started successfully: " + Str$(Server)
Else
	Print "Server failed to start."
	End
EndIf


While Not KeyHit(1)
	strStream = AcceptTCPStream(Server)

	Cnt = Cnt + 1

	If strStream Then
		Print ReadLine$(strStream)
	Else
		Print "No stream was received: " + Str$(Cnt)
		Delay 500
	EndIf
Wend

End


Client:
While Not KeyHit(1)
	T$ = Str$(Rand(0, 1000))

	strGame = OpenTCPStream("127.0.0.1", 5000)
	WriteLine strGame, T$
	CloseTCPStream strGame

	Print T$
	Delay 500
Wend

End


Is this the way it's done?
Every frame, opening a new TCPStream, writing to it, and closing it again?
Seems strange.

But now it works as I wanted it to work (the client sends every random number to the server, which in turn prints the received number to the screen).


PowerPC603(Posted 2010) [#6]
How can I modify my code to setup two-way communication?

I want to use this code for my game, where one player hosts the server and to which other players need to connect.
The server collects all data about the players and sends all data to each player, so every player has all the data.


PowerPC603(Posted 2010) [#7]
I tried to write back to the client via the same stream that was used to accept the stream sent bvy the client, and it worked.

But I wanna know if this is the correct way to do it.

In the following code, the server continually listenes for any messages.
The client:
- opens a TCPstream to the server
- sends a random number to the server
- the server evaluates the received number and sends a response based on that number through the same stream that was used to receive the message from the client
- the client checks if there was a response sent by the server and prints in onscreen
- the client closes the TCPstream

Server:
Global Port = 9876
Global Server = CreateTCPServer(Port)

If Server <> 0 Then
	Print "Server started successfully: " + Str$(Server)
Else
	Print "Server failed to start."
	End
EndIf



While Not KeyHit(1)
	TalkToClient$()
Wend

End



Function TalkToClient$(T$ = "")
	Local RecMsg
	Local stream = AcceptTCPStream(Server)

	; If a stream was received
	If stream Then
		; Read the contents of the stream and print it
		RecMsg = ReadLine$(stream)
		Print RecMsg

		; Select an appropriate answer based on the value received and send it back to the client
		Select True
			Case Int(RecMsg) > 500
				WriteLine stream, "Number was larger than 500"
			Case Int(RecMsg) < 500
				WriteLine stream, "Number was smaller than 500"
		End Select
	EndIf
End Function


Client:
; Setup port
Global Port = 9876
Global IP$ = "127.0.0.1"



; Do this 100 times
While i < 100
	; Increase the counter
	i = i + 1

	; Setup a random value to send
	T$ = Str$(Rand(0, 1000))

	; Send the random text to the server and print any response
	Print T$
	Print TalkToServer$(T$)



	; Wait 1000ms before sending another stream
	Delay 1000
Wend

End



; This function sends the given text to the server and returns the response (if there is any)
Function TalkToServer$(T$)
	Local Response$, stream

	; Open a TCP stream to the server
	stream = OpenTCPStream(IP$, Port)
	; Send the given text to the server
	WriteLine stream, T$
	; If the server responded
	If stream Then
		Response$ = ReadLine$(stream)
	EndIf
	; Close the stream
	CloseTCPStream stream

	; Return the response
	Return Response$
End Function


Server and client are run on the same machine, and it works as I want.
Is this the way to go, or is there a better/more reliable/... approach?


PowerPC603(Posted 2010) [#8]
I figured that, using the above approach, the server can't send messages to the players as they always close the stream after each transmission.

I used another approach where the server constantly (every frame) checks for new connections.
If one is found, it reads the name of the player, creates a new player instance and saves the name in the instance.
It also stores the stream-handle in the instance.
That way, the server can at all times send a message to that player using the stored stream-handle.

The stream now remains open for the duration of the program.

Server:
AppTitle "Server"

Global Port = 9876
Global Server = CreateTCPServer(Port)

Type TPlayer
	Field Name$
	Field stream
End Type



If Server <> 0 Then
	Print "Server started successfully: " + Str$(Server)
Else
	Print "Server failed to start."
	End
EndIf



While Not KeyHit(1)
	AddNewPlayer()

	For Player.TPlayer = Each TPlayer
		; If data was received
		If ReadAvail(Player\stream) Then
			; Read the integer that was sent to the server
			msg = ReadInt(Player\stream)
			; Print the message onscreen, including the playername who sent the number
			Print "Received number: " + Str$(msg) + " from " + Player\Name$
			; Return a message
			WriteString Player\stream, "Received from " + Player\Name$ + ": " + Str$(msg)
		EndIf

		Select Eof(Player\stream)
			Case 1
				Print "Player " + Player\Name$ + " closed"
				Delete Player
			Case -1
				Print "Player " + Player\Name$ + " aborted unexpectedly"
				Delete Player
		End Select
	Next
Wend

End



Function AddNewPlayer()
	; Check if a new TCPstream has been found (new connection)
	Local stream = AcceptTCPStream(Server)

	; If a new connection has been found
	If stream Then
		; Create a new player instance
		Player.TPlayer = New TPlayer
		; Read the name of the player from the stream
		Player\Name$ = ReadString$(stream)
		; Save the stream-handle in the player instance
		Player\stream = stream
		; Print the new playername to the screen
		Print "Found new player: " + Player\Name$
	EndIf
End Function


Client:
; Setup port
Global Port = 9876
Global IP$ = "127.0.0.1"
Global stream

Global Timer = MilliSecs()

SeedRnd MilliSecs()



; Try to connect to server
While stream = 0
	Counter = Counter + 1

	; Create a stream to the server
	stream = OpenTCPStream(IP$, Port)
	Print "Connecting to server..."
	Print "on IP$: " + IP$ + ", using port " + Str$(Port)
	Delay 1000

	If Counter = 5 Then RuntimeError "Cannot find server on IP: " + IP$
Wend

; Connection ok
Print ""
Print "Connected to server... Passed"



; Send a random playername to the server
Name$ = "Player " + Str$(Rand(1,5))
WriteString stream, Name$
; Set application title
AppTitle "Client for: " + Name$



; Main loop
While Not KeyHit(1)
	If MilliSecs() > (Timer + 1000) Then
		Timer = Timer + 1000

		; Generate a random number for sending to the server
		i = Rand(0, 1000)
		; Print the number to the screen for debugging
		Print i
		; Send the random number to the server
		WriteInt stream, i
	EndIf

	; Check if the server has sent a response
	If ReadAvail(stream) Then
		; Print the server's response to the screen
		Print ReadString$(stream)
	EndIf

	Select Eof(stream)
		Case 1
			Print "Server has closed"
			Delay 1000
			End
		Case -1
			Print "Server has aborted unexpectedly"
			Delay 1000
			End
	End Select
Wend

End


When a client is started, it makes a new connection to the server and sends a random playername (eg: "Player 1") to the server.

The server accepts this stream, reads the playername, creates a new TPlayer instance and stores both the name and stream-handle in the instance.

Then every second, the client sends a random number to the server, which in turn, displays the number and the playername to see who sent the number.
The server also sends a response to the correct client, which displays the response.

I've ran several different clients on the same machine (where the server runs too) and it all works.
Other people can use this code, as it's commented and easy to use.

The client also tries to connect 5 times to the server and closes when the server cannot be found.

Both the server and the client check if the connection was closed nicely or something was wrong (connection lost).

If the client closes, the server makes a note in it's window and deletes the player instance.
If the server is closed, all clients are closed as well.


Serpent(Posted 2010) [#9]
Your code looks good now. What are you using it for? A project, or just experimenting with networking?


jtassinari(Posted 2010) [#10]
Hi PPC603,

i was just working on something like the code you wrote down, and included some little differences.

in the server and client app I added a time limit in the loop to avoid cpu overloadas (but is not a big innovation...)

in the meanwhile i was wandering how i could you part of your code (if you do not mind) to transfer information from the server app to all the "other" client.

what i mean is a client says something to the server's community. All the community clients exept the sender should recive the message.
Do you have any suggestion?

I probably should use something like:

For Client.Player = Each Player
if Client.Player<>Sender then WriteString$(stream, "...")
next

maybe...
Any suggestion?

cheers,

jTassinari

- [www.absolute-line.net] -


PowerPC603(Posted 2010) [#11]
@Serpent:
I want to create a 3D version (heavily modified of course to avoid copyright issues) of the board game "Hotel", where one player creates a server and the other players connect to it, so you can play the game over LAN or even the internet.
I want to create it to learn something about modelling, networking and other stuff as well.

@jtassinari:
That's certainly possible as I've tried that before, except that the sender also got the message.
I tried to look at the start of a player's message to see "/all ".
When the server saw that header, he sent everything behind this to every client.

You can use this code freely, I've also added a slightly modified version to the code archives, in the Network section.

The code you use (the For-Next loop) should do it.
Just set Sender.Player to the same player instance as the player who sent the message and it should work.

The next example does exactly what you want.
By pressing "F1" on any of the clients, the message "/all Hi, how is everybody doing?" is sent to the server.
The server processes this message with the ProcessMessage function.
If it sees that the message starts with "/all ", the rest of the message is sent to all players, except the sender.

Note that the server always returns the sent message to the player who sent it. I did it that way to debug the connection with multiple players, to see if the correct player got the message from the server properly, and if the server got the message from the correct player.
You may leave that out of course, as it's not needed in a game.

So, all other clients should receive the message "Player xxx says: Hi, how is everybody doing?" (except the player who sent it), while the player who sent the message receives "Message "/all Hi, how is everybody doing?" received, xxx" (where xxx is the playername).



Server:
; Set application title
AppTitle "Server"
; Setup window size
Graphics 600, 200, 0, 2



; Setup the player-instance
Type TPlayer
	Field Name$ ; The name of the player
	Field stream ; The stream-handle for this player (all messages sent to / received from this player use this TCP-stream)
End Type



; Define a global variable that declares the port-number on which tcp-streams are sent and received
Global Port = 9876
; Create a TCP-server on the given port
Global Server = CreateTCPServer(Port)


; Check if the server was created successfully or not
If Server <> 0 Then
	; If the server was created successfully, print it on the screen
	Print "Server started successfully: " + Str$(Server)
Else
	; If the server couldn't be created, print it on the screen
	Print "Server failed to start."
	; Add 1 second delay
	Delay 1000
	; And end the program
	End
EndIf



; Main loop
While Not KeyHit(1)
	Local msg$ ; A variable used to read the data sent by a player
	Local stream = AcceptTCPStream(Server) ; Check if a new TCPstream has been found (new player requests a connection with the server)

	; If a new connection has been found
	If stream Then
		; Create a new TPlayer instance
		Player.TPlayer = New TPlayer
		; Read the name of the player from the stream
		Player\Name$ = ReadString$(stream)
		; Save the stream-handle in the TPlayer instance
		Player\stream = stream
		; Print the new playername to the screen
		Print "Found new player: " + Player\Name$
	EndIf



	; Process all players
	For Player.TPlayer = Each TPlayer
		; If data was received from this player
		If ReadAvail(Player\stream) Then
			; Read the integer value that was sent from the player's client to the server
			msg$ = ReadString$(Player\stream)

			; Check if the player sent something that needs to be processed (like messages to send to all players, ...)
			ProcessMessage(Player, msg$)

			; Print the message onscreen that was sent by the player
			Print "Message " + Chr$(34) + msg$ + Chr$(34) + " sent by " + Player\Name$

			; Return a message to the player (includes the message that was sent to the server and the playername)
			WriteString Player\stream, "Message " + Chr$(34) + msg$ + Chr$(34) + " received, " + Player\Name$
		EndIf

		; Check if the client disconnected
		Select Eof(Player\stream)
			Case 1 ; Player disconnected nicely
				; Print a message in the server-window to indicate which player disconnected
				Print "Player " + Player\Name$ + " closed"
				; Delete the TPlayer instance (the server would otherwise continue to send messages to this player)
				Delete Player
			Case -1 ; Connection lost
				; Print a message in the server-window to indicate which player disconnected
				Print "Player " + Player\Name$ + " aborted unexpectedly"
				; Delete the TPlayer instance (the server would otherwise continue to send messages to this player)
				Delete Player
		End Select
	Next

	; Wait 2ms (don't let the server-program use up all processor-power)
	Delay 2
Wend

; End the server
End



Function ProcessMessage(Player.TPlayer, msg$)
	; Always process this Select Case statement
	Select True
		; If a player sent a string to the server that starts with "/all "
		Case Left$(msg$, 5) = "/all "
			; Process all players
			For p.TPlayer = Each TPlayer
				; Check if the player isn't the player that sent the message
				If p <> Player Then
					; Send the message to all players except the sender
					WriteString p\stream, "Player " + Player\Name$ + " says: " + Mid$(msg$, 6)
				EndIf
			Next
	End Select
End Function


Client:
; Setup window size
Graphics 600, 200, 0, 2



; Declare port and IP-adress of the server
Global Port = 9876
Global IP$ = "127.0.0.1"
; Setup a global variable that holds the TCP-stream handle (used to talk to the server)
Global stream

; A global timer to time some events (used to send a message every second)
Global Timer = MilliSecs()

; Seed the random number generator
SeedRnd MilliSecs()

; Create an array with some playernames
Dim ANames$(10)
ANames$(1) = "Suzy"
ANames$(2) = "Tom"
ANames$(3) = "Adrian"
ANames$(4) = "Melissa"
ANames$(5) = "Ronaldo"
ANames$(6) = "Richard"
ANames$(7) = "Christine"
ANames$(8) = "Jackie"
ANames$(9) = "Arnold"
ANames$(10) = "Kevin"



; Try to connect to server
While stream = 0
	; Wait 1 second
	Delay 1000

	; Use a counter to count the number of tries
	Counter = Counter + 1

	; Try to open a TCP-stream to the server on the given port and IP-address
	stream = OpenTCPStream(IP$, Port)
	; Let the user know that the client is trying to connect to the server
	Print "Connecting to server..."
	Print "on IP$: " + IP$ + ", using port " + Str$(Port)

	; If the counter reached 5, then the client tried 5 times to connect to the server, but wasn't able to connect -> generate an error
	If Counter = 5 Then RuntimeError "Cannot find server on IP: " + IP$
Wend

; Connection established
Print ""
Print "Successfully connected to server..."
Print ""



; Use a random playername for this client
Global Name$ = ANames$(Rand(1, 10))
; Send the playername to the server
WriteString stream, Name$
; Set application title
AppTitle "Client for: " + Name$



; Main loop
While Not KeyHit(1)
	Local msg$ ; Variable used to read messages sent by the server

	; If the key "F1" is hit
	If KeyHit(59) Then
		WriteString stream, "/all Hi, how is everybody doing?"
	EndIf

	; Send a random number (as a string) to the server every second
	If MilliSecs() > (Timer + 1000) Then
		Timer = Timer + 1000

		; Generate a random number for sending to the server and convert it to a string
		msg$ = Str$(Rand(0, 1000))
		; Print the number to the screen for debugging
		Print msg$
		; Send the random number as a string to the server
		WriteString stream, msg$
	EndIf

	; Check if the server has sent a response (or some other message)
	If ReadAvail(stream) Then
		; Read the server's message into a variable
		msg$ = ReadString$(stream)
		; Print the server's response to the screen
		Print msg$
	EndIf

	; Check if the connection hasn't been lost
	Select Eof(stream)
		Case 1 ; Server has been closed
			; Print it to the screen
			Print "Server has closed"
			; Wait 1 second
			Delay 1000
			; End the client program
			End
		Case -1 ; Connection lost
			; Print it to the screen
			Print "Server has aborted unexpectedly"
			; Wait 1 second
			Delay 1000
			; End the client program
			End
	End Select

	; Wait 10ms
	Delay 10
Wend

; End the client program
End



jtassinari(Posted 2010) [#12]
Hi PPC603,

thanks for the scripting, i'll test it and try to leanr as more as possibile.

In the server and client code I would suggest this in the main loop:


t = CreateTimer(400)
While Not KeyHit(1)
WaitTimer (t)
....
wend


with a timer of 400 for the server and 50 to 100 for the clients.
this is like to say server run from 4 to 8 times faster then client, and still using really low cpu, while 3d grahp il going on 50 to 100 FPS, and limitating the connection to the server. And even, it would be good to allow the connection maybe to a lower rate to avoid an overflow where the clients are too many tor ead and write to, (Maybe once every 5 frame, that means from 5 to 20 CPS, connections per second)

This is probabile creating some dealy in the communication, i still need to test it it heavly with many clients at once and track if there is any/some packets lost.

cheers,

jTassinari

- [www.absolute-line.net] -


PowerPC603(Posted 2010) [#13]
The delay I've set in the server's main loop dropped my CPU-usage to 0-1%, so that's good enough for me. (Before I didn't use this delay and my computer had some trouble running more than 2 clients and the server, as the server used up all available processor-power)

The clients use a delay of 10ms every loop, so they are running a bit slower than the server.
This is to prevent sending too many messages to the server.

The above example was only used to test setting up a server and adding multiple clients.
In the final game, the clients would only send data to the server when needed, not every frame.
Also, the server only sends update-packets every few seconds or so to every connected client.

Creating a timer to wait up to 400ms could cause the server to receive too many data and cannot process it all in time.

I didn't test this, but if you were to create an MMORPG with a server-delay of 400ms every loop, the lag in the clients could be troublesome if there are +100 players connected.
Unless I'm wrong about this.

EDIT:
I checked the manual and the parameter for CreateTimer is the frequency.
My bad.
Waiting for a timer should do the same as the Delay command (almost the same).

But as mentioned before, the above example is just an example.
It can be tweaked to your specific needs.
It just shows how to setup a server which allows multiple clients connecting to it, each with their own private data-stream.


jtassinari(Posted 2010) [#14]
Hi PPC,

np, i'm so reach of missunderstanding in the manual dew to my few knowledge of English... :)

as you said, i'm just testing, and I must admit that i'm trying to leanr out of my mistackes and of any suggestion i canr each :D

your script teaches me more that i could ever think, the HOL in not really clear to me :(

thanks, cheers,

jTassinari
- [www.absolute-line.net] -