I created this tutorial to give users an in-depth look at 39dll. This DLL is used for online communication. If you don't know what a DLL is, shut down your computer and throw it out the window.
If you'd like me to add on to this tutorial, please PM me on a forum, talk to me on my current project GMechanism, or Email me at email@example.com
1. The DLL
The 39dll is a dll by 39ster (GMC) that enables Game Maker programmers to send data to and from a server and a client. You see, the built-in commands in Game Maker, often referred to as "Mplay", are complete trash.
The 39dll DLL comes with the Client and Server bases (keep reading)
You'll learn about only the online, TCP portions of the DLL. It also contains utilities, such as file encryption, and some other stuff I don't want to mess with right now. This tutorial should be fairly easy to follow. Downloadable examples, the DLL, and a client/server base (all made by me, except the DLL of course) are available to you throughout the tutorial.
NOTE: Read this whole tutorial. Don't think you can skip a section if you "already know it."
2. What does TCP mean?
TCP stands for Transmission Control Protocol. The current well-known brother of TCP is UDP, which is something that operates differently.. TCP will be the base for our Internet communication.
A server, also called a host, is the core of the Online world. If you can imagine a wheel on a market cart, there's the center "hub" with spokes going out to the edge. That hub, is the server in this case.
A client is what connects to the server. It is the spoke on our metaphorical wheel. Below is an artfully designed image that shows the relationships between clients and servers:
The UP TCP arrow shows the upload to the server. This is data transmitted from the client to the server.
The DOWN TCP arrow shows the download to the client.
This is the typical client-server model in TCP. Pretty simple, right? NOTE: Data can NOT be passed from client to client. It has to go THROUGH the server to reach another client. Otherwise, I'd have black lines connecting the clients.
3. The Server
By now, you should have a good understanding of TCP networking. Time to get our hands dirty.
First off, start with a base GMK that has all the 39dll function calls. Below, is a link to the server base I made for you:
If you look at that lovely collection of scripts, you'll see a bunch of stuff. For our online game, we'll be working with the TCP commands.
The Server_Init object sets up the whole server. In the CREATE event, we need to have the following code:
In the above code, dllinit() is the function required by the DLL to initialize it. The first parameter, 0, just means the regular DLL filename, "39dll.dll". The first Boolean, True, means we want to load socket functions. Socket basically means TCP. The final parameter, False, is to load utilities. (The other stuff I don't want to mess with)
Then we create a variable, servertcp, that literally is the TCP port we're listening on. The first parameter is the port. For the example, I set it to 8080. Please change it, since some programs use 8080. The second parameter, 10, is the maximum amount of connections (players) you want to allow connected. The 1 sets blocking to OFF. Don't worry about that, just keep it at 1.
Now, our Server_Init object is going to have a STEP event. Every step, it'll check for a client connection:
stcp = tcpaccept(servertcp,true);
The above code is executed every step. stcp is a temporary variable that stores the client's TCP ID. I'll go more into this later. tcpaccept() accepts the connection, the first parameter obviously the server object, and the second one is Blocking mode, which we'll keep as true.
Then, the setsync command sets Blocking to off. Again, don't worry about blocking. Take my word for it.
Our next command, makes a new player object. (We don't care where the object is, since this is just the server). THIS IS IMPORTANT! In the player object, is every function the server carries out for the client. Each player object represents the real-life client on someone else's computer. So if there are 13 people connected to your server, there will be 13 player objects in the server. NOTE: In the client, whenever you send the server data, it's specific player object is the one that receives that data and uses it. This will become more obvious later on.
The command o.tcp=stcp sets the player object's TCP ID. This TCP ID is how the player object knows WHICH client it is unique to. I'll make another artfully designed diagram in a bit, to help you visualize all this.
Another setsync() command sets blocking to off.
If you fully understand what has happened so far, you're good to go for the rest of it. If you're still having trouble, re-read the above sections and look at my amazing art skills below:
Notice how the clients didn't connect in order. The top one connected, then the bottom one, etc.
But you see, each player object is assigned to just one specific client
Now that you understand the fundamentals of hosting the server, and waiting for connections, let's take a close look at the player object.
What's in the CREATE event of the player object really is pointless. I can't remember why I stuck the tcp="" in there. I think it was to keep "tcp" an Integer instead of a string. Which sounds pointless. Who knows.
The player's STEP event is where it's happening. I'll type out the code below, then explain it. Please get acquainted with the Switch statement, and the Break statement. I'll touch lightly on these subjects as they come up. Here's the STEP event of the player object:
while (1) //Infinite loop
if (msize <=0) break;
mid = readbyte();
//Nothing below this
The basics, a while(1) or while(true) loop is an infinite loop. Our first command, receivemessage(), stores the waiting message into "msize". Notice, this is where the lesson we learned above applies. You see that "tcp" inside the ( )? That is the TCP ID of the currently connected client associated with that specific player object.
The next if statement checks if msize is equal to 0. Msize will only be equal to 0 if the player spontaneously disconnected, for no reason. If we didn't check for this, the player objects would build up, and we don't want that.
Then, the statement, if (msize <=0) break; basically makes sure msize has no message waiting. The BREAK command restarts the WHILE loop, so we can check again if there's a message.
Alright, here's where some complexity and misunderstandings begin. The statement mid = readbyte() stores the first element of the waiting message, which is a byte. This byte, at the front of every message you send from server/client, tells the program how to read the message. It doesn't necessarily need to be a byte. It can be a string, etc. But it's necessary so the client or server knows the order of the data inside the message. Read on, to the next section, about what exactly this means.
4. Message Structure
The way you structure a message to/from the server/client is very important. It's how you pass data. Data in your message MUST BE SENT IN THE ORDER THAT IT WILL BE READ!!! This section will list out the commands for writing information to a message, that you'll send to a client or server.
Let's get on the same page: A "Message" is a packet of data you send. It can contain strings, integers, bytes, doubles, etc. The data you write into a message on the client, needs to be read in the same order on the server. Here's an example:
(client sends the following message to the server... Note, all this data is in the same message.)
1 (This is the byte I was talking about above. It tells the server how to read the message)
"Hello, my name is Luke."
"The cake is a lie!!"
(Now, here's the server receiving the message PROPERLY)
byte (Oh look! First byte, 1, tells us we'll be reading a string, integer, double, then another string)
This may be hard to understand at first. Basically, on the client's side, he's sending a message with the following data: String, Integer, Double, String... The server has no clue what's inside the message, so we stick on that byte in the front to let the server know. Of course, you have to program into the player object what to do when it receives that byte. Anyways, that byte of 1, makes the player object's SWITCH statement go to CASE 1, which lets the server know, "Hey, this is case 1, so this message will have a string, an integer, a double, then a string!"
This is the way I have always structured it. The front byte is the CASE on the switch tree. So if we go back to our code:
mid = readbyte();
//Nothing below this
The variable "mid" is filled with that first byte, then it'll go to the CASE statement that resembles the format of the message. Make sense? I'll supply a chat program example at the end of the tutorial that I created. NOTE: The actual DLL commands used to add data to the message will be explained after the Recap.
So we've learned that clients connect to a server. The server is the center of all operations. Any data you want available to all clients needs to be sent to the server, then the server will deal with it from there.
A Server_Init object is required to start the DLL, and listen on your game's port for incomming connections. The connection is made in the STEP event of this same object, then a player object is created with the TCP ID of the newly connected client.
This new player object is specific to only one client, the client that was just joined. The variable "tcp", local to player, is the same TCP ID the client joined with.
When sending a message, it needs to be sent exactly how it'll be received, with the data following the byte that governs the server/client receiving the message. This byte determines how the message should be read and dealt with.
6. Writing to, and Reading From a Message
A good set of commands exists for writing data, like bytes, strings, etc. to a message, and then reading the data from the message. If you send a message on the client, you use the write commands to write data to it, then send it. Then, the server reads it using the read commands.
writebyte(number, from 0 to 255)
writestring(A string of characters in "quotes")
writedouble(Number with decimal, very precise) The difference between a double and float, is Doubles are more precise,
writefloat(Number with decimal, regular) they have many more numbers to the right of the decimal
readbyte() - Returns an integer
readstring() - Returns a string
readint() - Returns an integer
readdouble() - Returns a float
readfloat() - Returns a float
These are only the typical read/write commands. There are several more, like longs and ushorts, but those aren't necessary for you to memorize. Just these basic ones up here. Using those write commands, you can write your data to a message, then send it off to the receiving side. Both the write commands AND the read commands work on BOTH the Client and the Server.
7. Sending the Message
Sending the message is quite simple once you've got all your data written to it. Remember the TCP ID of each player object on the server? That ID represents the specific client attached to it. That's for the server.
On the other hand, in the Client, once it makes a connection to the server, it stores that TCP ID into a variable, global.tcp.
This means, that if you are the server, and you want to send a message to the client, you use sendmessage(tcp), while in the client, to send a message to the server, you use sendmessage(global.tcp). More detail below.
sendmessage(global.tcp) -or- sendmessage(tcp)
That clearbuffer() command MUST BE CALLED when you are WRITING DATA to a message. clearbuffer() clears the read/write buffer. You don't want old data going into your message, do you?
After the clearbuffer() command is called, then you use the write commands to write whatever data you want. In this case, the message only has a byte, who's value is 0.
The sendmessage() command sends the message to the specified connection. In the client, there can only be one connection, right? The connection to the server. So you use global.tcp (or whatever you named your variable)... In the server, since the TCP ID must be local, and not global, you just use tcp.
NOTE: I know we haven't talked about the client yet, but we're getting there. You're almost done!
8. The Client
Here's where the actual game is. The Client is what is opened on the user's computer. It makes only one connection, to the server. I have a client base for you that I've created, it's the bare bones of a client, but it's good to work with since it has all the necessary scripts, and objects.
Alright, the client will have two objects used for connection. online_connect, which makes the initial connection, and online_receive which will do all the data receiving from the server. It has the same switch() tree as the server, because we like to put a byte at the beginning of the message.
Here is the code for the CREATE event of the online_connect object:
show_message("Unable to connect to the server!")
In the above code, the dllinit() function is the same as the server. Here is our global.tcp variable. It is being assigned the TCP ID returned by the tcpconnect() function. The first parameter to tcpconnect() is the IP address of the server, which is a string. NOTE: 127.0.0.1 is the same as "localhost". The second parameter is the PORT NUMBER. That's set to 5123, but it should be changed to whatever port your server operates on. The third parameter sets blocking to OFF.
The rest of it is self-explanatory, if global.tcp is greater than 0, then the client made a successful connection to the server, and it's time to move on.
NOTE: The online_connect object NEEDS TO HAVE ITS OWN ROOM! The FIRST room in your game needs to contain nothing, except this object. It's the online_receive object that should be in the same room as your whole game.
Alright, now time for the online_receive object. This object needs to be placed in any rooms that actually need online capability, like the world full of zombies you're trying to kill. It'll only have a STEP event, because we want to check for a waiting message from the server, every step:
//To do when the SERVER randomly disconnects
show_message("Server has unexpectedly terminated.")
if (msize <=0) break;
mid = readbyte();
//Nothing below this
Notice how closely related the client STEP code is to the player object's STEP code. Also notice, the receivemessage() command has "global.tcp" in it, instead of the player object's "tcp" in it.
Everything else is easy to understand if you understood the player object on the server.
9. Putting it all Together
You now know how to set up a server, connect to it with a client, read/write a message, and send the message. Now, it's time for you to put it all together. Start simple, don't overwork yourself. This sort of thing takes time.
I created a sample chat program, server and client for you to look at. Just launch the server, minimize it, and launch two clients. Each client represents a user, so what you type on one client is sent to the server, and the server repeats it to all clients. It's a chatroom.
Thanks for reading!
If you found any spelling mistakes, or want me to expand on something, contact me.
39dll Online Tutorial, Luke Escude, 2010