top of page
Search
  • Writer's pictureMichael G

Exploring Network Programming with Sockets and Threads in Java

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! 🍺


Comments


bottom of page