Introduction
This tutorial is meant for beginner scripters that know how to use the
set_task natives and explains basic things about sockets.
After that it gives a simple example on how to do a version checker.
What is a socket?
I will give the definition that is provided on wikipedia.
In computer networking, an
Internet socket or
network socket is an endpoint of a
bidirectional inter-process communication flow across an
Internet Protocol-based computer network, such as the Internet.
A socket address is the combination of an
IP address (the location of the computer)
and a port (which is mapped to the application program process) into a single identity, much like one end of a telephone connection is the combination of a phone number and a particular extension.
The
Internet Protocol is responsible for addressing hosts and routing datagrams (packets) from a source host to the destination host across one or more IP networks. For this purpose the Internet Protocol defines an addressing system that has two functions. Addresses identify hosts and provide a logical location service.
If we summarize what above has bees stated.
We can conclude that through sockets
(a connection) we can transmit to another
host (person) using
an internet protocol (a known language) information for a specific
port (application).
Note: The terms that been placed between () and have been bolded are placed there to enable users to compare a socket with a human dialogue for easy understanding.
What are the basic functions of sockets?
I will also give the list that is posted on wikipedia since it is a good one.
Code:
socket() creates a new socket of a certain socket type, identified by an integer number, and allocates system resources to it.
bind() is typically used on the server side, and associates a socket with a socket address structure, i.e. a specified local port number and IP address.
listen() is used on the server side, and causes a bound TCP socket to enter listening state.
connect() is used on the client side, and assigns a free local port number to a socket. In case of a TCP socket, it causes an attempt to establish a new TCP connection.
accept() is used on the server side. It accepts a received incoming attempt to create a new TCP connection from the remote client, and creates a new socket associated with the socket address pair of this connection.
send() and recv(), or write() and read(), or recvfrom() and sendto(), are used for sending and receiving data to/from a remote socket.
close() causes the system to release resources allocated to a socket. In case of TCP, the connection is terminated.
gethostbyname() and gethostbyaddr() are used to resolve host names and addresses.
select() is used to prune a provided list of sockets for those that are ready to read, ready to write or have errors
poll() is used to check on the state of a socket. The socket can be tested to see if it can be written to, read from or has errors.
Which of these functions can I use in AmxModX scripting?
Well this depends on the module that you would like to use.
There are 2 modules here on AlliedMods:
1.
Sockets - this is the default module that comes with the AmxModX package and has some of the functions that are presented above.
2.
Sockets_HZ - this is a custom module that is the same as the one before but has an advantage of offering more of the functions that are posted above.
The module that we will talk about will be the basic Sockets module since we want to focus only on the basics.
Info about the functions/defines that come in the socket module.
First of all we take a look at the include since it will provide us with the most information that we need.
At first we look at the defines:
PHP Code:
// Use SOCKET_TCP for TCP Socket connections
#define SOCKET_TCP 1
// Use SOCKET_UDP for UDP Socket connections
#define SOCKET_UDP 2
These 2 defines represent the protocol type (the language) of the connection.
What is the difference between them?
TCP - stands for
Transmission Control Protocol and offers complete and correct data transmission between the end devices but the only problem of it is that if the data between the end devices is damaged it will be retransmitted. Because of this this protocol sometimes can be slow.
UDP - stands for
User Datagram Protocol and does not have control of the data integrity. Because of this is faster.
If you want to know more about them you just need to access wikipedia.
And now about the functions
As you can see if you understand the principles and the role of everything that has been explained above you will understand the purpose and the necessity of the arguments in the functions.
PHP Code:
/* Opens a new connection to hostname:port via protocol (either SOCKET_TCP or SOCKET_UDP),
* returns a socket (positive) or negative or zero on error.
* States of error:
* 0 - no error
* 1 - error while creating socket
* 2 - couldn't resolve hostname
* 3 - couldn't connect to given hostname:port
*/
native socket_open(const _hostname[], _port, _protocol = SOCKET_TCP, &_error);
/* Closes a Socket */
native socket_close(_socket);
/* Recieves Data to string with the given length */
native socket_recv(_socket, _data[], _length);
/* Sends data to the Socket */
native socket_send(_socket, const _data[], _length);
/* Same as socket_send but Data can contain null bytes */
native socket_send2(_socket, const _data[], _length);
/* This function will return true if the state (buffer content) have changed within the last recieve or
* the timeout, where timeout is a value in µSeconds, (1 sec =1000000 µsec).
* Use to check if new data is in your socket. */
native socket_change(_socket, _timeout=100000);
The info you need before creating a plugin.
I will now give the example on how to check if a plugin has a new version to see how all of these things that have been explained above come together in the end.
First of all we need to think on the things that we want to do.
In this example I want to check the forums from AlliedModders to see if my Block Wallhack plugin is up to date, in order to do that we need the plugin posted somewhere on the forums because we cannot check something that does not exist.
The plugin has the following link:
http://forums.alliedmods.net/showthread.php?t=100886
The next step is changing the link into a socket type addressing schematic.
These are the things we need:- Server Address
- Port
- Protocol
The server address (host name) is already specified in the link of the plugin:
http://forums.alliedmods.net/showthread.php?t=100886.
I have marked it with the orange color.
Now what about the port, this is also specified in the link of the plugin
http://forums.alliedmods.net/showthread.php?t=100886.
The port is HTTP (Hyper Text Transfer Protocol), if we search the net we can see that the value of this port is
80 for server access.
Now what about the protocol?
Well this is something that you need to search on the net to see what type of protocol is used with HTTP. In our situation TCP is the one.
What about the last part of the link?
http://forums.alliedmods.net/showthread.php?t=100886.
This will be used to access the correct page in order to do the checks that we are interested in, you will see that in the example.
So the list comes together like this:- forums.alliedmods.net
- 80
- TCP
The main parts of the plugin.
This is the part where all the theory comes together in one small script.
We already have all the info that we need.
I will first post the functions separately and comment them and after that I will post the entire script for better understanding.
First of all let's make some defines
PHP Code:
// We also need the topic of the plugin in order to get the data from the correct page.
#define PLUGIN_TOPIC "/showthread.php?t=100886"
#define PLUGIN_HOST "forums.alliedmods.net"
// These defines are used for tasks
#define TASKID_GETANSWER 0
#define TASKID_CLOSECONNECTION 1
Now we need to connect to the AM forums.
PHP Code:
g_Socket = socket_open(PLUGIN_HOST, 80, SOCKET_TCP, error)
If we have problems and the
socket does not work the
error argument will have a value different from 0. So we need to take care if something like that happens.
PHP Code:
switch (error)
{
case 1:
{
log_amx("[BW Version Checker] Unable to create socket.")
return
}
case 2:
{
log_amx("[BW Version Checker] Unable to connect to hostname.")
return
}
case 3:
{
log_amx("[BW Version Checker] Unable to connect to the HTTP port.")
return
}
}
Now we need to send info on the socket so that the server will give us the response that we need.
This will be done through this:
PHP Code:
new error, sendbuffer[512]
format(sendbuffer, 511, "GET %s HTTP/1.1^nHost:%s^r^n^r^n", PLUGIN_TOPIC, PLUGIN_HOST)
socket_send(g_Socket, sendbuffer, 511)
Note: The way that sendbuffer has been formated is the correct way to send data to a HTTP server for a page request. For more info check
this out.
Now we need to use set_tasks in order to wait for a response from the server. We need this part because the network traffic can be huge and the response from the forums might take some time. We just need to make sure that nothing happens.
That is why I will use 2 tasks. One for getting the server respons and another one for closing the connection if no info has been sent.
PHP Code:
// We repeat the first task 15 times in order to check 15 times every second if we get the response and to see if the info that we want to search is in that response.
set_task(1.0, "task_waitanswer", TASKID_GETANSWER, "", 0, "a", 15)
// The second task closes the connection if in the first task nothing has been done.
set_task(16.0, "task_closeconnection", TASKID_CLOSECONNECTION, "", 0, "", 0)
Now is the part where we need to access the info from the server.
First of all we check if the socket has info in it.
PHP Code:
socket_change(g_Socket)
If it does then we need to get the data from it and see if the buffer already has the title of the plugin and the version of the one released on the internet to compare with our version of the plugin.
PHP Code:
socket_recv(g_Socket, g_Data, 999)
This is what you will receive if the connection has been made.
Code:
HTTP/1.1 200 OK
Server: nginx/0.6.32
Date: Wed, 23 Feb 2011 01:10:57 GMT
Content-Type: text/html; charset=ISO-8859-1
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/5.3.5
Cache-Control: private
Pragma: private
X-UA-Compatible: IE=7
Set-Cookie: bblastvisit=1298423457; expires=Thu, 23-Feb-2012 01:10:57 GMT; path=/
Set-Cookie: bblastactivity=0; expires=Thu, 23-Feb-2012 01:10:57 GMT; path=/
8e07
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html dir="ltr" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<meta name="generator" content="vBulletin 3.8.1" />
<meta name="keywords" content="Block Wallhack v7 [CS & CZ], AMX Mod X,AMX Mod,Metamod,Half-Life Plugins,AMXX,AMX,Plugins,Counter-Strike,CSDM,Deathmatch,Servers,C++" />
<meta name="description" content="Block Wallhack v7 [CS & CZ] Approved Plugins" />
<!-- CSS Sty
If we look at it we can see that we already have the title in it so it will not be needed to check again for the buffer so we terminate the connection and end the tasks.
PHP Code:
socket_close(g_Socket)
remove_task(TASKID_GETANSWER)
remove_task(TASKID_CLOSECONNECTION)
Putting all together.
PHP Code:
// The includes
#include <amxmodx>
#include <sockets>
// The info about the plugin, we will use the VERSION define for coparison.
#define PLUGIN "BW Version Checker"
#define AUTHOR "OT"
#define VERSION "8.0"
// Host and topics
#define PLUGIN_TOPIC "/showthread.php?t=100886"
#define PLUGIN_HOST "forums.alliedmods.net"
// Tasks
#define TASKID_GETANSWER 0
#define TASKID_CLOSECONNECTION 1
// The socket handle
new g_Socket
// Data buffer
new g_Data[1000]
// Version string buffer
new g_StringVersion[10]
// Needed to update the plugin?
new bool:g_NeedToUpdate
public plugin_init()
{
register_plugin(PLUGIN, VERSION, AUTHOR)
}
public plugin_cfg()
{
// Connect
new error, sendbuffer[512]
g_Socket = socket_open(PLUGIN_HOST, 80, SOCKET_TCP, error)
// If we get error
switch (error)
{
case 1:
{
log_amx("[BW Version Checker] Unable to create socket.")
return
}
case 2:
{
log_amx("[BW Version Checker] Unable to connect to hostname.")
return
}
case 3:
{
log_amx("[BW Version Checker] Unable to connect to the HTTP port.")
return
}
}
log_amx("[BW Version Checker] Connection with %s has been established", PLUGIN_HOST)
// Send page request
format(sendbuffer, 511, "GET %s HTTP/1.1^nHost:%s^r^n^r^n", PLUGIN_TOPIC, PLUGIN_HOST)
socket_send(g_Socket, sendbuffer, 511)
log_amx("[BW Version Checker] Sending Block Wallhack page request.")
// Setting the tasks for handeling the response
set_task(1.0, "task_waitanswer", TASKID_GETANSWER, "", 0, "a", 15)
set_task(16.0, "task_closeconnection", TASKID_CLOSECONNECTION, "", 0, "", 0)
}
public task_waitanswer(id)
{
// It changed?
if (socket_change(g_Socket))
{
// Get the data
socket_recv(g_Socket, g_Data, 999)
// We seach for the title
new Position = containi(g_Data, "Block Wallhack v")
// Title found
if (Position >= 0)
{
log_amx("[BW Version Checker] Page found, comparing versions.")
// Now we start processing the buffer
// We get the start of the numbers of the version
Position += strlen("Block Wallhack v")
new length
// We copy the valid version characters in another string
for (new i=0;i<10;i++)
{
if ('0' <= g_Data[Position + i] <= '9' || g_Data[Position + i] == '.')
{
g_StringVersion[length] = g_Data[Position + i]
length++
}
}
// Compare the plugin version with the internet version
g_NeedToUpdate = (str_to_float(g_StringVersion) > str_to_float(VERSION))
log_amx("[BW Version Checker] Versions has been compared, closing connection. %s!", g_NeedToUpdate ? "Plugin is old" : "Plugin is updated")
// Close the connection
socket_close(g_Socket)
// Remove the tasks
remove_task(TASKID_GETANSWER)
remove_task(TASKID_CLOSECONNECTION)
}
}
}
public task_closeconnection(id)
{
// Close connection if no response in 16 seconds.
socket_close(g_Socket)
}
Sources
Other tuts/applications that can be useful after reading this
__________________