Winsock Tutorial using the dll made by 39ster

2 player pong using UDP and TCP

In this tutorial i will teach you how to use my winsock dll to create a online game using both UDP and TCP.

Advantages of UDP
The big advantage of UDP is its speed and low bandwidth use. UDP is a lot faster then TCP and should be utilized in a game were the main packet is sent continuously. Also UDP is connectionless so there is no connecting involve. You just send the message to the right IP and port and it’s done.

Disadvantages of UDP
One disadvantage of using UDP is it is unreliable. There is no guarantee that all your packets will arrive. This is why we also open a TCP connection so important packets can be sent through the TCP socket. Packets like the main packet should be sent through UDP because even if it doesn’t arrive, another one is sent straight after it.

Another big disadvantage is firewall issues with UDP. UDP requires a configured firewall just as if you were hosting a game with TCP. So with UDP even the clients need to have their firewall configured.

Initializing the dll.
For the dll to function you need to link the functions inside the dll to your game. To do this simply put in the
creation code of the first room:
dllinit(0, true, false);

If the first argument is a number then the dll will load the dll file "39dll.dll". If this is a string then the string will be the name of the dll. e.g If the first argument is "sock.dll" then the dll file must be named "sock.dll" and it will load it using that filename.
The second argument is true if you want to use winsock. In this case we do. The third argument should only be true if we want to use the file functions. In this example this is not needed so we wont load those functions from the
dll.

Setting up the server.
To create a multiplayer game, one person needs to host the server and the other person needs to connect.
To set up a server using the dll you first need to create an object which controls the setting up of the server and accepting any new connection. What you will need to do is create a menu that has 2 buttons. The first button says "Host" and the second button should say "Connect". When the user selects host, make it execute this code "global.master = true;" and make the room change to a room called "rmWaiting". Create a new object and call it "objWait". In the create event of this object add this code:

listen = tcplisten(14804, 2, true);
if(listen <= 0)
{
  show_message("Failed to listen on port 14804");
  game_end();
}

What that piece of code does is create a listening socket that will listen for any incoming connections on port 14804. The port number can be whatever you want but i have used 14804. The second argument is the maximum allowed connections in the waiting list. This is not the maximum allowed people in the game. When someone tries
to connect they are put on the waiting list until the server accepts them. The last argument is set to true because we want the listening socket to not freeze the game when you are using the tcpaccept() script and no one is has tried
to connect. The script will return a socket identifier number that is bigger than 0 if we succeeded. A number <= 0 is returned
if an error occurred.
The next line checks if we are successfully listening on port 14804. If the socket identifier is smaller than or equal to 0 then an error occurred so we end the game.

Accepting new connections. 
To accept new connections you need to create a step event for the "objWait" object we created. In the step event add this code:

client = tcpaccept(listen, true);
if(client <= 0) exit;
global.udpsock = udpconnect(14805, true);
global.otherplayer = client;
global.otherip = lastip();
global.otherudpport = 14803;
room_goto(rmGame);

The first line checks the waiting list to see if someone tried to connect to the listen socket. If no one connected then it returns a number smaller than 1. If someone did connect then it creates a new socket and returns its id. This socket will now be used to
send and receive data from the person who just connected. The second argument in tcpaccept means the new socket will be non-blocking. In this case when you are trying to receive a message it will not freeze the game if there is no message to be received.
The second line checks if tcpaccept() returned an error. If it did then it exits the script.
Every line of code after the second line will only be executed if there was no error. The third line creates a UDP socket which will be used to send messages through the UDP protocol. The next line sets the global variable "global.otherplayer" to the socket id that tcpaccept() returned. The next line then gets the IP address of the player that was last accepted and stores it in the variable “global.otherip”. This will be used when sending a UDP message to the other player. The next line stores the port that the UDP socket is using on the remote computer into the variable “global.otherudpport”. This will be used when sending a UDP message to the other player. The last line goes to the playing room. (You needed to create that room btw).

Connecting to the server.
To join a multiplayer game we need to connect to a server. In the room where you have the button "Host" and the button "Connect", make it so when the player clicks connect it executes this code:
global.master = false;
server = tcpConnect("127.0.0.1", 14804, true);
if(server <= 0)
{
  show_message("Unable to connect to server");
  game_end();
  exit;
}
global.otherplayer = server;
global.udp = udpconnect(14803, true);
global.otherip = tcpip(server);
global.otherudpport = 14805;
room_goto(rmGame);

The first line sets the global variable "master" to false. This is because we are not the server. We are the client connecting to the
server. The second line makes the actual connection to the server.
The first argument in tcpConnect is the ip address you want to connect to. If you are just testing locally use the address "127.0.0.1".
The second argument is the port number to connect to. The third argument is whether to use blocking or non blocking mode. We set it true which means non-blocking. This makes it so whenever we try and send or receive a message, the game doesn’t freeze until the operation finished. If tcpConnect successfully connected and was accepted by the server, the variable "server" should now
contain the socket id. If an error occurred a number < 1 will be returned.

The next line checks if an error occurred. An error could be because the server doesn’t exist or the server didn’t accept us. If an
error did occur then the game will end.

If no error occurs then the global variable "otherplayer" now becomes the socket id that the variable "server" has. Then it opens a UDP socket on the port “14803” and sets it to non-blocking mode. The next line retrieves the IP address of the server and stores it in the variable “global.otherip” which will be used when sending UDP messages. The next line then stores the port number that the host is using for its UDP socket and stores it in “global.otherudpport”. Then the game will start when we use room_goto().

Sending and receiving messages
For our game to work we need to know the y position of the paddle that the other player controls and the client needs to know the x, y
position of the ball which will be controlled by the server.

Sending
In the paddle which is controlled by you, you must send your Y co-ordinate to the other player so they can draw your paddle in the
right position. To do this, put this code in both the keyboard UP event and the keyboard DOWN event:

clearbuffer();
writebyte(0);
writeshort(y);
sendmessage(global.udpsock, global.otherip, global.otherudpport);

The first line clears the internal buffer of any data. This is used in case there is data already in the buffer. The second line writes the byte
that represents the Message Id. In this game the message id 0 will indicate the message containing the Y position.
The next line writes the actual Y position to the buffer. We have used the data type "short" because a short can be any number between
-32000 to +32000. A short utilizes 2 bytes. If we had used one byte to represent the Y position and the Y position is bigger than 255 it will end up being not what you
wanted. The last line sends all the data in the internal buffer to the other player through the UDP socket. The second argument in sendmessage is the IP of the person to send the message to. The third argument is the remote port to send it to. In this case the data is the Message Id and the 2 bytes
used for the Y position.

Now we need to send the x, y position of the ball to the other player if we are the server. To do this put in the step event of the ball object:

if(!global.master)exit;
clearbuffer();
writebyte(1);
writeshort(x);
writeshort(y);
sendmessage(global.udpsock, global.otherip, global.otherudpport);

The first line checks to see if we are the server. If we are NOT the server then exit the script and do not execute the code below.
If however we are the server then first clear the internal buffer. Now write the Message Id "1" which will indicate the message is the balls position. Now write a short which represents the x co-ordinate and write another short which represents the y co-ordinate.
Now just send the message to the other player through the UDP socket using their IP and remote port.

Sending chat messages
Below is the code we use for sending strings:
clearbuffer();
writebyte(2);         //chat message id
writestring(“Hello other player”);
sendmessage(global.otherplayer);

The second line is the ID we will use for chat messages. The third line is used to write the chat string to the buffer. The last line sends the message over the TCP port because we want to make sure it arrives.

Receiving a message.
In the paddle that you do not control...the one which is controlled by the other player, put this code in the step event:
var size;
while(true)
{
  size = receivemessage(global.udpsock);   //try receive a message on the udp socket
  if(size <= 0) size = receivemessage(global.otherplayer); //if no udp message try receive a tcp message
  if(size < 0) break;
  if(size == 0)
  {
    show_message("The other player left the game");
    game_end();
  }
  messageid = readbyte();
  switch(messageid)
  {
    case 0:
      y = readshort();
    break;
    
    case 1:
      objBall.x = readshort();
      objBall.y = readshort();:
    break;

  case 2:
    chatmessage = readstring();
    show_message(chatmessage);
  break;
  }
}

First this creates an infinite loop using while(true). The first line of the loop receives any messages from the other player that was sent to the UDP sockets and sets the variable "size" to how much bytes we received. The second line checks to see if no message was received. If no message was received then try and receive a TCP message. If no TCP message was received then exit the loop. The next line checks to see if the other player disconnected from the game. If the message size == 0 
that means the player left the game.
If the player did leave, then end the game. If we did receive a message then all data from the message is placed inside the internal buffer. We can now use the buffer scripts to return the data from the message. The first part returns the message ID (where it says “messageid = readbyte()”.
After that it uses the switch() statement to check what the ID equals. If the ID is 0 then this means the message is for the other players
y co-ordinate. We simply use readshort() to return the y position. Remember because we wrote the Y position as a short, we now must
read the Y position using a short.

If the Message ID equals 1, which represents the balls x, y position then use readshort() to set the balls x position to the right place and
readshort() again to set the balls y position in the right place.

If the Message ID equals 2, which represents a chat message then copy the chat string to the variable “chatmessage” using “readstring()” then display the chat message.

Freeing the dll
If you dont want any nasty error messages when your game ends then you must free the dll from memory. To do this first create an object that will be in every room. Now in the game end event of that object just put dllfree(). This will free winsock and free all memory used by the internal buffer.

Closing the sockets
When finished with the dll you should close all sockets opened during the game. To do this just use closesocket(socketid); on all the sockets that were used. Don’t forget to also close the listening socket.