Diferență între revizuiri ale paginii „OOP Lab Task 5”

De la WikiLabs
Jump to navigationJump to search
 
(Nu s-au afișat 21 de versiuni intermediare efectuate de același utilizator)
Linia 12: Linia 12:
 
* [[Serialization]]
 
* [[Serialization]]
 
* [[Network Sockets]]
 
* [[Network Sockets]]
* [[Concurrent Programming - Threads]]
 
  
 
== Requirements ==
 
== Requirements ==
  
* Modify class '''ClientPeer''' written for the previous assignment, making it into a thread which, in parallel with the main execution thread, has to read objects of type '''Message''' from the server and write them on the screen.
+
# For this task you need the previously designed class '''Message''', from OOP Lab Task 1. All your classes must be in the '''labutil''' package.
* Modify class '''ServerPeer''' written for the previous assignment, making it into a thread. This new thread must read objects of type '''Message''' and '''PrivateMessage''' from the associated client and correctly distribute them to the other clients.
+
# Add a class called <code style="color:green;">ClientPeer</code> with the following features:
* Modify class '''Server''' written for the previous assignment, such that when a new client connects, the Server main thread should create a new '''ServerPeer''' and start it as a Thread, and then return to blocking in method '''ServerSocket'''.''accept()'', waiting for a new incoming connection.
+
#* Only one constructor, that takes as arguments a string (the user/sender name) and a reference to a '''Socket''' object, that is supposed to be already instantiated and connected, and saves them in private fields.
* The server should not accept a new connection if the current number of connected clients is already equal to property MAX_CLIENTS read from the ''server.conf'' configuration file by the class '''ServerConfig'''.
+
#* A method <code style="color:green;">void sendMessage(String message)</code>, that creates a '''Message''' object with the content taken from the method's argument and the sender's name read from the class' fields, and sends the newly created object through the available Socket.
 +
#** In order to be able to send a '''Message''' object through a stream (to serialize that object), that object must be serializable.
 +
#** The object is sent through the output stream of the Socket. You get the reference to that stream using the Socket's method <code style="color:green;">getOutputStream</code>, but to be able to send objects through this stream, which is only a basic stream of bytes, you need to enclose it in a filter stream of type <code style="color:green;">ObjectOutputStream</code> (this object does the serialization for you, that is it converts the '''Message''' object into a stream of bytes).
 +
#* A method named <code style="color:green;">close</code>, without arguments and return values, that symply closes the output stream.
 +
#* The output stream is created at '''ClientPeer''' instantiation, used by '''sendMessage''' method which writes '''Message''' objects into, and closed by '''close''' method.
 +
#* The constructor and both methods employ a socket and a stream, whose methods may throw various exceptions of type '''IOException''' when called. You do not catch them, but, because they are checked exceptions, you must declare them to be thrown by the constructor and the methods.
 +
# All fields must be private.
 +
# Add to the '''labutil''' package another class, named <code style="color:green;">Client</code>, which is executable (has a '''main''' method). This class has
 +
#* A constructor, with two arguments, a string for an internet address, and an integer for a port number. This constructor creates a '''Socket''' object and connects it to the desired remote socket, whose address and port are given as arguments.
 +
#* The '''Socket''' reference must be an instance field so that it may be used by the other methods of this class.
 +
#* A method named <code style="color:green;">run</code>, without arguments, which does the following:
 +
#** First, it instantiates a '''ClientPeer''' object with your name as an argument (you are the sender!) and the reference to the '''Socket''' object as the other argument.
 +
#** After that, open an input stream of characters to read from the keyboard. The input stream from the keyboard is already enclosed in the object '''System.in''', which is opened and available by default. However, this stream is a byte stream, therefore you need to filter it properly, so that the bytes are transformed to characters. To do that, you enclose the '''System.in''' stream into an <code style="color:green;">InputStreamReader</code> object. The '''Reader''' object ('''InputStreamReader''' is derived from '''Reader''') delivers the stream character by character. To read a whole line from the keyboard without calling the '''read''' method for each character, you enclose further the '''InputStreamReader''' into a <code style="color:green;">BufferedReader</code> object, whose '''readLine''' method fits this purpose. The '''readLine''' method returns (with a string) only after you type the ''Enter'' key (but the newline character will not be part of the returned string).
 +
#** Then, read a string of characters from the keyboard (from the previously opened input stream) and call the '''sendMessage''' method of the '''ClientPeer''' object. Your typed messages will thus be sent to the destination.
 +
#** You may send messages in a continuous loop (type a line, press ''Enter'', type another line, press ''Enter'' a.s.o).
 +
#** To break the loop, you test for a special typed line, ''QUIT'' (it will act like a command, not a message to be sent). Remember how to check that two strings are equal!
 +
#** After you finish with sending messages don't forget to close the output stream of the '''ClientPeer''' object, the input stream and the socket! To be sure that they are closed in any circumstances, put the statements that closes them in a '''finally''' block, atached to a '''try''' block that encloses the other part of the '''main''' method. Be careful, the references used inside the '''finally''' block must be declared outside the '''try''' block and initialized with some value (the initialization value may be null).!
 +
#** Because the '''run''' method operates with sockets and streams it may also raise checked exceptions of type '''IOException'''. You do not catch them; simply declare that the method '''run''' throws them out.
 +
#* The '''main''' method instantiates a '''Client''' object with some destination IP address (try first with 127.0.0.1), and port 9000 (that connects the client's socket to the destination's socket, the localhost (127.0.0.1) on port 9000), and calls the method '''run''' for the newly created '''Client''' object.
 +
#** Because both the '''Client''' constructor and the '''run''' method may generate checked exceptions, throw them further outside the '''main''' method.
 +
# You may run the '''Client''' class, to see that it compiles, builds and runs, but the execution will end abruptly with an exception because there is still no server running, to which your client might connect (Connection refused). The following steps are devoted to the other part of the connection, the receiver of messages, which also acts as the server.
 +
# Add to the '''labutil''' package a class called <code style="color:green;">ServerPeer</code> with the following features:
 +
#* The constructor has as its sole argument a reference to a pre-initialized '''Socket''' object, that it stores in a private field.
 +
#* The method <code style="color:green;">void run()</code> opens an input stream for this socket, reads messages from it, and displays them on the screen, formatted as specified in OOP Lab Task 1.
 +
#** You get the reference to that stream using the Socket's method <code style="color:green;">getInputStream</code>, but to be able to receive objects through this stream, which is only a basic stream of bytes, you need to enclose it in a filter stream of type <code style="color:green;">ObjectInputStream</code> (this object does the deserialization for you, that is it converts the stream of bytes into a '''Message''' object). However, you need to explicitly downcast the return type of '''ObjectInputStream''', which is '''Object''', to your expected '''Message''' type, if you want to call methods that are specific only to '''Message''' objects.
 +
#** The formatted message is printed on the screen with the help of '''Message''' methods.
 +
#** Both the constructor and the method '''run''' employ a socket and a stream, whose methods may throw various checked exceptions of type '''IOException''' when called. You do not catch them; simply declare that they throw them out.
 +
#** In addition, the downcast may throw a '''ClassNotFoundException''', that you also simply propagate out of the method (you do not expect other types than '''Message''' to receive from the stream, but this exception is checked, so it must be explicitly handled somehow).
 +
#** Do these reads in a continuous loop (read a '''Message''' object from the stream, display the formatted message, read another object, display the new message, a.s.o.).
 +
#** As long as the connection is open, the '''readObject method''' waits for a complete object to be received from the stream and then returns with a reference to it. If the connection is closed (by the other side for example) the method aborts and throws the '''EOFException'''. Use this exception to neatly end the read loop. For this purpose, enclose the continuous loop in a '''try''' block and attach a '''catch''' for this particular exception, whithout statements. This catch will simply serve to continue the program, instead of aborting it with an exception. If you want a more realistic output, you may print on the screen, after the '''EOFException''' was caught, the message ''The client has disconnected.'' or something like that.
 +
#** Don't forget to close everything after the connection is closed (and you exited from the loop). You have just added a '''try''' block, so you need only to attach to it the '''finally''' block with statements to close the input stream and the socket. Be careful, the references used inside the '''finally''' block must be declared ouside the '''try''' block!
 +
# The last to be added to your package is a class called <code style="color:green;">Server</code>, which is executable (has a main method). It encloses:
 +
#* One constructor, with a single argument which is the public port number of your server. This constructor instantiates a '''ServerSocket''' object connected the given port.
 +
#** The '''ServerSocket''' reference must be an instance field, so that it may be used by the other methods of this class.
 +
#* A method, named <code style="color:green;">void run()</code>, without arguments, which:
 +
#** calls the '''ServerSocket''''s method '''accept''', to listen for clients. This method returns after a client request is accepted and a connection with that client is established. The method returns a reference to a local '''Socket''' object created specifically for this connection. You use this '''Socket''' reference to instantiate a '''ServerPeer''' object.
 +
#** calls next the '''run''' method for this newly created '''ServerPeer''' object.
 +
#** both the '''accept''' method and the '''run''' method of the '''ServerPeer''' may generate exceptions. You do not handle them, but they must be declared as thrown by the method.
 +
#** it must return only after the connection is closed (remember, you just added the proper '''try'''-'''catch''' construction to handle the '''EOFException''' for the '''run''' method of the '''ServerPeer'''. It closes only the '''Socket''' object. However, the '''ServerSocket''' is up and you may call again its '''accept''' method to connect to another client, a.s.o.
 +
#** Don't forget to close the '''SeverSocket''' object after you decide to no longer accept clients and to exit your program. Use the '''try'''/'''finally''' blocks to be sure that the '''ServerSocket''' will be always closed.
 +
#* If it still has compilation errors it's because you forget about that annoying exceptions. The '''run''' method uses various sockets and streams, that may throw exceptions ... already a classic story. Don't bother with them, simply append the necessary declaration to the '''main''' method signature. In addition to '''IOException'''s, a '''ClassNotFoundException''' may be thrown from '''run''', so it needs to be declared too, or you declare their base class of exceptions to be thrown.
 +
#* The main method instantiates a '''Server''' object attached to some port (for example 9000), and calls its '''run''' method.
 +
# Run the Server class (with the Server.java opened in the editor window right-click and select ''Run File'', or click ''Run''->''Run File'' from the general Menu). If there are no errors the program runs, but nothing is output, and it appears to run without end. It's because there are no clients running around and it listens continuously waiting for some client to appear (the '''accept''' method returns only after it catches a client, or something bad happens).
 +
# How to test your classes? Actually you have two separate applications, a server and a client. You have just started the server, but it runs without end waiting for a client to send messages. You need to start separately a client. To do that, switch the editor window to Client.java and run that file too (right-click and select ''Run File'', or bla bla). A second output tab appears. You have now two programs running at the same time, with their outputs displayed on separate tabs in the output window at the bottom of the NetBeans environment.
 +
# Type something in the output window of the Client and press ''Enter''. Look to the other tab and see that the Sever outputs the message you have just typed. Try with other messages. To close the Client, type ''QUIT'' and press ''Enter''. Depending on how you have written the '''run''' method of the '''Server''' class, the server will end too, or it will continue, listening for other clients to connect (in which case you run again the Client.java, type something a.s.o)
 +
# If you want more fun, and know the IP address of the computer of your colleague, ask him to open a server on some port, change your '''Client''' class to open a Socket with that IP instead of the localhost IP 127.0.0.1, and the desired port, then run locally your client and type messages for your colleague. Please, send him only peacefull messages!
  
Notes:
+
== Submitting ==
* Class '''Server''' should keep a list of all the connected clients (objects of type '''ServerPeer'''). For this, you can use class [http://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html java.util.ArrayList].
+
* The assignment will be evaluated automatically by the [http://homework.dcae.pub.ro:/WebObjects/Web-CAT.woa Web-CAT] platform.
* This list should be kept consistent, meaning new clients should be added, and disconnected clients should be removed.
+
* You could access the Web-CAT platform using the username and the password with which you acces the <span style="color:red">electronica.curs.pub.ro</span> intranet.
* This list should be available to the '''ServerPeer''' execution threads through synchronized methods.
+
* Select the OOP Lab Task 5 assignment.
* To check if an object is instance of a specific class, you can use operator '''instanceof''':
+
* Submit your work as a single .zip archive (give it whatever name you choose) containing only the Java source code files.
<syntaxhighlight lang="java">
+
* <font color="red">'''Attention'''</font> Any deviation from these instructions may lead to the loss of the entire amount of points.
Object o = ois.readOject();
 
if(o instanceof String){
 
    String s = (String)o;
 
    System.out.print(s);
 
}else{
 
    System.out.print("The object is not a String!");
 
}
 
</syntaxhighlight>
 

Versiunea curentă din 22 decembrie 2016 01:58

Required Tutorials

Requirements

  1. For this task you need the previously designed class Message, from OOP Lab Task 1. All your classes must be in the labutil package.
  2. Add a class called ClientPeer with the following features:
    • Only one constructor, that takes as arguments a string (the user/sender name) and a reference to a Socket object, that is supposed to be already instantiated and connected, and saves them in private fields.
    • A method void sendMessage(String message), that creates a Message object with the content taken from the method's argument and the sender's name read from the class' fields, and sends the newly created object through the available Socket.
      • In order to be able to send a Message object through a stream (to serialize that object), that object must be serializable.
      • The object is sent through the output stream of the Socket. You get the reference to that stream using the Socket's method getOutputStream, but to be able to send objects through this stream, which is only a basic stream of bytes, you need to enclose it in a filter stream of type ObjectOutputStream (this object does the serialization for you, that is it converts the Message object into a stream of bytes).
    • A method named close, without arguments and return values, that symply closes the output stream.
    • The output stream is created at ClientPeer instantiation, used by sendMessage method which writes Message objects into, and closed by close method.
    • The constructor and both methods employ a socket and a stream, whose methods may throw various exceptions of type IOException when called. You do not catch them, but, because they are checked exceptions, you must declare them to be thrown by the constructor and the methods.
  3. All fields must be private.
  4. Add to the labutil package another class, named Client, which is executable (has a main method). This class has
    • A constructor, with two arguments, a string for an internet address, and an integer for a port number. This constructor creates a Socket object and connects it to the desired remote socket, whose address and port are given as arguments.
    • The Socket reference must be an instance field so that it may be used by the other methods of this class.
    • A method named run, without arguments, which does the following:
      • First, it instantiates a ClientPeer object with your name as an argument (you are the sender!) and the reference to the Socket object as the other argument.
      • After that, open an input stream of characters to read from the keyboard. The input stream from the keyboard is already enclosed in the object System.in, which is opened and available by default. However, this stream is a byte stream, therefore you need to filter it properly, so that the bytes are transformed to characters. To do that, you enclose the System.in stream into an InputStreamReader object. The Reader object (InputStreamReader is derived from Reader) delivers the stream character by character. To read a whole line from the keyboard without calling the read method for each character, you enclose further the InputStreamReader into a BufferedReader object, whose readLine method fits this purpose. The readLine method returns (with a string) only after you type the Enter key (but the newline character will not be part of the returned string).
      • Then, read a string of characters from the keyboard (from the previously opened input stream) and call the sendMessage method of the ClientPeer object. Your typed messages will thus be sent to the destination.
      • You may send messages in a continuous loop (type a line, press Enter, type another line, press Enter a.s.o).
      • To break the loop, you test for a special typed line, QUIT (it will act like a command, not a message to be sent). Remember how to check that two strings are equal!
      • After you finish with sending messages don't forget to close the output stream of the ClientPeer object, the input stream and the socket! To be sure that they are closed in any circumstances, put the statements that closes them in a finally block, atached to a try block that encloses the other part of the main method. Be careful, the references used inside the finally block must be declared outside the try block and initialized with some value (the initialization value may be null).!
      • Because the run method operates with sockets and streams it may also raise checked exceptions of type IOException. You do not catch them; simply declare that the method run throws them out.
    • The main method instantiates a Client object with some destination IP address (try first with 127.0.0.1), and port 9000 (that connects the client's socket to the destination's socket, the localhost (127.0.0.1) on port 9000), and calls the method run for the newly created Client object.
      • Because both the Client constructor and the run method may generate checked exceptions, throw them further outside the main method.
  5. You may run the Client class, to see that it compiles, builds and runs, but the execution will end abruptly with an exception because there is still no server running, to which your client might connect (Connection refused). The following steps are devoted to the other part of the connection, the receiver of messages, which also acts as the server.
  6. Add to the labutil package a class called ServerPeer with the following features:
    • The constructor has as its sole argument a reference to a pre-initialized Socket object, that it stores in a private field.
    • The method void run() opens an input stream for this socket, reads messages from it, and displays them on the screen, formatted as specified in OOP Lab Task 1.
      • You get the reference to that stream using the Socket's method getInputStream, but to be able to receive objects through this stream, which is only a basic stream of bytes, you need to enclose it in a filter stream of type ObjectInputStream (this object does the deserialization for you, that is it converts the stream of bytes into a Message object). However, you need to explicitly downcast the return type of ObjectInputStream, which is Object, to your expected Message type, if you want to call methods that are specific only to Message objects.
      • The formatted message is printed on the screen with the help of Message methods.
      • Both the constructor and the method run employ a socket and a stream, whose methods may throw various checked exceptions of type IOException when called. You do not catch them; simply declare that they throw them out.
      • In addition, the downcast may throw a ClassNotFoundException, that you also simply propagate out of the method (you do not expect other types than Message to receive from the stream, but this exception is checked, so it must be explicitly handled somehow).
      • Do these reads in a continuous loop (read a Message object from the stream, display the formatted message, read another object, display the new message, a.s.o.).
      • As long as the connection is open, the readObject method waits for a complete object to be received from the stream and then returns with a reference to it. If the connection is closed (by the other side for example) the method aborts and throws the EOFException. Use this exception to neatly end the read loop. For this purpose, enclose the continuous loop in a try block and attach a catch for this particular exception, whithout statements. This catch will simply serve to continue the program, instead of aborting it with an exception. If you want a more realistic output, you may print on the screen, after the EOFException was caught, the message The client has disconnected. or something like that.
      • Don't forget to close everything after the connection is closed (and you exited from the loop). You have just added a try block, so you need only to attach to it the finally block with statements to close the input stream and the socket. Be careful, the references used inside the finally block must be declared ouside the try block!
  7. The last to be added to your package is a class called Server, which is executable (has a main method). It encloses:
    • One constructor, with a single argument which is the public port number of your server. This constructor instantiates a ServerSocket object connected the given port.
      • The ServerSocket reference must be an instance field, so that it may be used by the other methods of this class.
    • A method, named void run(), without arguments, which:
      • calls the ServerSocket's method accept, to listen for clients. This method returns after a client request is accepted and a connection with that client is established. The method returns a reference to a local Socket object created specifically for this connection. You use this Socket reference to instantiate a ServerPeer object.
      • calls next the run method for this newly created ServerPeer object.
      • both the accept method and the run method of the ServerPeer may generate exceptions. You do not handle them, but they must be declared as thrown by the method.
      • it must return only after the connection is closed (remember, you just added the proper try-catch construction to handle the EOFException for the run method of the ServerPeer. It closes only the Socket object. However, the ServerSocket is up and you may call again its accept method to connect to another client, a.s.o.
      • Don't forget to close the SeverSocket object after you decide to no longer accept clients and to exit your program. Use the try/finally blocks to be sure that the ServerSocket will be always closed.
    • If it still has compilation errors it's because you forget about that annoying exceptions. The run method uses various sockets and streams, that may throw exceptions ... already a classic story. Don't bother with them, simply append the necessary declaration to the main method signature. In addition to IOExceptions, a ClassNotFoundException may be thrown from run, so it needs to be declared too, or you declare their base class of exceptions to be thrown.
    • The main method instantiates a Server object attached to some port (for example 9000), and calls its run method.
  8. Run the Server class (with the Server.java opened in the editor window right-click and select Run File, or click Run->Run File from the general Menu). If there are no errors the program runs, but nothing is output, and it appears to run without end. It's because there are no clients running around and it listens continuously waiting for some client to appear (the accept method returns only after it catches a client, or something bad happens).
  9. How to test your classes? Actually you have two separate applications, a server and a client. You have just started the server, but it runs without end waiting for a client to send messages. You need to start separately a client. To do that, switch the editor window to Client.java and run that file too (right-click and select Run File, or bla bla). A second output tab appears. You have now two programs running at the same time, with their outputs displayed on separate tabs in the output window at the bottom of the NetBeans environment.
  10. Type something in the output window of the Client and press Enter. Look to the other tab and see that the Sever outputs the message you have just typed. Try with other messages. To close the Client, type QUIT and press Enter. Depending on how you have written the run method of the Server class, the server will end too, or it will continue, listening for other clients to connect (in which case you run again the Client.java, type something a.s.o)
  11. If you want more fun, and know the IP address of the computer of your colleague, ask him to open a server on some port, change your Client class to open a Socket with that IP instead of the localhost IP 127.0.0.1, and the desired port, then run locally your client and type messages for your colleague. Please, send him only peacefull messages!

Submitting

  • The assignment will be evaluated automatically by the Web-CAT platform.
  • You could access the Web-CAT platform using the username and the password with which you acces the electronica.curs.pub.ro intranet.
  • Select the OOP Lab Task 5 assignment.
  • Submit your work as a single .zip archive (give it whatever name you choose) containing only the Java source code files.
  • Attention Any deviation from these instructions may lead to the loss of the entire amount of points.