mirror of
https://github.com/michaelrausch/Party-Parrots-At-Sea.git
synced 2026-05-09 14:28:43 +00:00
22fdf1e4ac
#story[1274]
349 lines
12 KiB
Java
349 lines
12 KiB
Java
package seng302.gameServer;
|
|
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.InputStreamReader;
|
|
import java.io.OutputStream;
|
|
import java.net.Socket;
|
|
import java.net.SocketException;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
import java.util.concurrent.ThreadLocalRandom;
|
|
import java.util.stream.Collectors;
|
|
import java.util.zip.CRC32;
|
|
import java.util.zip.Checksum;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
import seng302.gameServer.messages.BoatAction;
|
|
import seng302.gameServer.messages.ChatterMessage;
|
|
import seng302.gameServer.messages.ClientType;
|
|
import seng302.gameServer.messages.CustomizeRequestType;
|
|
import seng302.gameServer.messages.Message;
|
|
import seng302.gameServer.messages.RegistrationResponseMessage;
|
|
import seng302.gameServer.messages.RegistrationResponseStatus;
|
|
import seng302.gameServer.messages.XMLMessage;
|
|
import seng302.gameServer.messages.XMLMessageSubType;
|
|
import seng302.model.Player;
|
|
import seng302.model.ServerYacht;
|
|
import seng302.model.stream.packets.PacketType;
|
|
import seng302.model.stream.packets.StreamPacket;
|
|
import seng302.model.stream.xml.generator.RaceXMLTemplate;
|
|
import seng302.utilities.XMLGenerator;
|
|
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
|
|
|
/**
|
|
* A class describing a single connection to a Client for the purposes of sending and receiving on
|
|
* its own thread. All server threads created and owned by the server thread handler which can
|
|
* trigger client updates on its threads Created by wmu16 on 13/07/17.
|
|
*/
|
|
public class ServerToClientThread implements Runnable {
|
|
|
|
/**
|
|
* Called to notify listeners when this thread receives a connection correctly.
|
|
*/
|
|
@FunctionalInterface
|
|
interface ConnectionListener {
|
|
void notifyConnection ();
|
|
}
|
|
|
|
// TODO: 17/08/17 this is only temporary disconnects should be handled consistently
|
|
@FunctionalInterface
|
|
interface DisconnectListener {
|
|
void notifyDisconnect (Player player);
|
|
}
|
|
|
|
private Logger logger = LoggerFactory.getLogger(ServerToClientThread.class);
|
|
|
|
private Thread thread;
|
|
|
|
private InputStream is;
|
|
private OutputStream os;
|
|
private Socket socket;
|
|
|
|
private ByteArrayOutputStream crcBuffer;
|
|
|
|
private Integer seqNo;
|
|
private Integer sourceId;
|
|
|
|
private ClientType clientType;
|
|
private Boolean isRegistered = false;
|
|
private Boolean isHost = false;
|
|
|
|
private XMLGenerator xmlGenerator;
|
|
|
|
private List<ConnectionListener> connectionListeners = new ArrayList<>();
|
|
private DisconnectListener disconnectListener;
|
|
|
|
private ServerYacht yacht;
|
|
private Player player;
|
|
|
|
public ServerToClientThread(Socket socket) {
|
|
this.socket = socket;
|
|
seqNo = 0;
|
|
|
|
try{
|
|
is = socket.getInputStream();
|
|
os = socket.getOutputStream();
|
|
} catch (IOException e) {
|
|
return;
|
|
}
|
|
|
|
thread = new Thread(this, "ServerToClient");
|
|
thread.start();
|
|
}
|
|
|
|
public Integer getSourceId() {
|
|
return sourceId;
|
|
}
|
|
|
|
private void setUpPlayer(){
|
|
BufferedReader fn;
|
|
String fName = "";
|
|
BufferedReader ln;
|
|
String lName = "";
|
|
|
|
fn = new BufferedReader(
|
|
new InputStreamReader(
|
|
ServerToClientThread.class.getResourceAsStream(
|
|
"/server_config/CSV_Database_of_First_Names.csv"
|
|
)
|
|
)
|
|
);
|
|
List<String> all = fn.lines().collect(Collectors.toList());
|
|
fName = all.get(ThreadLocalRandom.current().nextInt(0, all.size()));
|
|
ln = new BufferedReader(
|
|
new InputStreamReader(
|
|
ServerToClientThread.class.getResourceAsStream(
|
|
"/server_config/CSV_Database_of_Last_Names.csv"
|
|
)
|
|
)
|
|
);
|
|
all = ln.lines().collect(Collectors.toList());
|
|
lName = all.get(ThreadLocalRandom.current().nextInt(0, all.size()));
|
|
|
|
ServerYacht yacht = new ServerYacht(
|
|
BoatMeshType.DINGHY, sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ"
|
|
);
|
|
|
|
player = new Player(socket, yacht);
|
|
GameState.addYacht(sourceId, yacht);
|
|
GameState.addPlayer(player);
|
|
}
|
|
|
|
private void completeRegistration(ClientType clientType) throws IOException {
|
|
// Fail if not a player
|
|
if (!clientType.equals(ClientType.PLAYER)){
|
|
RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(0, RegistrationResponseStatus.FAILURE_GENERAL);
|
|
os.write(responseMessage.getBuffer());
|
|
return;
|
|
}
|
|
|
|
if (GameState.getPlayers().size() >= GameState.getCapacity()){
|
|
RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(0, RegistrationResponseStatus.FAILURE_FULL);
|
|
os.write(responseMessage.getBuffer());
|
|
return;
|
|
}
|
|
|
|
Integer sourceId = GameState.getUniquePlayerID();
|
|
RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(sourceId, RegistrationResponseStatus.SUCCESS_PLAYING);
|
|
|
|
this.clientType = clientType;
|
|
this.sourceId = sourceId;
|
|
isRegistered = true;
|
|
os.write(responseMessage.getBuffer());
|
|
|
|
setUpPlayer();
|
|
|
|
for (ConnectionListener listener : connectionListeners) {
|
|
listener.notifyConnection();
|
|
}
|
|
}
|
|
|
|
public void run() {
|
|
int sync1;
|
|
int sync2;
|
|
// TODO: 14/07/17 wmu16 - Work out how to fix this while loop
|
|
|
|
while (socket.isConnected() && !socket.isClosed()) {
|
|
try {
|
|
crcBuffer = new ByteArrayOutputStream();
|
|
sync1 = readByte();
|
|
sync2 = readByte();
|
|
//checking if it is the start of the packet
|
|
if (sync1 == 0x47 && sync2 == 0x83) {
|
|
int type = readByte();
|
|
//No. of milliseconds since Jan 1st 1970
|
|
long timeStamp = Message.bytesToLong(getBytes(6));
|
|
skipBytes(4);
|
|
long payloadLength = Message.bytesToLong(getBytes(2));
|
|
byte[] payload = getBytes((int) payloadLength);
|
|
Checksum checksum = new CRC32();
|
|
checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size());
|
|
long computedCrc = checksum.getValue();
|
|
long packetCrc = Message.bytesToLong(getBytes(4));
|
|
if (computedCrc == packetCrc) {
|
|
switch (PacketType.assignPacketType(type, payload)) {
|
|
case BOAT_ACTION:
|
|
BoatAction actionType = ServerPacketParser
|
|
.extractBoatAction(
|
|
new StreamPacket(type, payloadLength, timeStamp, payload));
|
|
GameState.updateBoat(sourceId, actionType);
|
|
break;
|
|
|
|
case RACE_REGISTRATION_REQUEST:
|
|
ClientType requestedType = ServerPacketParser.extractClientType(
|
|
new StreamPacket(type, payloadLength, timeStamp, payload));
|
|
|
|
completeRegistration(requestedType);
|
|
break;
|
|
case CHATTER_TEXT:
|
|
ChatterMessage chatterMessage = ServerPacketParser
|
|
.extractChatterText(
|
|
new StreamPacket(type, payloadLength, timeStamp, payload));
|
|
GameState.processChatter(chatterMessage, isHost);
|
|
break;
|
|
case RACE_CUSTOMIZATION_REQUEST:
|
|
Long sourceID = Message
|
|
.bytesToLong(Arrays.copyOfRange(payload, 0, 3));
|
|
CustomizeRequestType requestType = ServerPacketParser
|
|
.extractCustomizationType(
|
|
new StreamPacket(type, payloadLength, timeStamp, payload));
|
|
GameState.customizePlayer(sourceID, requestType,
|
|
Arrays.copyOfRange(payload, 6, payload.length));
|
|
GameState.setCustomizationFlag();
|
|
// TODO: 17/08/2017 ajm412: Send a response packet here, not really necessary until we do shapes.
|
|
break;
|
|
}
|
|
} else {
|
|
logger.warn("Packet has been dropped", 1);
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
closeSocket();
|
|
GameState.setPlayerHasLeftFlag(true);
|
|
return;
|
|
}
|
|
}
|
|
GameState.setPlayerHasLeftFlag(true);
|
|
logger.warn("Closed serverToClientThread" + thread, 1);
|
|
}
|
|
|
|
public void sendSetupMessages() {
|
|
xmlGenerator = new XMLGenerator();
|
|
RaceXMLTemplate race = new RaceXMLTemplate(new ArrayList<>(GameState.getYachts().values()), new ArrayList<>());
|
|
|
|
xmlGenerator.setRaceTemplate(race);
|
|
|
|
XMLMessage xmlMessage;
|
|
xmlMessage = new XMLMessage(xmlGenerator.getRegattaAsXml(), XMLMessageSubType.REGATTA,
|
|
xmlGenerator.getRegattaAsXml().length());
|
|
sendMessage(xmlMessage);
|
|
|
|
xmlMessage = new XMLMessage(xmlGenerator.getBoatsAsXml(), XMLMessageSubType.BOAT,
|
|
xmlGenerator.getBoatsAsXml().length());
|
|
sendMessage(xmlMessage);
|
|
|
|
xmlMessage = new XMLMessage(xmlGenerator.getRaceAsXml(), XMLMessageSubType.RACE,
|
|
xmlGenerator.getRaceAsXml().length());
|
|
sendMessage(xmlMessage);
|
|
}
|
|
|
|
private void closeSocket() {
|
|
try {
|
|
socket.close();
|
|
} catch (IOException e) {
|
|
System.out.println("IO error in server thread upon trying to close socket");
|
|
}
|
|
}
|
|
|
|
public Boolean isSocketOpen() {
|
|
return !socket.isClosed();
|
|
}
|
|
|
|
private int readByte() throws Exception {
|
|
int currentByte = -1;
|
|
try {
|
|
currentByte = is.read();
|
|
crcBuffer.write(currentByte);
|
|
} catch (SocketException se) {
|
|
disconnectListener.notifyDisconnect(this.player);
|
|
} catch (IOException e) {
|
|
disconnectListener.notifyDisconnect(this.player);
|
|
logger.warn("Socket read failed", 1);
|
|
}
|
|
if (currentByte == -1) {
|
|
throw new Exception();
|
|
}
|
|
return currentByte;
|
|
}
|
|
|
|
private byte[] getBytes(int n) throws Exception {
|
|
byte[] bytes = new byte[n];
|
|
for (int i = 0; i < n; i++) {
|
|
bytes[i] = (byte) readByte();
|
|
}
|
|
return bytes;
|
|
}
|
|
|
|
private void skipBytes(long n) throws Exception {
|
|
for (int i = 0; i < n; i++) {
|
|
readByte();
|
|
}
|
|
}
|
|
|
|
public void sendMessage(Message message) {
|
|
try {
|
|
os.write(message.getBuffer());
|
|
} catch (SocketException e) {
|
|
logger.warn("Player " + sourceId + " side socket disconnected", 1);
|
|
} catch (IOException e) {
|
|
logger.warn("Message send failed", 1);
|
|
}
|
|
}
|
|
|
|
private int getSeqNo() {
|
|
seqNo++;
|
|
return seqNo;
|
|
}
|
|
|
|
public Thread getThread() {
|
|
return thread;
|
|
}
|
|
|
|
public Socket getSocket() {
|
|
return socket;
|
|
}
|
|
|
|
public ServerYacht getYacht() {
|
|
return yacht;
|
|
}
|
|
|
|
public void addConnectionListener(ConnectionListener listener) {
|
|
connectionListeners.add(listener);
|
|
}
|
|
|
|
public void removeConnectionListener(ConnectionListener listener) {
|
|
connectionListeners.remove(listener);
|
|
}
|
|
|
|
public void terminate () {
|
|
try {
|
|
socket.close();
|
|
} catch (IOException ioe) {
|
|
logger.warn("IOException attempting to terminate serverToClientThread " + this.thread);
|
|
}
|
|
}
|
|
|
|
public void addDisconnectListener(DisconnectListener disconnectListener) {
|
|
this.disconnectListener = disconnectListener;
|
|
}
|
|
|
|
public void setAsHost() {
|
|
isHost = true;
|
|
}
|
|
}
|