
Originally Posted by
Hermpie
Hello, my friends!
My idea was to send all the information in 1 string.
Like :
Or without ';'
Atleast, is this a good idea?
The choice is going to depend a bit on how much data you are sending and how much freedom you want to support older versions of the client.
You didn't mention what language you are targeting, so for the sake of discussion I'll assume Delphi. Most of this applies regardless of language, though the details of how you parse messages will vary.
If you don't need a very large amount of data you can do a simple scheme using TStringList's support for Name=Value pairs. This makes for a very robust and easy to understand protocol where fields are named. On the other hand, you end up sending more data because you are sending the field names along with the data.
It works like this. When you want to send some data across the connection you create an instance of a TStringList, then you populate it with a list of strings containing your data using name=value pairs. You then read the .CommaText property to convert the list to a simple string (with quoted comma separated values), then send that string across the connection. At the other end you create a TStringList, receive the transmitted string and assign it to the .CommaText property. The TStringList will convert it back into a list of strings. To read the commands back you use the .Name property to get the values.
So what do you send? Again, depends on what kind of a communication model you are using. A simple Command-Response model is easy to deal with and versatile. It consists of a command, lets say a 'Login' command, and may include some parameters, we'll use UserName, Password and ClientVersion (the version number of the client software).
In a pas unit that is shared between the client and server applications you define the routines used to manage the communications protocol. Something like this:
Code:
unit CommProtocol;
interface
uses Classes, Sysutils;
const
CommandID = 'CMD';
cmdLOGIN = 'Login';
type
TLoginRequestInfo = record
UserName : string;
Password : string;
ClientVersion : string;
ErrCode : Integer;
end;
function IsLoginRequestMsg(Parser: TStrings): Boolean;
procedure SetupLoginRequestMsg(InStrings: TStrings; UserName, Password, ClientVersion: string);
function ParseUploadLoginRequestMsg(Parser: TStrings; var LoginRequest: TLoginRequestInfo): Boolean;
implementation
function IsLoginRequestMsg(Parser: TStrings): Boolean;
begin
Result := (Parser.Values[CommandID] = cmdLOGIN);
end;
procedure SetupLoginRequestMsg(InStrings: TStrings; UserName, Password, ClientVersion: string);
begin
InStrings.Clear;
InStrings.Add(CommandID+'='+cmdLOGIN)
InStrings.Add('UserName='+UserName);
InStrings.Add('Password='+Password);
InStrings.Add('ClientVersion='+ClientVersion);
end;
function ParseLoginRequestMsg(Parser: TStrings; var LoginRequest: TLoginRequestInfo): Boolean;
begin
Result := IsLoginRequestMsg(Parser);
if Result then
with LoginRequest do begin
UserName := Trim(Parser.Values['UserName']);
Password := Trim(Parser.Values['Password']);
ClientVersion := Trim(Parser.Values['ClientVersion']);
end;
end;
end;
So now on the client when you need to log into the server you just have to do something like this:
Code:
procedure TGameClientClass.DoLogin( User, Pass : string );
begin
sl : TStringList.Create;
try
SetupLoginRequestMessage(sl, User, Pass, GameClientNumber );
SendStringToServer( sl.commaText );
finally
sl.Free;
end;
end;
This builds a stringlist that looks like this:
Code:
CMD=Login
UserName=whatever
Password=what,ever
ClientVersion=whatever
After it goes through the CommaText routine it is a simple string (notice that the user's password had a comma in it):
Code:
CMD=Login,UserName=whatever,"Password=what,ever",ClientVersion=whatever
See how it quoted the Password field so that the comma could pass through? It'll handle the quote characters automatically too.
The 'SendStringToServer()' method is just a placeholder in this example. It would do something like use the current TCP connection to the server to send the string.
On the server side you would have a TCP receive method that parsed the messages coming from the client:
Code:
procedure TGameServerClass.TCPReceive(AContext: TIDContext);
var
Buffer : TStringList;
LoginRequest : TLoginRequestInfo;
begin
Buffer := TStringList.Create;
try
// Get the incoming text
Buffer.CommaText := AContext.Connection.IOHandler.ReadLn;
// Process the command
if ParseLoginRequestMsg( Buffer, LoginRequest ) then begin
DoLogin( LoginRequest );
BuildLoginResponseMsg( Buffer, LoginRequest.ErrCode );
SendMessage( AContext, Buffer );
end
else if ParseSomethingElse1( Buffer, SomeThingElse1 ) then begin
DoSomethingElse1( SomethingElse1 );
BuildSomethingElse1ResponseMsg( Buffer );
SendMessage( AContext, Buffer );
end
else if ParseSomethingElse2( Buffer, SomeThingElse2 ) then begin
DoSomethingElse2( SomethingElse2 );
BuildSomethingElse2ResponseMsg( Buffer );
SendMessage( AContext, Buffer );
end;
finally
Buffer.Free;
end;
end;
If you don't want your data on the wire in the clear where people could snoop on it, just use a TCP component that encrypts it before sending it.
Now, obviously, if you are sending rapid sync updates like character position and suchlike where you might generate a lot of data you can just encode the data into a string and use the same system.
You just build a string containing your update and send it. The command might be:
Code:
const
cmdPlayerSync = 'PlySnc';
And you can give it a single parameter that consists of your sync data encoded into a string:
Code:
procedure SetupPlayerSyncMsg(InStrings: TStrings; SyncData: string);
begin
InStrings.Clear;
InStrings.Add(CommandID+'='+cmdPlayerSync)
InStrings.Add('SyncData='+SyncData);
end;
Your player object might have a property that generates the sync data based on some of it's information, like the world position, current animation, health, etc:
Code:
function TPlayer.GetSyncData: string;
begin
result := Format( '%s,%s,%s', [WorldX, WorldY, Health] );
end;
So then the message that goes up to the server would look like:
Code:
CMD=PlySnc,"SyncData=2343,8989,12"
On the server you would read the SyncData field and then parse it by field number nstead of by field name. You could do this by dumping the Value['SyncData'] into a second string list's .CommaText which would split the string at the commas resulting in a list containing:
Then you'd just read that string list by using defined constants:
Code:
const
cSyncWorldX = 0;
cSyncWorldY = 1;
cSyncHealth = 2;
var
syncData : TStringList;
begin
... := syncData[cSyncWorldX];
... := syncData[cSyncWorldY];
... := syncData[cSyncHealth];
end;
Second thing. How do we send this over a server?
How can we use our own pc as "server" and how to use for example Ruler's hosting service?
You'll have to find a hosting service that supports whatever platform you intend to run the server on. IME most web hosts don't let you run things like game servers, but there are plenty of hosting solutions available.
Hope that helps.