How a network connection is created ? A network connection is initiated by a client program when it creates a socket for the communication with the server. To create the socket in Java, the client calls the Socket constructor and passes the server address and the the specific server port number to it. At this stage the server must be started on the machine having the specified address and listening for connections on its specific port number. The server uses a specific port dedicated only to listening for connection requests from clients. It can not use this specific port for data communication with the clients because the server must be able to accept the client connection at any instant. So, its specific port is dedicated only to listening for new connection requests. The server side socket associated with specific port is called server socket. When a connection request arrives on this socket from the client side, the client and the server establish a connection. This connection is established as follows: The java.net package in the Java development environment provides the class Socket that implements the client side and the class serverSocket class that implements the server side sockets. The client and the server must agree on a protocol. They must agree on the language of the information transferred back and forth through the socket. There are two communication protocols : The stream communication protocol is known as TCP (transfer control protocol). TCP is a connection-oriented protocol. It works as described in this document. In order to communicate over the TCP protocol, a connection must first be established between two sockets. While one of the sockets listens for a connection request (server), the other asks for a connection (client). Once the two sockets are connected, they can be used to transmit and/or to receive data. When we say "two sockets are connected" we mean the fact that the server accepted a connection. As it was explained above the server creates a new local socket for the new connection. The process of the new local socket creation, however, is transparent for the client. The datagram communication protocol, known as UDP (user datagram protocol), is a connectionless protocol. No connection is established before sending the data. The data are sent in a packet called datagram. The datagram is sent like a request for establishing a connection. However, the datagram contains not only the addresses, it contains the user data also. Once it arrives to the destination the user data are read by the remote application and no connection is established. This protocol requires that each time a datagram is sent, the local socket and the remote socket addresses must also be sent in the datagram. These addresses are sent in each datagram. The java.net package in the Java development environment provides the class DatagramSocket
for programming datagram communications. UDP is an unreliable protocol. There is no guarantee that the datagrams will be delivered in a good order to the destination socket. For, example, a long text, split in several pages and sent one page per datagram, can be received in a different page order. On the other side, TCP is a reliable protocol. TCP guarantee that the pages will be received in the order in which they are sent. When programming TCP and UDP based applications in Java, different types of sockets are used. These sockets are implemented in different classes. The classes ServerSocket and Socket implement TCP based sockets and the class DatagramSocket implements UDP based sockets as follows: This document shows how to program TCP based client/server applications. The UDP oriented programming is not covered in document. Opening a socket The client side When programming a client, a socket must be opened like below: This code, however, must be put in a try/catch block to catch the IOException: where When selecting a port number, one has to keep in mind that the port numbers in the range from 0 to 1023 are reserved for standard services, such as email, FTP, HTTP, etc. For our service (the chat server) the port number should be chosen greater than 1023. The server side When programming a server, a server socket must be created first, like below: The server socket is dedicated to listen to and accept connections from clients. After accepting a request from a client the server creates a client socket to communicate (to send/receive data) with the client, like below : Now the server can send/receive data to/from the clients. Since the sockets are like the file descriptors the send/receive operations are implemented like read/write file operations on the input/output streams. Creating an input stream On the client side, you can use the DataInputStream class to create an input stream to receive responses from the server: The class DataInputStream allows you to read lines of text and Java primitive data types in a portable way. It has several read methods such as read, readChar, readInt, readDouble, and readLine. One has to use whichever function depending on the type of data to receive from the server. On the server side, the DataInputStream is used to receive inputs from the client: Create an output stream
On the client side, an output stream must be created to send the data to the server socket using the classPrintStream or DataOutputStream of java.io package: The class PrintStream implements the methods for displaying Java primitive data types values, like write andprintln methods. Also, one may want to use the DataOutputStream: The class DataOutputStream allows you to write Java primitive data types; many of its methods write a single Java primitive type to the output stream. On the server side, one can use the class PrintStream to send data to the client. Closing sockets Closing a socked is like closing a file. You have to close a socket when you do not need it any more. The output and the input streams must be closed as well but before closing the socket. On the client side you have to close the input and the output streams and the socket like below: On the server you have to close the input and output streams and the two sockets as follows: Usually, on the server side you need to close only the client socket after the client gets served. The server socket is kept open as long as the server is running. A new client can connect to the server on the server socket to establish a new connection, that is, a new client socket. A simple Client/Server application In this section we present a simple client/server application. The client This is a simple client which reads a line from the standard input and sends it to the echo server. The client keeps then reading from the socket till it receives the message "Ok" from the server. Once it receives the"Ok" message then it breaks. The server This is a simple echo server. The server is dedicated to echo messages received from clients. When it receives a message it sends the message back to the client. Also, it appends the string "From server :" in from of the echoed message. Compiling and running the application To try this application you have to compile the two programs: Example 23 and Example 24. Save these programs on your computer. Name the files Client.java and Server.java. Open a shell window on your computer and change the current directory to the directory where you saved these files. Type the following two commands in the shell window. If java compiler is installed on your computer and the PATH variable is configured for the shell to find javaccompiler, then these two command lines will create two new files in the current directory : the files Server.classand Client.class Start the server in the shell window using the command: You will see the following message in this window
telling you that the server is started. Open a new shell window and change the current directory to the directory where you saved the application files. Start the client in the shell window using the command: You will see the following message in this window telling you that the client is started. Type, for example, the text Hello in this window. You will see the following output. telling you that the message Hello was sent to the server and the echo was received by the client from the server. A multi-threaded Client/Server application The next example is a chat application. A chat application consists of a chat server and a chat client. The server accepts connections from the clients and delivers all messages from each client to other clients. This is a tool to communicate with other people over Internet in real time. The client is implemented using two threads - one thread to interact with the server and the other with the standard input. Two threads are needed because a client must communicate with the server and, simultaneously, it must be ready to read messages from the standard input to be sent to the server. The server is implemented using threads also. It uses a separate thread for each connection. It spawns a new client thread every time a new connection from a client is accepted. This simplifies a lot the design of the server. Multi-threading, however, creates synchronization issues. We will present two implementations of the chat server. An implementation that focus on multi-threading without considering the synchronization issues will be presented first. Then we will focus on the synchronization issues that a multi-threaded implementation creates. Finally, an updated version of the multi-threaded chat server that fixes the synchronization issues is presented. The chat Client The code below is the multi-threaded chat client. It uses two threads: one to read the data from the standard input and to sent it to the server, the other to read the data from the server and to print it on the standard output. The chat server We continue with the multi-threaded chat server. It uses a separate thread for each client. It spawns a new client thread every time a new connection from a client is accepted. This thread opens the input and the output streams for a particular client, it ask the client's name, it informs all clients about the fact that a new client has joined the chat room and, as long as it receive data, echos that data back to all other clients. When the client leaves the chat room, this thread informs also the clients about that and terminates.
The synchronization issues of the multi-threaded chat server implementation Consider now the synchronization issues such an implementation creates. To simplify our task let us split the chat server code as follows, see the partitioned code below. Consider the portions of code colored in green, that is, the portions 2,4,6,8,10 (see the code above). All these portions use the array threads[]. This array, however, is shared by all threads of the server. The array is passed by reference to the constructor of the thread every time a new thread is created. The modification of this array by a thread is visible by all other threads. These portions of code are called critical sections because, if used uncontrolled, they can cause unexpected behaviour end even exceptions as explained below. Since all threads run concurrently, the access to this array is also concurrent. Suppose now that a thread (Thread 1) enters the portion 4 while another thread (Thread 2) enters the section 10 of the code. The section 4 uses the array threads[] to inform the clients about a new client. The section 10, however, removes from this array the thread references of the client that leaves the chat room. It can happen, that a threads[i] reference, while being used in section 4, is set to null in section 10 by another thread - by the thread of the client leaving the chat room. The table below shows this scenario. In this scenario the Thread 1 executes the if statement in portion 4. Suppose threads[i] is not null at this instant. Suppose, also, Thread 1 is interrupted by the operating system immediately after evaluating the if statement condition. It means, Thread 1 is put in a waiting queue, while the Thread 2 starts executing portion 10. Such kind of execution is called inter-living. Suppose, Thread 2 sets threads[i] to null when executing portion 10. Finally, Thread 1 is resumed and executesthreads[i].os.println() statement. But threads[i] is null at this instant. This will cause a null pointer exception. This exception will close abnormally the connection with a client. And all that because of another client that decided to leave the chat room. The same situation can arise if we consider the concurrency of any of the section 2,6,8 with the section 10. Such situations are not acceptable and must be resolved correctly in a concurrent multi-threaded application. To avoid such kind of exception, the threads must be synchronized so that they execute the critical portions of code (in green) sequentially, and thus, without inter-living. For example, in the table below, the execution of the two portions of code is sequential - the critical sections executes without interruption. We call such execution synchronized. To archive this synchronization we have to use the synchronized(this){} statement, like below. All synchronized(this){} statements exclude mutually each other. It means, that when a thread enters thesynchronized(this){} statement it verifies first that any other synchronized(this){} statement is not being executed by another thread. If a such a statement is being executed by a thread, then this thread, as well as all other threads trying to execute a synchronized(this){} statement, are forced to wait until the thread executing the synchronized(this){} terminates this
statement. When the thread executing a synchronized(this){}statement leaves the critical section, that is, when it terminates the synchronized(this){} statement, a thread waiting for critical section enters its synchronized(this){}. When a thread enters synchronized(this){}statement it blocks all other threads from entering their synchronized(this){} statements. Thus, putting all critical sections in synchronized(this){} statements we are guarantied that the chat server will execute correctly without rising null pointer exceptions caused by concurrent execution of other critical sections. The synchronized(this){} statement is a powerful tool. However, using it requires a good understanding of the synchronization issue. The incorrect use of synchronized(this){} statement can cause deadlocks of the program. A deadlock is a scenario when one thread waits for another thread to leave its critical section forever. To explain this scenario, suppose we extended the critical section 6 like below. This is, suppose thesynchronized(this){} statement includes a loop that potentially can execute forever. The while (true) loop will execute until it receives "/quit" command from the input stream. Suppose the"/quit" command never arrives or it arrives after a very long time. The thread executing this loop inside thesynchronized(this){} statement will block all other threads from executing their synchronized code because they will wait at their synchronized(this){} statements. For example, the portion of code in red (see below) will be never executed by Thread 2, if Thread 1 entered the while (true) loop and stays in forever. Thus, when synchronizing programs, an appropriate solution must be implemented to solve such issues, otherwise the synchronized(this){} statement can cause very long delays and even deadlocks. And certainly, you have to avoid putting unnecessary synchronized(this){} statements in the program. For example, it is not necessary to synchronize the portion 2 of code (see the table of the partitioned code). Even if this code modifies threads[] array, a better inspection of the code discovers that there is no risk this modification will create null pointer exceptions or other problems to the program. The synchronized version of the chat server In this section we present the updated version of the chat server that fixes the synchronization issues described in the previous section. The synchronized(this){} statement is used to solve the synchronization issues. Also, this version of chat server is improved to deliver private messages to clients. Compiling and running the application To try this application you have to compile the two programs: Example 25 and Example 26 (updated). Save these programs on your computer. Name the files MultiThreadChatClient.java
andMultiThreadChatServerSync.java. Open a shell window on your computer and change the current directory to the directory where you saved these files. Type the following two commands in the shell window. If java compiler is installed on your computer and the PATH variable is configured for the shell to find javaccompiler, then these two command lines will create two new files in the current directory : the filesMultiThreadChatServerSync.class and MultiThreadChatClient.class Start the server in the shell window using the command: You will see the following message in this window telling you that the chat server is started and that it is listening for connections on port number 2222. The phraseUsage: java MultiThreadChatServerSync tells you that you can start the server specifying a parameter - the port number. By default, however, the port 2222 is used. Open a new shell window and change the current directory to the directory where you saved the application files. Start the client in the shell window using the command: You will see the following message in this window telling you that the client is started. Type, for example, the name Anonymous1 in this window. You will see the following output. telling you that the client Anonymous1 entered the chat room. It tells you also that to quit the chat room the client has to enter /quit command. Open one more shell window and change the current directory to the directory where you saved the application files. Start a new client client in the shell window using the command: You will see the following message in this window telling you that the client is started. Now you have two clients connected to the server. Type, for example, the text Anonymous2 in this window. You will see the following output. telling you that the client Anonymous2 entered the chat room. It tells you also that to quit the chat room the client has to enter /quit command. In the window of the client Anonymous1 the following message will be printed. If we enter now a message in any of the client window the message will be printed also in the window of the other client. This kind of message exchange is a chat session. A chat session example Below we show a possible scenario of a chat session between the two clients. Conclusions Java sockets API (Socket and ServerSocket classes) is a powerful and flexible interface for network programming of client/server applications. On the other hand, Java threads is another powerful programming framework for client/server
applications. Multi-threading simplifies the implementation of complex client/server applications. However, it introduces synchronization issues. These issues are caused by the concurrent execution of critical sections of the program by different threads. The synchronized(this){} statement allows us to synchronize the execution of the critical sections. Using this statement, however, requires a good understanding of the synchronization issues. The incorrect use ofsynchronized(this){} statement can cause other problems, such as deadlocks and/or performance degradation of the program. Socket MyClient; MyClient = new Socket("MachineName", PortNumber); Solution How a network connection is created ? A network connection is initiated by a client program when it creates a socket for the communication with the server. To create the socket in Java, the client calls the Socket constructor and passes the server address and the the specific server port number to it. At this stage the server must be started on the machine having the specified address and listening for connections on its specific port number. The server uses a specific port dedicated only to listening for connection requests from clients. It can not use this specific port for data communication with the clients because the server must be able to accept the client connection at any instant. So, its specific port is dedicated only to listening for new connection requests. The server side socket associated with specific port is called server socket. When a connection request arrives on this socket from the client side, the client and the server establish a connection. This connection is established as follows: The java.net package in the Java development environment provides the class Socket that implements the client side and the class serverSocket class that implements the server side sockets. The client and the server must agree on a protocol. They must agree on the language of the information transferred back and forth through the socket. There are two communication protocols : The stream communication protocol is known as TCP (transfer control protocol). TCP is a connection-oriented protocol. It works as described in this document. In order to communicate over the TCP protocol, a connection must first be established between two sockets. While one of the sockets listens for a connection request (server), the other asks for a connection (client). Once the two sockets are connected, they can be used to transmit and/or to receive data. When we say "two sockets are connected" we mean the fact that the server accepted a connection. As it was explained above the server creates a new local socket for the new connection. The process of the
new local socket creation, however, is transparent for the client. The datagram communication protocol, known as UDP (user datagram protocol), is a connectionless protocol. No connection is established before sending the data. The data are sent in a packet called datagram. The datagram is sent like a request for establishing a connection. However, the datagram contains not only the addresses, it contains the user data also. Once it arrives to the destination the user data are read by the remote application and no connection is established. This protocol requires that each time a datagram is sent, the local socket and the remote socket addresses must also be sent in the datagram. These addresses are sent in each datagram. The java.net package in the Java development environment provides the class DatagramSocket for programming datagram communications. UDP is an unreliable protocol. There is no guarantee that the datagrams will be delivered in a good order to the destination socket. For, example, a long text, split in several pages and sent one page per datagram, can be received in a different page order. On the other side, TCP is a reliable protocol. TCP guarantee that the pages will be received in the order in which they are sent. When programming TCP and UDP based applications in Java, different types of sockets are used. These sockets are implemented in different classes. The classes ServerSocket and Socket implement TCP based sockets and the class DatagramSocket implements UDP based sockets as follows: This document shows how to program TCP based client/server applications. The UDP oriented programming is not covered in document. Opening a socket The client side When programming a client, a socket must be opened like below: This code, however, must be put in a try/catch block to catch the IOException: where When selecting a port number, one has to keep in mind that the port numbers in the range from 0 to 1023 are reserved for standard services, such as email, FTP, HTTP, etc. For our service (the chat server) the port number should be chosen greater than 1023. The server side When programming a server, a server socket must be created first, like below: The server socket is dedicated to listen to and accept connections from clients. After accepting a request from a client the server creates a client socket to communicate (to send/receive data) with the client, like below : Now the server can send/receive data to/from the clients. Since the sockets are like the file descriptors the send/receive operations are implemented like read/write file operations on the
input/output streams. Creating an input stream On the client side, you can use the DataInputStream class to create an input stream to receive responses from the server: The class DataInputStream allows you to read lines of text and Java primitive data types in a portable way. It has several read methods such as read, readChar, readInt, readDouble, and readLine. One has to use whichever function depending on the type of data to receive from the server. On the server side, the DataInputStream is used to receive inputs from the client: Create an output stream On the client side, an output stream must be created to send the data to the server socket using the classPrintStream or DataOutputStream of java.io package: The class PrintStream implements the methods for displaying Java primitive data types values, like write andprintln methods. Also, one may want to use the DataOutputStream: The class DataOutputStream allows you to write Java primitive data types; many of its methods write a single Java primitive type to the output stream. On the server side, one can use the class PrintStream to send data to the client. Closing sockets Closing a socked is like closing a file. You have to close a socket when you do not need it any more. The output and the input streams must be closed as well but before closing the socket. On the client side you have to close the input and the output streams and the socket like below: On the server you have to close the input and output streams and the two sockets as follows: Usually, on the server side you need to close only the client socket after the client gets served. The server socket is kept open as long as the server is running. A new client can connect to the server on the server socket to establish a new connection, that is, a new client socket. A simple Client/Server application In this section we present a simple client/server application. The client This is a simple client which reads a line from the standard input and sends it to the echo server. The client keeps then reading from the socket till it receives the message "Ok" from the server. Once it receives the"Ok" message then it breaks. The server This is a simple echo server. The server is dedicated to echo messages received from clients. When it receives a message it sends the message back to the client. Also, it appends the string "From server :" in from of the echoed message. Compiling and running the application
To try this application you have to compile the two programs: Example 23 and Example 24. Save these programs on your computer. Name the files Client.java and Server.java. Open a shell window on your computer and change the current directory to the directory where you saved these files. Type the following two commands in the shell window. If java compiler is installed on your computer and the PATH variable is configured for the shell to find javaccompiler, then these two command lines will create two new files in the current directory : the files Server.classand Client.class Start the server in the shell window using the command: You will see the following message in this window telling you that the server is started. Open a new shell window and change the current directory to the directory where you saved the application files. Start the client in the shell window using the command: You will see the following message in this window telling you that the client is started. Type, for example, the text Hello in this window. You will see the following output. telling you that the message Hello was sent to the server and the echo was received by the client from the server. A multi-threaded Client/Server application The next example is a chat application. A chat application consists of a chat server and a chat client. The server accepts connections from the clients and delivers all messages from each client to other clients. This is a tool to communicate with other people over Internet in real time. The client is implemented using two threads - one thread to interact with the server and the other with the standard input. Two threads are needed because a client must communicate with the server and, simultaneously, it must be ready to read messages from the standard input to be sent to the server. The server is implemented using threads also. It uses a separate thread for each connection. It spawns a new client thread every time a new connection from a client is accepted. This simplifies a lot the design of the server. Multi-threading, however, creates synchronization issues. We will present two implementations of the chat server. An implementation that focus on multi-threading without considering the synchronization issues will be presented first. Then we will focus on the synchronization issues that a multi-threaded implementation creates. Finally, an updated version of the multi-threaded chat server that fixes the synchronization issues is presented. The chat Client
The code below is the multi-threaded chat client. It uses two threads: one to read the data from the standard input and to sent it to the server, the other to read the data from the server and to print it on the standard output. The chat server We continue with the multi-threaded chat server. It uses a separate thread for each client. It spawns a new client thread every time a new connection from a client is accepted. This thread opens the input and the output streams for a particular client, it ask the client's name, it informs all clients about the fact that a new client has joined the chat room and, as long as it receive data, echos that data back to all other clients. When the client leaves the chat room, this thread informs also the clients about that and terminates. The synchronization issues of the multi-threaded chat server implementation Consider now the synchronization issues such an implementation creates. To simplify our task let us split the chat server code as follows, see the partitioned code below. Consider the portions of code colored in green, that is, the portions 2,4,6,8,10 (see the code above). All these portions use the array threads[]. This array, however, is shared by all threads of the server. The array is passed by reference to the constructor of the thread every time a new thread is created. The modification of this array by a thread is visible by all other threads. These portions of code are called critical sections because, if used uncontrolled, they can cause unexpected behaviour end even exceptions as explained below. Since all threads run concurrently, the access to this array is also concurrent. Suppose now that a thread (Thread 1) enters the portion 4 while another thread (Thread 2) enters the section 10 of the code. The section 4 uses the array threads[] to inform the clients about a new client. The section 10, however, removes from this array the thread references of the client that leaves the chat room. It can happen, that a threads[i] reference, while being used in section 4, is set to null in section 10 by another thread - by the thread of the client leaving the chat room. The table below shows this scenario. In this scenario the Thread 1 executes the if statement in portion 4. Suppose threads[i] is not null at this instant. Suppose, also, Thread 1 is interrupted by the operating system immediately after evaluating the if statement condition. It means, Thread 1 is put in a waiting queue, while the Thread 2 starts executing portion 10. Such kind of execution is called inter-living. Suppose, Thread 2 sets threads[i] to null when executing portion 10. Finally, Thread 1 is resumed and executesthreads[i].os.println() statement. But threads[i] is null at this instant. This will cause a null pointer exception. This exception will close abnormally the connection with a client. And all that because of another client that decided to leave the chat room. The same situation can arise if we consider the concurrency of any of the section 2,6,8 with the section 10. Such situations are not acceptable and must be resolved correctly in a concurrent multi-threaded application.
To avoid such kind of exception, the threads must be synchronized so that they execute the critical portions of code (in green) sequentially, and thus, without inter-living. For example, in the table below, the execution of the two portions of code is sequential - the critical sections executes without interruption. We call such execution synchronized. To archive this synchronization we have to use the synchronized(this){} statement, like below. All synchronized(this){} statements exclude mutually each other. It means, that when a thread enters thesynchronized(this){} statement it verifies first that any other synchronized(this){} statement is not being executed by another thread. If a such a statement is being executed by a thread, then this thread, as well as all other threads trying to execute a synchronized(this){} statement, are forced to wait until the thread executing the synchronized(this){} terminates this statement. When the thread executing a synchronized(this){}statement leaves the critical section, that is, when it terminates the synchronized(this){} statement, a thread waiting for critical section enters its synchronized(this){}. When a thread enters synchronized(this){}statement it blocks all other threads from entering their synchronized(this){} statements. Thus, putting all critical sections in synchronized(this){} statements we are guarantied that the chat server will execute correctly without rising null pointer exceptions caused by concurrent execution of other critical sections. The synchronized(this){} statement is a powerful tool. However, using it requires a good understanding of the synchronization issue. The incorrect use of synchronized(this){} statement can cause deadlocks of the program. A deadlock is a scenario when one thread waits for another thread to leave its critical section forever. To explain this scenario, suppose we extended the critical section 6 like below. This is, suppose thesynchronized(this){} statement includes a loop that potentially can execute forever. The while (true) loop will execute until it receives "/quit" command from the input stream. Suppose the"/quit" command never arrives or it arrives after a very long time. The thread executing this loop inside thesynchronized(this){} statement will block all other threads from executing their synchronized code because they will wait at their synchronized(this){} statements. For example, the portion of code in red (see below) will be never executed by Thread 2, if Thread 1 entered the while (true) loop and stays in forever. Thus, when synchronizing programs, an appropriate solution must be implemented to solve such issues, otherwise the synchronized(this){} statement can cause very long delays and even deadlocks. And certainly, you have to avoid putting unnecessary synchronized(this){} statements in the program. For example, it is not necessary to synchronize the portion 2 of code (see the table of the partitioned code). Even if this code modifies threads[] array, a better inspection of the code discovers that there is no risk this modification will create null pointer exceptions or other
problems to the program. The synchronized version of the chat server In this section we present the updated version of the chat server that fixes the synchronization issues described in the previous section. The synchronized(this){} statement is used to solve the synchronization issues. Also, this version of chat server is improved to deliver private messages to clients. Compiling and running the application To try this application you have to compile the two programs: Example 25 and Example 26 (updated). Save these programs on your computer. Name the files MultiThreadChatClient.java andMultiThreadChatServerSync.java. Open a shell window on your computer and change the current directory to the directory where you saved these files. Type the following two commands in the shell window. If java compiler is installed on your computer and the PATH variable is configured for the shell to find javaccompiler, then these two command lines will create two new files in the current directory : the filesMultiThreadChatServerSync.class and MultiThreadChatClient.class Start the server in the shell window using the command: You will see the following message in this window telling you that the chat server is started and that it is listening for connections on port number 2222. The phraseUsage: java MultiThreadChatServerSync tells you that you can start the server specifying a parameter - the port number. By default, however, the port 2222 is used. Open a new shell window and change the current directory to the directory where you saved the application files. Start the client in the shell window using the command: You will see the following message in this window telling you that the client is started. Type, for example, the name Anonymous1 in this window. You will see the following output. telling you that the client Anonymous1 entered the chat room. It tells you also that to quit the chat room the client has to enter /quit command. Open one more shell window and change the current directory to the directory where you saved the application files. Start a new client client in the shell window using the command: You will see the following message in this window telling you that the client is started. Now you have two clients connected to the server. Type, for example, the text Anonymous2 in this window. You will see the following output. telling you that the client Anonymous2 entered the chat room. It tells you also that to quit the
chat room the client has to enter /quit command. In the window of the client Anonymous1 the following message will be printed. If we enter now a message in any of the client window the message will be printed also in the window of the other client. This kind of message exchange is a chat session. A chat session example Below we show a possible scenario of a chat session between the two clients. Conclusions Java sockets API (Socket and ServerSocket classes) is a powerful and flexible interface for network programming of client/server applications. On the other hand, Java threads is another powerful programming framework for client/server applications. Multi-threading simplifies the implementation of complex client/server applications. However, it introduces synchronization issues. These issues are caused by the concurrent execution of critical sections of the program by different threads. The synchronized(this){} statement allows us to synchronize the execution of the critical sections. Using this statement, however, requires a good understanding of the synchronization issues. The incorrect use ofsynchronized(this){} statement can cause other problems, such as deadlocks and/or performance degradation of the program. Socket MyClient; MyClient = new Socket("MachineName", PortNumber);

How a network connection is created A network connection is initi.pdf

  • 1.
    How a networkconnection is created ? A network connection is initiated by a client program when it creates a socket for the communication with the server. To create the socket in Java, the client calls the Socket constructor and passes the server address and the the specific server port number to it. At this stage the server must be started on the machine having the specified address and listening for connections on its specific port number. The server uses a specific port dedicated only to listening for connection requests from clients. It can not use this specific port for data communication with the clients because the server must be able to accept the client connection at any instant. So, its specific port is dedicated only to listening for new connection requests. The server side socket associated with specific port is called server socket. When a connection request arrives on this socket from the client side, the client and the server establish a connection. This connection is established as follows: The java.net package in the Java development environment provides the class Socket that implements the client side and the class serverSocket class that implements the server side sockets. The client and the server must agree on a protocol. They must agree on the language of the information transferred back and forth through the socket. There are two communication protocols : The stream communication protocol is known as TCP (transfer control protocol). TCP is a connection-oriented protocol. It works as described in this document. In order to communicate over the TCP protocol, a connection must first be established between two sockets. While one of the sockets listens for a connection request (server), the other asks for a connection (client). Once the two sockets are connected, they can be used to transmit and/or to receive data. When we say "two sockets are connected" we mean the fact that the server accepted a connection. As it was explained above the server creates a new local socket for the new connection. The process of the new local socket creation, however, is transparent for the client. The datagram communication protocol, known as UDP (user datagram protocol), is a connectionless protocol. No connection is established before sending the data. The data are sent in a packet called datagram. The datagram is sent like a request for establishing a connection. However, the datagram contains not only the addresses, it contains the user data also. Once it arrives to the destination the user data are read by the remote application and no connection is established. This protocol requires that each time a datagram is sent, the local socket and the remote socket addresses must also be sent in the datagram. These addresses are sent in each datagram. The java.net package in the Java development environment provides the class DatagramSocket
  • 2.
    for programming datagramcommunications. UDP is an unreliable protocol. There is no guarantee that the datagrams will be delivered in a good order to the destination socket. For, example, a long text, split in several pages and sent one page per datagram, can be received in a different page order. On the other side, TCP is a reliable protocol. TCP guarantee that the pages will be received in the order in which they are sent. When programming TCP and UDP based applications in Java, different types of sockets are used. These sockets are implemented in different classes. The classes ServerSocket and Socket implement TCP based sockets and the class DatagramSocket implements UDP based sockets as follows: This document shows how to program TCP based client/server applications. The UDP oriented programming is not covered in document. Opening a socket The client side When programming a client, a socket must be opened like below: This code, however, must be put in a try/catch block to catch the IOException: where When selecting a port number, one has to keep in mind that the port numbers in the range from 0 to 1023 are reserved for standard services, such as email, FTP, HTTP, etc. For our service (the chat server) the port number should be chosen greater than 1023. The server side When programming a server, a server socket must be created first, like below: The server socket is dedicated to listen to and accept connections from clients. After accepting a request from a client the server creates a client socket to communicate (to send/receive data) with the client, like below : Now the server can send/receive data to/from the clients. Since the sockets are like the file descriptors the send/receive operations are implemented like read/write file operations on the input/output streams. Creating an input stream On the client side, you can use the DataInputStream class to create an input stream to receive responses from the server: The class DataInputStream allows you to read lines of text and Java primitive data types in a portable way. It has several read methods such as read, readChar, readInt, readDouble, and readLine. One has to use whichever function depending on the type of data to receive from the server. On the server side, the DataInputStream is used to receive inputs from the client: Create an output stream
  • 3.
    On the clientside, an output stream must be created to send the data to the server socket using the classPrintStream or DataOutputStream of java.io package: The class PrintStream implements the methods for displaying Java primitive data types values, like write andprintln methods. Also, one may want to use the DataOutputStream: The class DataOutputStream allows you to write Java primitive data types; many of its methods write a single Java primitive type to the output stream. On the server side, one can use the class PrintStream to send data to the client. Closing sockets Closing a socked is like closing a file. You have to close a socket when you do not need it any more. The output and the input streams must be closed as well but before closing the socket. On the client side you have to close the input and the output streams and the socket like below: On the server you have to close the input and output streams and the two sockets as follows: Usually, on the server side you need to close only the client socket after the client gets served. The server socket is kept open as long as the server is running. A new client can connect to the server on the server socket to establish a new connection, that is, a new client socket. A simple Client/Server application In this section we present a simple client/server application. The client This is a simple client which reads a line from the standard input and sends it to the echo server. The client keeps then reading from the socket till it receives the message "Ok" from the server. Once it receives the"Ok" message then it breaks. The server This is a simple echo server. The server is dedicated to echo messages received from clients. When it receives a message it sends the message back to the client. Also, it appends the string "From server :" in from of the echoed message. Compiling and running the application To try this application you have to compile the two programs: Example 23 and Example 24. Save these programs on your computer. Name the files Client.java and Server.java. Open a shell window on your computer and change the current directory to the directory where you saved these files. Type the following two commands in the shell window. If java compiler is installed on your computer and the PATH variable is configured for the shell to find javaccompiler, then these two command lines will create two new files in the current directory : the files Server.classand Client.class Start the server in the shell window using the command: You will see the following message in this window
  • 4.
    telling you thatthe server is started. Open a new shell window and change the current directory to the directory where you saved the application files. Start the client in the shell window using the command: You will see the following message in this window telling you that the client is started. Type, for example, the text Hello in this window. You will see the following output. telling you that the message Hello was sent to the server and the echo was received by the client from the server. A multi-threaded Client/Server application The next example is a chat application. A chat application consists of a chat server and a chat client. The server accepts connections from the clients and delivers all messages from each client to other clients. This is a tool to communicate with other people over Internet in real time. The client is implemented using two threads - one thread to interact with the server and the other with the standard input. Two threads are needed because a client must communicate with the server and, simultaneously, it must be ready to read messages from the standard input to be sent to the server. The server is implemented using threads also. It uses a separate thread for each connection. It spawns a new client thread every time a new connection from a client is accepted. This simplifies a lot the design of the server. Multi-threading, however, creates synchronization issues. We will present two implementations of the chat server. An implementation that focus on multi-threading without considering the synchronization issues will be presented first. Then we will focus on the synchronization issues that a multi-threaded implementation creates. Finally, an updated version of the multi-threaded chat server that fixes the synchronization issues is presented. The chat Client The code below is the multi-threaded chat client. It uses two threads: one to read the data from the standard input and to sent it to the server, the other to read the data from the server and to print it on the standard output. The chat server We continue with the multi-threaded chat server. It uses a separate thread for each client. It spawns a new client thread every time a new connection from a client is accepted. This thread opens the input and the output streams for a particular client, it ask the client's name, it informs all clients about the fact that a new client has joined the chat room and, as long as it receive data, echos that data back to all other clients. When the client leaves the chat room, this thread informs also the clients about that and terminates.
  • 5.
    The synchronization issuesof the multi-threaded chat server implementation Consider now the synchronization issues such an implementation creates. To simplify our task let us split the chat server code as follows, see the partitioned code below. Consider the portions of code colored in green, that is, the portions 2,4,6,8,10 (see the code above). All these portions use the array threads[]. This array, however, is shared by all threads of the server. The array is passed by reference to the constructor of the thread every time a new thread is created. The modification of this array by a thread is visible by all other threads. These portions of code are called critical sections because, if used uncontrolled, they can cause unexpected behaviour end even exceptions as explained below. Since all threads run concurrently, the access to this array is also concurrent. Suppose now that a thread (Thread 1) enters the portion 4 while another thread (Thread 2) enters the section 10 of the code. The section 4 uses the array threads[] to inform the clients about a new client. The section 10, however, removes from this array the thread references of the client that leaves the chat room. It can happen, that a threads[i] reference, while being used in section 4, is set to null in section 10 by another thread - by the thread of the client leaving the chat room. The table below shows this scenario. In this scenario the Thread 1 executes the if statement in portion 4. Suppose threads[i] is not null at this instant. Suppose, also, Thread 1 is interrupted by the operating system immediately after evaluating the if statement condition. It means, Thread 1 is put in a waiting queue, while the Thread 2 starts executing portion 10. Such kind of execution is called inter-living. Suppose, Thread 2 sets threads[i] to null when executing portion 10. Finally, Thread 1 is resumed and executesthreads[i].os.println() statement. But threads[i] is null at this instant. This will cause a null pointer exception. This exception will close abnormally the connection with a client. And all that because of another client that decided to leave the chat room. The same situation can arise if we consider the concurrency of any of the section 2,6,8 with the section 10. Such situations are not acceptable and must be resolved correctly in a concurrent multi-threaded application. To avoid such kind of exception, the threads must be synchronized so that they execute the critical portions of code (in green) sequentially, and thus, without inter-living. For example, in the table below, the execution of the two portions of code is sequential - the critical sections executes without interruption. We call such execution synchronized. To archive this synchronization we have to use the synchronized(this){} statement, like below. All synchronized(this){} statements exclude mutually each other. It means, that when a thread enters thesynchronized(this){} statement it verifies first that any other synchronized(this){} statement is not being executed by another thread. If a such a statement is being executed by a thread, then this thread, as well as all other threads trying to execute a synchronized(this){} statement, are forced to wait until the thread executing the synchronized(this){} terminates this
  • 6.
    statement. When thethread executing a synchronized(this){}statement leaves the critical section, that is, when it terminates the synchronized(this){} statement, a thread waiting for critical section enters its synchronized(this){}. When a thread enters synchronized(this){}statement it blocks all other threads from entering their synchronized(this){} statements. Thus, putting all critical sections in synchronized(this){} statements we are guarantied that the chat server will execute correctly without rising null pointer exceptions caused by concurrent execution of other critical sections. The synchronized(this){} statement is a powerful tool. However, using it requires a good understanding of the synchronization issue. The incorrect use of synchronized(this){} statement can cause deadlocks of the program. A deadlock is a scenario when one thread waits for another thread to leave its critical section forever. To explain this scenario, suppose we extended the critical section 6 like below. This is, suppose thesynchronized(this){} statement includes a loop that potentially can execute forever. The while (true) loop will execute until it receives "/quit" command from the input stream. Suppose the"/quit" command never arrives or it arrives after a very long time. The thread executing this loop inside thesynchronized(this){} statement will block all other threads from executing their synchronized code because they will wait at their synchronized(this){} statements. For example, the portion of code in red (see below) will be never executed by Thread 2, if Thread 1 entered the while (true) loop and stays in forever. Thus, when synchronizing programs, an appropriate solution must be implemented to solve such issues, otherwise the synchronized(this){} statement can cause very long delays and even deadlocks. And certainly, you have to avoid putting unnecessary synchronized(this){} statements in the program. For example, it is not necessary to synchronize the portion 2 of code (see the table of the partitioned code). Even if this code modifies threads[] array, a better inspection of the code discovers that there is no risk this modification will create null pointer exceptions or other problems to the program. The synchronized version of the chat server In this section we present the updated version of the chat server that fixes the synchronization issues described in the previous section. The synchronized(this){} statement is used to solve the synchronization issues. Also, this version of chat server is improved to deliver private messages to clients. Compiling and running the application To try this application you have to compile the two programs: Example 25 and Example 26 (updated). Save these programs on your computer. Name the files MultiThreadChatClient.java
  • 7.
    andMultiThreadChatServerSync.java. Open ashell window on your computer and change the current directory to the directory where you saved these files. Type the following two commands in the shell window. If java compiler is installed on your computer and the PATH variable is configured for the shell to find javaccompiler, then these two command lines will create two new files in the current directory : the filesMultiThreadChatServerSync.class and MultiThreadChatClient.class Start the server in the shell window using the command: You will see the following message in this window telling you that the chat server is started and that it is listening for connections on port number 2222. The phraseUsage: java MultiThreadChatServerSync tells you that you can start the server specifying a parameter - the port number. By default, however, the port 2222 is used. Open a new shell window and change the current directory to the directory where you saved the application files. Start the client in the shell window using the command: You will see the following message in this window telling you that the client is started. Type, for example, the name Anonymous1 in this window. You will see the following output. telling you that the client Anonymous1 entered the chat room. It tells you also that to quit the chat room the client has to enter /quit command. Open one more shell window and change the current directory to the directory where you saved the application files. Start a new client client in the shell window using the command: You will see the following message in this window telling you that the client is started. Now you have two clients connected to the server. Type, for example, the text Anonymous2 in this window. You will see the following output. telling you that the client Anonymous2 entered the chat room. It tells you also that to quit the chat room the client has to enter /quit command. In the window of the client Anonymous1 the following message will be printed. If we enter now a message in any of the client window the message will be printed also in the window of the other client. This kind of message exchange is a chat session. A chat session example Below we show a possible scenario of a chat session between the two clients. Conclusions Java sockets API (Socket and ServerSocket classes) is a powerful and flexible interface for network programming of client/server applications. On the other hand, Java threads is another powerful programming framework for client/server
  • 8.
    applications. Multi-threading simplifiesthe implementation of complex client/server applications. However, it introduces synchronization issues. These issues are caused by the concurrent execution of critical sections of the program by different threads. The synchronized(this){} statement allows us to synchronize the execution of the critical sections. Using this statement, however, requires a good understanding of the synchronization issues. The incorrect use ofsynchronized(this){} statement can cause other problems, such as deadlocks and/or performance degradation of the program. Socket MyClient; MyClient = new Socket("MachineName", PortNumber); Solution How a network connection is created ? A network connection is initiated by a client program when it creates a socket for the communication with the server. To create the socket in Java, the client calls the Socket constructor and passes the server address and the the specific server port number to it. At this stage the server must be started on the machine having the specified address and listening for connections on its specific port number. The server uses a specific port dedicated only to listening for connection requests from clients. It can not use this specific port for data communication with the clients because the server must be able to accept the client connection at any instant. So, its specific port is dedicated only to listening for new connection requests. The server side socket associated with specific port is called server socket. When a connection request arrives on this socket from the client side, the client and the server establish a connection. This connection is established as follows: The java.net package in the Java development environment provides the class Socket that implements the client side and the class serverSocket class that implements the server side sockets. The client and the server must agree on a protocol. They must agree on the language of the information transferred back and forth through the socket. There are two communication protocols : The stream communication protocol is known as TCP (transfer control protocol). TCP is a connection-oriented protocol. It works as described in this document. In order to communicate over the TCP protocol, a connection must first be established between two sockets. While one of the sockets listens for a connection request (server), the other asks for a connection (client). Once the two sockets are connected, they can be used to transmit and/or to receive data. When we say "two sockets are connected" we mean the fact that the server accepted a connection. As it was explained above the server creates a new local socket for the new connection. The process of the
  • 9.
    new local socketcreation, however, is transparent for the client. The datagram communication protocol, known as UDP (user datagram protocol), is a connectionless protocol. No connection is established before sending the data. The data are sent in a packet called datagram. The datagram is sent like a request for establishing a connection. However, the datagram contains not only the addresses, it contains the user data also. Once it arrives to the destination the user data are read by the remote application and no connection is established. This protocol requires that each time a datagram is sent, the local socket and the remote socket addresses must also be sent in the datagram. These addresses are sent in each datagram. The java.net package in the Java development environment provides the class DatagramSocket for programming datagram communications. UDP is an unreliable protocol. There is no guarantee that the datagrams will be delivered in a good order to the destination socket. For, example, a long text, split in several pages and sent one page per datagram, can be received in a different page order. On the other side, TCP is a reliable protocol. TCP guarantee that the pages will be received in the order in which they are sent. When programming TCP and UDP based applications in Java, different types of sockets are used. These sockets are implemented in different classes. The classes ServerSocket and Socket implement TCP based sockets and the class DatagramSocket implements UDP based sockets as follows: This document shows how to program TCP based client/server applications. The UDP oriented programming is not covered in document. Opening a socket The client side When programming a client, a socket must be opened like below: This code, however, must be put in a try/catch block to catch the IOException: where When selecting a port number, one has to keep in mind that the port numbers in the range from 0 to 1023 are reserved for standard services, such as email, FTP, HTTP, etc. For our service (the chat server) the port number should be chosen greater than 1023. The server side When programming a server, a server socket must be created first, like below: The server socket is dedicated to listen to and accept connections from clients. After accepting a request from a client the server creates a client socket to communicate (to send/receive data) with the client, like below : Now the server can send/receive data to/from the clients. Since the sockets are like the file descriptors the send/receive operations are implemented like read/write file operations on the
  • 10.
    input/output streams. Creating aninput stream On the client side, you can use the DataInputStream class to create an input stream to receive responses from the server: The class DataInputStream allows you to read lines of text and Java primitive data types in a portable way. It has several read methods such as read, readChar, readInt, readDouble, and readLine. One has to use whichever function depending on the type of data to receive from the server. On the server side, the DataInputStream is used to receive inputs from the client: Create an output stream On the client side, an output stream must be created to send the data to the server socket using the classPrintStream or DataOutputStream of java.io package: The class PrintStream implements the methods for displaying Java primitive data types values, like write andprintln methods. Also, one may want to use the DataOutputStream: The class DataOutputStream allows you to write Java primitive data types; many of its methods write a single Java primitive type to the output stream. On the server side, one can use the class PrintStream to send data to the client. Closing sockets Closing a socked is like closing a file. You have to close a socket when you do not need it any more. The output and the input streams must be closed as well but before closing the socket. On the client side you have to close the input and the output streams and the socket like below: On the server you have to close the input and output streams and the two sockets as follows: Usually, on the server side you need to close only the client socket after the client gets served. The server socket is kept open as long as the server is running. A new client can connect to the server on the server socket to establish a new connection, that is, a new client socket. A simple Client/Server application In this section we present a simple client/server application. The client This is a simple client which reads a line from the standard input and sends it to the echo server. The client keeps then reading from the socket till it receives the message "Ok" from the server. Once it receives the"Ok" message then it breaks. The server This is a simple echo server. The server is dedicated to echo messages received from clients. When it receives a message it sends the message back to the client. Also, it appends the string "From server :" in from of the echoed message. Compiling and running the application
  • 11.
    To try thisapplication you have to compile the two programs: Example 23 and Example 24. Save these programs on your computer. Name the files Client.java and Server.java. Open a shell window on your computer and change the current directory to the directory where you saved these files. Type the following two commands in the shell window. If java compiler is installed on your computer and the PATH variable is configured for the shell to find javaccompiler, then these two command lines will create two new files in the current directory : the files Server.classand Client.class Start the server in the shell window using the command: You will see the following message in this window telling you that the server is started. Open a new shell window and change the current directory to the directory where you saved the application files. Start the client in the shell window using the command: You will see the following message in this window telling you that the client is started. Type, for example, the text Hello in this window. You will see the following output. telling you that the message Hello was sent to the server and the echo was received by the client from the server. A multi-threaded Client/Server application The next example is a chat application. A chat application consists of a chat server and a chat client. The server accepts connections from the clients and delivers all messages from each client to other clients. This is a tool to communicate with other people over Internet in real time. The client is implemented using two threads - one thread to interact with the server and the other with the standard input. Two threads are needed because a client must communicate with the server and, simultaneously, it must be ready to read messages from the standard input to be sent to the server. The server is implemented using threads also. It uses a separate thread for each connection. It spawns a new client thread every time a new connection from a client is accepted. This simplifies a lot the design of the server. Multi-threading, however, creates synchronization issues. We will present two implementations of the chat server. An implementation that focus on multi-threading without considering the synchronization issues will be presented first. Then we will focus on the synchronization issues that a multi-threaded implementation creates. Finally, an updated version of the multi-threaded chat server that fixes the synchronization issues is presented. The chat Client
  • 12.
    The code belowis the multi-threaded chat client. It uses two threads: one to read the data from the standard input and to sent it to the server, the other to read the data from the server and to print it on the standard output. The chat server We continue with the multi-threaded chat server. It uses a separate thread for each client. It spawns a new client thread every time a new connection from a client is accepted. This thread opens the input and the output streams for a particular client, it ask the client's name, it informs all clients about the fact that a new client has joined the chat room and, as long as it receive data, echos that data back to all other clients. When the client leaves the chat room, this thread informs also the clients about that and terminates. The synchronization issues of the multi-threaded chat server implementation Consider now the synchronization issues such an implementation creates. To simplify our task let us split the chat server code as follows, see the partitioned code below. Consider the portions of code colored in green, that is, the portions 2,4,6,8,10 (see the code above). All these portions use the array threads[]. This array, however, is shared by all threads of the server. The array is passed by reference to the constructor of the thread every time a new thread is created. The modification of this array by a thread is visible by all other threads. These portions of code are called critical sections because, if used uncontrolled, they can cause unexpected behaviour end even exceptions as explained below. Since all threads run concurrently, the access to this array is also concurrent. Suppose now that a thread (Thread 1) enters the portion 4 while another thread (Thread 2) enters the section 10 of the code. The section 4 uses the array threads[] to inform the clients about a new client. The section 10, however, removes from this array the thread references of the client that leaves the chat room. It can happen, that a threads[i] reference, while being used in section 4, is set to null in section 10 by another thread - by the thread of the client leaving the chat room. The table below shows this scenario. In this scenario the Thread 1 executes the if statement in portion 4. Suppose threads[i] is not null at this instant. Suppose, also, Thread 1 is interrupted by the operating system immediately after evaluating the if statement condition. It means, Thread 1 is put in a waiting queue, while the Thread 2 starts executing portion 10. Such kind of execution is called inter-living. Suppose, Thread 2 sets threads[i] to null when executing portion 10. Finally, Thread 1 is resumed and executesthreads[i].os.println() statement. But threads[i] is null at this instant. This will cause a null pointer exception. This exception will close abnormally the connection with a client. And all that because of another client that decided to leave the chat room. The same situation can arise if we consider the concurrency of any of the section 2,6,8 with the section 10. Such situations are not acceptable and must be resolved correctly in a concurrent multi-threaded application.
  • 13.
    To avoid suchkind of exception, the threads must be synchronized so that they execute the critical portions of code (in green) sequentially, and thus, without inter-living. For example, in the table below, the execution of the two portions of code is sequential - the critical sections executes without interruption. We call such execution synchronized. To archive this synchronization we have to use the synchronized(this){} statement, like below. All synchronized(this){} statements exclude mutually each other. It means, that when a thread enters thesynchronized(this){} statement it verifies first that any other synchronized(this){} statement is not being executed by another thread. If a such a statement is being executed by a thread, then this thread, as well as all other threads trying to execute a synchronized(this){} statement, are forced to wait until the thread executing the synchronized(this){} terminates this statement. When the thread executing a synchronized(this){}statement leaves the critical section, that is, when it terminates the synchronized(this){} statement, a thread waiting for critical section enters its synchronized(this){}. When a thread enters synchronized(this){}statement it blocks all other threads from entering their synchronized(this){} statements. Thus, putting all critical sections in synchronized(this){} statements we are guarantied that the chat server will execute correctly without rising null pointer exceptions caused by concurrent execution of other critical sections. The synchronized(this){} statement is a powerful tool. However, using it requires a good understanding of the synchronization issue. The incorrect use of synchronized(this){} statement can cause deadlocks of the program. A deadlock is a scenario when one thread waits for another thread to leave its critical section forever. To explain this scenario, suppose we extended the critical section 6 like below. This is, suppose thesynchronized(this){} statement includes a loop that potentially can execute forever. The while (true) loop will execute until it receives "/quit" command from the input stream. Suppose the"/quit" command never arrives or it arrives after a very long time. The thread executing this loop inside thesynchronized(this){} statement will block all other threads from executing their synchronized code because they will wait at their synchronized(this){} statements. For example, the portion of code in red (see below) will be never executed by Thread 2, if Thread 1 entered the while (true) loop and stays in forever. Thus, when synchronizing programs, an appropriate solution must be implemented to solve such issues, otherwise the synchronized(this){} statement can cause very long delays and even deadlocks. And certainly, you have to avoid putting unnecessary synchronized(this){} statements in the program. For example, it is not necessary to synchronize the portion 2 of code (see the table of the partitioned code). Even if this code modifies threads[] array, a better inspection of the code discovers that there is no risk this modification will create null pointer exceptions or other
  • 14.
    problems to theprogram. The synchronized version of the chat server In this section we present the updated version of the chat server that fixes the synchronization issues described in the previous section. The synchronized(this){} statement is used to solve the synchronization issues. Also, this version of chat server is improved to deliver private messages to clients. Compiling and running the application To try this application you have to compile the two programs: Example 25 and Example 26 (updated). Save these programs on your computer. Name the files MultiThreadChatClient.java andMultiThreadChatServerSync.java. Open a shell window on your computer and change the current directory to the directory where you saved these files. Type the following two commands in the shell window. If java compiler is installed on your computer and the PATH variable is configured for the shell to find javaccompiler, then these two command lines will create two new files in the current directory : the filesMultiThreadChatServerSync.class and MultiThreadChatClient.class Start the server in the shell window using the command: You will see the following message in this window telling you that the chat server is started and that it is listening for connections on port number 2222. The phraseUsage: java MultiThreadChatServerSync tells you that you can start the server specifying a parameter - the port number. By default, however, the port 2222 is used. Open a new shell window and change the current directory to the directory where you saved the application files. Start the client in the shell window using the command: You will see the following message in this window telling you that the client is started. Type, for example, the name Anonymous1 in this window. You will see the following output. telling you that the client Anonymous1 entered the chat room. It tells you also that to quit the chat room the client has to enter /quit command. Open one more shell window and change the current directory to the directory where you saved the application files. Start a new client client in the shell window using the command: You will see the following message in this window telling you that the client is started. Now you have two clients connected to the server. Type, for example, the text Anonymous2 in this window. You will see the following output. telling you that the client Anonymous2 entered the chat room. It tells you also that to quit the
  • 15.
    chat room theclient has to enter /quit command. In the window of the client Anonymous1 the following message will be printed. If we enter now a message in any of the client window the message will be printed also in the window of the other client. This kind of message exchange is a chat session. A chat session example Below we show a possible scenario of a chat session between the two clients. Conclusions Java sockets API (Socket and ServerSocket classes) is a powerful and flexible interface for network programming of client/server applications. On the other hand, Java threads is another powerful programming framework for client/server applications. Multi-threading simplifies the implementation of complex client/server applications. However, it introduces synchronization issues. These issues are caused by the concurrent execution of critical sections of the program by different threads. The synchronized(this){} statement allows us to synchronize the execution of the critical sections. Using this statement, however, requires a good understanding of the synchronization issues. The incorrect use ofsynchronized(this){} statement can cause other problems, such as deadlocks and/or performance degradation of the program. Socket MyClient; MyClient = new Socket("MachineName", PortNumber);