Hi there! 👋 it's Michael and in this article we will get familiarized a bit with network programming. Within the scope of this article, a distributed messaging system will be developed that utilizes a simple request-reply protocol.
The Clients send a Request to the Server, and then the Server responds with a Response, and their connection terminates. The technology we will use involves Sockets (Sockets-I/O streams-Threads).
In this system, different users will be able to create accounts and send messages to each other, while the server manages them. This functionality will be provided to them by:
a) a server program, which will have the capability to handle multiple client requests simultaneously.
b) client programs, each of which will have the ability to send requests to the server.
Message:
The Message class represents a message in the communication system. Each message includes a unique identifier messageId, a sender sender, the message text body, and a read field that indicates whether the message has been read. The class provides access methods for the message fields and methods to set the read field.
Account:
The Account class represents a user account in the system. Each account has a username, an authorization token authToken, and a message list messageBox.
Server:
The Server class implements the server in the communication system. Below are the key member variables and methods of the class:
Member Variables:
ServerSocket server: A `ServerSocket` object for listening to incoming connections.
List<String> existingUsernames: A list of usernames that have already been created.
List<String> clientAddresses: A list of the IP addresses of connected clients.
int authTokenCounter: A counter for generating unique `authTokens`.
Map<Integer, String> authTokenToUsername: A map correlating `authTokens` to usernames.
Map<String, List<Message>> userInboxes: A map correlating usernames with their messages.
List<Account> accounts: A list containing the user accounts.
Constructor Server(int port):
Creates the `Server` object and initializes the lists and the map.
Creates a `ServerSocket` for connecting with clients on the specified `port`.
Starts the `startListening()` method to begin listening for incoming connections.
Method startListening():
Displays a message that the server is active and listening on a specific port.
In a loop, accepts new connections from clients.
For each new connection, executes a new `ClientHandler` thread to serve the client.
Method handleCreateAccount(Account account, DataOutputStream out):
Handles the account creation request.
Checks if the username already exists or if it is valid. If the username already exists or if the username is not valid, it returns a different identifier to display the appropriate error message to the user. If the username passes these checks, then it creates a user account in the system.
Returns the authToken of the new account.
Method handleShowAccounts(int senderAuthToken, DataOutputStream out):
In case of a valid authToken, returns the list of users with their corresponding numbered identifiers.
Method handleSendMessage(int senderAuthToken, String recipient, String messageBody, DataOutputStream out):
Checks the sender's authToken and the existence of the recipient.
Sends a message from the sender to the recipient and announces the success or failure of the delivery.
Method sendMessage(String sender, String recipient, String messageBody):
Creates a new message from the sender to the recipient and adds it to the recipient's inbox.
Method handleShowInbox(int authToken, DataOutputStream out):
Checks the authToken and returns the user's incoming messages, including both read and unread.
Method handleReadMessage(int authToken, int messageId, DataOutputStream out):
Checks the authToken and searches for the message based on the messageId.
Marks the message as read and returns it, or returns an appropriate error message.
Method handleDeleteMessage(int authToken, int messageId, DataOutputStream out):
Checks the authToken and searches for the message based on the messageId.
Deletes it from the user's inbox and returns an appropriate message.
Method generateAuthToken():
Generates and returns a unique authToken by increasing the authTokenCounter.
Method isValidUsername(String username):
Checks if the username meets the validity criteria (alphabetic characters).
Main method:
It initializes a default port number 3030.
It checks if command-line arguments (args) are provided when running the program.
If command-line arguments are provided, it attempts to parse the first argument as an integer representing the port number. If successful, it updates the port variable with the provided value.
If the parsing fails (due to a non-integer argument), it catches the NumberFormatException and prints an error message indicating that the port number is invalid.
Finally, regardless of whether command-line arguments were provided or not, it creates a new Server object using the determined port number. This effectively starts the server and makes it listen for incoming connections on the specified port.
ClientHandler implements Runnable:
It is an inner class that implements client service through a thread. It manages the operations undertaken by the client based on the function identifiers (fnId) it receives.
Below are the key elements of the class:
Member Variables:
Socket clientSocket: The `socket` used for communicating with the client.
DataInputStream in: The `stream` for reading requests from the client.
DataOutputStream out: The `stream` for writing data to the client.
Constructor ClientHandler(Socket socket):
Creates a ClientHandler object with the specified socket.
Initializes the DataInputStream and DataOutputStream objects for reading and writing, respectively.
Method run():
The method that implements the service thread's lifecycle.
Waits for incoming data from the client.
Depending on the `fnId (function identifier)` received, it executes the corresponding operations using the methods of the main class `Server`.
If it receives an `fnId` with a value of 0, it terminates the loop and closes the connection with the client.
Prints error messages in case of an error.
Method close():
Closes the `DataInputStream`, `DataOutputStream`, and the client's `socket`.
Prints a message about the closing of the connection.
Overall, the `ClientHandler` class is responsible for managing the communication with each client, servicing their requests through the appropriate server methods.
Client:
The Client class implements the logic of a client that connects to a server. Below, the basic elements of the class are analyzed:
Member Variables:
Socket socket: The socket used by the client to connect to the server.
DataOutputStream out: The stream for writing data to the server.
DataInputStream in: The stream for reading data from the server.
Scanner scanner: A Scanner object for reading input from the user.
Constructor Client(String ip, int port, int fnId, String[] args):
Creates a Client object with the given data.
Creates Socket, DataOutputStream, and DataInputStream objects for communication with the server.
Calls the executeFunction method to perform the desired function.
Method executeFunction(int fnId, String[] args):
Executes the specific function based on the provided fnId.
Calls appropriate methods such as createAccount, showAccounts, sendMessage, etc., based on fnId.
createAccount(String[] args): Handles the creation of an account on the server.
showAccounts(int authToken): Requests the server to display the accounts.
sendMessage(int authToken, String recipient, String messageBody): Sends a message to another user.
showInbox(int authToken): Requests the server to display the messages in the inbox.
readMessage(int authToken, int messageId): Requests the server to read a specific message.
deleteMessage(int authToken, int messageId): Requests the server to delete a specific message.
close(): Closes the client, sending a termination signal to the server and closing the streams and socket.
Main Method: Creates a Client object based on parameters passed from the terminal. If the terminate option is provided, the program closes after executing the functions.
Example:
Server:
We initialize a server running on port 5002 and ready to listen to requests comming from the clients.
Client:
We initialize two clients with the user names dragon and demousername.
The server has given to each client a unique authentication token.
With the next command we check the users list.
Then user dragon sends a HELLO WORLD message to user demousername.
We check the inbox of demousername which has an unread message from dragon.
Then we open the message.
Finally we delete the message.
Until the next one, cheers! 🍺
Comentarios