Page 1 of 2

lua-enet only sending some messages

Posted: Thu Aug 05, 2021 9:41 am
by mxmlnbndsmnn
Hey there, it's a network question again :D
After fiddling with luasocket and UDP for a while I moved on to using enet which seems kinda cool.
I got a working server-client connection which I have tested to work fine on two win10 machines in the same WLAN.
However, before actually trying to building a game around this working connection ;) I tested how reliable the message exchange actually is... and here I run into a roadblock that I don't understand.

When I try to send multiple messages right after another, the peer will only receive a fraction of those, even if all messages are send in the default "reliable" mode. Client sends message 1, 2, ..., 5 but the server only receives messages 1,3 and 5 OR sometimes only message 2 and 4 (wtf?).
Yes I know, for i = 1,5 do client:send(...) does not seem practical (I could merge them in this case) but I assume that later I might have multiple things that should be exchanged simultaneously.
I am also aware of the channel functionality, which looks very useful, but this will be the next step on my research plan.

See the attached file. You can configure the server IP in an extra file if you unzip it. For now it is set to "localhost" for testing on the same PC.

Things I already tried:
- decreased the time between updates
- put client.host:service() before a client.server:send
- instead of service() I also tried a timeout of 10 and 100, meaning service(100)
- I explicitly passed "reliable" as argument to the send function, as well as "unreliable" and "unsequenced"

For those of you how just want to take a look at the (mostly) working code without downloading, here is the main file:
(edited, the suggestion from zorg was correct, code should be working now)

Code: Select all

local lg = love.graphics
local enet = require "enet"
local updateRate = 0.1

-- so far, both "*" and "0.0.0.0" seem to work (same PC or WLAN)
-- and ofc "localhost" when running on the same PC
local SERVER_IP = require "server_ip"
local SERVER_PORT = 16666
local MAX_PEER_COUNT = 4

----------------------------------------------------------------------------------------------------

function love.load()
	
end

----------------------------------------------------------------------------------------------------

function love.update( dt )
	if server then
		server:tick( dt )
	elseif client then
		client:tick( dt )
	end
end

----------------------------------------------------------------------------------------------------

function love.draw()
	
	if server then
		lg.print( "SERVER", 10, 5 )
		
		for i = 1, MAX_PEER_COUNT do
			local peer = server.host:get_peer( i )
			if peer then
				lg.print( string.format( "%d - %s [%d]", i, peer:state(), peer:last_round_trip_time() ), 10, 5 + 10 * i)
			end
		end
		
	elseif client then
		lg.print( "CLIENT", 10, 5 )
		lg.print( string.format( "%s [%d]", client.server:state(), client.server:last_round_trip_time() ), 10, 15 )
		
	end
	
end

----------------------------------------------------------------------------------------------------

local Server = {}

function Server:create()
	local server = {}
	
	server.time = 0
	server.updateTick = 0
	
	server.host = enet.host_create( string.format( "%s:%d", SERVER_IP, SERVER_PORT) , MAX_PEER_COUNT )
	print( "Create server at", server.host:get_socket_address() )
	
	function server:tick( dt )
		
		self.time = self.time + dt
		
		-- update rate
		self.updateTick = self.updateTick + dt
		if self.updateTick > updateRate then
			self.updateTick = 0
			
			self:listen()
		end
		
	end
	
	
	function server:listen()
		local event
		repeat
			event = self.host:service()
			if event then
				if event.type == "receive" then
					print( "received:", event.data )
					print( "from", event.peer )
					event.peer:send( "((answer))" )
					
				elseif event.type == "connect" then
					print( "Client connected", event.peer )
					event.peer:send( "hallo, client" )
					
					print( "peer:", event.peer:index() .. "/" .. self.host:peer_count() )
				elseif event.type == "disconnect" then
					print( "Client disconnected", event.peer )
					
				end
			end
		until not event
	end
	
	
	return server
end

----------------------------------------------------------------------------------------------------

local Client = {}

function Client:create()
	local client = {}
	
	client.time = 0
	client.updateTick = 0
	
	client.host = enet.host_create()
	client.server = client.host:connect( string.format( "%s:%d", SERVER_IP, SERVER_PORT) )
	print( "Create client at", client.host:get_socket_address() )
	
	function client:tick( dt )
		
		self.time = self.time + dt
		
		-- update rate
		self.updateTick = self.updateTick + dt
		if self.updateTick > updateRate then
			self.updateTick = 0
			
			self:listen()
		end
		
	end
	
	
	function client:listen()
		
		local event
		repeat
			event = self.host:service()
			if event then
				if event.type == "receive" then
					print( "received:", event.data )
					print( "from", event.peer )
				elseif event.type == "connect" then
					print( "Client connected", event.peer )
				elseif event.type == "disconnect" then
					print( "Client disconnected", event.peer )
				end
			end
		until not event
		
	end
	
	return client
end

----------------------------------------------------------------------------------------------------

function love.keypressed( key, scancode, isrepeat )
	
	if not server and not client then
		if key == "s" then
			server = Server:create()
		elseif key == "c" then
			client = Client:create()
		end
	end
	
	if key == "escape" then
		if client then
			client.server:disconnect()
			client.host:flush()
			client.host:destroy()
			client = nil
			
		elseif server then
			server.host:destroy()
			server = nil
			
		end
		
	elseif key == "m" then
		if client then
			-- client.host:service()
			for i = 1,5 do
				client.server:send( "hi " .. i )
			end
			
		elseif server then
			server.host:broadcast( "hello @ all" )
			
		end
		
	end
	
end

Re: lua-enet only sending some messages

Posted: Thu Aug 05, 2021 10:07 am
by togFox
I gave up on enet on my project and used socket - it was far more reliable.

Re: lua-enet only sending some messages

Posted: Thu Aug 05, 2021 10:21 am
by mxmlnbndsmnn
togFox wrote: Thu Aug 05, 2021 10:07 am I gave up on enet on my project and used socket - it was far more reliable.
For me it's pretty much the opposite... I was (and am) not keen on writing my own fully fletched protocol on top of UDP when there is already software that does just what I need. When using socket, that's exactly what I would have (and tried to) do. How is it more reliable to you? Did you write your own protocol for reliable and ordered transmission?

Re: lua-enet only sending some messages

Posted: Thu Aug 05, 2021 10:53 am
by togFox
I constructed a string or table, serialised it and then sent it via socket. If that's what you mean by 'protocol' it's not terribly hard. I think I struck a limit in the number of bytes I could send (not surprising) but I could send and then deserialise it (with bitser) on the receiving end.

Enet drove me nuts. There are so many examples that 'work' but never on my PC and I could never work out if that was me, my configuration or an old example that use to work and now doesn't.

Code: Select all

local function SendActionsToClients()
-- need to send the host actions to the client

	while #garrHostCommSend > 0 do
		for j = 1,#garrClientNodeList do
			local serialdata = bitser.dumps(garrHostCommSend[1])
			udphost:sendto(serialdata, garrClientNodeList[j].ip,garrClientNodeList[j].port)		
		end
		table.remove(garrHostCommSend,1)
	end
end

local function ListenOnHostPort()
    local data, ip, port = udphost:receivefrom()
    if data then
		local unpackeddata = bitser.loads(data)
        table.insert(garrHostCommRecv,unpackeddata)

        local node = {}
        node.ip = ip
        node.port = port
        table.insert(garrClientNodeList,node)
        gbolIsAHost = true
        gbolIsAClient = false
    end
    socket.sleep(0.01)    --! will this interfere with the client?
end
garrHostCommSend is my table (array) that holds outgoing messages. I take the first element/message, serialise that, then send it to all the clients.

On the listening end, it receives the serialised string, unpacks it, and the stores that in a table for later processing.

There is extra gunk there you can remove.

Re: lua-enet only sending some messages

Posted: Thu Aug 05, 2021 11:12 am
by grump
There is a perfectly reliable, error-correcting, in-order standard network protocol available that works really well. It's called TCP. You should first check your requirements and only consider other protocols if TCP is not suited for your application.
togFox wrote: Thu Aug 05, 2021 10:53 am I constructed a string or table, serialised it and then sent it via socket. If that's what you mean by 'protocol' it's not terribly hard. I think I struck a limit in the number of bytes I could send (not surprising) but I could send and then deserialise it (with bitser) on the receiving end.
The "protocol" part is the part that makes sure the data arrives at all and in correct order. What you're doing is yell out the window and hope someone hears it, and if not then you're SOL. This will break immediately in a real-world scenario.

Re: lua-enet only sending some messages

Posted: Thu Aug 05, 2021 12:01 pm
by togFox
Oh - in that case - I was just yelling out the window. :) I was updating client actions to the server so dropped packets now and then would make zero difference as the next correct packet would make the previously dropped packet redundant. Sure - if that got bad then you get lag.

If I was doing a commercial system then I would rethink this design choice.

Re: lua-enet only sending some messages

Posted: Thu Aug 05, 2021 12:39 pm
by mxmlnbndsmnn
togFox wrote: Thu Aug 05, 2021 10:53 am I constructed a string or table, serialised it and then sent it via socket. If that's what you mean by 'protocol' it's not terribly hard.
No, that's not what I mean. I also used binser, but that's not the problem here.
grump wrote: Thu Aug 05, 2021 11:12 am There is a perfectly reliable, error-correcting, in-order standard network protocol available that works really well. It's called TCP. You should first check your requirements and only consider other protocols if TCP is not suited for your application.
Well yeah, we are talking about a multiplayer game (nothing turn based) here, so TCP is out of the question ;)

Back to my OP. With protocol I mean something like what enet does on top of UDP. I will use UDP. Period. However, I do not want to have to do all the coding to ensure that messages my game needs to transmitt will arrive in order - or arrive at all in the first place! That's the reason why I switched from luasocket to enet. Hope this can erase the lack of clarity in the previous posts here.

Re: lua-enet only sending some messages

Posted: Thu Aug 05, 2021 12:40 pm
by pgimeno
UDP packets can easily get to the destination out of order if they are sent very close in time. Are you sure you're using reliable mode? Because this discarding sounds a lot like what unreliable mode does:
ENet provides sequencing for all packets by assigning to each sent packet a sequence number that is incremented as packets are sent. ENet guarantees that no packet with a higher sequence number will be delivered before a packet with a lower sequence number, thus ensuring packets are delivered exactly in the order they are sent.

For unreliable packets, ENet will simply discard the lower sequence number packet if a packet with a higher sequence number has already been delivered.

Re: lua-enet only sending some messages

Posted: Thu Aug 05, 2021 12:56 pm
by mxmlnbndsmnn
pgimeno wrote: Thu Aug 05, 2021 12:40 pm UDP packets can easily get to the destination out of order if they are sent very close in time. Are you sure you're using reliable mode? Because this discarding sounds a lot like what unreliable mode does:
True, it does sound very much like that. BUT. Three things. The doc says:
flag is one of "reliable", "unsequenced", or "unreliable". Reliable packets are guaranteed to arrive, and arrive in the order in which they are sent. Unsequenced packets are unreliable and have no guarantee on the order they arrive. Defaults to reliable.
(https://leafo.net/lua-enet/#peersenddata__channel_flag)
So by just calling send this should always be reliable.
2nd thing is: I already added to my initial post that passing the "reliable" mode flag explicitly is something I already tried... no changes.
And finally: this happens over and over again in the same way! When sending messages from i =1,5 I never had any other result than either 1,3 and 5 OR 2 and 4 arrive. To me this looks like some other error apart from the messages simply getting lost over the network - and I don't see how they should when I use the "reliable" mode. This is confusing me :o

Re: lua-enet only sending some messages

Posted: Thu Aug 05, 2021 2:22 pm
by zorg
I might be wrong on this, but carefully analyze either of your infinite loops:

Code: Select all

repeat
	event = self.host:service() -- get event
	if event then -- handle event
		if event.type == "receive" then
			print( "received:", event.data )
			print( "from", event.peer )
			event.peer:send( "((answer))" )

		elseif event.type == "connect" then
			print( "Client connected", event.peer )
			event.peer:send( "hallo, client" )
			print( "peer:", event.peer:index() .. "/" .. self.host:peer_count() )
		elseif event.type == "disconnect" then
			print( "Client disconnected", event.peer )
		end
	end
	event = self.host:service() -- get event...?
until not event
end
effectively, i think you're discarding every other event (since, you know, the loop repeats and you lose an event by getting one both at the start and the end...)

Get rid of that last "event = self.host:service()" line from both client and server and test it out.