[英]Lag Over Sockets java
我正在使用套接字在Java中制作多人蛇游戲。 所有傳輸都是通過服務器完成的,所有服務器都已連接。 相同的代碼還沒有完全完成,但是它完成了基本工作:如果特定客戶吃了它的食物,則四處移動蛇並增加得分。
我從服務器端生成食物坐標的隨機數,並將其中繼給所有客戶端。 如果客戶按下一個鍵,則會計算請求的移動,並將移動的方向發送到服務器,然后服務器將移動中繼給所有客戶端(包括發送該消息的客戶),並且只有在收到移動信息后,客戶端才會進行更改到動了的蛇。 因此,每一個動作都通過網絡進行跟蹤,並且客戶端自己直到收到“玩家1”要求移動的消息后才做出移動決定。
我面臨的問題是,即使有兩個玩家,在蛇周圍移動了一點之后,坐標似乎也有所不同。
我可以對我的代碼采取什么可能的補救措施,以消除蛇的位置之間的明顯滯后?
這是客戶端代碼:
package mycode;
import java.awt.Point;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.Map;
import javax.swing.JOptionPane;
public class ConnectionManager implements Runnable {
Socket socket;
boolean start = false;
DataInputStream in;
DataOutputStream out;
Map<String, Snake> map;
ConnectionManager(String name, String IP, Map<String, Snake> m) {
this.map = m;
try {
socket = new Socket(IP, 9977);
in = new DataInputStream(new BufferedInputStream(
socket.getInputStream()));
out = new DataOutputStream(new BufferedOutputStream(
socket.getOutputStream()));
out.writeUTF(name);
out.flush();
} catch (Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(null, "Could Not Find Server",
"ERROR", JOptionPane.ERROR_MESSAGE);
System.exit(0);
}
}
void populateMap() {
try {
String name = in.readUTF();
System.out.println("Name received: " + name);
if (name.equals("start_game_9977")) {
start = true;
System.out.println("Game Started");
return;
} else if (name.equals("food_coord")) {
Game.foodx = in.readInt();
Game.foody = in.readInt();
return;
}
map.put(name, new Snake(5));
} catch (Exception e) {
e.printStackTrace();
}
}
boolean start() {
return start;
}
void increaseSnakeLength(String thisname){
Snake temp = map.get(thisname);
Point temp1=new Point(0,0);
temp.length++;
switch (temp.move) {
case DOWN:
temp1= new Point(temp.p[temp.length - 2].x,
temp.p[temp.length - 2].y+6);
break;
case LEFT:
temp1= new Point(temp.p[temp.length - 2].x-6,
temp.p[temp.length - 2].y);
break;
case RIGHT:
temp1= new Point(temp.p[temp.length - 2].x+6,
temp.p[temp.length - 2].y);
break;
case UP:
temp1= new Point(temp.p[temp.length - 2].x,
temp.p[temp.length - 2].y-6);
break;
default:
break;
}
if(temp1.y>Game.max)
temp1.y=Game.min;
if(temp1.x>Game.max)
temp1.x=Game.min;
if(temp1.y<Game.min)
temp1.y=Game.max;
if(temp1.x<Game.min)
temp1.x=Game.max;
temp.p[temp.length-1]=temp1;
}
void readMotion() {
try {
while (true) {
if (Game.changedirection) {
String mov = "";
mov = Game.move.name();
// System.out.println(Game.move);
out.writeUTF(mov);
out.flush();
Game.changedirection = false;
}
if (Game.foodeaten) {
out.writeUTF("food_eaten");
out.flush();
Game.foodeaten = false;
}
Thread.sleep(50);
}
} catch (Exception e) {
e.printStackTrace();
}
}
void otherRunMethod() {
try {
while (true) {
String mname = in.readUTF();
String mov = in.readUTF();
if (mov.equals("Resigned")) {
map.remove(mname);
} else if (mov.length() >= 10) {
if (mov.substring(0, 10).equals("food_eaten")) {
String[] s = mov.split(",");
Game.foodx = Integer.parseInt(s[1]);
Game.foody = Integer.parseInt(s[2]);
int score = ++map.get(mname).score;
increaseSnakeLength(mname);
System.out.println(mname + ":" + score+" Length:"+map.get(mname).length);
}
} else {
Game.move = Direction.valueOf(mov);
map.get(mname).move = Game.move;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
if (!start) {
populateMap();
} else if (start) {
new Thread(new Runnable() {
public void run() {
otherRunMethod();
}
}).start();
readMotion();
break;
}
try {
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
代碼很長,所以我只是在服務器端管理連接。
package mycode;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.Socket;
import java.util.Map;
public class Playerhandler implements Runnable {
Socket player;
String thisname;
Map<String, Socket> map;
DataInputStream in = null;
DataOutputStream out = null;
ObjectInputStream ob;
Snake snake;
Playerhandler(Socket player, Map<String, Socket> m) {
this.player = player;
this.map = m;
try {
in = new DataInputStream(new BufferedInputStream(
player.getInputStream()));
thisname = in.readUTF();
map.put(thisname, this.player);
populatePlayers();
System.out.println("Connected Client " + thisname);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
void populatePlayers() {
try {
out = new DataOutputStream(new BufferedOutputStream(
player.getOutputStream()));
for (String name : map.keySet()) {
out.writeUTF(name);
out.flush();
}
for (String name : map.keySet()) {
out = new DataOutputStream(new BufferedOutputStream(map.get(
name).getOutputStream()));
out.writeUTF(thisname);
out.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
}
void relay(String move) {
try {
if (move.equals("food_eaten")) {
move = move + ","
+ (Snakeserver.randomGenerator.nextInt(100) * 6) + ","
+ (Snakeserver.randomGenerator.nextInt(100) * 6);
}
for (String name : map.keySet()) {
out = new DataOutputStream(new BufferedOutputStream(map.get(
name).getOutputStream()));
out.writeUTF(thisname);
out.flush();
out.writeUTF(move);
// System.out.println(Direction.valueOf(move));
out.flush();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void run() {
while (true) {
try {
relay(in.readUTF());
} catch (IOException e) {
// TODO Auto-generated catch block
System.out.println("Player " + thisname + " Resigned");
map.remove(thisname);
relay("Resigned");
return;
}
}
}
}
答案是總結對話框以找到解決方案,並指出其他一些需要研究或嘗試的領域。
軟件的主要行為問題是,擁有多個客戶端會導致多個客戶端在幾次移動后顯示出不同的蛇形位置。
經過大量問題和評論的答復后,問題的發布者修改了其軟件,以便服務器將所有蛇的對象發送給所有客戶端,從而使所有客戶端同步,從而使所有客戶端現在都處於使用相同的蛇對象。 以前,每個客戶端都維護自己的蛇對象數據,並且僅接收蛇數據中的更改或增量。 進行此更改后,所有客戶端現在都通過服務器傳輸的蛇對象進行了同步,但是仍然存在客戶端顯示稍有不同的位置的問題,該位置在一兩分鍾后便會得到糾正,因為每個客戶端都會收到所有蛇,客戶再次變得同步。
下一步是研究另一種方法,以使客戶端將使用UDP / IP作為網絡傳輸協議(而不是當前使用的TCP / IP)更加緊密地保持同步。 使用UDP / IP的預期結果是減少TCP網絡傳輸協議引入的各種延遲,以便提供TCP提供的面向連接的順序字節流。 但是,使用UDP網絡傳輸協議要求TCP所使用的某些傳遞機制,以提供可靠的字節序列,必須由UDP用戶承擔。
UDP的一些問題是:(1)數據包可能未按發送的相同順序接收,(2)數據包可能丟失或丟失,使得某些發送的數據包可能無法接收,以及(3)數據必須將使用UDP發送的數據顯式放入數據包中進行傳輸,以便發送方和接收方可以看到數據包,而不是字節流。
該蛇游戲的基本架構如下所示。
客戶端會將蛇更新發送到服務器。 這種交互將需要服務器發送回客戶端的確認。 如果客戶端未收到此類確認,則客戶端將在一段時間后重新發送蛇更新。
然后,服務器將更新其數據以反映更改,並使用其客戶端列表將相同的數據包發送給所有客戶端。 每個收到數據包的客戶端都會發送一個確認。 通過發送確認,每個客戶端會通知服務器它們仍在游戲中。 如果服務器不再接收客戶端確認,它將知道客戶端可能已經退出游戲或存在某種網絡問題。
每個分組將具有在發送分組之后遞增的序列號。 該序列號提供了唯一的標識符,以便客戶端和服務器可以檢測是否丟失了數據包,或者接收到的數據包是否與已接收到的數據包重復。
使用UDP時,最好使數據包盡可能小。 大於在基礎IP網絡協議中可以發送的UDP數據包將被拆分為多個IP數據包,一次發送多個IP數據包,然后在接收網絡節點處重新組合為UDP數據包。
這是使用Java編程語言的UDP網絡協議的一些資源。
Stackoverflow:使用java在UDP上發送和接收序列化對象 。
我以前從未實現過網絡多人游戲,但是我認為這里使用最廣泛的“解決方案”是作弊。
我認為它被稱為“死亡推算”,盡管反正蛇的工作方式與此完全相同。
http://www.gamasutra.com/view/feature/3230/dead_reckoning_latency_hiding_for_.php
基本上,您可以使游戲循環與網絡更新脫鈎。 讓每個客戶保持自己的狀態,並簡單地預測對手在每一幀的位置。 然后,當來自服務器的更新到達時,您可以將對手調整到他們的真實位置。 為了掩蓋這種差異,我認為渲染游戲狀態(通常是幾毫秒前)而不是當前狀態是很常見的。 這樣,網絡更新就有了更實際的趕上游戲循環的機會,因此顯得不那么混亂。
正如我所說,YMMV從未真正實現過。 這是游戲開發中較難的問題之一。
我傾向於添加一個顯式調用setTcpNoDelay(true)。 這樣可以確保關閉http://en.wikipedia.org/wiki/Nagle%27s_algorithm ,從而禁用通常以少量增加的延遲來提高效率的優化。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.