A little TCP problem (reusing streams)...

Blitz3D Forums/Blitz3D Programming/A little TCP problem (reusing streams)...

ChrML(Posted 2004) [#1]
server=CreateTCPServer(2345)
If server=0 Then
  RuntimeError "Failed to create server!"
EndIf

timer=CreateTimer(10)

While Not KeyDown(1)
  WaitTimer(timer)

  stream=AcceptTCPStream(server)

  If stream Then
	Print ReadString(stream)
  EndIf

  Flip
Wend


client=OpenTCPStream("217.13.12.90",2345)
If client=0 Then
  RuntimeError "Failed to connect!"
EndIf


While Not KeyDown(1)
  tosend$ = Input("")
  WriteString client,tosend$
Wend


I must say I got quite amazed about how straight forward blitz3d's TCP programming is compared to Delphi (which I always thought was very straight forward too :P), but I've run into one problem. Look at the code samples above. It won't seem to let me reuse the streams. Why?

I first start the server prog. Then when I start the client, and the client connects, then the server won't respond until I sent a string to it. Then it displays the first string, but if I try to send more strings, it doesn't display them (I guess AcceptTCPStream doesn't trigger when sending streams for 2nd time). What can I then use to know if there are any strings waiting...? Is really putting the read timeout to 0, and just ReadString, and then just check if it's 0 or not the best way, or is it a better way? Btw, I want to use TCP, not DirectPlay, because it's supposed to transfer files after some testing with these strings to see how well Blitz can handle this stuff...

Added:
I just thought of that the timeout would in that case kill the stream, so now that possibility isn't there too...


Klaas(Posted 2004) [#2]
you overwrite the stream variable in each loop .. you have todo something like this

acceptstream() only returns NEW streams not streams that are allready astablished
This code below is for single connection only .. for multi connections you have to store your streams into types for example. Then test each loop for every stream if there is a readavail() on the stream

server=CreateTCPServer(2345)
If server=0 Then
  RuntimeError "Failed to create server!"
EndIf

timer=CreateTimer(10)

While Not KeyDown(1)
  WaitTimer(timer)

  if not stream
     stream=AcceptTCPStream(server)
  elseif readavail(stream)
	Print ReadString(stream)
  elseif eof(stream)
    stream = false
  EndIf
  Flip
Wend



ChrML(Posted 2004) [#3]
Ah, I see. Thanks!


ChrML(Posted 2004) [#4]
That over works, but now I'm wondering about different stuff:

TCP: Seems like TCP can transfer anything, and I have full control, but is this fast? How would this protocol work for for example FPS multiplayer with 5 clients and one host?

UDP: Seems like UDP also can transfer anything, but is this fast? Is it faster than TCP?

DirectPlay: I don't think I will use DirectPlay, because it can't transfer streams, and I don't have full control over what hosts what, etc...


One more problem is the multithreading that should've been being used here. What I worry about is that one of the clients might cause the server to lag because of slow transfer, or not receive anything at all if the timeout is set too low. This *should* definitly be multithreaded, but blitz doesn't support it yet :(. Also, if a client joins, and it doesn't have a mod that the server runs, then it will download it off the server. That should also be done in a seperate thread, but sending 1kb buffers each mainloop should work fine, right?


Klaas(Posted 2004) [#5]
The main drawback of TCP is the larger packetsize, the validation of packet transmisson sucess (time consuming) and that blitz hangs till timeout if a stream break up (this can be several seconds)

UDP is a non reliable protocol .. there is no validation if the packet has arrived its destination but in most cases in a action game a dropped packet isnt that worse and you can validate this by yourself if you need it in a certain case.
UDP packets are smaller cause of there smaller header, this is extremly faster when you send a lot of small packets.

UDP drawback is a more complex programming if you want to serve multiply host through routers. A Router opens a UDP-session and those sessions can last longer then you want them to. UDP is not a stream you canot determ when a stream breaks up ... you have to code some sort of "keepalive"

I allways would prefer UDP cause TCP is damned laggy in Blitz ... but beware ... there is some more work to do !


ChrML(Posted 2004) [#6]
I see. When a router opens UDP ports, isn't that just uPnP routers? For many small packets telling position of players, and so on, you recommend UDP for that? But, when I code the keep alive thing, I can just make it send something like "36+13" each mainloop, the other side calculates it, and if I get the answer "49" then it's still up, right? And if not, reconnect.

For the filetransfers over UDP, I can make it control CRC (I remember when I made a TCP filetransfer program, then about 1 of 10 000 packages got wrong transferred (2kB packages), so the files always got slightly corrupted. I made a CRC checking function, and then it works fine when resending error packages, but way slower.


Klaas(Posted 2004) [#7]
Don't know exactly what you mean with uPnP .. the router remeaps the ports and keeps this rout for a certain time. Sometimes this rout last long enough to reconnect on the same port even if you restart the game on the client, this can cause some problems.
Yes, UDP is perfect for small packets.
I always store my player in a type, each player got a TTL time if the client does not send a packet until the TTL elapsed (this can be an empty packet) the player is treated as disconnected.
You can assign something like session numbers that are unique, then you know that this client is new and has not reconnected

-- edit ---
you can use a portrange where the server tells the client to use a certain port .. this port is then exclusive for this client, much like FTP does.


ChrML(Posted 2004) [#8]
You got me convinced; UDP is best for this kind of games :D.

I've done network programming before, but not over blitz, so I've got a certain level of experience that packages do get lost :P, like in my TCP filetransfer prog (even when TCP should be relieable), I lost about 1 of 10 000 packages all time when sending 2 kB buffers. I will make a CRC checker for the filetransfer part, so that should also work out fine. You know kinda how much UDP fails to deliver (fail-percent)?

Btw, uPnP is Universal Plug 'n Play routers that maps the incomming port to the correct computer under the LAN for a specific time. You may be talking about an another "port-opening", I don't know.


Klaas(Posted 2004) [#9]
Sorry, dont know how reliable UDP is

CRC checksum: there is a damned fast CRC in "zlib.dll" and with this DLL you can crunch(ZIP) your data in banks send them and decrunch them on the client in no time. This can save a lot of bandwidth and is easy to use, you can even CRC check this bank with the DLL so you can check each transmisson to be valid.


ChrML(Posted 2004) [#10]
Okay, I will check it out. Thanks for helping me :).


ChrML(Posted 2004) [#11]
Ok, I've made a simple test thing working over UDP now (a plane with a cube on which is the player, and the second cube is created when the other player joins, and then the second player can control the cube and the other side can see it moves). The only problem is that it's slower than my grandma. Higher FPS I have, then slower it becomes. Here's my code (for both client and server):

;Types
Type clients
  Field stream
  Field model
  Field id%
  Field ip%
  Field joined
End Type

;Constants
Const port%=1234
Const clientport%=1235
Const ip$="x"

;Globals
Global intip% = HostIP(CountHostIPs("x"))

;Initialize
Graphics3D 640,480,32,2
SetBuffer BackBuffer()

;Host/join
.start
mode$=Input("Host or join?: ")

;Host/join/return
If Lower(mode$)="host" Then
  server=CreateUDPStream(port%)
  If server=0 Then
	RuntimeError "Failed to create server at port "+port%+"!"
  EndIf

  host=1
ElseIf Lower(mode$)="join" Then
  ;Create server
  client=CreateUDPStream(clientport%)
  UDPTimeouts 1000
  If client=0 Then
	RuntimeError "Client failed to connect!"
  EndIf

  ;Connect
  WriteString client,"connect"
  SendUDPMsg client,intip%,port%

  src=RecvUDPMsg(client)
  If src=0 Then
	RuntimeError "Lost connection!"
  EndIf

  comclient$=ReadString(client)
  If Not comclient$="connect_ok" Then
	RuntimeError "Failed to establish connection! "+comclient$
  EndIf


  ;ID
  WriteString client,"request_id"
  SendUDPMsg client,intip%,port%

  RecvUDPMsg(client)
  comclient$ = ReadInt(client)
  clientid% = Int(comclient$)

  WriteString client,"join"
  SendUDPMsg client,intip%,port%
  
  RecvUDPMsg(client)
  comclient$ = ReadString(client)
  If Not comclient$ = "joined" Then
	RuntimeError "Not joined "+comclient$
  EndIf


  host=0
Else
  Goto start
EndIf



cube=CreatePlane()
tex=LoadTexture("C:\wall.bmp")
EntityType cube,1
ScaleTexture tex,100,100
EntityTexture cube,tex
FreeTexture tex

cam=CreateCamera()
PositionEntity cam,100,100,100

lig=CreateLight()
PositionEntity lig,-100,100,100

player=CreateCube()
PositionEntity player,0,50,0
ScaleEntity player,2,6,1
EntityType player,2
EntityRadius player,6

Collisions 2,1,2,2


;FPS limit
timer=CreateTimer(10)

While Not KeyDown(1)
  WaitTimer(timer)

  ;Client
  If host=0 Then
	WriteString client,"position"
	SendUDPMsg client,intip%,port%
	
	WriteInt client,EntityX(player)
	WriteInt client,EntityY(player)
	WriteInt client,EntityZ(player)
	WriteInt client,EntityPitch(player)
	WriteInt client,EntityYaw(player)
	WriteInt client,EntityRoll(player)
	SendUDPMsg client,intip%,port%
  EndIf


  ;Server
  If host=1 Then
	For tmp=0 To 2
	;Check for new connection
	recv=RecvUDPMsg(server)
	If recv<>0 Then
	  com$ = ReadString(server)
	  cnt=0

	  ;Connect
	  If com$="connect" Then
		WriteString server,"connect_ok"
		SendUDPMsg server,recv,UDPMsgPort(server)
		cnt=1
	  EndIf
	
	
	  ;Request ID
	  If com$="request_id" Then
		WriteInt server,Rnd(1,50000)
		SendUDPMsg server,recv,UDPMsgPort(server)
		cnt=1
	  EndIf
	
	
	  ;Join
	  If com$="join" Then
		joined%=1
		model=CreateCube()
		ScaleMesh model,2,6,1
		
		WriteString server,"joined"
		SendUDPMsg server,recv,UDPMsgPort(server)
		cnt=1
	  EndIf
	
	
	  ;Position
	  If com$="position" Then
		RecvUDPMsg(server)
		
		x=ReadInt(server)
		y=ReadInt(server)
		z=ReadInt(server)
		pitch=ReadInt(server)
		yaw=ReadInt(server)
		roll=ReadInt(server)
		
		PositionEntity model,x,y,z
		RotateEntity model,pitch,yaw,roll
		cnt=1
	  EndIf
	
	
	  ;Error
	  If cnt=0 Then
		RuntimeError "Command not understood: "+com$
	  EndIf
	EndIf
	Next
  EndIf


  ;Input
  If KeyDown(200) Then
	MoveEntity player,0,0,4
  EndIf
	
  If KeyDown(208) Then
	MoveEntity player,0,0,-4
  EndIf
	
  If KeyDown(203) Then
	TurnEntity player,0,4,0
  EndIf
  
  If KeyDown(205) Then
	TurnEntity player,0,-4,0
  EndIf

  ;Precalculate
  TranslateEntity player,0,-4,0
  PointEntity cam,player

  ;Draw 3D
  UpdateWorld
  RenderWorld

  ;Draw 2D
  ; Client
  If host=0 Then
	Text 0,0,"ID: "+clientid%
  EndIf

  Flip
Wend


There's lot of rubbish code there, but I'm sure you'll understand. The reason for that the client and server ports are different is that I use the same computer for both.


semar(Posted 2004) [#12]
...and the oscar goes to:
timer=CreateTimer(10)


That means that your while..wend loop runs at 10 frames per second. Sure it is quite slow !

Try to adjust it to 50 or 60....

More, you should also try to use the ReadAvail command. For a running example, refers to this simple and effective UDP chat program, written by Wayne:
http://www.blitzbasic.com/codearcs/codearcs.php?code=152

UDP communication is *very* fast, so if you get poor performance, most of the time it's source code fault (no offence at all m8 !).

;-)

Sergio.


ChrML(Posted 2004) [#13]
I know 10 FPS is very slow, but that was only for testing. If I put it at 50-60, then I even get more lagging (seems like I get large UDP queries, so it moves kinda 10 seconds after I've moved it on the remote comp).

Ok, will look at the code now...

How fast can I expect the ping to be on remote computers at the opposite side of the world?


Klaas(Posted 2004) [#14]
to have a better control over the interval of submissions try that.

hz = 60
nethz = 10
netTick = hz / nethz
timer = createtimer(hz)

while not keyhit(1)
   waittimer(timer)
  tick = tick + 1
  tick = tick mod netTick
  if not tick
     ;do submissions only here
  endif
  ;but always look for incomming streams
  
wend


this will let your code run on 60hz but sends to the server only with 10hz


ChrML(Posted 2004) [#15]
Good idea :D! Thanks for all help Klaas and Semar. I've got a better understanding of MP games now. I will make my entire MP system command based.


Kanati(Posted 2004) [#16]
don't forget one more thing... UDP packets aren't guaranteed to arrive in the order they were sent either. TCP packets are.

Kanati


ChrML(Posted 2004) [#17]
Oh...I guess it will be kinda pain in the ass to program then. Atleast I've included the current ALPHA of my multiplayer UDP system in my game called The Underground so far. Currently I can host, join any IP, chat, ping, and disconnect by using commands in the console menu opened with Shift+Plus I made for it.

I guess I have to rewrite something, because not all chat messages do arrive (actually about 95% of them do, but some fails (maybe because the chat message itself arrives before the command 'chat'?)). I guess I should make it send one package instead, like:

Chat "this is the chat message"

And when sending the position/rotation, it sends this:

Orientation 50.4|204.2|86.1|586.33|95.22|509.9

Where | seperates the parameters. Sounds like a good idea, or might the package end up too big to get over in one piece in a timeout of 40 ms (it can't be higher because then it wouldn't have time to treat the other clients, because blitz doesn't support multithreading)? Over the LAN, the ping time (time from where a client sends a text saying 'ping', till when it gets a message back saying 'ping_reply') is 0ms. I gotta test this over internet too.


Klaas(Posted 2004) [#18]
a simple idea to measure the ping time is ... send something like "ping millisecs()", where millisecs is the millisecs time ! then the server sends back the millisecs time .. easy to calcuate then ... and your client don't have to store when a ping has been send.

you should develop some kind of "header" ... cause, always sending the whole string "orientation" is waste of bandwidth ... make a 1 byte constant like "const com_orientation = 1" then you only have to submit 1 byte then 12 bytes for the string


ChrML(Posted 2004) [#19]
Good Idea, I suppose one byte instead of 11 bytes for the string will also boost the speed a bit :). Thanks.


ChrML(Posted 2004) [#20]
Sorry, haven't got possibility to test this function right now (the other comp on our LAN is busy doing some filetransfers), but would this code work in the theory?:

Function Proto_BroadCast(p%)
  tempbuf = CreateBank(128)
  SeekFile(mp_server,0)
  CopyStream mp_server,tempbuf
	
  For mp.multiplayers = Each multiplayers
    SendUDPMsg mp_server,mp.multiplayers\ipint%,mp.multiplayers\port%
	
    CopyStream tempbuf,mp_server
  Next
	
  SendUDPMsg mp_server,0,0
	
  FreeBank tempbuf
End Function


I don't know if SeekFile can be used on UDP streams, neither I know if I can use CopyStream to copy the UDP stream to the bank. Neither I know if I can copy the bank back to the stream with CopyStream. And there must be an easier way to clear the UDP stream than sending it to an address that doesn't exist?


Klaas(Posted 2004) [#21]
don't know what youre doing there but have a look a that code

Function Proto_BroadCast(p%)
	avail = ReadAvail(mp_server)
	tempbuf = CreateBank(avail)
	ReadBytes tempbuf,mp_server,0,avail
		
	For mp.multiplayers = Each multiplayers
		WriteBytes(tempbuf,mp_server,0,avail)
		SendUDPMsg mp_server,mp.multiplayers\ipint%,mp.multiplayers\port%
	Next

	SendUDPMsg mp_server,0,0

	FreeBank tempbuf
End Function



ChrML(Posted 2004) [#22]
Okay, thanks, that code looks like it should work. What the code should be able to do is to broadcast a message to all clients on the server, and the code you provided me looks like it should be able to do that.


Klaas(Posted 2004) [#23]
ah, i see ... you don't have to flush streams (SendUDPMsg mp_server,0,0) .. they get automaticly flushed if you send them


ChrML(Posted 2004) [#24]
Ok.

Just one more thing. I need a way to detect if there are lots of UDP packages in queue. Like if I send 5 packages a second, and the receiver is only programmed to 2 packages a second (just an example). How can I then program the receiver to know that he's receiving too much, and then clear the queue? It seems like those queues makes the receiver lay more and more back in time. What I need is a way to detect that. Any idea? (I might've explained it a bit bad).


Klaas(Posted 2004) [#25]
you cannot clear the queue ... not in blitz
you can do something like syncronising between client and server ... send a sync. time within a packet ... then the receiver could see that he is falling back in time.

to avoid that the receiver lay back in time you should always read as many packets are available. They are allready deliverd then and should not hit the performance that much.
[code]
avail = readavail(stream)
while readavail(stream)
readbytes buffer,stream,0,avail
;do whatever you do with the data
wend
[\code]

UDP is fast ... but network through internet is always "slow" , try to submit as few as possible data. There are many possiblitys to lower the data amount that has to be submited ... interpolate the position and orientations between submissions to get smooth movement with low bandwidth. Do not send the data each frame ! Just send it 10 times per sec. or something like that, maybe you can link that to the ping value or bandwith avail.
Try to make the packets as small as you can ... a hint:
position and orientation is the most needed information but you must not always send the absolut position, you could just send the difference between the last position, then ervery sec. or 2 sec. do a correction and send the full position. This shrinks data very much !
Take a look in the code Archive .. there is a example on how to use cubic interpolation to fight lags.

--- edit -----
another idea: you dont have to submit a orientation as a full float ! Use a short instead or even a single byte will look good in figthing action .. then every degree is is not a fraction of 360 ... but a fraction of 256 (can't explain that easily, but i think you will get it)


ChrML(Posted 2004) [#26]
Ah, I see. My problems should be all solved now. Great thanks for the help, Klaas.

Yeah, I get your idea of what you said in the bottom, and it all sounds like a good idea, thanks. I guess it would be best sending data int(1000/ping) times a second, and as you said, read everything available. Again, thanks for help :).


eBusiness(Posted 2004) [#27]
Can I ask a question? Well, I'll do so: How is data divided into packages? When I just write sendbyte, will this single byte then take up a whole package? If so, how to do it smarter?


Klaas(Posted 2004) [#28]
in UDP you send streams with "sendUDPmessage" .. i think blitz will handle this as a packet but i dont know how big one packet can be, so blitz evantulay splits a stream into more then one packet. In TCP the data is not in a single package cause its a true stream and is not send with a command like sendstream.


Litobyte(Posted 2007) [#29]
Hi guys,

I have a big big crazy problem.
I took back this old post because you are talking udp here.

I have this problem with big IP integers numbers,
Blitz3D seems to support only (talking about C/C++ data types) INT32 numbers but not UINT32!

The difference as you probably already know is that INT32 can store smaller numbers than UINT32 infact when my DOTTED ip number in my LAN is in the class 100.x.x.x or more than 100 the clients fail to connect the server!

This because the common non-native function ConvertIP which converts the DOTTEDIP to internal integer ip, fails if the first row is >100.

This because IPV4 protocol works as follows:

first number is multiplied by 16777216
second number multiplied by 65536
third number is multiplied by 256
fourth number is just as is

The sum of this numbers gives the internal INTEGER IP number.

I tried to just lower the class of ip of my LAN to 99.x.x.x and everything worked just fine as blitz3D can convert these ips to some positive number, differently with a negative one, the connection just doesnt work....

I didnt try with directplay panel, perhaps in that way the problem doesnot exist ?

Thanks in advance for your attention.

Regards,


BIG BUG(Posted 2007) [#30]
Unsigned and signed integer are stored identically, the only difference is in computing. So just use SHL instead of multiplication and everything should work fine.

However, there is no need for an own converting function at all, CountHostIPs does this job very nice.


Wings(Posted 2007) [#31]
FYI

****************************************
*** TCP IN BLITZ HANDLES OVER 128 USERS ***
****************************************

but you must be very carfully then coding it.
basicly only 2 instructions is alowed.

Readavail()
Readbytes()

All other commands slow tcp usage so slow that it cant even handle 5 users :)

/master coder of TCP ( daniel@... )


Litobyte(Posted 2007) [#32]
Im using UDP stream, not TCP connection sockets.

My problem is not a limit of users.
The software just won't work on a 192.168.x.x network,
or a 101.1.1.x network mask.

It works as a charm if the server LAN ip address (and so the clients are ) is below 100 as first number eg: 99.1.1.x

Why ?


Wings(Posted 2007) [#33]
Well dont use UDP

TCP handle with ease.

Even if port is disabled by techical staff TCP port 80 always get away with traffic.

The key thing is to use same stream for sending and reciving. i show a peace of code.


Litobyte(Posted 2007) [#34]
Even if I use bit operations such as SHL instead multiply, I have the same value returned (-16770XXXX)

IMO the problem is not UDP or TCP related, but as a TCP/IP protocol ip numbers masking problem!

I try to explain again..
My software just works good if the machines are under a 99(or less).x.x.x IP mask.

Blitz's client exe just doesn't find any server if the IP number of the first row is high! (eg: 192.168.0.1 but also 100.0.0.1 will fail)

So the problem is in the IP integer number I send with UDP commands that doesnt work, not the UDP command itself!

Please help!!!


BIG BUG(Posted 2007) [#35]
I don't know what your problem exactly is, but it's definitly in your code or network setup, not in how BB handles integer.
IPs like 192.168.x.x work fine here.
Maybe you should post some code, so we can help you.


Litobyte(Posted 2007) [#36]
Ok, I will do.

Sorry for blowing!
I am just getting mad with this problem really...

I will do other tests and report the code...which works great on low ip number LANs...


Litobyte(Posted 2007) [#37]
There is a UDP client/server 2Ddraw code sample somewhere, I used that code as a start, and everything works fine with low IPs.

Is perhaps a problem with the newest release of WinXP SP2 ?
Because these are very new machines with a brand new WinXP installed, and one of the things I noticed is that the firewall is set as default for blocking ICMP requests and such, so that the machines at first were UNABLE TO PING EACH OTHER !!! (with any class of IPs).

Solved this problem I start to wonder if this problem is due to some new XP default setting wich is blocking high class ips !?!?!