Tutorial:Networking with UDP-TheClient (日本語)

UDP クライアント用の完全なソースです。

-- 最初に、 'socket' ライブラリ (LÖVE に組み込まれています) を require する必要があります。
-- socket は低級ネットワーキング機能を提供します。 
local socket = require "socket"

-- サーバーのアドレスおよびポート
local address, port = "localhost", 12345

local entity -- entity は制御用です
local updaterate = 0.1 -- 更新の要求前の、秒単位による待機時間の長さ

local world = {} -- 世界の状態は空です
local t

-- love.load は、コールバックのチュートリアルにより熟知されていることが望ましい
function love.load()

	-- 最初に、 UDP ソケットが必要であり、
	-- その後に全てのネットワーキングを行います。
	udp = socket.udp()
	
	-- 通常はデータを保有するか、一定量の時間が経過するまで
	-- 読み取りは阻止されます。
	-- それは目的に適合していませんので、 
	-- 'タイムアウト' へ 0 を設定して読み取り阻止を解除します。
	udp:settimeout(0)
	
	-- サーバーとは異なり、一台の機器のみと対話を行うため、 
	-- 'setpeername' を使用してサーバーのアドレスおよびポートへ
	-- 当ソケットを"接続"します。
	--
	-- [注釈: UDP において実際にはコネクションレスであり、これは純粋に
	-- socket ライブラリにより提供される便宜であって、それは実際には 
	--'回線上のビットを変更しない'ので、事実上はいつでも変更/削除ができます。]
	udp:setpeername(address, port)
	
	-- 乱数生成器の種を使うため、従って同値を
	-- 毎回取得することはありません。
	math.randomseed(os.time())
	
	-- このチュートリアルの目的のために entity は制御を行うためのものです。
	-- それは単なる数値ですが、有用です。
	-- 小さな努力に対する合理的な固有識別子を付与するために乱数を使用します。
	--
	-- [注釈: これを行うために識別子に乱数を用いるのは実際のところ非常に悪い方法ですが、
	-- "正しい"方法は本記事の範囲外です。*最も単純*な方法は 
	-- 自動カウントであり、そこから*非常*に多くの考えを取り入れます ]
	
	entity = tostring(math.random(99999))

	-- ここで、ちょっとした初めての実際のネットワーキングを行います:
	-- 送信を行いたい ('string.format' を使用して) データを内包している文字列を設定してから
	-- 'udp.send' を使用して送信を行います。以前に 'setpeername' を使用したので、
	-- それをどこへ送信すべきかどうかを再度明示する必要はありません。
	--
	-- 本当に…、それだけです。その他のものは、このコンテキストおよび実用的に使用するために記入します。
	local dg = string.format("%s %s %d %d", entity, 'at', 320, 240)
	udp:send(dg) -- 論点の魔法行。
	
	-- t は love.update での更新比率を支援するために使用する単なる変数です。
	t = 0 -- t へ 0 を(再)設定
end

-- love.update は、コールバックのチュートリアルにより熟知されていることが望ましい
function love.update(deltatime)

	t = t + deltatime -- デルタ時間により t を増加します
	
	-- 送信 (または要求) するパケットに注意を払わなければ、ネットワーク接続を完全に満杯にするのは*非常に容易*であるため、
	-- 従ってどのくらい頻繁に更新を送信 (および要求)するかどうかの
	-- 制限をすることにより対策を見込みます。
	-- 
	-- 記録に関しては、ほとんどの普通のゲームにおいて一秒当たり十回が良いと考えられ (多くの MMO も該当します)、
	-- 高速に歩測されたゲームであっても、 
	-- 実際には常時三十回以上の更新を必要としないようにしてください。
	if t > updaterate then
		-- 全ての小さな転送に対して更新を送信することはできますが、
		-- ここで単一パケットへ統合することで最終の更新は価値のあるものとなり、
		-- 使用する帯域幅は徹底的に減少します。
		local x, y = 0, 0
		if love.keyboard.isDown('up') then	y=y-(20*t) end
		if love.keyboard.isDown('down') then	y=y+(20*t) end
		if love.keyboard.isDown('left') then	x=x-(20*t) end
		if love.keyboard.isDown('right') then	x=x+(20*t) end


		-- 再び、 string.format を使用してパケットの*ペイロード*を準備してから、 
		-- udp:send による方法で送信します。
		-- これは上記にて言及された転送の更新です。
		local dg = string.format("%s %s %f %f", entity, 'move', x, y)
		udp:send(dg)   

		-- さらにもう一度! これはサーバーが世界の状態に対して
		--  更新を送信するための要求です。
		--
		-- [注釈: ほとんどの設計において世界の状態に対しての更新を要求しないため、
		-- それらを周期的に送信を受けて取得します。 その他のところで、これには様々な理由がありますが、
		-- 厳粛に注意を必要しなければならない*重大*な事項が一つあります:
		-- "破壊対策" です。世界の更新は最も大きな問題の一つで一般的な基本原理として
		-- 恐らく平均的なゲームサーバーが定期的に送り出すため、偽造された更新の要求による破壊は
		-- 単純で効果的です。従って、その様な更新の要求には対応を行わず、
		-- その代わりに適切だと感じる場合には、
		-- それらを支給することです]
		local dg = string.format("%s %s $", entity, 'update')
		udp:send(dg)

		t=t-updaterate -- 次の周回のために t を設定します
	end

   
	-- 可能な限り一つ以上のメッセージを待機するため、
	-- 従って実行終了までループします!
	repeat
		-- また、ここでは新しいものとして、もう一方では udp:send だけが予期されています!
		-- udp:receive は待機しているパケットを返します (または nil, およびエラーメッセージ)。
		-- data は文字列であり、最終端の udp:send におけるペイロードです。
		-- Lua にて他の任意の文字列を取り扱うのと同様の方法で取り扱うことができます (無論、
		-- Lua の文字列処理関数に精通していることは不可欠です)。
		data, msg = udp:receive()

		if data then -- 覚えていますね? Lua では全ての値を true として評価しますが、残りの nil および false は?
   
			-- ここでは match は仲間であり、 string.* の一部、および
			-- data (またそうであるに違いありません!) は文字列です。素晴らしい文字集合を明かすために
			-- その一部を説明します。
			-- (パターンの要約が必要ならば、セクション5.4.1 のリンクがあります)
			ent, cmd, parms = data:match("^(%S*) (%S*) (.*)")
			if cmd == 'at' then
				-- より多くのパターンであり、この時は集合であり、さらに長さ選別器です!
				local x, y = parms:match("^(%-?[%d.e]*) (%-?[%d.e]*)$")
				assert(x and y) -- 検証は良いことですが、アサーションは役立つものです。
		   
				-- 忘れないで欲しいことは、"数値"と一致した場合でもまだ結果は文字列のままであることです。
				-- Lua の tonumber() を使用するお陰で変換は容易になります。
				x, y = tonumber(x), tonumber(y)
				-- それは最終的にずっと隠匿されます
				world[ent] = {x=x, y=y}
			else
				-- この場合は頻繁にトリガーを起動すべきではありませんが、
				-- 予期しないメッセージおよびイベントを常に検査する(およびログも!)ことは良い考えです。
				-- コードに存在するバグまたはサーバに対して不正行為を試みようとする人々を発見するための支援ができます…。
				-- 決して忘れないで欲しいことは、クライアントは信用できないものであるということです。
				print("unrecognised command:", cmd)
			end
	   
		-- data において nil の場合は、問題に関する短い説明を msg へ
		-- 内包します (エラー ID に関しても兼用されます…)。
		-- 最も一般的なものは 'timeout'であり、 socket:settimeout() は 0 であるため、
		-- データの*待機*をしていない時は常に、 'timeout' になります。
		--
		-- しかし*異なる*エラー、およびその結果としての振る舞いを目視で確認すべきです。
		-- この場合は自ら保存を行おうとしないため、エラーが出ます。
		elseif msg ~= 'timeout' then
			error("Network error: "..tostring(msg))
		end

	until not data
end

-- love.draw は、コールバックのチュートリアルにより熟知されていることが望ましい
function love.draw()
	-- 非常に単純であり、 ''world'' テーブルでのループが完了すると、
	-- 全ての名前(キー)が表示され、それらの自身も統合的に格納されます。
	for k, v in pairs(world) do
		love.graphics.print(k, v.x, v.y)
	end
end
-- これで UDP クライアントの用例は終了です。

そのほかの言語