This school project is a distributed peer-to-peer chat system written in Java using Maven build tools. It is intented to allow employees to communicate on the local network without a centralized server. More information and detailed specifications can be found under doc/uml.
java -jar chat-system-1.0-SNAPSHOT.jarThis will run the chat system in GUI mode, on ports 8000-8002, and will attempt to find an IP address with internet access to run on. This should be the desired mode of operation for most users.
However, you can supply the following arguments to the jar to change the behavior:
- -c : Run in CLI mode
- -a [ip] : Run on a specific IP address
- -p [port] : Run on ports [port]-[port+2]
- -f [file] : Use a specific database file. Otherwise it default to the ip address.
Example:
java -jar chat-system-1.0-SNAPSHOT.jar -a 192.168.0.1 -cThis is done automatically in .github/workflows/release.yaml, but can be done manually by using the shade profile defined at the bottom of pom.xml.
mvn package -PshadeTo build the project, you need to have Maven installed.
Some scripts are supplied for convenience under ./bin.
Note that the start_gui and start_cli scripts work differently on different systems due to differences in socket implementations.
./bin/update_cp.sh # Installs dependencies and saves the classpath to classpath.txt
./bin/reset.sh # Deletes the databases
./bin/build.sh # Build and packages into a jar, skipping all tests
mvn test # Run all tests
# Windows
.\bin\start_gui.ps1 2 # Starts 2 instances of the GUI application on 127.0.0.1 and 127.0.0.2
.\bin\start_cli.ps1 1 # Start one instance of the CLI application on 127.0.0.1
.\bin\start_cli.ps1 2 # Start one instance of the CLI application on 127.0.0.2
# Linux
./bin/start_gui.sh # Starts one instance of the application with default settings.
./bin/start_cli.sh # Starts one instance of the application in CLI mode with default settings.To start the application without the scripts (on Linux):
classpath=$(cat classpath.txt)
java -cp "target/chatsystem-1.0-SNAPSHOT.jar:$classpath" com.mgreen.Main <options>The project is divided into the following packages:
- com.mgreen
- Contains the main class which parses command line arguments and starts the GUI or CLI application
- .models
- Contains the data classes that are stored in the database
- .database
- Contains DAOs (direct access objects) that can be subclassed for easy access to the database, along with other utilities
- .communication
- Contains different message types and logic to serialize and deserialize them
- .user
- .storage
- Contains logic to store user data in database and in memory
- .discovery
- Contains logic to discover other instances on the network and select usernames
- .storage
- .message
- .storage
- Contains logic to store messages in database
- .exchange
- Contains logic to exchange messages between instances
- .storage
- .network
- Contains the TCP and UDP servers and clients
- .views
- Contains interfaces for views
- .cli
- Contains the CLI views
- .swing
- Contains the GUI views
- .controllers
- Contains the controllers that link the views to the services
The project follows an improved MVC architecture:
- Models are pure data classes, with minimal methods
- Services under com.mgreen.user and com.mgreen.message contain the business logic and interact with the models
- Views allow for user interaction and call the controllers
- Controllers link the views to the services
This project currently serializes class instances implementing com.mgreen.communication.protocol.Message to bytes. These can easily be reconstructed by casting the deserialized object to Message. This approach was chosen for its simplicity and ease of use. However, it has some downsides, such as the lack of a communication standard and low compatibility between different versions of the software.
In order to simplify the network communication, I chose to use two different constant ports for UDP communication. Each message type specifies which port to be sent to. Messages which can be handled independently (CheckUsernameMessage, SetOnlineMessage) are sent to the passive server on port n+1. Messages that need to be handled along with other messages from other instances (CheckUsernameResponse, SetOnlineResponse) are send to the active client/server on port n+2. A TCP server is also running on port n+3, for "chat" connections between two instances.
The project uses SQLite for simplicity. The name of the .db file is important for testing (to avoid having a shared database), so we default to the IP address of the instance. A static DatabaseConnectionManager class is used to get the connection, and DAO classes are used to access each table.
- Allow users to change username and delete old contacts/messages.
- Allow users to send images/files.
- Put the database file in a better location.
- Add more unit tests.
- Document view classes.
- Improve CLI views.
- QA testing.
-
Local network + IP address detection
- Q: Which network to broadcast on?
- Could broadcast on 255.255.255.255 to broadcast on all networks connected to device
- Problem: Can't get ip address on the network, and we want ip address for device identification
- Ask administrator/user to input network name and/or IP address (Optional solution)
- For all local networks, run checks such (networkInterface.isUp) then (Selected solution)
- For all addresses on this network
- Check if ipv4 and address.isReachable
- Try to ping google
- Select this network and address
- Try to ping google
- Check if ipv4 and address.isReachable
- For all addresses on this network
- Could broadcast on 255.255.255.255 to broadcast on all networks connected to device
- Q: Which network to broadcast on?
-
Database management:
- Using SQLite for simplicity
- Name of .db file is important for testing (avoid shared db), so we choose ip address
- Static class to get connection
- DAO classes for each table
-
Communication protocol
- Choices
- Use some JSON/XML library and define schemas for our messages
- +Communication standard
- -Need external dependency, custom serializer + validator
- Use .properties
- +No need for external dependency
- -Custom serializer + validator
- Serialize class instances to bytes
- +Easy, just need messages to implement Serializable
- -Versioning more difficult, not a communication standard
- Use some JSON/XML library and define schemas for our messages
- Chose serialize to bytes since it's so simple (Selected solution)
- interface Message extends Serializable
- implement to create new message type
- MessageContainer = Message + Metadata
- Easier to deserialize because of casting
- interface Message extends Serializable
- Choices
-
TCP/UDP client/servers
- UDP:
- One "passive" server running on port 8000
- For "background" tasks (responding to UsernameOK and Connection)
- One "active" socket running on port 8001
- For "active" tasks (broadcasting then awaiting all responses)
- One "passive" server running on port 8000
- TCP:
- One TCP Server running on port 8002
- When TCP connects, creates a TCPConnectionHandler to handle that connection
- Class also allows us to connect to others, returning a TCPConnectionHandler
- Each message type specifies which port to be sent to
- UDP:
-
Architecture
- Instead of just MVC, we do Model < DAO < Service < Controller > View
- Model is just pure "dataclass"
- DAO handles DB operations
- Service handles business logic