Server : When using the word 'server' I mean, the game server who will handle the clients request.
Clients : When using the word 'client' I mean, the game server who will connect and answer to the server request.
Introduction
In this small tutorial, I'm going to try to explain you how you can send and receive data over servers using the socket extension.
The schema above represent how our server will handle client request.
The blue arrow represent data being send by the server, the gree arrow represent data being send by the client.
So, if "client 2" send "hello" to the server, the server will read the data and send to all other clients ("client 1", "client 3", "client 4") "hello" as well.
If you understand this, go ahead and continue to read.
Creating the server
And now, enough theory, let's practice !
It's god damn important to read the orange line !!
Step 1) Plugin is empty, you got nothing :
PHP Code:
#include <sourcemod> #include <sdktools> #include <socket> //Obviously, we have to include the socket.inc file to our project.
public Plugin myinfo = { name = "[ANY] A simple test for sockets", author = PLUGIN_AUTHOR, description = "A simple exemple plugin for introducing to socket", version = PLUGIN_VERSION, url = "http://www.sourcemod.net" };
Handle serverHandle; //Handle to server, allow us to acces it from everywhere in the plugin Handle ARRAY_Connections; //Handle to connections, so we can store the client's connections.
public Plugin myinfo = { name = "[ANY] A simple test for sockets", author = PLUGIN_AUTHOR, description = "A simple exemple plugin for introducing to socket", version = PLUGIN_VERSION, url = "http://www.sourcemod.net" };
public void OnPluginStart() { ARRAY_Connections = CreateArray(); // Init the array }
Handle serverHandle; //Handle to server, allow us to acces it from everywhere in the plugin Handle ARRAY_Connections; //Handle to connections, so we can store the client's connections.
Handle CVAR_MasterChatServer; //Allow the user to set if the game server is the server or a client bool isMasterServer; //Define if the game server is a server or a client public Plugin myinfo = { name = "[ANY] A simple test for sockets", author = PLUGIN_AUTHOR, description = "A simple exemple plugin for introducing to socket", version = PLUGIN_VERSION, url = "http://www.sourcemod.net" };
public void OnPluginStart() { CVAR_MasterChatServer = CreateConVar("sm_csc_is_server", "0", "Is this server the server ? 1 = yes | 0 = no, it's a client", _, true, 0.0, true, 1.0); ARRAY_Connections = CreateArray(); // Init the array }
public void OnConfigsExecuted() { isMasterServer = GetConVarBool(CVAR_MasterChatServer);
if(isMasterServer) CreateServer(); //If the game server is a server, create the server else ConnecToMasterServer(); //If the game server is not the server, connect to the server (so it's a client) }
Step 4) Initialise the server
PHP Code:
//Create the server, not that hard, hu ? stock void CreateServer() { serverSocket = SocketCreate(SOCKET_TCP, OnServerSocketError); SocketBind(serverSocket, "0.0.0.0", 2044); //Listen everything on port 2044 SocketListen(serverSocket, OnSocketIncoming); //On SocketIncoming will fire when a client connect to the server ! }
Step 5) What to do with clients ?!
PHP Code:
//When someone sucessfully connected to the server : public OnSocketIncoming(Handle socket, Handle newSocket, char[] remoteIP, remotePort, any arg) { if(isMasterServer) //This is the job of the server, he have to handle clients : { PrintToServer("Another server connected to the chat server ! (%s:%d)", remoteIP, remotePort); SocketSetReceiveCallback(newSocket, OnChildSocketReceive); //When a client send a data, go inside of the 'OnChildSocketReceive' function. SocketSetDisconnectCallback(newSocket, OnChildSocketDisconnected); //When a client disconnect, go inside of the 'OnChildSocketDisconnected' function. SocketSetErrorCallback(newSocket, OnChildSocketError); //When a client crash, go inside of the 'OnChildSocketError' function. PushArrayCell(ARRAY_Connections, newSocket); //Save the handle to the connection into a array to send futur messages } }
//When the server crash, we can't do something but wait for a admin to reload the plugin. public OnServerSocketError(Handle socket, const int errorType, const int errorNum, any ary) { LogError("socket error %d (errno %d)", errorType, errorNum); CloseHandle(socket); }
//When a client sent a message to the server OR the server sent a message to the client : public OnChildSocketReceive(Handle socket, char[] receiveData, const int dataSize, any hFile) { PrintToServer(receiveData); //In any case, always print the message
if(isMasterServer) //If the game server is the server, then send the message to all clients SendToAllClients(receiveData, dataSize, socket); }
//When a client disconnect public OnChildSocketDisconnected(Handle socket, any hFile) { if(!isMasterServer) { PrintToServer("Lost connection to server"); } CloseHandle(socket); }
//When a client crash : public OnChildSocketError(Handle socket, const int errorType, const int errorNum, any ary) { LogError("child socket error %d (errno %d)", errorType, errorNum); CloseHandle(socket); }
Our server is now ready ! He can handle clients and send message to others ! Cool. But without clients, nothing happens ! Let's see how we can fix that :
Creating and connectings clients !
Here we go, clients are a bit easier to handle, since they just receive and send data and do nothing else.
Remember the step 3) on 'creating the server part' ? Well there was a stock called : step 1) ConnecToMasterServer
PHP Code:
//Connect to the server stock void ConnecToMasterServer() { if(isMasterServer) //Should never happen, but yeah.. return;
clientHandle = SocketCreate(SOCKET_TCP, OnClientSocketError); //Create the socket PrintToServer("Attempt to connect to %s:%i ...", "yourgameserver.com", 2044); //Simple message... SocketConnect(clientHandle, OnClientSocketConnected, OnChildSocketReceive, OnChildSocketDisconnected, "yourgameserver.com", 2044); //Set callbacks for clients }
//When the client crash public OnClientSocketError(Handle socket, const int errorType, const int errorNum, any ary) { LogError("socket error %d (errno %d)", errorType, errorNum); CloseHandle(socket); }
//When the CLIENT connected to the server : public OnClientSocketConnected(Handle socket, any arg) { PrintToServer("Sucessfully connected to master chat server ! (%s:%i)", "yourgameserver.com", 2044);
//Nothing much to say... }
//Client disconnected from the server public OnChildSocketDisconnected(Handle socket, any hFile) { if(!isMasterServer) PrintToServer("Lost connection to master chat server !"); CloseHandle(socket); }
But where is 'OnChildSocketReceive' ? Well, look back up, we alread spoke about it.
And that's it ! Your clients can communicate with your server and your server can communicate with your clients ! Now, we just need to see HOW you can communicate...
Sending messages
Step 1) And our last missing stock :
This one is very important, she allow you to send messages to all clients registred in connections array !
PHP Code:
//Stock to send a message to all clients : stock void SendToAllClients(char[] finalMessage, int msgSize, Handle sender) { //Loop through all clients : for(int i = 0; i < GetArraySize(ARRAY_Connections); i++) { //Get client : Handle clientSocket = GetArrayCell(ARRAY_Connections, i); if(clientSocket != sender) { //If the handle to the client socket and the socket is connected, send the message : if(clientSocket != INVALID_HANDLE && SocketIsConnected(clientSocket)) SocketSend(clientSocket, finalMessage, msgSize); } } }
step 2) One little test command
I just added a RegServerCmd() to the OnPluginStart() function and the small callback :
PHP Code:
public void OnPluginStart() { CVAR_MasterChatServer = CreateConVar("sm_csc_is_server", "0", "Is this server the server ? 1 = yes | 0 = no, it's a client", _, true, 0.0, true, 1.0);
ARRAY_Connections = CreateArray(); // Init the array
RegConsoleCmd("sm_sendmsg", CMD_SendMessage, "Send a message"); }
//And the call back public Action CMD_SendMessage(client, args) { if(isMasterServer) SendToAllClients("test", 7, INVALID_HANDLE); //If the game server is a server, create the server else SocketSend(clientHandle, "test", 7); }
That's definitly it. You finish your base plugin, now you can add your features our do what ever you want.
Handle serverHandle; //Handle to server, allow us to acces it from everywhere in the plugin Handle clientHandle; //Handle to server, allow us to acces it from everywhere in the plugin Handle ARRAY_Connections; //Handle to connections, so we can store the client's connections.
Handle CVAR_MasterChatServer; //Allow the user to set if the game server is the server or a client bool isMasterServer; //Define if the game server is a server or a client public Plugin myinfo = { name = "[ANY] A simple test for sockets", author = PLUGIN_AUTHOR, description = "A simple exemple plugin for introducing to socket", version = PLUGIN_VERSION, url = "http://www.sourcemod.net" };
public void OnPluginStart() { CVAR_MasterChatServer = CreateConVar("sm_csc_is_server", "0", "Is this server the server ? 1 = yes | 0 = no, it's a client", _, true, 0.0, true, 1.0);
ARRAY_Connections = CreateArray(); // Init the array
RegConsoleCmd("sm_sendmsg", CMD_SendMessage, "Send a message"); }
public Action CMD_SendMessage(client, args) { if(isMasterServer) SendToAllClients("test", 7, INVALID_HANDLE); //If the game server is a server, create the server else SocketSend(clientHandle, "test", 7); }
public void OnConfigsExecuted() { isMasterServer = GetConVarBool(CVAR_MasterChatServer);
if(isMasterServer) CreateServer(); //If the game server is a server, create the server else ConnecToMasterServer(); //If the game server is not the server, connect to the server (so it's a client) }
//When the server crash, we can't do something but wait for a admin to reload the plugin. public OnServerSocketError(Handle socket, const int errorType, const int errorNum, any ary) { LogError("socket error %d (errno %d)", errorType, errorNum); CloseHandle(socket); }
//When a client sent a message to the server OR the server sent a message to the client : public OnChildSocketReceive(Handle socket, char[] receiveData, const int dataSize, any hFile) { PrintToServer(receiveData); //In any case, always print the message
if(isMasterServer) //If the game server is the server, then send the message to all clients SendToAllClients(receiveData, dataSize, socket); }
public OnChildSocketDisconnected(Handle socket, any hFile) { if(!isMasterServer) { PrintToServer("Lost connection to master chat server, reconnecting..."); } CloseHandle(socket); }
//When a client crash : public OnChildSocketError(Handle socket, const int errorType, const int errorNum, any ary) { LogError("child socket error %d (errno %d)", errorType, errorNum); CloseHandle(socket); }
//When someone sucessfully connected to the server : public OnSocketIncoming(Handle socket, Handle newSocket, char[] remoteIP, remotePort, any arg) { if(isMasterServer) //This is the job of the server, he have to handle clients : { PrintToServer("Another server connected to the chat server ! (%s:%d)", remoteIP, remotePort); SocketSetReceiveCallback(newSocket, OnChildSocketReceive); //When a client send a data, go inside of the 'OnChildSocketReceive' function. SocketSetDisconnectCallback(newSocket, OnChildSocketDisconnected); //When a client disconnect, go inside of the 'OnChildSocketDisconnected' function. SocketSetErrorCallback(newSocket, OnChildSocketError); //When a client crash, go inside of the 'OnChildSocketError' function. PushArrayCell(ARRAY_Connections, newSocket); //Save the handle to the connection into a array to send futur messages } }
//Create the server, not that hard, hu ? stock void CreateServer() { serverHandle = SocketCreate(SOCKET_TCP, OnServerSocketError); SocketBind(serverHandle, "0.0.0.0", 2044); //Listen everything on port 2044 SocketListen(serverHandle, OnSocketIncoming); //On SocketIncoming will fire when a client connect to the server ! }
//When the client crash public OnClientSocketError(Handle socket, const int errorType, const int errorNum, any ary) { LogError("socket error %d (errno %d)", errorType, errorNum); CloseHandle(socket); }
//When the CLIENT connected to the server : public OnClientSocketConnected(Handle socket, any arg) { PrintToServer("Sucessfully connected to master chat server ! (%s:%i)", "yourgameserver.com", 2044);
//Nothing much to say... }
//Connect to the server stock void ConnecToMasterServer() { if(isMasterServer) //Should never happen, but yeah.. return;
clientHandle = SocketCreate(SOCKET_TCP, OnClientSocketError); //Create the socket PrintToServer("Attempt to connect to %s:%i ...", "yourgameserver.com", 2044); //Simple message... SocketConnect(clientHandle, OnClientSocketConnected, OnChildSocketReceive, OnChildSocketDisconnected, "yourgameserver.com", 2044); //Set callbacks for clients }
//Stock to send a message to all clients : stock void SendToAllClients(char[] finalMessage, int msgSize, Handle sender) { //Loop through all clients : for(int i = 0; i < GetArraySize(ARRAY_Connections); i++) { //Get client : Handle clientSocket = GetArrayCell(ARRAY_Connections, i); if(clientSocket != sender) { //If the handle to the client socket and the socket is connected, send the message : if(clientSocket != INVALID_HANDLE && SocketIsConnected(clientSocket)) SocketSend(clientSocket, finalMessage, msgSize); } } }