add: first version of client to client chat
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.chat;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -11,7 +12,7 @@ import java.util.regex.Pattern;
|
||||
|
||||
public class Message {
|
||||
private final MessageType type;
|
||||
private String message;
|
||||
private final String message;
|
||||
public String user;
|
||||
public String timestamp;
|
||||
public int game_id = 0;
|
||||
@@ -20,8 +21,6 @@ public class Message {
|
||||
GLOBAL, LOBBY, WHISPER
|
||||
};
|
||||
|
||||
public static final String SEPERATOR = " ";
|
||||
|
||||
/**
|
||||
* Constructor for creating Messages with all information given
|
||||
* @param type - Either global, local or whisper
|
||||
@@ -55,8 +54,9 @@ public class Message {
|
||||
this.user = user;
|
||||
this.target = target;
|
||||
this.message = message;
|
||||
LocalTime now = LocalTime.now();
|
||||
this.timestamp = now.toString();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
|
||||
this.timestamp = now.format(formatter);
|
||||
}
|
||||
|
||||
|
||||
@@ -64,33 +64,33 @@ public class Message {
|
||||
return message;
|
||||
}
|
||||
|
||||
public String getMessageType() {
|
||||
return type.toString();
|
||||
public MessageType getMessageType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* Method to test the system
|
||||
* @return - String representation of the Message instance, as for example "player1: Hello World"
|
||||
*/
|
||||
|
||||
public String toString() {
|
||||
return String.format("%s: %s", this.user, this.message);
|
||||
}
|
||||
|
||||
*/
|
||||
/**
|
||||
* Method to create the request representation of the message object, to be sent to the server
|
||||
* @return - request as specified in the network protocol, as String
|
||||
*/
|
||||
public String toRequest() {
|
||||
String request = String.format("SEND_MESSAGE TYPE=%s GAME=%d USER=%s TARGET=%s TIME=%s TEXT=%s",
|
||||
public String toArgsString() {
|
||||
return String.format("TYPE=%s GAME=%d USER=%s TARGET=%s TIME=%s TEXT=%s",
|
||||
this.type.toString(), this.game_id, this.user, this.target, this.timestamp, this.message);
|
||||
return request;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pattern, to analyze the response String with the given parameters
|
||||
*/
|
||||
public static Pattern msgRex = Pattern.compile("TYPE=(?<type>\\w+) GAME=(?<game>\\w+) USER=(?<user>\\w+) TARGET=(?<target>\\w+) TIME=(?<time>[0-9:]+) TEXT=(?<text>.*)$");
|
||||
public static Pattern msgRex = Pattern.compile(
|
||||
"TYPE=(?<type>\\w+) GAME=(?<game>\\w+) USER=(?<user>\\w+) TARGET=(?<target>\\w+) TIME=(?<time>[0-9:.]+) TEXT=(?<text>.*)$");
|
||||
|
||||
/**
|
||||
* Method to create a Message Object, from the information given by the String
|
||||
@@ -98,10 +98,9 @@ public class Message {
|
||||
* @return - New Message Object
|
||||
*/
|
||||
public static Message toMessage(String response) {
|
||||
var parts = response.split(SEPERATOR);
|
||||
Matcher m = msgRex.matcher(response);
|
||||
if (! m.matches()) {
|
||||
throw new RuntimeException("Can not parse message: " + response);
|
||||
throw new RuntimeException("Can not parse message: '" + response+"'");
|
||||
}
|
||||
String typeString=m.group("type");
|
||||
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.network;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -13,9 +9,13 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.RawPacket;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TcpTransport;
|
||||
|
||||
|
||||
/**
|
||||
* Responsible for the transferring of the data from the Client to the Server and the other way around
|
||||
@@ -23,16 +23,16 @@ import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message;
|
||||
|
||||
public class ClientService {
|
||||
|
||||
private Socket socket;
|
||||
private BufferedReader input;
|
||||
private BufferedWriter output;
|
||||
private final TcpTransport clienttcptransport;
|
||||
private final Socket socket;
|
||||
|
||||
private String ip;
|
||||
private int port;
|
||||
private final String ip;
|
||||
private final int port;
|
||||
|
||||
private ExecutorService executor;
|
||||
private final ExecutorService executor;
|
||||
|
||||
public static ArrayList<String> response;
|
||||
private final AtomicInteger idGenerator;
|
||||
|
||||
/**
|
||||
* Creates a new ClientSession with a Socket, a Reader and Writer of the Input- and the Outputstream and a pool of threads to send requests and receive responses.
|
||||
@@ -44,20 +44,15 @@ public class ClientService {
|
||||
|
||||
this.ip = ip;
|
||||
this.port = port;
|
||||
this.idGenerator = new AtomicInteger(0);
|
||||
|
||||
try {
|
||||
socket = new Socket(this.ip, this.port);
|
||||
System.out.println("Connected");
|
||||
input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
output = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
|
||||
}
|
||||
catch (UnknownHostException u) {
|
||||
System.out.println(u);
|
||||
return;
|
||||
clienttcptransport = new TcpTransport(socket);
|
||||
}
|
||||
catch (IOException i) {
|
||||
System.out.println(i);
|
||||
return;
|
||||
throw new RuntimeException(i);
|
||||
}
|
||||
|
||||
executor = Executors.newSingleThreadExecutor();
|
||||
@@ -67,30 +62,47 @@ public class ClientService {
|
||||
* Sends a Request to get all the messages, the other clients sent, and that the client is not currently aware of.
|
||||
*/
|
||||
|
||||
public List<String> getMessage() {
|
||||
return processMessage("GET_MESSAGE");
|
||||
public List<Message> getMessages() {
|
||||
String countStr = processMessage("GET_MESSAGE_COUNT");
|
||||
int count = Integer.parseInt(countStr);
|
||||
System.out.println("Got " + count + " messages");
|
||||
List<Message> messages = new ArrayList<>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
String message = processMessage("GET_NEXT_MESSAGE");
|
||||
if (message != null) {
|
||||
Message message1 = Message.toMessage(message);
|
||||
messages.add(message1);
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
public List<String> sendMessage(Message message) { return processMessage(message.toRequest()); }
|
||||
public String sendMessage(Message message) {
|
||||
String request = "SEND_MESSAGE " + message.toArgsString();
|
||||
return processMessage(request);
|
||||
}
|
||||
|
||||
private List<String> processMessage(String message) {
|
||||
List<String> response = new ArrayList<>();
|
||||
private String processMessage(String message) {
|
||||
AtomicReference<String> response = new AtomicReference<>();
|
||||
sendRequest(() -> {
|
||||
try {
|
||||
output.write(message+"\n");
|
||||
output.flush();
|
||||
String line;
|
||||
while ((line = input.readLine()) != null) {
|
||||
if (line.toLowerCase().startsWith(".")) {
|
||||
break;
|
||||
writeToTransport(message);
|
||||
String responseLine=null;
|
||||
do {
|
||||
responseLine = clienttcptransport.read().payload();
|
||||
System.out.println("Raw message '" + responseLine + "'");
|
||||
if ("+OK".equals(responseLine)) {
|
||||
return;
|
||||
} else if ("-FAIL".equals(responseLine)) {
|
||||
throw new RuntimeException(responseLine);
|
||||
}
|
||||
response.add(line);
|
||||
}
|
||||
response.set(responseLine);
|
||||
} while(true);
|
||||
} catch (Exception e) {
|
||||
throw getRuntimeException(e);
|
||||
}
|
||||
});
|
||||
return response;
|
||||
return response.get();
|
||||
}
|
||||
|
||||
private void sendRequest(Runnable request) {
|
||||
@@ -122,12 +134,17 @@ public class ClientService {
|
||||
public void closeSocket() {
|
||||
try {
|
||||
executor.shutdown();
|
||||
input.close();
|
||||
output.close();
|
||||
clienttcptransport.close();
|
||||
socket.close();
|
||||
} catch (IOException j) {
|
||||
System.out.println(j);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void writeToTransport(String s) throws IOException {
|
||||
int id = this.idGenerator.incrementAndGet();
|
||||
this.clienttcptransport.write(new RawPacket(id, s));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,6 +25,10 @@ public class ServerApp {
|
||||
Logger logger = LogManager.getLogger(ServerApp.class);
|
||||
logger.info("Starting server at port {}", port);
|
||||
|
||||
startServerCore(port);
|
||||
}
|
||||
|
||||
public static void startServerCore(int port) {
|
||||
EventBus eventBus = new EventBus();
|
||||
SessionManager sessionManager = new SessionManager();
|
||||
eventBus.subscribe(
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.events;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message;
|
||||
|
||||
public class ChatMessageEvent implements Event {
|
||||
private final Message message;
|
||||
|
||||
public ChatMessageEvent(Message message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public Message getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.handlers;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.events.ChatMessageEvent;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.events.EventBus;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
|
||||
public class ChatHandler {
|
||||
private final EventBus eventBus;
|
||||
private final ArrayDeque<Message> receivedMessages = new ArrayDeque<>();
|
||||
public ChatHandler(EventBus eventBus) {
|
||||
eventBus.subscribe(ChatMessageEvent.class, (event) -> {
|
||||
processMessageEvent(event.getMessage());
|
||||
});
|
||||
this.eventBus = eventBus;
|
||||
}
|
||||
|
||||
private synchronized void processMessageEvent(Message message) {
|
||||
// TODO: filter
|
||||
this.receivedMessages.offer(message);
|
||||
}
|
||||
|
||||
public synchronized int getMessageCount() {
|
||||
return this.receivedMessages.size();
|
||||
}
|
||||
public synchronized Message getNextMessage() {
|
||||
return this.receivedMessages.poll();
|
||||
}
|
||||
|
||||
public void sendMessage(String messageString) {
|
||||
Message msg = Message.toMessage(messageString);
|
||||
this.eventBus.publish(new ChatMessageEvent(msg));
|
||||
}
|
||||
}
|
||||
+56
-14
@@ -1,24 +1,30 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.network.sessions;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.events.DisconnectEvent;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.events.EventBus;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.parser.PrimitiveRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.parser.ProtocolParser;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.RawPacket;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TransportLayer;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.handlers.ChatHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.RawPacket;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/** Represents a client session in the network server. */
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.events.DisconnectEvent;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.events.EventBus;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TransportLayer;
|
||||
|
||||
/**
|
||||
* Represents a client session in the network server.
|
||||
*/
|
||||
public class Session implements Runnable {
|
||||
private final ChatHandler chatHandler;
|
||||
private SessionId id;
|
||||
private Thread thread;
|
||||
private TransportLayer transport;
|
||||
private Logger logger;
|
||||
private Boolean running;
|
||||
private EventBus eventBus;
|
||||
private AtomicInteger idGenerator;
|
||||
|
||||
/**
|
||||
* Creates a new Session with the given transport and event bus.
|
||||
@@ -36,6 +42,8 @@ public class Session implements Runnable {
|
||||
|
||||
this.logger = LogManager.getLogger(Session.class.toString() + id.value());
|
||||
this.logger.info("Created new session");
|
||||
this.idGenerator = new AtomicInteger();
|
||||
this.chatHandler = new ChatHandler(eventBus);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,7 +55,9 @@ public class Session implements Runnable {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/** Starts the session thread. */
|
||||
/**
|
||||
* Starts the session thread.
|
||||
*/
|
||||
public void start() {
|
||||
thread.start();
|
||||
}
|
||||
@@ -62,16 +72,43 @@ public class Session implements Runnable {
|
||||
this.running = false;
|
||||
}
|
||||
|
||||
/** Runs the session loop, reading from the transport. */
|
||||
/**
|
||||
* Runs the session loop, reading from the transport.
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
while (running) {
|
||||
try {
|
||||
RawPacket rawPacket = transport.read();
|
||||
logger.debug("Recieved: {}", rawPacket);
|
||||
|
||||
PrimitiveRequest primitiveRequest = ProtocolParser.parse(rawPacket);
|
||||
logger.debug("Parsed request to {}", primitiveRequest);
|
||||
String clientCommand = transport.read().payload();
|
||||
String[] commandAndArgs = clientCommand.split("\\s+", 2);
|
||||
String commandWord = commandAndArgs[0];
|
||||
System.out.println("Session "+id.value()+" Received: " + commandAndArgs[0]);
|
||||
switch (commandWord) {
|
||||
case "SEND_MESSAGE":
|
||||
this.chatHandler.sendMessage(commandAndArgs[1]);
|
||||
writeToTransport("+OK");
|
||||
writeToTransport("+OK");
|
||||
break;
|
||||
case "GET_MESSAGE_COUNT":
|
||||
int count = chatHandler.getMessageCount();
|
||||
writeToTransport(""+count);
|
||||
writeToTransport("+OK");
|
||||
break;
|
||||
case "GET_NEXT_MESSAGE":
|
||||
if (chatHandler.getMessageCount() == 0) {
|
||||
logger.warn("FAIL No more messages!");
|
||||
writeToTransport("-FAIL");
|
||||
} else {
|
||||
String msgString = chatHandler.getNextMessage().toArgsString();
|
||||
logger.debug("Session "+id.value()+" Write next message "+msgString);
|
||||
writeToTransport(msgString);
|
||||
writeToTransport("+OK");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
this.logger.warn("Unknown command: " + commandWord);
|
||||
writeToTransport("-FAIL");
|
||||
}
|
||||
} catch (EOFException e) {
|
||||
logger.info("Client disconnected");
|
||||
eventBus.publish(new DisconnectEvent(id));
|
||||
@@ -82,4 +119,9 @@ public class Session implements Runnable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeToTransport(String s) throws IOException {
|
||||
int id = this.idGenerator.incrementAndGet();
|
||||
this.transport.write(new RawPacket(id, s));
|
||||
}
|
||||
}
|
||||
|
||||
+3
-4
@@ -8,8 +8,8 @@ import java.nio.charset.StandardCharsets;
|
||||
|
||||
/** Implements TCP-based transport layer for network communication. */
|
||||
public class TcpTransport implements TransportLayer {
|
||||
private Socket socket;
|
||||
private DataInputStream in;
|
||||
private final Socket socket;
|
||||
private final DataInputStream in;
|
||||
private DataOutputStream out;
|
||||
|
||||
/**
|
||||
@@ -49,9 +49,8 @@ public class TcpTransport implements TransportLayer {
|
||||
public void write(RawPacket data) throws IOException {
|
||||
int requestId = data.requestId();
|
||||
byte[] rawPayload = data.payload().getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
out.writeInt(requestId);
|
||||
out.writeInt(rawPayload.length);
|
||||
out.writeInt(requestId);
|
||||
out.write(rawPayload);
|
||||
out.flush();
|
||||
}
|
||||
|
||||
+33
-9
@@ -2,14 +2,15 @@ package ch.unibas.dmi.dbis.cs108.casono.client.network;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.Server;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.ServerApp;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class ClientServiceTest {
|
||||
|
||||
@@ -18,19 +19,42 @@ public class ClientServiceTest {
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
server = new Server(5000);
|
||||
serverThread = new Thread(()-> server.simpleListenLoop());
|
||||
serverThread.start();
|
||||
//server = new Server(5000);
|
||||
//serverThread = new Thread(()-> server.simpleListenLoop());
|
||||
//serverThread.start();
|
||||
ServerApp.startServerCore(5000);
|
||||
}
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
serverThread.interrupt();
|
||||
|
||||
//serverThread.interrupt();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientService() throws ExecutionException, InterruptedException {
|
||||
ClientService client = new ClientService("localhost", 5000);
|
||||
List<String> response = client.sendMessage(new Message(Message.MessageType.GLOBAL, 0, "Mathis", null, "blahh"));
|
||||
client.closeSocket();
|
||||
ClientService client1 = new ClientService("localhost", 5000);
|
||||
ClientService client2 = new ClientService("localhost", 5000);
|
||||
Thread.sleep(2000);
|
||||
String response = client1.sendMessage(new Message(Message.MessageType.GLOBAL, 0, "Mathis", null, "blahh"));
|
||||
Thread.sleep(5000);
|
||||
List<Message> received = client2.getMessages();
|
||||
|
||||
assertEquals(1, received.size());
|
||||
Message s = received.get(0);
|
||||
assertEquals(Message.MessageType.GLOBAL, s.getMessageType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessage() {
|
||||
Message msg = new Message(Message.MessageType.GLOBAL, 0, "Mathis", null, "blah");
|
||||
Message msg2 = new Message(Message.MessageType.WHISPER, 1, "Mathis", "Julian", "blah");
|
||||
String serial = msg.toArgsString();
|
||||
Message roundTripped = Message.toMessage(serial);
|
||||
String serial2 = msg2.toArgsString();
|
||||
Message roundTripped2 = Message.toMessage(serial2);
|
||||
|
||||
assert(roundTripped.game_id == msg.game_id);
|
||||
assertEquals(0, roundTripped.game_id);
|
||||
assert(roundTripped2.game_id == msg2.game_id);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user