TCP + UDP, CLIENT + SERVER model

Miscellaneous Forums/General Discussion/TCP + UDP, CLIENT + SERVER model

Ugh, this one has had some thinking into it. It is still in concept. May i introduce to you, my Simple Server Packet Protocol. It is designed for MMOG.
When it is done, i will be useing this for my MMORPG Fantasaar. Well, lets say i then have a decent base to build on.

Any comment and suggestions are welcome. If many like this idea i would suggest we make it an OpenSource.

For now i have only the client model:
SuperStrict

''Simple Server Packet Protocol (SSPP)  - Revision 1
'Author: Patrick Weijtenburg
'Last update: 31-01-2008 23:07

Type TClient
	Field name:String
	Field id:Int 'read only, set by server
	Field host:String
	Field port:Int
	Field channels:TList = CreateList() 
	Field channel:Int
	Field packetout:TBank
	Field packetin:TBank
	Field pointer:Int 'used for reading packets
	
	Method SetPlayerName(name:String) 
		Self.name = name
		'if is connected, send new name
	End Method
	
	Method GetPlayerName() 
		Return Self.name
	End Method
	
	Method GetPlayerID() 
		Return Self.id
	End Method
	
	Method Connect(host:String, port:Int, timeout:Int = 30) 
		Self.host = host
		Self.port = port
		'connection code, if timeout OnConnectTimeout()
	End Method
	
	Method OnConnectTimeout() 
	End Method
	
	Method OnAuthorize(key:String) 
		'parse key from server and send back to authorize
		Authorize(key) 
	End Method
	
	Method Authorize(key:String) 
		'return key to server
	End Method
	
	Method IsConnected() 
		'check if is connected or not
	End Method
	
	Method Disconnect() 
		'close connection
	End Method
	
	Method ChannelSignOn(channel:String) 
		Self.channels.AddLast(channel) 
		Self.channel = Self.channels.Count() - 1
		'signon at "channel"
	End Method
	
	Method ChannelSignOff() 
		'signoff at "GetChannelName()"
	End Method
	
	Method GetChannelID() 
		Return Self.channel
	End Method
	
	Method GetChannelName() 
		Return Self.channels.ValueAtIndex(Self.channel) 
	End Method
		
	Method SelectChannelByID(id:Int) 
		If Self.channels.ValueAtIndex(id) 
			Self.channel = id
			Return True
		Else
			Return False
		End If
	End Method
	
	Method SelectChannelByName(channel:String) 
		Local id:Int = 0
		For c = EachIn Self.channels
			If c = channel
				Self.channel = id
				Return True
			End If
			id:+1
		Next
		Return False
	End Method
	
	Method PacketNew(OnSubchannel:Int = 0) 
		Self.packetout = CreateBank(1) 
		Self.packetout.PokeByte(0, Byte(OnSubchannel)) 'set packet identifier for serverside parsing. a.k.a. subchannel
	End Method
	
	Method PacketAddByte(b:Byte) 
		Local size:Int = Self.packetout.Size() 
		Self.packetout.Resize(size + SizeOf(b)) 
		Self.packetout.PokeByte(size - 1, b) 
	End Method
	
	Method PacketAddShort(s:Short) 
		Local size:Int = Self.packetout.Size() 
		Self.packetout.Resize(size + SizeOf(s)) 
		Self.packetout.PokeShort(size - 1, s) 
	End Method
	
	Method PacketAddInt(i:Int) 
		Local size:Int = Self.packetout.Size() 
		Self.packetout.Resize(size + SizeOf(i)) 
		Self.packetout.PokeInt(size - 1, i) 
	End Method
	
	Method PacketAddLong(l:Long) 
		Local size:Int = Self.packetout.Size() 
		Self.packetout.Resize(size + SizeOf(l)) 
		Self.packetout.PokeLong(size - 1, l) 
	End Method
	
	Method PacketAddFloat(f:Float) 
		Local size:Int = Self.packetout.Size() 
		Self.packetout.Resize(size + SizeOf(f)) 
		Self.packetout.PokeFloat(size - 1, f) 
	End Method
	
	Method PacketAddDouble(d:Double) 
		Local size:Int = Self.packetout.Size() 
		Self.packetout.Resize(size + SizeOf(d)) 
		Self.packetout.PokeDouble(size - 1, d) 
	End Method
	
	Method PacketAddString(text:String) 
		Local size:Int = Self.packetout.Size() 
		Local s:Short = SizeOf(text) 
		Local x:Int
		text = Left(text, 65535) 
		Self.packetout.Resize(size + SizeOf(s) + SizeOf(text)) 
		Self.packetout.PokeShort(size - 1, s) 
		For x = 0 To Len(text) 
			Self.packetout.PokeByte(size + 1 + x, Byte(Mid(text, x))) 
		Next
	End Method
	
	rem
	Method PacketAddBinary(data:?) 
		Local size:Int = packetout.Size() 
		'add filesize
		'add file to packet
	End Method
	endrem
	
	Method PacketSendTCP() 
		'send Self.packetout over TCP on active channel to server
	End Method
	
	Method PacketSendUDP() 
		'send Self.packetout over UDP on active channel to server
	End Method
		
	Method OnPacketReceived(packet:TBank) 
		Self.packetin = packet
		Self.pointer = 0
	End Method
	
	Method PacketReadByte() 
		Local b:Byte = Self.packetin.PeekByte(Self.pointer) 
		Self.pointer:+SizeOf(b) 
		return b 
	End Method
	
	Method PacketReadShort() 
		Local s:Short = Self.packetin.PeekShort(Self.pointer) 
		Self.pointer:+SizeOf(s) 
		Return s
	End Method
	
	Method PacketReadInt() 
		Local i:Int = Self.packetin.PeekInt(Self.pointer) 
		Self.pointer:+SizeOf(i) 
		Return i
	End Method
	
	Method PacketReadLong() 
		Local l:Long = Self.packetin.PeekLong(Self.pointer) 
		Self.pointer:+SizeOf(l) 
		Return l
	End Method
	
	Method PacketReadFloat() 
		Local f:Float = Self.packetin.PeekFloat(Self.pointer) 
		Self.pointer:+SizeOf(f) 
		Return f
	End Method
	
	Method PacketReadDouble() 
		Local d:Double = Self.packetin.PeekDouble(Self.pointer) 
		Self.pointer:+SizeOf(d) 
		Return d
	End Method
	
	Method PacketReadString() 
		Local text:String
		Local s:Short = Self.packetin.PeekShort(Self.pointer) 
		Local size:Int = Int(s) 
		Self.pointer:+SizeOf(s) 
		Local x:Int
		For x = 0 To size
			text = text + Chr(Self.packetin.PeekByte(Self.pointer + x)) 
		Next
		Self.pointer:+size
		Return text
	End Method
	
	rem
	Method PacketReadBinary() 
		Local data:?
		'read filesize
		'read file from packet
		'return file
	End Method
	endrem

EndType


!! attention: in concept !! ~contains psuedo setup

How this would work:

client:TClient = new TClient

client.SetPlayerName("Patrick")
client.Connect("www.fantasaar.com",1234)

onauthorize:
client.authorize() 'do some math with the key and return it.

onconnect:
client.ChannelSignOn("Lobby")
client.ChannelSignOn("gamezonex0y0")

onsignon:
client.NewPacket(1) '1= commando/subchannel: create player
client.PacketAddInt(player.x)
client.PacketAddInt(player.y)
client.PacketAddInt(player.dir)
client.PacketAddInt(player.speed)
client.PacketAddInt(player.ani)
client.PacketSendTCP()
client.NewPacket(2) '2= commando/subchannel: broadcast chat
client.PacketAddString(GetPlayerName()+ " says hello to you!")
client.PacketSendTCP()

main game loop:
client.NewPacket(3) '1= commando/subchannel: update player
client.PacketAddInt(player.x)
client.PacketAddInt(player.y)
client.PacketAddInt(player.dir)
client.PacketAddInt(player.speed)
client.PacketAddInt(player.ani)
client.PacketSendUDP()

great idea beside the TCP.
TCP + Blitz -> no go if the app that uses it is meant to work in realtime.

Use reliable ordered UDP instead (pub.enet to name the simplest and already wrapped solution)

well, TCP works for chat. And i can write it so its non-blocking.

The plan is tough to connect both a TCP and a UDP socket. So you can use both.

I set myself a target not to use any libraries and just raw sockets. Reason is that i fear the ENet is rather limited, and therefore maybe more intense on bandwith useage OR slower in overall...

well, TCP works for chat. And i can write it so its non-blocking.


that would be great if this would be possible. Not sure if it is when you remain on pure socket.
But if you do, I would suggest having a look at Vertex' BNetEx. It does not more than wrap the sockets into UDP and TCP stream and add the needed "surrounding" to make them work as there are things that must be met for them to work when used in real cases on varying systems.

PS: ENet is actually quite unlimited. Its a simple host - host (point <-> point) connection using UDP with ordered packet send. the reliability can be enabled where needed which allows very flexible performance - latency throttling.

You don't need to do `Self.` within a method because methods automatically refer to the local instance's fields as implied. You only need `Self.` within a function within a type.

But otherwise this seems interesting. I'm trying to design a networking system myself at the moment so good luck to ya.

I'm not wetting my hands with MMO coz it'll be tons of work for both coding and media spend.

However I suggest you look at RakNet for BlitMax if you are doing things mainly for Win32.

IH: you don't need self. within a function within a type. A function has no self. If you don't hand it over as parameter it will not exist at all.

Functions are global -> they know the class, global and const.
They don't know the instance nor can they access fields unless you hand the type instance to them or they iterate through a datastructure that contains instances of the type.

(given the fact that Mark finally removed the bugs on that end, which was meant to happen for 1.26 - 1.28)

Thanks for the comments!
@Dreamora, is there any documentation on ENet? i couldn't find except by opening the individual files in the lib. But that is rather messy.

p.s. i heard that - 1.28 is slower then 1.24? i am still using 1.26 myself. Is this true? ...you would expect an increase of preformance, not a decrease.

Dreamora, ah yah, forgot that part.

eh...
Type TClient
	Field name:String
	
	Method SetPlayerName(name:String) 
		name = name
	End Method


How would that work? i think i need Self. or i should change the variable names. Whats the point of NOT using Self. really?

on a note: i found out methods cannot return?!

ugh. nevermind. i forgot to declare the method :P

You don't need to do `Self.` within a method because methods automatically refer to the local instance's fields as implied. You only need `Self.` within a function within a type.


You also don't need to use remarks either, the compiler will compile the program without any remarks.....

Professionally, as in every software development job I have had outside the game industry, not using self in this case would get you close to canned for being an inconsiderate prick. It's just common courtesy to use me.(vb) $this->(php, etc) and so on in your methods to indicate where this variable is coming from, that it in fact belongs to the class/type/struct/namespace structure and just isnt some local variable created on the fly by the lack of strict. So many sloppy coders out there use a global function in a method (or a global Variable) and you spend a huge amount of wasted time hunting down which file that global function is in (out of the oodles of includeded files).

Best one I have ever seen was a variable in a method of a c program on a VMS system called "thisthing".... It wasn't a member of the structure, and 15 included files later I found it was pointed to by a global....

In my opinion, take the extra min to type the "self"'s as it saves on a load of headache for yourself and others later on when methodologies become loosened by a long winded project that just wont finish itself.

Wouldn't it be impossible to create the variable on the fly if it exists as a field name, anyway? But I can see your point, it might make it easier to follow. But I'm not so sure how effective it is to do things in code for readability's sake versus how it will perform when compiled, if there is a difference.