Skip to content

Commit ca91741

Browse files
BAEL-9200 | Thread per Connection vs Thread per Request (#18448)
* BAEL-9200 | Socket web servers * BAEL-9200 | Liam guidelines. * BAEL-9200 | rollback * BAEL-9200 | rollback * Guidelines * BAEL-9200 | fix pom * BAEL-9200 | moved in core-java * BAEL-9200 | fixes * Update core-java-modules/core-java-sockets/src/main/java/com/baeldung/threading/request/RequestHandler.java Co-authored-by: Liam Williams <[email protected]> * Update core-java-modules/core-java-sockets/src/test/java/com/baeldung/threading/ThreadModelManualTest.java Co-authored-by: Liam Williams <[email protected]> * BAEL-9200 | try with resources * BAEL-9200 | refactor method * Apply suggestions from code review Co-authored-by: Liam Williams <[email protected]> * BAEL-9200 | That's clean code. * BAEL-9200 | try with resources * BAEL-9200 | pr comments --------- Co-authored-by: Liam Williams <[email protected]>
1 parent fa0a67a commit ca91741

File tree

8 files changed

+296
-0
lines changed

8 files changed

+296
-0
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns="http://maven.apache.org/POM/4.0.0"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<artifactId>core-java-sockets</artifactId>
7+
<version>0.0.1-SNAPSHOT</version>
8+
<packaging>jar</packaging>
9+
<name>core-java-sockets</name>
10+
11+
<parent>
12+
<groupId>com.baeldung.core-java-modules</groupId>
13+
<artifactId>core-java-modules</artifactId>
14+
<version>0.0.1-SNAPSHOT</version>
15+
</parent>
16+
17+
</project>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.baeldung.threading;
2+
3+
import java.io.BufferedReader;
4+
import java.io.Closeable;
5+
import java.io.IOException;
6+
import java.io.InputStreamReader;
7+
import java.io.PrintWriter;
8+
import java.io.Reader;
9+
import java.io.Writer;
10+
import java.net.Socket;
11+
12+
public class ClientConnection implements Closeable {
13+
14+
private final Socket socket;
15+
private final BufferedReader reader;
16+
private final PrintWriter writer;
17+
18+
public ClientConnection(Socket socket) throws IOException {
19+
this.socket = socket;
20+
this.reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
21+
this.writer = new PrintWriter(socket.getOutputStream(), true);
22+
}
23+
24+
public Socket getSocket() {
25+
return socket;
26+
}
27+
28+
public BufferedReader getReader() {
29+
return reader;
30+
}
31+
32+
public PrintWriter getWriter() {
33+
return writer;
34+
}
35+
36+
@Override
37+
public void close() throws IOException {
38+
try (Writer writer = this.writer; Reader reader = this.reader; Socket socket = this.socket) {
39+
// resources all closed when this block exits
40+
}
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.baeldung.threading.connection;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
import com.baeldung.threading.ClientConnection;
7+
8+
public class ThreadPerConnection extends Thread {
9+
10+
private static final Logger logger = LoggerFactory.getLogger(ThreadPerConnection.class);
11+
12+
private final ClientConnection clientConnection;
13+
14+
public ThreadPerConnection(ClientConnection clientConnection) {
15+
this.clientConnection = clientConnection;
16+
}
17+
18+
@Override
19+
public void run() {
20+
try (ClientConnection client = this.clientConnection) {
21+
String request;
22+
while ((request = client.getReader()
23+
.readLine()) != null) {
24+
Thread.sleep(1000); // simulate server doing work
25+
logger.info("Processing request: {}", request);
26+
clientConnection.getWriter()
27+
.println("HTTP/1.1 200 OK - Processed request: " + request);
28+
logger.info("Processed request: {}", request);
29+
}
30+
} catch (Exception e) {
31+
logger.error("Error processing request", e);
32+
}
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.baeldung.threading.connection;
2+
3+
import java.io.IOException;
4+
import java.net.ServerSocket;
5+
import java.net.Socket;
6+
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
10+
import com.baeldung.threading.ClientConnection;
11+
12+
public class ThreadPerConnectionServer {
13+
14+
private static final Logger logger = LoggerFactory.getLogger(ThreadPerConnectionServer.class);
15+
16+
private static final int PORT = 8080;
17+
18+
public static void main(String[] args) {
19+
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
20+
logger.info("Server started on port {}", PORT);
21+
while (!serverSocket.isClosed()) {
22+
try {
23+
Socket newClient = serverSocket.accept();
24+
logger.info("New client connected: {}", newClient.getInetAddress());
25+
ClientConnection clientConnection = new ClientConnection(newClient);
26+
new ThreadPerConnection(clientConnection).start();
27+
} catch (IOException e) {
28+
logger.error("Error accepting connection", e);
29+
}
30+
}
31+
} catch (IOException e) {
32+
logger.error("Error starting server", e);
33+
}
34+
}
35+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.baeldung.threading.request;
2+
3+
import java.io.PrintWriter;
4+
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
7+
8+
public class ThreadPerRequest extends Thread {
9+
10+
private static final Logger logger = LoggerFactory.getLogger(ThreadPerRequest.class);
11+
12+
private final PrintWriter writer;
13+
private final String request;
14+
15+
public ThreadPerRequest(PrintWriter writer, String request) {
16+
this.writer = writer;
17+
this.request = request;
18+
}
19+
20+
@Override
21+
public void run() {
22+
try {
23+
Thread.sleep(1000); // simulate server doing work
24+
logger.info("Processing request: {}", request);
25+
writer.println("HTTP/1.1 200 OK - Processed request: " + request);
26+
logger.info("Processed request: {}", request);
27+
} catch (Exception e) {
28+
logger.error("Error processing request", e);
29+
}
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package com.baeldung.threading.request;
2+
3+
import java.io.BufferedReader;
4+
import java.io.IOException;
5+
import java.net.ServerSocket;
6+
import java.net.Socket;
7+
import java.net.SocketException;
8+
import java.util.ArrayList;
9+
import java.util.Iterator;
10+
import java.util.List;
11+
12+
import org.slf4j.Logger;
13+
import org.slf4j.LoggerFactory;
14+
15+
import com.baeldung.threading.ClientConnection;
16+
17+
public class ThreadPerRequestServer {
18+
19+
private static final Logger logger = LoggerFactory.getLogger(ThreadPerRequestServer.class);
20+
private static final int PORT = 8080;
21+
22+
public static void main(String[] args) {
23+
List<ClientConnection> clientConnections = new ArrayList<>();
24+
25+
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
26+
logger.info("Server started on port {}", PORT);
27+
28+
while (!serverSocket.isClosed()) {
29+
acceptNewConnections(serverSocket, clientConnections);
30+
handleRequests(clientConnections);
31+
}
32+
33+
} catch (IOException e) {
34+
logger.error("Server error", e);
35+
} finally {
36+
closeClientConnection(clientConnections);
37+
}
38+
}
39+
40+
private static void acceptNewConnections(ServerSocket serverSocket, List<ClientConnection> clientConnections) throws SocketException {
41+
serverSocket.setSoTimeout(100);
42+
try {
43+
Socket newClient = serverSocket.accept();
44+
ClientConnection clientConnection = new ClientConnection(newClient);
45+
clientConnections.add(clientConnection);
46+
logger.info("New client connected: {}", newClient.getInetAddress());
47+
} catch (IOException ignored) {
48+
// ignore expected socket timeout
49+
}
50+
}
51+
52+
private static void handleRequests(List<ClientConnection> clientConnections) {
53+
Iterator<ClientConnection> iterator = clientConnections.iterator();
54+
while (iterator.hasNext()) {
55+
ClientConnection client = iterator.next();
56+
57+
if (client.getSocket()
58+
.isClosed()) {
59+
logger.info("Client disconnected: {}", client.getSocket()
60+
.getInetAddress());
61+
iterator.remove();
62+
continue;
63+
}
64+
65+
try {
66+
BufferedReader reader = client.getReader();
67+
if (reader.ready()) {
68+
String request = reader.readLine();
69+
if (request != null) {
70+
new ThreadPerRequest(client.getWriter(), request).start();
71+
}
72+
}
73+
} catch (IOException e) {
74+
logger.error("Error reading from client {}", client.getSocket()
75+
.getInetAddress(), e);
76+
}
77+
}
78+
}
79+
80+
private static void closeClientConnection(List<ClientConnection> clientConnections) {
81+
for (ClientConnection client : clientConnections) {
82+
try {
83+
client.close();
84+
} catch (IOException e) {
85+
logger.error("Error closing client connection", e);
86+
}
87+
}
88+
}
89+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.baeldung.threading;
2+
3+
import java.io.BufferedReader;
4+
import java.io.IOException;
5+
import java.io.InputStreamReader;
6+
import java.io.PrintWriter;
7+
import java.net.Socket;
8+
9+
import org.junit.jupiter.api.Assertions;
10+
import org.junit.jupiter.api.Test;
11+
12+
// Note: ThreadPerConnectionServer or ThreadPerRequestServer needs to be started externally in order to execute this test.
13+
class ThreadModelManualTest {
14+
15+
private static final String HOST = "localhost";
16+
private static final int PORT = 8080;
17+
18+
@Test
19+
void whenSendingRequestWithDifferentConnections_thenResponseReceived() throws IOException {
20+
for (int i = 1; i <= 3; i++) {
21+
try (Socket socket = new Socket(HOST, PORT);
22+
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
23+
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
24+
String request = "Request " + i;
25+
writer.println(request);
26+
String response = reader.readLine();
27+
Assertions.assertEquals("HTTP/1.1 200 OK - Processed request: " + request, response);
28+
}
29+
}
30+
}
31+
32+
@Test
33+
void whenSendingRequestWithSameConnection_thenResponseReceived() throws IOException, InterruptedException {
34+
try (Socket socket = new Socket(HOST, PORT);
35+
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
36+
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
37+
for (int i = 1; i <= 3; i++) {
38+
String request = "Request " + i;
39+
writer.println(request);
40+
Thread.sleep(2000); // simulate gap between client requests
41+
String response = reader.readLine();
42+
43+
Assertions.assertEquals("HTTP/1.1 200 OK - Processed request: " + request, response);
44+
}
45+
}
46+
}
47+
}

core-java-modules/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@
224224
<module>core-java-security-5</module>
225225
<module>core-java-security-algorithms</module>
226226
<module>core-java-serialization</module>
227+
<module>core-java-sockets</module>
227228
<module>core-java-streams</module>
228229
<module>core-java-streams-simple</module>
229230
<module>core-java-streams-3</module>

0 commit comments

Comments
 (0)