Trying to create a network
Blitz3D Forums/Blitz3D Programming/Trying to create a network
| ||
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). |
| ||
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? |
| ||
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 |
| ||
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. |
| ||
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). |
| ||
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. |
| ||
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? |
| ||
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. |
| ||
Your code looks good now. What are you using it for? A project, or just experimenting with networking? |
| ||
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] - |
| ||
@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 |
| ||
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] - |
| ||
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. |
| ||
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] - |