Socket
BSD UNIX first introduced a socket in 1982.
A socket is a communication connection point written in software and uses TCP/IP.
Java also has Socket API.
Once the client and server sockets are connected, they can communicate using the stream.
To pass a message from a client to a server, you can implement:
- A client program creates an output stream whose destination is its socket.
- A server program creates an input stream whose source is its socket.
- A client sends messages to the output stream, and a server gets them through the input stream.
Both clients and servers use the java.net.Socket class to communicate with each other. To connect these sockets, you need the java.net.ServerSocket class on the server-side. ServerSocket acts as a front desk. When a socket connection request comes from the outside, a ServerSocket creates a server-side socket and connects it to the client socket.
Server.java
package net.java_school.socket; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(3000); Socket socket = serverSocket.accept(); //TODO } }
Socket socket = serverSocket.accept();
The above code stops the program and waits for external socket connection requests. When a socket connection request comes in, the accept() method creates a server-side socket, connects it to the client socket, and returns its reference. Server-side sockets will have randomly chosen numbers among free port numbers.
Server.java
package net.java_school.socket; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(3000); Socket socket = serverSocket.accept(); OutputStream os = socket.getOutputStream(); OutputStreamWriter osw = new OutputStreamWriter(os); BufferedWriter bw = new BufferedWriter(osw); PrintWriter pw = new PrintWriter(bw); pw.println("Socket Connected[" + socket.getPort() + "]"); } }
Create a client program.
The client needs to know the server IP and the port number of the ServerSocket to send a connection request.
If you test with a single computer, you can replace IP with localhost. If you have two computers, replace localhost with the server IP address in the source below.
The port number is 3000 because the ServerSocket uses port 3000.
Client.java
package net.java_school.socket; import java.io.IOException; import java.net.Socket; import java.net.UnknownHostException; public class Client { public static void main(String[] args) throws UnknownHostException, IOException { Socket socket = new Socket("localhost", 3000); //TODO } }
When sockets are connected, the server delivers the message to the client.
Add the following code to output things sent by the server.
Client.java
package net.java_school.socket; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; import java.net.UnknownHostException; public class Client { public static void main(String[] args) throws UnknownHostException, IOException { Socket socket = new Socket("localhost", 3000); OutputStream os = socket.getOutputStream(); OutputStreamWriter osw = new OutputStreamWriter(os); BufferedWriter bw = new BufferedWriter(osw); PrintWriter pw = new PrintWriter(bw); pw.println("Socket Connected[Port:" + socket.getPort() + "]"); pw.flush(); } }
The server program passes the connected server-side socket port number as a message to the client and ends immediately. The client program receives the message from the server and prints the message to the console and exits.
Run the server first, then run the client. When running on the same PC, run the server and client with separate command prompts.
When a connection request comes in from a client, the server sends a message and shuts down immediately. Modifying the server to not exit allows multiple clients to connect to the server.
Server.java
public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(3000); while (true) { Socket socket = serverSocket.accept(); OutputStream os = socket.getOutputStream(); OutputStreamWriter osw = new OutputStreamWriter(os); BufferedWriter bw = new BufferedWriter(osw); PrintWriter pw = new PrintWriter(bw); pw.println("Socket Connected[Port:" + socket.getPort() + "]"); pw.flush(); pw.close(); socket.close(); } }
Typing Ctrl + C will force the server to shut down.
Let's practice a more advanced communication example.
The following echo program sends the message sent by the client back to the client.
Server.java
package net.java_school.socket; public class Server { //TODO }
The server has to do two things at the same time.
- Handling socket connection requests
- Receiving the message from the client and sending it back to the client
Our echo program needs a thread to do two things together.
Let code that receives the client's message and sends it back to the client run on the new thread.
We need the server code to receive the client's message and send it back to the client.
Let's create a server program so that an input stream, whose source is a socket, and an output stream, whose source is a socket, run on a thread.
It would be nice to create a thread class that consists of a socket, an input stream, and an output stream. Let's name this class Echo. --By making the Echo class an inner class of the Server class, the Echo class can easily access the Server class's resources--
Server.java
package net.java_school.socket; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; public class Server { private class Echo extends Thread { private Socket socket; private BufferedReader br; private PrintWriter pw; public Echo(Socket socket) throws IOException { this.socket = socket; InputStream is = socket.getInputStream(); br = new BufferedReader(new InputStreamReader(is)); OutputStream os = socket.getOutputStream(); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os)); pw = new PrintWriter(bw); } @Override public void run() { try { while (true) { String str = br.readLine(); pw.println("From Server: " + str); pw.flush(); } } catch (Exception e) { e.printStackTrace(); close(); } } private void close() { try { br.close(); } catch (IOException e) { e.printStackTrace(); } pw.close(); try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } }//Echo inner class end }
To maintain a socket connection, we must keep an Echo object. To do this, add an ArrayList to hold the Echo instance. At the end of Echo's close() method, add code to remove the Echo reference from the ArrayList.
Server.java
package net.java_school.socket; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; import java.util.ArrayList; public class Server { private ArrayList<Echo> echos = new ArrayList<Echo>(); private class Echo extends Thread { private Socket socket; private BufferedReader br; private PrintWriter pw; public Echo(Socket socket) throws IOException { this.socket = socket; InputStream is = socket.getInputStream(); br = new BufferedReader(new InputStreamReader(is)); OutputStream os = socket.getOutputStream(); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os)); pw = new PrintWriter(bw); } @Override public void run() { try { while (true) { String str = br.readLine(); pw.println("From Server: " + str); pw.flush(); } } catch (Exception e) { e.printStackTrace(); close(); } } private void close() { try { br.close(); } catch (IOException e) { e.printStackTrace(); } pw.close(); try { socket.close(); } catch (IOException e) { e.printStackTrace(); } echos.remove(this); } }//Echo inner class end }
Next, add the method to handle external socket connection requests to the Server class. -- It would be nice to put the ServerSocket's accept() in an infinite loop in this method --
Have main() execute this method immediately after creating the Server instance.
Server.java
package net.java_school.socket; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; public class Server { private ArrayList<Echo> echos = new ArrayList<Echo>(); public void startServer() { ServerSocket serverSocket = null; try { serverSocket = new ServerSocket(3000); while (true) { Socket socket = serverSocket.accept(); Echo echo = new Echo(socket); echo.start(); echos.add(echo); } } catch (IOException e) { e.printStackTrace(); } } private class Echo extends Thread { private Socket socket; private BufferedReader br; private PrintWriter pw; public Echo(Socket socket) throws IOException { this.socket = socket; InputStream is = socket.getInputStream(); br = new BufferedReader(new InputStreamReader(is)); OutputStream os = socket.getOutputStream(); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os)); pw = new PrintWriter(bw); } @Override public void run() { try { while (true) { String str = br.readLine(); pw.println("From Server: " + str); pw.flush(); } } catch (Exception e) { e.printStackTrace(); close(); } } private void close() { try { br.close(); } catch (IOException e) { e.printStackTrace(); } pw.close(); try { socket.close(); } catch (IOException e) { e.printStackTrace(); } echos.remove(this); } }//Echo inner class end public static void main(String[] args) { new Server().startServer(); } }
Next, create a client program.
Client.java
package net.java_school.socket; public class Client { //TODO }
Should the client be a multi-threaded program?
If the message comes from the server while the user is writing, the answer is yes. -- For the same reason, the client of the chat program must be a multi-threaded program -- But in the echo program, the server and client are synchronized. The server can send a message to the client only when the client sends something to the server. So, the Echo client does not need extra thread.
Add code to connect sockets.
Client.java
package net.java_school.socket; import java.io.IOException; import java.net.Socket; import java.net.UnknownHostException; public class Client { public static void main(String[] args) throws UnknownHostException, IOException { Socket socket = new Socket("localhost", 3000); //TODO } }
Next, add an input stream whose source is the keyboard.
Client.java
package net.java_school.socket; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; import java.net.UnknownHostException; public class Client { public static void main(String[] args) throws UnknownHostException, IOException { Socket socket = new Socket("localhost", 3000); BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in)); //TODO } }
Next, add an output stream whose destination is the socket.
Client.java
package net.java_school.socket; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; import java.net.UnknownHostException; public class Client { public static void main(String[] args) throws UnknownHostException, IOException { Socket socket = new Socket("localhost", 3000); BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in)); OutputStream os = socket.getOutputStream(); OutputStreamWriter osw = new OutputStreamWriter(os); PrintWriter pw = new PrintWriter(osw); //TODO } }
Next, add an input stream whose source is the socket.
This input stream is needed to read messages sent from the server.
Client.java
package net.java_school.socket; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; import java.net.UnknownHostException; public class Client { public static void main(String[] args) throws UnknownHostException, IOException { Socket socket = new Socket("localhost", 3000); BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in)); OutputStream os = socket.getOutputStream(); OutputStreamWriter osw = new OutputStreamWriter(os); PrintWriter pw = new PrintWriter(osw); InputStream is = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); //TODO } }
Because it is a single-threaded program with only the main thread, add a flag to distinguish whether the user is waiting for input on the keyboard or waiting for a message from the server.
Client.java
package net.java_school.socket; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; import java.net.UnknownHostException; public class Client { public static void main(String[] args) throws UnknownHostException, IOException { Socket socket = new Socket("localhost", 3000); BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in)); OutputStream os = socket.getOutputStream(); OutputStreamWriter osw = new OutputStreamWriter(os); PrintWriter pw = new PrintWriter(osw); InputStream is = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); boolean isCommandLineInputWaiting = true; String str = null; while (true) { if (isCommandLineInputWaiting) { str = keyboard.readLine(); pw.println(str); pw.flush(); isCommandLineInputWaiting = false; continue; } if (isCommandLineInputWaiting == false) { str = br.readLine(); System.out.println(str); isCommandLineInputWaiting = true; continue; } } } }
Test on two computers
Replace "localhost" with the server IP in the Client source.
The computer running the server should open port 3000. If your system is Windows, you need to take steps to open the ports in Windows Firewall.
Chat program
Let's create a simple chat program by extending the example above. The difference with the echo program is that the server forwards the received message to all users.
Client.java
package net.java_school.socket; public class Client { //TODO }
The chat client must be a multi-threaded program, as messages can come from the server while the user is typing on the keyboard.
Client.java
package net.java_school.socket; public class Client extends Thread { @Override public void run() { //TODO } }
When the client runs, it connects sockets with the server and gets an input stream whose source is its socket.
Client.java
package net.java_school.socket; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.Socket; public class Client extends Thread { private Socket socket; private BufferedReader br; public Client() throws IOException { this.socket = new Socket("localhost", 3000); InputStream is = socket.getInputStream(); br = new BufferedReader(new InputStreamReader(is)); //TODO } @Override public void run() { //TODO } public static void main(String[] args) throws IOException { new Client(); } }
The run() method prints messages from the server.
Client.java
package net.java_school.socket; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.Socket; public class Client extends Thread { private Socket socket; private BufferedReader br; public Client() throws IOException { this.socket = new Socket("localhost", 3000); InputStream is = socket.getInputStream(); br = new BufferedReader(new InputStreamReader(is)); //TODO } @Override public void run() { String str = null; while(true) { try { str = br.readLine(); System.out.println(str); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) throws IOException { new Client(); } }
To send a message to the server, you need an input stream whose source is the keyboard and an output stream whose socket is the destination.
Client.java
package net.java_school.socket; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; public class Client extends Thread { private Socket socket; private BufferedReader br; private BufferedReader keyboard; private PrintWriter pw; public Client() throws IOException { this.socket = new Socket("localhost", 3000); InputStream is = socket.getInputStream(); br = new BufferedReader(new InputStreamReader(is)); keyboard = new BufferedReader(new InputStreamReader(System.in)); OutputStream os = socket.getOutputStream(); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os)); pw = new PrintWriter(bw); } @Override public void run() { String str = null; while(true) { try { str = br.readLine(); System.out.println(str); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) throws IOException { new Client(); } }
The chatStart() method transfers input from the keyboard to the server.
Client.java
package net.java_school.socket; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; public class Client extends Thread { private Socket socket; private BufferedReader br; private BufferedReader keyboard; private PrintWriter pw; public Client() throws IOException { this.socket = new Socket("localhost", 3000); InputStream is = socket.getInputStream(); br = new BufferedReader(new InputStreamReader(is)); keyboard = new BufferedReader(new InputStreamReader(System.in)); OutputStream os = socket.getOutputStream(); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os)); pw = new PrintWriter(bw); } public void chatStart() { start(); String str = null; while (true) { try { str = keyboard.readLine(); pw.println(str); pw.flush(); } catch (IOException e) { e.printStackTrace(); } } } @Override public void run() { String str = null; while(true) { try { str = br.readLine(); System.out.println(str); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) throws IOException { new Client().chatStart(); } }
You can create a chat server simply by modifying the echo server.
- Rename the inner class Echo of the echo server to Chatter
- Add code to check if the message sent by the client is null. --The client sends null to the server when it exits--
@Override public void run() { try { String str = null; while (true) { str= br.readLine(); if (str != null) { for (Echo echo : echos) { echo.pw.println(str); echo.pw.flush(); } } else { throw new Exception("null!"); } } } catch (Exception e) { e.printStackTrace(); close(); } }
Bug fixes
When you shut down the server, all clients constantly output null to the console.
Modify the client's run() method as follows:
@Override public void run() { String str = null; try { while((str = br.readLine()) != null) { System.out.println(str); } } catch (IOException e) { e.printStackTrace(); } }