Network Socket

Network Socket

Create Network Connection via Socket

In network programming, a server is a program that keeps listening to messages from clients. And a client is a program that uses a server.

If two programs want to create a network connection, one of them should act as the server, and the other should be the client. The server creates a server socket, attaches it to a particular port on its own computer(i.e., attach the socket to an IP address and port number), and then listens for incoming connections:

1
ServerSocket serverSocket = new ServerSocket(9527); // In Java, the server creates a ServerSocket, listening to the 9527 port.

As for the client, it creates an ordinary socket, and uses it to make a connection request:

1
Socket socket = new Socket("localhost", 9527); // In Java, the client creates a Socket, using the IP address and port number to connect the ServerSocket. Note that "localhost" is the server's IP address.

If everything goes well, the server will accept the connection. In Java, the ServerSocket uses the accept method to accept a connection request from a client. Then it creates a dedicated ordinary Socket for talking to this client:

1
Socket newClientSocket = serverSocket.accept();

After the connection, the server and the client can communicate via socket(owned by the client) and newClientSocket(owned by the server).

network_socket_1

Communication via Socket

In Java,java.net.Sockethas the following methods:

1
2
3
4
Socket(String hostname, int port) // a constrcutor that creates a new Socket to talk to hostname at port
OutputStream getOutputStream() // send messages to the other program
InputStream getInputStream() // receive messages from the other program
void close() // close a socket, which means ending the connection

And java.net.ServerSocket has the following methods:

1
2
3
4
5
ServerSocket(int port) // a constructor that creates a new ServerSocket that listens at port
OutputStream getOutputStream () // send messages to the other program
InputStream getInputStream () // receive messages from the other program
void close () // close a socket, which means ending the connection
Socket accept ()// accept a connection request from a client and create a dedicated ordinary Socket for talking to this client

Therefore, in the view of a client, the communication via socket would like this:

  1. Create a Socket to talk to a server.
  2. Use the Socket’s OutputStream to send messages to the server.
  3. Use the Socket’s InputStream to receive messages from the server.
  4. Keep doing this(sending&receiving message) as long as the protocol requires.
  5. Close the connection.

In the view of a single-thread server, the communication would like this:

  1. Create a ServerSocket on a port.
  2. Accept connection request from a client, creating a dedicated Socket.
  3. Use the dedicated Socket’s InputStream to receive messages.
  4. Use the Socket’s OutputStream to send messages.
  5. Keep doing this as long as the protocol requires.
  6. Close the dedicated Socket.
  7. Go back to receive connection requests.

You might have noticed that a single-thread server can only service one client at a time. For this reason, we normally use multi-threaded server, which creates a separate thread to service each client. And a multi-thread server can be available at all times.

Network Input/Output

For network I/O, we can use DataInputStream/DataOutputStream and ObjectInputStream/ObjectOutputStream.

DataInputStream/DataOutputStream are sequences of basic data values, such as integers, floats, characters, etc. We can use them to send/recieve data like this:

1
2
3
4
5
6
7
8
9
10
11
Socket socket = new Socket("localhost", 9527);

try(DataOutputStream out = DataOutputStream(socket.getOutputStream);
DataInputStream in = DataInputStream(socket.getInputStream);) {

out.writeInt(25);// send integer 25 to the server
int response = in.readInt();// receive response(which is an integer) from the server

} catch (Exception e) {
// handle exception here
}

ObjectInputStream/ObjectOutputStream are sequences of objects, and these objects are so-called serialized objects, which are represented as sequences of bytes. Suppose we have a class Person which implements the interface Serializable:

1
public class People implements Serializable {...}

We can use ObjectInputStream/ObjectOutputStream to send/recieve Person object:

1
2
3
4
5
6
7
8
9
10
11
12
Person person = new Person();
Socket socket = new Socket("localhost", 9527);

try(ObjectOutputStream out = ObjectOutputStream(socket.getOutputStream);
ObjectInputStream in = ObjectInputStream(socket.getInputStream);) {

out.writeObject(person);// send object 'person' to the server
Person newPerson = in.readObject();// receive response(which is a Person object) from the server

} catch (Exception e) {
// handle exception here
}

Simple Server&Client Demo

In Java, a simple server is like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* A simple multi-threaded server implementation
*
*/
public class SimpleServer {

public static final int MAX_THREADS = 10;

/**
* Constructor for the SimpleServer class
*
* @param port port number to listen on
*/
public SimpleServer(int port) {

// initialise ServerSocket object that will be used to accept new clients
try (ServerSocket serverSocket = new ServerSocket(port)) {

System.out.println("Creating server listening "
+ "to port " + port);

// initialise a fixed size thread pool that can allow up to MAX_THREADS concurrent threads
ExecutorService threadPool =
Executors.newFixedThreadPool(MAX_THREADS);

// an infinite loop to accept clients indefinitely (on the main thread)
while (true) {
System.out.println("Waiting for client to connect");
// call .accept to wait for a new client to connect
// a new socket object is returned by .accept when the
// new client connects successfully
Socket newClientSocket = serverSocket.accept();
// pass the socket created for the new client to a separate
// ClientHandlerThread object and execute it on the thread pool
threadPool.execute(
new ClientHandlerThread(newClientSocket));
}

}

catch (IOException e) {
e.printStackTrace();
}

}

/**
* Inner class to represent a dedicated task that handles a particular client
*/
private class ClientHandlerThread implements Runnable {

private Socket clientSocket;

/**
* Simple constructor that takes
* the socket created by the ServerSocket.accept()
* method
*
* @param clientSocket the socket created
* by serverSocket.accept() that
* communicates to a specific client
*/
public ClientHandlerThread(Socket clientSocket) {
this.clientSocket = clientSocket;
}

/*
* every thing that happens inside the run method
* will execute on a new thread
* (put as much work in here as you can)
*/

@Override
public void run() {
System.out.println("Connected to client");

try (ObjectOutputStream clientOut =
new ObjectOutputStream(clientSocket.getOutputStream());
ObjectInputStream clientIn =
new ObjectInputStream(clientSocket.getInputStream());) {

// Since I am sending String objects,
// I can safely downcast to String here
String request = (String) clientIn.readObject();
System.out.println("received request: "
+ request);

String response = request; // echo server

System.out.println("sending response: "
+ response);
clientOut.writeObject(response);

} catch (IOException e) {
System.out.println("Connection with client lost.");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

}

}

public static void main(String[] args) {
int port = 9527;
new SimpleServer(port);
}

}

And a simple client is like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

/**
* A simple client class implementation
*/
public class SimpleClient {

private Scanner in;

/**
* Constructor for client
* @param host IP address of host
* @param port port number that server
* is listening to
*/
public SimpleClient(String host, int port) {

// socket created on client side with BOTH host IP address and port
try (Socket socket = new Socket(host, port);
// wrap input/output streams in object input/output streams
ObjectInputStream serverIn =
new ObjectInputStream(socket.getInputStream());
ObjectOutputStream serverOut =
new ObjectOutputStream(socket.getOutputStream());) {

System.out.println("Connected: " + socket);

in = new Scanner(System.in);

// take input from terminal and send it to server
System.out.println("Enter the message to "
+ "be sent to the server:");
String request = in.nextLine();

System.out.println("sending request: " + request);
serverOut.writeObject(request);

// hang here until server replies
String response =
(String) serverIn.readObject();

System.out.println("received response from server: "
+ response);

} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
System.out.println("connection with server lost");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

}

public static void main(String[] args) {
String host = "localhost";
int port = 9527;

new SimpleClient(host, port);
}

}

We can use terminal to run the above server and client seperately, and use them to communicate with each other:

network_socket_2