diff --git a/week14/rabbit-mq/hello_rabbitmq_java/.gitignore b/week14/rabbit-mq/hello_rabbitmq_java/.gitignore
new file mode 100644
index 0000000..5ff6309
--- /dev/null
+++ b/week14/rabbit-mq/hello_rabbitmq_java/.gitignore
@@ -0,0 +1,38 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/week14/rabbit-mq/hello_rabbitmq_java/pom.xml b/week14/rabbit-mq/hello_rabbitmq_java/pom.xml
new file mode 100644
index 0000000..05ece4d
--- /dev/null
+++ b/week14/rabbit-mq/hello_rabbitmq_java/pom.xml
@@ -0,0 +1,57 @@
+
+ 4.0.0
+
+ dev.mq
+ hello_rabbitmq_java
+ 1.0-SNAPSHOT
+ jar
+
+ hello_rabbitmq_java
+ http://maven.apache.org
+
+
+ UTF-8
+
+
+
+
+ junit
+ junit
+ 3.8.1
+ test
+
+
+ com.rabbitmq
+ amqp-client
+ 5.18.0
+
+
+
+
+ org.slf4j
+ slf4j-simple
+ 2.0.16
+ test
+
+
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.16
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 8
+ 8
+
+
+
+
+
diff --git a/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/App.java b/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/App.java
new file mode 100644
index 0000000..75cf853
--- /dev/null
+++ b/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/App.java
@@ -0,0 +1,13 @@
+package dev.mq;
+
+/**
+ * Hello world!
+ *
+ */
+public class App
+{
+ public static void main( String[] args )
+ {
+ System.out.println( "Hello World!" );
+ }
+}
diff --git a/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/steo01/Receiver.java b/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/steo01/Receiver.java
new file mode 100644
index 0000000..7a5b7f3
--- /dev/null
+++ b/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/steo01/Receiver.java
@@ -0,0 +1,40 @@
+package dev.mq.steo01;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeoutException;
+
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+import com.rabbitmq.client.DeliverCallback;
+
+/**
+ * Receiver - 메시지 소비자 (Consumer)행
+ *
+ */
+public class Receiver {
+ private static final String QUEUE_NAME = "hello";
+
+ public static void main(String[] args) throws IOException, TimeoutException {
+
+ ConnectionFactory factory = new ConnectionFactory();
+ factory.setHost("localhost");
+
+ // RabbitMQ 서버(5672)와 커넥션 연결 수행
+ Connection connection = factory.newConnection();
+ // 채녈 생성
+ Channel channel = connection.createChannel();
+
+ // 큐 생성
+ channel.queueDeclare(QUEUE_NAME, false, false, false, null);
+ System.out.println(" [Receiver] Waiting ... ");
+
+ DeliverCallback deliverCallback = (consumerTag, delivery) -> {
+ String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
+ System.out.println("[Receiver] 메시지를 받음: " + message);
+ };
+
+ channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {});
+ }
+}
diff --git a/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/steo01/Sender.java b/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/steo01/Sender.java
new file mode 100644
index 0000000..005bf07
--- /dev/null
+++ b/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/steo01/Sender.java
@@ -0,0 +1,39 @@
+package dev.mq.steo01;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeoutException;
+
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+
+/**
+ * Sender - 메시지 발행자 (Publisher)
+ *
+ * 퍼블리셔는 RabbitMq와 연결, 메시지를 전송하는 처리 수행
+ * 컨슈머는 메시지 큐로부터 퍼블리셔가 적재한 메시지를 소비하는 처리 수행
+ */
+public class Sender {
+ private static final String QUEUE_NAME = "hello";
+
+ public static void main(String[] args) throws IOException, TimeoutException {
+ ConnectionFactory factory = new ConnectionFactory();
+ factory.setHost("localhost");
+
+ // RabbitMQ 서버(5672)와 커넥션 연결 수행
+ Connection connection = factory.newConnection();
+ // 채녈 생성
+ Channel channel = connection.createChannel();
+
+ // 큐 생성
+ channel.queueDeclare(QUEUE_NAME, false, false, false, null);
+
+ String messgae = "Hell World!";
+
+ // Publish
+ channel
+ .basicPublish("", QUEUE_NAME, null, messgae.getBytes(StandardCharsets.UTF_8));
+ System.out.println(" [Publisher] Sent = " + messgae);
+ }
+}
diff --git a/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/step02/NewTask.java b/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/step02/NewTask.java
new file mode 100644
index 0000000..f9d14be
--- /dev/null
+++ b/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/step02/NewTask.java
@@ -0,0 +1,46 @@
+package dev.mq.step02;
+
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+import com.rabbitmq.client.MessageProperties;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+/*
+ 작업(Task)을 작업 큐(Work queue)에 스케줄링하는 프로그램
+ 문자열값이 어떤 복잡한 작업이라고 가정할 때,
+ ex. Hello...이라고 하면
+ . 하나는 1초가 소요되는 작업이라고 가정
+
+ 따라서 Hello...는 3초가 걸리는 작업이라고 가정
+
+ 실행 인자값(args) 활용해서 Hello...과 같은 값 입력
+ */
+public class NewTask {
+
+ private static final String TASK_QUEUE_NAME = "task_queue";
+
+ public static void main(String[] args) throws IOException, TimeoutException {
+ // 서버 연결
+ ConnectionFactory factory = new ConnectionFactory();
+ factory.setHost("localhost");
+ try (Connection connection = factory.newConnection();
+ Channel channel = connection.createChannel()) {
+
+ // 큐 생성
+ boolean durable = true;
+ channel.queueDeclare(TASK_QUEUE_NAME, durable, false, false, null);
+ // 실행 옵션으로 메시지를 생성, ex. Hello...
+ String message = String.join(" ", args);
+
+ // 메시지 적재
+ channel.basicPublish("", TASK_QUEUE_NAME,
+ MessageProperties.PERSISTENT_TEXT_PLAIN,
+ message.getBytes("UTF-8"));
+ System.out.println(" [Publisher] Sent " + message);
+ }
+
+ }
+}
diff --git a/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/step02/Worker.java b/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/step02/Worker.java
new file mode 100644
index 0000000..f193297
--- /dev/null
+++ b/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/step02/Worker.java
@@ -0,0 +1,62 @@
+package dev.mq.step02;
+
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+import com.rabbitmq.client.DeliverCallback;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+/*
+ 작업 큐에서 꺼낸 작업의 메시지를 처리하는 역할
+ */
+public class Worker {
+
+ private static final String TASK_QUEUE_NAME = "task_queue";
+
+ public static void main(String[] args) throws IOException, TimeoutException {
+ ConnectionFactory factory = new ConnectionFactory();
+ factory.setHost("localhost");
+ final Connection connection = factory.newConnection();
+ final Channel channel = connection.createChannel();
+
+ boolean durable = true;
+ channel.queueDeclare(TASK_QUEUE_NAME, durable, false, false, null);
+ System.out.println("[Consumer(Worker)] 메시지를 기다리는 중..");
+
+ channel.basicQos(1);
+
+ DeliverCallback deliverCallback = (consumerTag, delivery) -> {
+ // 메시지 추출
+ String message = new String(delivery.getBody(), "UTF-8");
+
+ System.out.println(" [Consumer(Worker)] Received '" + message + "'");
+ try {
+ doWork(message); // 받은 메시지 처리 작업 수행
+ } finally {
+ System.out.println(" [Consumer(Worker)] Done");
+ channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
+ }
+ };
+
+ boolean autoAcknowledgement = false;
+ channel.basicConsume(TASK_QUEUE_NAME, autoAcknowledgement, deliverCallback, consumerTag -> {
+ });
+ }
+
+ // Task 처리 메서드, Hello...일 경우 .하나당 1초씩 지연해서 처리해야함
+ private static void doWork(String task) {
+ int cnt = 0;
+ for (char ch : task.toCharArray()) {
+ if (ch == '.') { // .(dot) 한 개당 1초씩 지연
+ try {
+ System.out.println(++cnt + "초 동안 작업 수행중");
+ Thread.sleep(1000);
+ } catch (InterruptedException _ignored) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+ }
+}
diff --git a/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/step03/EmitLogs.java b/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/step03/EmitLogs.java
new file mode 100644
index 0000000..e518a75
--- /dev/null
+++ b/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/step03/EmitLogs.java
@@ -0,0 +1,45 @@
+package dev.mq.step03;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+import com.rabbitmq.client.MessageProperties;
+
+/*
+ 작업(Task)을 작업 큐(Work queue)에 스케줄링하는 프로그램
+ 문자열값이 어떤 복잡한 작업이라고 가정할 때,
+ ex. Hello...이라고 하면
+ . 하나는 1초가 소요되는 작업이라고 가정
+
+ 따라서 Hello...는 3초가 걸리는 작업이라고 가정
+
+ 실행 인자값(args) 활용해서 Hello...과 같은 값 입력
+ */
+public class EmitLogs {
+
+
+ private static final String EXCHANGE_NAME = "logs";
+
+ public static void main(String[] args) throws IOException, TimeoutException {
+ // 서버 연결
+ ConnectionFactory factory = new ConnectionFactory();
+ factory.setHost("localhost");
+ try (Connection connection = factory.newConnection();
+ Channel channel = connection.createChannel()) {
+
+ String message = args.length < 1 ? "info: Hello World!" :
+ String.join(" ", args);
+
+
+ // Exchange 생성
+ channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
+
+ channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));
+ System.out.println(" [x] Sent '" + message + "'");
+ }
+
+ }
+}
diff --git a/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/step03/ReceiveLogs.java b/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/step03/ReceiveLogs.java
new file mode 100644
index 0000000..a23f312
--- /dev/null
+++ b/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/step03/ReceiveLogs.java
@@ -0,0 +1,34 @@
+package dev.mq.step03;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+import com.rabbitmq.client.DeliverCallback;
+import com.rabbitmq.client.MessageProperties;
+
+public class ReceiveLogs {
+
+ private static final String EXCHANGE_NAME = "logs";
+
+ public static void main(String[] argv) throws Exception {
+ ConnectionFactory factory = new ConnectionFactory();
+ factory.setHost("localhost");
+ Connection connection = factory.newConnection();
+ Channel channel = connection.createChannel();
+
+ channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
+ String queueName = channel.queueDeclare().getQueue();
+ channel.queueBind(queueName, EXCHANGE_NAME, "");
+
+ System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
+
+ DeliverCallback deliverCallback = (consumerTag, delivery) -> {
+ String message = new String(delivery.getBody(), "UTF-8");
+ System.out.println(" [x] Received '" + message + "'");
+ };
+ channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { });
+ }
+}
diff --git a/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/step03/Worker.java b/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/step03/Worker.java
new file mode 100644
index 0000000..ba8c8e7
--- /dev/null
+++ b/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/step03/Worker.java
@@ -0,0 +1,62 @@
+package dev.mq.step03;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+import com.rabbitmq.client.DeliverCallback;
+
+/*
+ 작업 큐에서 꺼낸 작업의 메시지를 처리하는 역할
+ */
+public class Worker {
+
+ private static final String TASK_QUEUE_NAME = "task_queue";
+
+ public static void main(String[] args) throws IOException, TimeoutException {
+ ConnectionFactory factory = new ConnectionFactory();
+ factory.setHost("localhost");
+ final Connection connection = factory.newConnection();
+ final Channel channel = connection.createChannel();
+
+ boolean durable = true;
+ channel.queueDeclare(TASK_QUEUE_NAME, durable, false, false, null);
+ System.out.println("[Consumer(Worker)] 메시지를 기다리는 중..");
+
+ channel.basicQos(1);
+
+ DeliverCallback deliverCallback = (consumerTag, delivery) -> {
+ // 메시지 추출
+ String message = new String(delivery.getBody(), "UTF-8");
+
+ System.out.println(" [Consumer(Worker)] Received '" + message + "'");
+ try {
+ doWork(message); // 받은 메시지 처리 작업 수행
+ } finally {
+ System.out.println(" [Consumer(Worker)] Done");
+ channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
+ }
+ };
+
+ boolean autoAcknowledgement = false;
+ channel.basicConsume(TASK_QUEUE_NAME, autoAcknowledgement, deliverCallback, consumerTag -> {
+ });
+ }
+
+ // Task 처리 메서드, Hello...일 경우 .하나당 1초씩 지연해서 처리해야함
+ private static void doWork(String task) {
+ int cnt = 0;
+ for (char ch : task.toCharArray()) {
+ if (ch == '.') { // .(dot) 한 개당 1초씩 지연
+ try {
+ System.out.println(++cnt + "초 동안 작업 수행중");
+ Thread.sleep(1000);
+ } catch (InterruptedException _ignored) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+ }
+}
diff --git a/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/step04/EmitLogTopic.java b/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/step04/EmitLogTopic.java
new file mode 100644
index 0000000..1c61a9e
--- /dev/null
+++ b/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/step04/EmitLogTopic.java
@@ -0,0 +1,42 @@
+package dev.mq.step04;
+
+import java.util.Arrays;
+
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+
+
+public class EmitLogTopic {
+
+
+
+ private static final String EXCHANGE_NAME = "topic_logs";
+
+ public static void main(String[] argv) throws Exception {
+ ConnectionFactory factory = new ConnectionFactory();
+ factory.setHost("localhost");
+ try (Connection connection = factory.newConnection();
+ Channel channel = connection.createChannel()) {
+
+ channel.exchangeDeclare(EXCHANGE_NAME, "topic");
+
+ String routingKey = getRouting(argv);
+ String message = getMessage(argv);
+
+ channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes("UTF-8"));
+ System.out.println(" [x] Sent '" + routingKey + "':'" + message + "'");
+ }
+ }
+
+ private static String getRouting(String[] strings) {
+ if (strings.length < 1) return "#"; // 기본값은 #
+ return strings[0]; // 첫 번째 인자를 Topic 간주
+ }
+
+ private static String getMessage(String[] strings) {
+ if (strings.length < 2) return "Hello World";
+ return String.join(" ", Arrays.copyOfRange(strings, 1, strings.length));
+ }
+
+}
diff --git a/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/step04/ReceiveLogsTopic.java b/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/step04/ReceiveLogsTopic.java
new file mode 100644
index 0000000..331378f
--- /dev/null
+++ b/week14/rabbit-mq/hello_rabbitmq_java/src/main/java/dev/mq/step04/ReceiveLogsTopic.java
@@ -0,0 +1,42 @@
+package dev.mq.step04;
+
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Connection;
+import com.rabbitmq.client.ConnectionFactory;
+import com.rabbitmq.client.DeliverCallback;
+
+public class ReceiveLogsTopic {
+
+ private static final String EXCHANGE_NAME = "topic_logs";
+
+ public static void main(String[] argv) throws Exception {
+ ConnectionFactory factory = new ConnectionFactory();
+ factory.setHost("localhost");
+
+ Connection connection = factory.newConnection();
+ Channel channel = connection.createChannel();
+
+ channel.exchangeDeclare(EXCHANGE_NAME, "topic");
+ String queueName = channel.queueDeclare().getQueue();
+
+ if (argv.length < 1) {
+ System.err.println("Usage: ReceiveLogsTopic [binding_key]...");
+ System.exit(1);
+ }
+
+ // RoutingKey로 사용할 토픽을 Exchange에 붙여서 구독중인 토픽에 대한 메시지 수신하게 함.
+ // 조건에 맞지 않는 메시지는 자동 삭제
+ for (String bindingKey : argv) {
+ channel.queueBind(queueName, EXCHANGE_NAME, bindingKey);
+ }
+
+ System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
+
+ DeliverCallback deliverCallback = (consumerTag, delivery) -> {
+ String message = new String(delivery.getBody(), "UTF-8");
+ System.out.println(" [x] Received '" +
+ delivery.getEnvelope().getRoutingKey() + "':'" + message + "'");
+ };
+ channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { });
+ }
+}
diff --git a/week14/rabbit-mq/hello_rabbitmq_java/src/test/java/dev/mq/AppTest.java b/week14/rabbit-mq/hello_rabbitmq_java/src/test/java/dev/mq/AppTest.java
new file mode 100644
index 0000000..c624fd1
--- /dev/null
+++ b/week14/rabbit-mq/hello_rabbitmq_java/src/test/java/dev/mq/AppTest.java
@@ -0,0 +1,38 @@
+package dev.mq;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Unit test for simple App.
+ */
+public class AppTest
+ extends TestCase
+{
+ /**
+ * Create the test case
+ *
+ * @param testName name of the test case
+ */
+ public AppTest( String testName )
+ {
+ super( testName );
+ }
+
+ /**
+ * @return the suite of tests being tested
+ */
+ public static Test suite()
+ {
+ return new TestSuite( AppTest.class );
+ }
+
+ /**
+ * Rigourous Test :-)
+ */
+ public void testApp()
+ {
+ assertTrue( true );
+ }
+}
diff --git a/week14/src-backend-java-nio/.gitignore b/week14/src-backend-java-nio/.gitignore
new file mode 100644
index 0000000..8af3bb4
--- /dev/null
+++ b/week14/src-backend-java-nio/.gitignore
@@ -0,0 +1,2 @@
+*/target/
+*/.idea/
diff --git a/week14/src-backend-java-nio/README.md b/week14/src-backend-java-nio/README.md
new file mode 100644
index 0000000..82d9ff5
--- /dev/null
+++ b/week14/src-backend-java-nio/README.md
@@ -0,0 +1 @@
+# src-backend-java-nio
diff --git a/week14/src-backend-java-nio/step01_blocking_socket/.gitignore b/week14/src-backend-java-nio/step01_blocking_socket/.gitignore
new file mode 100644
index 0000000..5ff6309
--- /dev/null
+++ b/week14/src-backend-java-nio/step01_blocking_socket/.gitignore
@@ -0,0 +1,38 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/week14/src-backend-java-nio/step01_blocking_socket/pom.xml b/week14/src-backend-java-nio/step01_blocking_socket/pom.xml
new file mode 100644
index 0000000..6d43a96
--- /dev/null
+++ b/week14/src-backend-java-nio/step01_blocking_socket/pom.xml
@@ -0,0 +1,37 @@
+
+ 4.0.0
+
+ dev.oio
+ step01_blocking_socket
+ 1.0-SNAPSHOT
+ jar
+
+ step01_blocking_socket
+ http://maven.apache.org
+
+
+ UTF-8
+
+
+
+
+ junit
+ junit
+ 3.8.1
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 8
+ 8
+
+
+
+
+
diff --git a/week14/src-backend-java-nio/step01_blocking_socket/src/main/java/dev/oio/App.java b/week14/src-backend-java-nio/step01_blocking_socket/src/main/java/dev/oio/App.java
new file mode 100644
index 0000000..e28988f
--- /dev/null
+++ b/week14/src-backend-java-nio/step01_blocking_socket/src/main/java/dev/oio/App.java
@@ -0,0 +1,13 @@
+package dev.oio;
+
+/**
+ * Hello world!
+ *
+ */
+public class App
+{
+ public static void main( String[] args )
+ {
+ System.out.println( "Hello World!" );
+ }
+}
diff --git a/week14/src-backend-java-nio/step01_blocking_socket/src/main/java/dev/oio/step01_thread_per_request/EchoClient.java b/week14/src-backend-java-nio/step01_blocking_socket/src/main/java/dev/oio/step01_thread_per_request/EchoClient.java
new file mode 100644
index 0000000..d76be31
--- /dev/null
+++ b/week14/src-backend-java-nio/step01_blocking_socket/src/main/java/dev/oio/step01_thread_per_request/EchoClient.java
@@ -0,0 +1,31 @@
+package dev.oio.step01_thread_per_request;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.Socket;
+
+public class EchoClient {
+ public static void main(String[] args) {
+ String serverAddress = "localhost"; // 서버 주소
+ int port = 12345; // 서버 포트
+
+ try (Socket socket = new Socket(serverAddress, port);
+ PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
+ BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+ BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in))) {
+
+ System.out.println("서버에 연결되었습니다.");
+
+ String userInput;
+ while ((userInput = stdIn.readLine()) != null) {
+ out.println(userInput); // 서버로 메시지 전송
+ System.out.println("서버로부터 수신: " + in.readLine()); // 서버로부터 에코 수신
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
+
diff --git a/week14/src-backend-java-nio/step01_blocking_socket/src/main/java/dev/oio/step01_thread_per_request/EchoServer.java b/week14/src-backend-java-nio/step01_blocking_socket/src/main/java/dev/oio/step01_thread_per_request/EchoServer.java
new file mode 100644
index 0000000..f592ebd
--- /dev/null
+++ b/week14/src-backend-java-nio/step01_blocking_socket/src/main/java/dev/oio/step01_thread_per_request/EchoServer.java
@@ -0,0 +1,67 @@
+package dev.oio.step01_thread_per_request;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+
+public class EchoServer {
+ public static boolean isClientConnected = false; // 클라이언트 연결 상태
+
+ public static void main(String[] args) {
+ int port = 12345; // 서버 포트
+ try (ServerSocket serverSocket = new ServerSocket(port)) {
+ System.out.println("서버가 시작되었습니다. 포트: " + port);
+ while (true) {
+ Socket clientSocket = serverSocket.accept();
+ isClientConnected = true; // 클라이언트 연결 상태 설정
+ System.out.println("클라이언트 연결: " + clientSocket.getInetAddress());
+
+ // 클라이언트와의 통신을 위한 스레드 생성
+ new ClientHandler(clientSocket).start();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
+
+class ClientHandler extends Thread {
+ private Socket clientSocket;
+
+ public ClientHandler(Socket socket) {
+ this.clientSocket = socket;
+ }
+
+ @Override
+ public void run() {
+ System.out.println("클라이언트 스레드 ID: " + Thread.currentThread().getId()); // 스레드 ID 출력
+
+ try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
+ PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)) {
+
+ String inputLine;
+ while ((inputLine = in.readLine()) != null) {
+ System.out.println("클라이언트 스레드 ID: " + Thread.currentThread().getId() + " 클라이언트로부터 수신: " + inputLine);
+ out.println(inputLine); // 클라이언트에게 에코
+ }
+ } catch (SocketException e) {
+ System.out.println("클라이언트가 연결을 종료했습니다.");
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ clientSocket.close();
+ System.out.println("클라이언트 연결 종료: " + clientSocket.getInetAddress());
+ EchoServer.isClientConnected = false; // 연결 종료 시 상태 변경
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
+
+
diff --git a/week14/src-backend-java-nio/step01_blocking_socket/src/main/java/dev/oio/step02_thread_pooling/EchoClient.java b/week14/src-backend-java-nio/step01_blocking_socket/src/main/java/dev/oio/step02_thread_pooling/EchoClient.java
new file mode 100644
index 0000000..562470b
--- /dev/null
+++ b/week14/src-backend-java-nio/step01_blocking_socket/src/main/java/dev/oio/step02_thread_pooling/EchoClient.java
@@ -0,0 +1,37 @@
+package dev.oio.step02_thread_pooling;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+public class EchoClient {
+ public static void main(String[] args) {
+ String serverAddress = "localhost"; // 서버 주소
+ int port = 12345; // 서버 포트
+
+ try {
+ // 서버에 연결 시도
+ Socket socket = new Socket(serverAddress, port);
+ System.out.println("서버에 연결되었습니다."); // 연결 성공 시 메시지 출력
+
+ try (PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
+ BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+ BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in))) {
+
+ String userInput;
+ while ((userInput = stdIn.readLine()) != null) {
+ out.println(userInput); // 서버로 메시지 전송
+ System.out.println("서버로부터 수신: " + in.readLine()); // 서버로부터 에코 수신
+ }
+ }
+ } catch (UnknownHostException e) {
+ System.out.println("서버를 찾을 수 없습니다: " + e.getMessage());
+ } catch (IOException e) {
+ System.out.println("서버에 연결 대기 중입니다."); // 연결 실패 시 대기 중 메시지 출력
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/week14/src-backend-java-nio/step01_blocking_socket/src/main/java/dev/oio/step02_thread_pooling/EchoServer.java b/week14/src-backend-java-nio/step01_blocking_socket/src/main/java/dev/oio/step02_thread_pooling/EchoServer.java
new file mode 100644
index 0000000..91ca23c
--- /dev/null
+++ b/week14/src-backend-java-nio/step01_blocking_socket/src/main/java/dev/oio/step02_thread_pooling/EchoServer.java
@@ -0,0 +1,98 @@
+package dev.oio.step02_thread_pooling;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadPoolExecutor;
+
+public class EchoServer {
+ private static final int MAX_THREADS = 2; // 최대 스레드 수
+ private static final int MAX_QUEUE_SIZE = 5; // 대기 큐 크기
+ private static ThreadPoolExecutor threadPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(MAX_THREADS); // 스레드 풀 생성
+ private static ArrayBlockingQueue waitingQueue = new ArrayBlockingQueue<>(MAX_QUEUE_SIZE); // 대기 큐
+
+ public static void main(String[] args) {
+ int port = 12345; // 서버 포트
+ // 대기 클라이언트 처리 스레드 시작
+ new Thread(EchoServer::handleWaitingClients).start();
+
+ try (ServerSocket serverSocket = new ServerSocket(port)) {
+ System.out.println("서버가 시작되었습니다. 포트: " + port);
+ while (true) {
+ // 실제 accept() 시스템 콜 호출 부분? -> PlainSocketImpl.socketAccept()의 accept0
+ Socket clientSocket = serverSocket.accept();
+ String clientAddress = clientSocket.getInetAddress().getHostAddress();
+ int clientPort = clientSocket.getPort(); // 클라이언트 포트 번호
+ System.out.println(clientAddress + ":" + clientPort + "클라이언트로부터 연결 시도");
+
+ // 현재 활성화된 스레드 수를 체크
+ if (threadPool.getActiveCount() < MAX_THREADS) {
+ threadPool.execute(new ClientHandler(clientSocket));
+ System.out.println(clientAddress + ":" + clientPort + " 클라이언트가 서버에 연결되었습니다");
+ } else {
+ if (waitingQueue.offer(clientSocket)) {
+ System.out.println(clientAddress + ":" + clientPort + " 클라이언트가 서버에 연결 대기 중: ");
+ } else {
+ System.out.println("대기 큐가 가득 차서 연결을 거부합니다: " + clientAddress + ":" + clientPort);
+ clientSocket.close(); // 큐가 가득 차면 연결 종료
+ }
+ }
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static void handleWaitingClients() {
+ while (true) {
+ try {
+ Socket clientSocket = waitingQueue.take(); // 대기 큐에서 클라이언트 소켓을 가져옴
+ if (clientSocket != null) {
+ threadPool.execute(new ClientHandler(clientSocket));
+ String clientAddress = clientSocket.getInetAddress().getHostAddress();
+ int clientPort = clientSocket.getPort();
+ // System.out.println(clientAddress + ":" + clientPort + " 클라이언트가 서버에 연결되었습니다");
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ break;
+ }
+ }
+ }
+}
+
+class ClientHandler implements Runnable {
+ private Socket clientSocket;
+
+ public ClientHandler(Socket socket) {
+ this.clientSocket = socket;
+ }
+
+ @Override
+ public void run() {
+ System.out.println("클라이언트 스레드 ID: " + Thread.currentThread().getId()); // 스레드 ID 출력
+ try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
+ PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)) {
+
+ String inputLine;
+ while ((inputLine = in.readLine()) != null) {
+ System.out.println("클라이언트로부터 수신: " + inputLine);
+ out.println(inputLine); // 클라이언트에게 에코
+ }
+ } catch (IOException e) {
+ System.out.println("클라이언트가 연결을 종료했습니다.");
+ } finally {
+ try {
+ clientSocket.close();
+ System.out.println("클라이언트 연결 종료: " + clientSocket.getInetAddress().getHostAddress() + ":" + clientSocket.getPort());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/week14/src-backend-java-nio/step01_blocking_socket/src/test/java/dev/oio/AppTest.java b/week14/src-backend-java-nio/step01_blocking_socket/src/test/java/dev/oio/AppTest.java
new file mode 100644
index 0000000..55af013
--- /dev/null
+++ b/week14/src-backend-java-nio/step01_blocking_socket/src/test/java/dev/oio/AppTest.java
@@ -0,0 +1,38 @@
+package dev.oio;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Unit test for simple App.
+ */
+public class AppTest
+ extends TestCase
+{
+ /**
+ * Create the test case
+ *
+ * @param testName name of the test case
+ */
+ public AppTest( String testName )
+ {
+ super( testName );
+ }
+
+ /**
+ * @return the suite of tests being tested
+ */
+ public static Test suite()
+ {
+ return new TestSuite( AppTest.class );
+ }
+
+ /**
+ * Rigourous Test :-)
+ */
+ public void testApp()
+ {
+ assertTrue( true );
+ }
+}
diff --git a/week14/src-backend-java-nio/step02_non_blocking/.gitignore b/week14/src-backend-java-nio/step02_non_blocking/.gitignore
new file mode 100644
index 0000000..5ff6309
--- /dev/null
+++ b/week14/src-backend-java-nio/step02_non_blocking/.gitignore
@@ -0,0 +1,38 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/week14/src-backend-java-nio/step02_non_blocking/index.txt b/week14/src-backend-java-nio/step02_non_blocking/index.txt
new file mode 100644
index 0000000..e00a42b
--- /dev/null
+++ b/week14/src-backend-java-nio/step02_non_blocking/index.txt
@@ -0,0 +1,61 @@
+1. 네트워크 프로그램 구현 방식
+ - 블로킹 소켓 기반의 구현
+ Q. 활용 API?
+ -> 소켓과 인풋/아웃풋 스트림
+
+2. 서버 - 클라이언트 간 데이터 송/수신 방식
+ - 스트림
+ - 버퍼
+ Q. 버퍼란?
+ -> 임시 메모리를 활용하여 스트림보다 더 빠르고 재사용 가능한 클래스
+
+ Q. 버퍼를 활용하여 송수신을 보다 빠르게 처리하는 방법?
+ -> 버퍼가 적용된 BufferedReader
+
+3. 다중 사용자 접속 처리하기
+ Q. 처음에 구현한 블로킹 방식의 네트워크 프로그램의 특징?
+ -> Java OIO(Old IO, Java 1.4 이전) API인 ServerSocket과 Socket을 활용,
+ 블로킹으로 동작, 1:1 연결 밖에 지원하지 않음
+
+ Q. 하나의 서버로 둘 이상의 클라이언트를 받는 방법?
+ -> 총 2가지
+ 1. 멀티 프로세싱
+ 2. 멀티 스레딩
+ 2-1. 요청당 스레딩 모델
+ Q. 요청당 스레드 모델의 장단점?
+
+ 2-2. 스레드 풀링 모델
+ Q. 스레드 풀링 모델의 장단점?
+ Q. 스레드 풀에 생성될 적정 스레드 수 계산 방법?
+
+ Q. 스레드 풀링 모델의 단점 개선하기 위한 방법?
+ -> 스레드가 IO 연산을 수행하는 동안에도 쉬지 않고, 다른 작업을 처리할 수 있는
+ 논블로킹 메커니즘을 활용
+
+ 논블로킹에서는 스레드가 IO 작업을 수행하는 메서드를 호출하는 즉시 반환되며,
+ 작업이 아직 완료되지 않아도 스레드가 차단되지 않음
+
+ 이를 통해 하나의 스레드만 가지고도 둘 이상의 다중 접속 사용자를 처리할 수 있음
+ (OS 레벨의 메커니즘에서는 IO 멀티 플렉싱, 입출력 다중화라고 함)
+ 정리하면, 하나의 서버로 둘 이상의 사용자 접속을 받는 방법은 두 가지가 아닌 총 세 가지
+ -> 1. 멀티 프로세싱
+ 2. 멀티 스레딩
+ 3. 멀티 플렉싱(Java 1.4~에서 등장한 Java NIO, New, Non-blocking)
+4. 논블로킹 기반의 네트워크 프로그램을 구현하기 위해 필요한 몇 가지 API
+ Q. 채널이란?
+ -> 스트림 기반의 블로킹 IO를 사용했던 초기 Java OIO에서는 대량의 데이터를 효율적으로 처리하거나
+ 고성능 네트워크 서버를 구축하기에는 한계가 있었기 때문에 등장한 API
+ Selector와 결합할 경우에는 논블로킹 메커니즘을 구현할 수 있음
+
+ Q. 채널의 특징?
+ -> 채널은 Buffer를 활용하여 데이터를 블록 단위로 처리, 스트림에 비해 속도가 빠르고 효율적임
+ 단방향으로 통신하는 스트림과는 다르게(InputStream, OutputStream)
+ 채널은 양방향으로 통신
+
+ Q. 셀렉터란?
+ -> OS의 IO 멀티플렉싱 메커니즘을 지원하는 API,
+ 여러 개의 Channel 들을 단일 스레드를 통해 감시하여(select()) OIO에 비해 서버의 확장성 개선
+ Selector는 하나의 스레드가 여러 개의 Non-blocking 채널들을 감시할 수 있도록 도와주는 멀티 플렉싱 툴
+ 이벤트 기반으로 IO를 처리함
+ 이 맥락에서 이벤트의 예시는?
+ ex. 클라이언트 - 서버 간의 연결, 데이터 송/수신, 과정에서 발생한 에러 등
\ No newline at end of file
diff --git a/week14/src-backend-java-nio/step02_non_blocking/pom.xml b/week14/src-backend-java-nio/step02_non_blocking/pom.xml
new file mode 100644
index 0000000..8326a0c
--- /dev/null
+++ b/week14/src-backend-java-nio/step02_non_blocking/pom.xml
@@ -0,0 +1,25 @@
+
+ 4.0.0
+
+ dev.nio
+ step02_non_blocking
+ 1.0-SNAPSHOT
+ jar
+
+ step02_non_blocking
+ http://maven.apache.org
+
+
+ UTF-8
+
+
+
+
+ junit
+ junit
+ 3.8.1
+ test
+
+
+
diff --git a/week14/src-backend-java-nio/step02_non_blocking/src/main/java/dev/nio/App.java b/week14/src-backend-java-nio/step02_non_blocking/src/main/java/dev/nio/App.java
new file mode 100644
index 0000000..ebde45c
--- /dev/null
+++ b/week14/src-backend-java-nio/step02_non_blocking/src/main/java/dev/nio/App.java
@@ -0,0 +1,13 @@
+package dev.nio;
+
+/**
+ * Hello world!
+ *
+ */
+public class App
+{
+ public static void main( String[] args )
+ {
+ System.out.println( "Hello World!" );
+ }
+}
diff --git a/week14/src-backend-java-nio/step02_non_blocking/src/main/java/dev/nio/step01_java_nio/ChannelOverview.java b/week14/src-backend-java-nio/step02_non_blocking/src/main/java/dev/nio/step01_java_nio/ChannelOverview.java
new file mode 100644
index 0000000..af09db6
--- /dev/null
+++ b/week14/src-backend-java-nio/step02_non_blocking/src/main/java/dev/nio/step01_java_nio/ChannelOverview.java
@@ -0,0 +1,34 @@
+package dev.nio.step01_java_nio;
+
+/**
+ * java.nio.channels.Channel
+ *
+ java.nio.channels 패키지(https://docs.oracle.com/javase/8/docs/api/java/nio/channels/package-summary.html)
+ *
+ * 파일 및 소켓과 같이 I/O 작업을 수행할 수 있는 엔티티에 대한 연결을 수행하는 역할인 Channel 클래스를 정의,
+ * 또한 다중화된(multiplexed) 논블로킹 I/O 작업을 위한 Selector 클래스를 정의함
+ *
+ SelectableChannel
+ * A channel that can be multiplexed via a Selector.
+ * -> Selector를 통해 멀티플렉싱으로 동작하도록 하기 위해 필요한 채널
+ *
+ * 이하 클래스들은 논블로킹이 가능하도록 Selector와 함께 사용될 수 있도록 상속받은 하위 클래스
+ * -> XxxChannel 'extends AbstractSelectableChannel, SelectableChannel'
+ *
+ * DatagramChannel
+ * A selectable channel for datagram-oriented sockets.
+ * -> UDP 프로토콜 기반 통신 프로그램 구현 시 활용
+ *
+ * FileChannel
+ * A channel for reading, writing, mapping, and manipulating a file.
+ * -> 파일 입출력 기반 통신 프로그램 구현 시 사용
+ *
+ * ServerSocketChannel
+ * A selectable channel for stream-oriented listening sockets.
+ * -> TCP 프로토콜 기반 통신 프로그램 구현 시 사용
+ */
+public class ChannelOverview {
+ public static void main(String[] args) {
+
+ }
+}
diff --git a/week14/src-backend-java-nio/step02_non_blocking/src/main/java/dev/nio/step01_java_nio/SelectorOverview.java b/week14/src-backend-java-nio/step02_non_blocking/src/main/java/dev/nio/step01_java_nio/SelectorOverview.java
new file mode 100644
index 0000000..b156b10
--- /dev/null
+++ b/week14/src-backend-java-nio/step02_non_blocking/src/main/java/dev/nio/step01_java_nio/SelectorOverview.java
@@ -0,0 +1,35 @@
+package dev.nio.step01_java_nio;
+
+import java.io.IOException;
+import java.nio.channels.Selector;
+
+/**
+ * java.nio.channels.Selector
+ *
+ Selector
+ * A multiplexor of SelectableChannel objects.
+ * -> 논블로킹 IO(멀티플렉서)를 관리하는 핵심 컴포넌트
+ *
+ */
+public class SelectorOverview {
+ public static void main(String[] args) {
+ try {
+ Selector selector = Selector.open(); // Selector는 open()을 통해 생성할 수 있음
+ System.out.println("selector = " + selector); // 각 OS 전용 셀렉터 구현체가 생성됨
+ /*
+ windows, selector = sun.nio.ch.WEPollSelectorImpl@cac736f
+ Mac OS, selector = sun.nio.ch.KQueueSelectorImpl@452b3a41
+ */
+
+ // Selector가 열려있는지 확인할 수 있는 메서드, 열려있을 경우 true 반환
+ // close() 메서드를 호출하기 전까지는 셀렉터는 열린 채로 유지됨
+ System.out.println("selector.isOpen() = " + selector.isOpen());
+
+ // 클라이언트로부터 유입 이벤트를 기다림
+ selector.select(); // 사용자의 연결이 올 때까지 스레드는 해당 라인에서 블로킹됨(프로그램이 종료되지 않음)
+
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/week14/src-backend-java-nio/step02_non_blocking/src/main/java/dev/nio/step02_using_selectable_channel/SampleClient.java b/week14/src-backend-java-nio/step02_non_blocking/src/main/java/dev/nio/step02_using_selectable_channel/SampleClient.java
new file mode 100644
index 0000000..3713305
--- /dev/null
+++ b/week14/src-backend-java-nio/step02_non_blocking/src/main/java/dev/nio/step02_using_selectable_channel/SampleClient.java
@@ -0,0 +1,15 @@
+package dev.nio.step02_using_selectable_channel;
+
+public class SampleClient {
+ /*
+ cmd에서 telnet 클라이언트를 통해 요청 테스트
+ telnet localhost 5555와 같이 요청 수행
+
+ 둘 이상의 클라이언트를 테스트할 경우에는
+ 새로운 터미널 실행 후 telnet~으로 동일하게 요청 수행
+
+ 한계점.
+ 현재 다중 클라이언트의 연결만 수용할 수 있도록 구현되어 있기 때문에
+ 이후 클라이언트의 데이터 송/수신 처리는 아직 불가능
+ */
+}
diff --git a/week14/src-backend-java-nio/step02_non_blocking/src/main/java/dev/nio/step02_using_selectable_channel/SelectableChannelOverview.java b/week14/src-backend-java-nio/step02_non_blocking/src/main/java/dev/nio/step02_using_selectable_channel/SelectableChannelOverview.java
new file mode 100644
index 0000000..8d637fb
--- /dev/null
+++ b/week14/src-backend-java-nio/step02_non_blocking/src/main/java/dev/nio/step02_using_selectable_channel/SelectableChannelOverview.java
@@ -0,0 +1,96 @@
+package dev.nio.step02_using_selectable_channel;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.channels.*;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * SelectableChannel
+ *
+ * Selector는 둘 이상의 Channel을 하나의 스레드를 통해 감시(select())할 수 있음
+ *
+ * Selector에 채널을 등록하기 위해서는 SelectableChannel을 상속받아야 하며,
+ * SelectableChannel을 상속받았으며, TCP 연결용 채널인 ServerSocketChannel을 활용함
+ *
+ * 결과적으로 Selector와 ServerSocketChannel을 통해 하나의 스레드로 둘 이상의 다중 클라이언트의 접속을 처리할 수 있음
+ */
+public class SelectableChannelOverview {
+ public static void main(String[] args) {
+ final int DEFAULT_PORT = 5555;
+
+ try {
+ // Selector 생성
+ Selector selector = Selector.open();
+
+ // TCP 통신을 위한 ServerSocketChannel을 생성
+ ServerSocketChannel serverSocketChannel
+ = ServerSocketChannel.open();
+
+ // Selector와 ServerSocketChannel이 성공적으로 열렸는지 확인
+ if (serverSocketChannel.isOpen() && selector.isOpen()) {
+ // 생성한 논블로킹 소켓인 ServerSocketChannel을 논블로킹 모드로 설정
+ serverSocketChannel.configureBlocking(false);
+
+ // 클라이언트의 연결을 대기할 포트번호 지정(생성)
+ InetSocketAddress inetSocketAddress
+ = new InetSocketAddress(DEFAULT_PORT);
+
+ // 생성한 포트를 서버 소켓 채널에 바인딩
+ serverSocketChannel.bind(inetSocketAddress);
+ // -> serverSocketChannel 객체가 지정된 포트로부터 클라이언트의 연결을 받을 수 있게 됨
+
+ // 현재 생성된 서버 소켓 채널(serverSocketChannel)을 Selector 객체에 등록
+ // SelectionKey.OP_ACCEPT - 셀렉터가 감지할 이벤트들 중에서 서버가 클라이언트의 연결 요청 수락
+ // 두 번째 인자는 채널에서 발생하는 이벤트들 중 셀렉터를 통해 확인하고자(알림받고자) 하는 이벤트의 종류를 전달할 때 사용
+ serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
+
+ System.out.println("서버는 클라이언트의 접속 대기 중..");
+ while (true) {
+ selector.select(); // 클라이언트의 유입 이벤트가 올 때까지 대기(기다림)
+
+ // SelectionKey 목록 조회
+ Set selectionKeys = selector.selectedKeys();
+
+ Iterator keys = selectionKeys.iterator();
+
+ while (keys.hasNext()) {
+ SelectionKey key = keys.next();
+
+ // 현재 키에 해당하는 채널에서 조회된 IO 이벤트의 종류가 새로운 소켓 커넥션 연결 요청인지 확인
+ if (key.isAcceptable()) { // 현재 키에 담긴 이벤트가 새로운 소켓 연결을 수락할 수 있는지?
+ // 소켓 연결 작업 수행 로직 작성 부분
+ acceptOperation(key, selector);
+ }
+ }
+ }
+ }
+
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ /*
+ 키의 채널(여기서는 ServerSocketChannel)이 새 소켓 연결을 수락할 수 있는 경우,
+ 처리할 수행 로직이 담긴 메서드
+ */
+ private static void acceptOperation(SelectionKey key, Selector selector) throws IOException {
+ // 클라이언트의 연결 요청 이벤트가 발생한 채널은 항상 ServerSocketChannel이기 때문에
+ // 이벤트가 발생한 채널을 ServerSocketChannel 타입으로 캐스팅
+ ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
+
+// ServerSocketChannel을 활용하여 클라이언트의 연결을 수락하고, 연결된 클라이언트 SocketChannel 객체를 가져옴
+ SocketChannel clientSocketChannel
+ = serverSocketChannel.accept();// accept()는 연결된 클라이언트 소켓 객체를 반환함
+ System.out.println("clientSocketChannel = " + clientSocketChannel);
+
+ // 연결된 클라이언트 소켓 모드를 논블로킹 모드로 설정
+ clientSocketChannel.configureBlocking(false);
+ System.out.println(clientSocketChannel.getRemoteAddress() + " 로부터 클라이언트가 연결됨");
+ }
+}
+
+
diff --git a/week14/src-backend-java-nio/step02_non_blocking/src/main/java/dev/nio/step03_adding_more_event/SampleClient.java b/week14/src-backend-java-nio/step02_non_blocking/src/main/java/dev/nio/step03_adding_more_event/SampleClient.java
new file mode 100644
index 0000000..c4b0d0c
--- /dev/null
+++ b/week14/src-backend-java-nio/step02_non_blocking/src/main/java/dev/nio/step03_adding_more_event/SampleClient.java
@@ -0,0 +1,15 @@
+package dev.nio.step03_adding_more_event;
+
+public class SampleClient {
+ /*
+ cmd에서 telnet 클라이언트를 통해 요청 테스트
+ telnet localhost 5555와 같이 요청 수행
+
+ 둘 이상의 클라이언트를 테스트할 경우에는
+ 새로운 터미널 실행 후 telnet~으로 동일하게 요청 수행
+
+ 한계점.
+ 현재 다중 클라이언트의 연결만 수용할 수 있도록 구현되어 있기 때문에
+ 이후 클라이언트의 데이터 송/수신 처리는 아직 불가능
+ */
+}
diff --git a/week14/src-backend-java-nio/step02_non_blocking/src/main/java/dev/nio/step03_adding_more_event/SelectableChannelOverview.java b/week14/src-backend-java-nio/step02_non_blocking/src/main/java/dev/nio/step03_adding_more_event/SelectableChannelOverview.java
new file mode 100644
index 0000000..72e0801
--- /dev/null
+++ b/week14/src-backend-java-nio/step02_non_blocking/src/main/java/dev/nio/step03_adding_more_event/SelectableChannelOverview.java
@@ -0,0 +1,240 @@
+package dev.nio.step03_adding_more_event;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.*;
+
+/**
+ * 현재 서버는 클라이언트의 연결 요청에 대한 이벤트만 처리할 수 있음
+ *
+ * TODO: 따라서 추가적으로 클라이언트가 전송한 데이터를 읽을 수 있는 처리(Read)
+ * 읽은 데이터를 그대로 클라이언트에게 반환(Write)할 수 있는 처리
+ */
+public class SelectableChannelOverview {
+
+ /*
+ keepDataTrack
+ 클라이언트로부터 수신된 데이터를 추적, 관리하는 역할
+ 각 클라이언트의 채널을 의미하는 SocketChannel을 key로 가지며,
+ 해당 클라이언트로부터 수신된 데이터를 저장하는 리스트(List)를 값으로 갖는 HashMap 객체
+ 각 클라이언트가 보낸 메시지는 byte[] 형태로 리스트에 저장됨
+ */
+ private static Map> keepDataTrack = new HashMap<>();
+
+ /*
+ IO 작업 시 데이터의 임시 저장소 역할인 버퍼 생성
+ */
+ private static ByteBuffer buffer = ByteBuffer.allocate(2 * 1024);
+
+ public static void main(String[] args) {
+ final int DEFAULT_PORT = 5555;
+
+ try {
+ // Selector 생성
+ Selector selector = Selector.open();
+
+ // TCP 통신을 위한 ServerSocketChannel을 생성
+ ServerSocketChannel serverSocketChannel
+ = ServerSocketChannel.open();
+
+ // Selector와 ServerSocketChannel이 성공적으로 열렸는지 확인
+ if (serverSocketChannel.isOpen() && selector.isOpen()) {
+ // 생성한 논블로킹 소켓인 ServerSocketChannel을 논블로킹 모드로 설정
+ serverSocketChannel.configureBlocking(false);
+
+ // 클라이언트의 연결을 대기할 포트번호 지정(생성)
+ InetSocketAddress inetSocketAddress
+ = new InetSocketAddress(DEFAULT_PORT);
+
+ // 생성한 포트를 서버 소켓 채널에 바인딩
+ serverSocketChannel.bind(inetSocketAddress);
+ // -> serverSocketChannel 객체가 지정된 포트로부터 클라이언트의 연결을 받을 수 있게 됨
+
+ // 현재 생성된 서버 소켓 채널(serverSocketChannel)을 Selector 객체에 등록
+ // SelectionKey.OP_ACCEPT - 셀렉터가 감지할 이벤트들 중에서 서버가 클라이언트의 연결 요청 수락
+ // 두 번째 인자는 채널에서 발생하는 이벤트들 중 셀렉터를 통해 확인하고자(알림받고자) 하는 이벤트의 종류를 전달할 때 사용
+ serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
+
+ /*
+ SelectionKey
+ 채널이 Selector로 등록될 때마다 채널은 java.nio.channels.SelectionKey 클래스의 인스턴스를 통해 표현됨
+ 이 인스턴스를 SelectionKey라고 함
+ -> 서로 다른 채널에 속한 클라이언트들의 요청을 정렬하기 위해 셀렉터가 사용하는 헬퍼 객체
+
+ 각 헬퍼(key)는 단 하나의 클라이언트 서브 리퀘스트로 표현되며,
+ 클라이언트와 요청 유형(연결, 읽기, 쓰기 등)을 식별하는 정보가 들어있음
+
+ SelectionKey.OP_ACCEPT - 수락 가능, 연관된 클라이언트가 연결을 요청함
+ SelectionKey.OP_CONNECT - 연결 가능, 서버가 연결을 수락함
+ SelectionKey.OP_READ - 읽기 가능, 읽기 연산을 가리킴
+ SelectionKey.OP_WRITE - 쓰기 가능, 쓰기 연산을 가리킴
+ */
+
+
+ System.out.println("서버는 클라이언트의 접속 대기 중..");
+ while (true) {
+ selector.select(); // 클라이언트의 유입 이벤트가 올 때까지 대기(기다림)
+
+ // SelectionKey 목록 조회
+ Set selectionKeys = selector.selectedKeys();
+
+ Iterator keys = selectionKeys.iterator();
+
+ while (keys.hasNext()) {
+ SelectionKey key = keys.next();
+
+ // 같은 키가 반복해서 오는 것을 막기 위해 처리한 키는 제거, IO 이벤트가 발생한 채널에서 동일한 이벤트가 감지되는 것을 방지하기 위함
+ keys.remove();
+
+ if (!key.isValid()) { // 키가 유효한지 확인, 키가 취소되거나 키의 채널이 닫혔거나 셀렉터가 닫혔다면 유효하지 않은 키
+ continue;
+ }
+
+ // 현재 키에 해당하는 채널에서 조회된 IO 이벤트의 종류가 새로운 소켓 커넥션 연결 요청인지 확인
+ if (key.isAcceptable()) { // 현재 키에 담긴 이벤트가 새로운 소켓 연결을 수락할 수 있는지?
+ // 소켓 연결 작업 수행 로직 작성 부분
+ acceptOperation(key, selector);
+ } else if (key.isReadable()) { // 조회된 IO 이벤트의 종류가 데이터 수신 요청인지 확인(키의 채널을 읽을 수 있는지 확인)
+ System.out.println("dd");
+ readOperation(key);
+ } else if (key.isWritable()) { // 조회된 IO 이벤트의 종류가 데이터 쓰기 요청인지 확인(키의 채널에 쓸 수 있는지 확인)
+ writeOperation(key);
+ }
+ }
+ }
+ }
+
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ /*
+ 키의 채널(여기서는 ServerSocketChannel)이 새 소켓 연결을 수락할 수 있는 경우,
+ 처리할 수행 로직이 담긴 메서드
+ */
+ private static void acceptOperation(SelectionKey key, Selector selector) throws IOException {
+ // 클라이언트의 연결 요청 이벤트가 발생한 채널은 항상 ServerSocketChannel이기 때문에
+ // 이벤트가 발생한 채널을 ServerSocketChannel 타입으로 캐스팅
+ ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
+
+ // ServerSocketChannel을 활용하여 클라이언트의 연결을 수락하고, 연결된 클라이언트 SocketChannel 객체를 가져옴
+ SocketChannel clientSocketChannel
+ = serverSocketChannel.accept();// accept()는 연결된 클라이언트 소켓 객체를 반환함
+ System.out.println("clientSocketChannel = " + clientSocketChannel);
+
+ // 연결된 클라이언트 소켓 모드를 논블로킹 모드로 설정
+ clientSocketChannel.configureBlocking(false);
+ System.out.println(clientSocketChannel.getRemoteAddress() + " 로부터 클라이언트가 연결됨");
+
+ // IO 처리를 위해 채널을 셀렉터에 등록
+ keepDataTrack.put(clientSocketChannel, new ArrayList());
+ // 클라이언트 채널에서 OP_READ가 발생하면 감지하여 처리할 수 있도록
+ clientSocketChannel.register(selector, SelectionKey.OP_READ);
+ }
+
+ /*
+ Key가 속한 채널의 데이터를 서버가 읽을 수 있는 경우 수행할 처리 로직
+ → 클라이언트로부터 수신한 데이터를 읽어들임
+ */
+ private static void readOperation(SelectionKey key) {
+ try {
+ SocketChannel socketChannel = (SocketChannel) key.channel();
+
+ // 이전에 저장되었던 데이터를 지우고, 새로운 데이터를 받을 준비
+ // 버퍼의 포지션을 0으로 설정, limit을 버퍼의 용량으로 설정하여 새로운 데이터를 쓸 수 있게 해줌
+ buffer.clear();
+
+ // numRead - 클라이언트로부터 읽은 데이터의 바이트 길이를 나타내는 임시 변수
+ int numRead = -1;
+
+ try {
+ // 채널에 할당된 버퍼를 통해 바이트 값을 읽어들임, read()는 블로킹 방식으로 동작하기 때문에 클라이언트가 데이터를 보내기 전까지 호출한 스레드는 블로킹됨
+ numRead = socketChannel.read(buffer); // 읽은 바이트 수를 반환, 읽을 수 있는 데이터가 없는 경우 -1을 반환(* -1은 클라이언트가 연결을 종료했음을 의미)
+ }
+ catch (IOException e) {
+ System.err.println("데이터 읽기 에러!");
+ }
+
+ if (numRead == -1) { // 클라이언트가 연결을 종료했을 경우,
+ keepDataTrack.remove(socketChannel);
+ System.out.println("클라이언트 연결(Connection) 종료 : "
+ + socketChannel.getRemoteAddress());
+ socketChannel.close();
+ key.cancel();
+ return;
+ }
+
+ // 클라이언트로부터 받은 데이터를 읽어서 클라이언트에게 그대로 응답해주는 처리 로직
+ byte[] data = new byte[numRead]; // 읽은 데이터를 byte[] 타입 배열인 data 변수에 복사
+ System.arraycopy(buffer.array(), 0, data, 0, numRead);
+
+ System.out.println(new String(data, "UTF-8")
+ + " from " + socketChannel.getRemoteAddress()); // 문자열로 변환하여 콘솔에 출력
+
+ // 클라이언트에게 응답 처리
+ doEchoJob(key, data);
+
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /*
+ Key가 속한 채널에 서버가 데이터를 쓸 수 있는 경우 수행할 처리 로직
+ → 클라이언트에게 데이터 응답
+ */
+ private static void writeOperation(SelectionKey key) throws IOException {
+ SocketChannel socketChannel = (SocketChannel) key.channel();
+ List channelData = keepDataTrack.get(socketChannel);
+
+ Iterator its = channelData.iterator(); // 특정 클라이언트 채널이 가진 데이터를 추출하기 위한 이터레이터 생성
+
+ while (its.hasNext()) { // 이터레이터를 순회하면서 데이터 추출
+ byte[] it = its.next();
+ its.remove();
+ socketChannel.write(ByteBuffer.wrap(it));
+ }
+
+ // 관심 이벤트를 읽기 가능한 상태로 변경
+ // 즉, 클라이언트에게 데이터를 전송한 후 서버는 다시 클라이언트로부터 수신할 데이터를 기다릴 준비가 되었음을 나타냄
+ // 이 설정을 통해 서버는 클라이언트가 보낸 새로운 메시지를 읽기 위해 다시 준비됨
+ key.interestOps(SelectionKey.OP_READ);
+ }
+
+
+ /*
+ 클라이언트로부터 수신한 데이터를 다시 그대로 클라이언트에게 응답하기 위한 처리 로직
+ */
+ private static void doEchoJob(SelectionKey key, byte[] data) {
+ // 특정 클라이언트에 해당하는 소켓채널을 불러옴
+ SocketChannel socketChannel = (SocketChannel) key.channel();
+
+ // keepDataTrack에서 현재 클라이언트에 해당하는 채널과 관련된 데이터를 담고 있는 리스트를 가져옴
+ List channelData
+ = keepDataTrack.get(socketChannel);// 특정 클라이언트가 가진 데이터 추출
+
+ channelData.add(data); // 클라이언트로부터 수신한 데이터를 byte[]에 추가
+
+ /*
+ SelectionKey가 감지해야할 IO이벤트의 관심사를 설정,
+ 여기서는 쓰기 가능한 상태(OP_WRITE)에 대한 관심사를 추가
+
+ 정리하면, 클라이언트로부터 수신한 데이터를 keepDataTrack에 추가한 후,
+ 서버가 클라이언트에게 데이터를 쓰기 위한 준비가 되었음을 나타냄
+
+ 따라서 이후 Selector가 SelectionKey에 대해 OP_WRITE 이벤트가 발생했을 때,
+ 클라이언트 소켓 채널에 데이터를 쓰기 위한 처리를 수행할 수 있게 됨
+ */
+ key.interestOps(SelectionKey.OP_WRITE);
+
+ }
+}
+
+
diff --git a/week14/src-backend-java-nio/step02_non_blocking/src/test/java/dev/nio/AppTest.java b/week14/src-backend-java-nio/step02_non_blocking/src/test/java/dev/nio/AppTest.java
new file mode 100644
index 0000000..48b1801
--- /dev/null
+++ b/week14/src-backend-java-nio/step02_non_blocking/src/test/java/dev/nio/AppTest.java
@@ -0,0 +1,38 @@
+package dev.nio;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Unit test for simple App.
+ */
+public class AppTest
+ extends TestCase
+{
+ /**
+ * Create the test case
+ *
+ * @param testName name of the test case
+ */
+ public AppTest( String testName )
+ {
+ super( testName );
+ }
+
+ /**
+ * @return the suite of tests being tested
+ */
+ public static Test suite()
+ {
+ return new TestSuite( AppTest.class );
+ }
+
+ /**
+ * Rigourous Test :-)
+ */
+ public void testApp()
+ {
+ assertTrue( true );
+ }
+}
diff --git a/week14/src-backend-java-nio/step03_netty/.gitignore b/week14/src-backend-java-nio/step03_netty/.gitignore
new file mode 100644
index 0000000..5ff6309
--- /dev/null
+++ b/week14/src-backend-java-nio/step03_netty/.gitignore
@@ -0,0 +1,38 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/week14/src-backend-java-nio/step03_netty/pom.xml b/week14/src-backend-java-nio/step03_netty/pom.xml
new file mode 100644
index 0000000..909e856
--- /dev/null
+++ b/week14/src-backend-java-nio/step03_netty/pom.xml
@@ -0,0 +1,44 @@
+
+ 4.0.0
+
+ dev.netty
+ step03_netty
+ 1.0-SNAPSHOT
+ jar
+
+ step03_netty
+ http://maven.apache.org
+
+
+ UTF-8
+
+
+
+
+ junit
+ junit
+ 3.8.1
+ test
+
+
+ io.netty
+ netty-all
+ 4.1.119.Final
+
+
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.7
+
+
+
+
+ ch.qos.logback
+ logback-classic
+ 1.4.8
+
+
+
diff --git a/week14/src-backend-java-nio/step03_netty/src/main/java/dev/netty/App.java b/week14/src-backend-java-nio/step03_netty/src/main/java/dev/netty/App.java
new file mode 100644
index 0000000..4e8edf0
--- /dev/null
+++ b/week14/src-backend-java-nio/step03_netty/src/main/java/dev/netty/App.java
@@ -0,0 +1,13 @@
+package dev.netty;
+
+/**
+ * Hello world!
+ *
+ */
+public class App
+{
+ public static void main( String[] args )
+ {
+ System.out.println( "Hello World!" );
+ }
+}
diff --git a/week14/src-backend-java-nio/step03_netty/src/main/java/dev/netty/step01_hello_netty/DiscardServer.java b/week14/src-backend-java-nio/step03_netty/src/main/java/dev/netty/step01_hello_netty/DiscardServer.java
new file mode 100644
index 0000000..1b5a763
--- /dev/null
+++ b/week14/src-backend-java-nio/step03_netty/src/main/java/dev/netty/step01_hello_netty/DiscardServer.java
@@ -0,0 +1,41 @@
+package dev.netty.step01_hello_netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.sctp.nio.NioSctpServerChannel;
+import io.netty.channel.socket.SocketChannel;
+
+public class DiscardServer {
+ public static void main(String[] args) throws InterruptedException {
+ /*
+ worker와 boss를 구분 각각은 실제 작업(Data I/O)과 연결요청을 담당
+ */
+ EventLoopGroup bossGroup = new NioEventLoopGroup(1);
+ EventLoopGroup workerGroup = new NioEventLoopGroup(1);
+
+ try {
+ ServerBootstrap bootstrap = new ServerBootstrap();
+
+ bootstrap.group(bossGroup, workerGroup) // event loop
+ .channel(NioSctpServerChannel.class) //socker mode (blocking or nonblocking)
+ .childHandler(new ChannelInitializer() {
+ @Override
+ protected void initChannel(SocketChannel socketChannel) throws Exception {
+ ChannelPipeline pipeline = socketChannel.pipeline();
+ pipeline.addLast(new DiscardServerHandler());
+ }
+ });
+
+ ChannelFuture future = bootstrap.bind(8888).sync();
+
+ future.channel().closeFuture().sync();
+ } finally {
+ workerGroup.shutdownGracefully();
+ bossGroup.shutdownGracefully();
+ }
+ }
+}
diff --git a/week14/src-backend-java-nio/step03_netty/src/main/java/dev/netty/step01_hello_netty/DiscardServerHandler.java b/week14/src-backend-java-nio/step03_netty/src/main/java/dev/netty/step01_hello_netty/DiscardServerHandler.java
new file mode 100644
index 0000000..76eee99
--- /dev/null
+++ b/week14/src-backend-java-nio/step03_netty/src/main/java/dev/netty/step01_hello_netty/DiscardServerHandler.java
@@ -0,0 +1,21 @@
+package dev.netty.step01_hello_netty;
+
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.util.concurrent.EventExecutorGroup;
+
+public class DiscardServerHandler extends ChannelInboundHandlerAdapter {
+
+ @Override
+ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+ super.channelRead(ctx, msg);
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+ super.exceptionCaught(ctx, cause);
+ }
+}
+
diff --git a/week14/src-backend-java-nio/step03_netty/src/main/java/dev/netty/step01_hello_netty/EchoServer.java b/week14/src-backend-java-nio/step03_netty/src/main/java/dev/netty/step01_hello_netty/EchoServer.java
new file mode 100644
index 0000000..b1af917
--- /dev/null
+++ b/week14/src-backend-java-nio/step03_netty/src/main/java/dev/netty/step01_hello_netty/EchoServer.java
@@ -0,0 +1,61 @@
+package dev.netty.step01_hello_netty;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+
+/*
+ Echo: 응답하다
+ 클라이언트의 요청을 받고 응답하는 서버
+ */
+public class EchoServer {
+ public static void main(String[] args) throws InterruptedException {
+ // Boss - 주로 클라이언트 연결 요청 처리 담당
+ // 1 -> 부모 이벤트 루프 스레드 그룹은 단일 스레드로 동작
+ EventLoopGroup bossGroup = new NioEventLoopGroup(1);
+
+ // Worker - 데이터 송수신 처리 담당
+ EventLoopGroup workerGroup = new NioEventLoopGroup();
+ // NioEventLoopGroup()을 인수 없이 생성 -> 사용할 스레드 수를 서버 애플리케이션이 동작하는 하드웨어 코어 수를 기준으로 지정
+ // 일반적으로 스레드 수는 하드웨어가 가지고 있는 CPU 코어 수의 2배를 사용함
+ // 만약 서버 애플리케이션이 동작하는 하드웨어가 4코어 CPU이고 하이퍼 스레딩을 지원할 경우 스레드는 16개가 생성됨
+
+ try {
+ // 서버 초기화용 부트스트랩 객체 생성
+ ServerBootstrap bootstrap = new ServerBootstrap();
+ // 부트스트랩 객체를 통해 각종 네트워크 옵션 설정 적용
+ bootstrap.group(bossGroup, workerGroup) // 이벤트 루프 설정, 미리 생성한 EventRoopGroup을 인수로 전달 group(부모 스레드, 자식 스레드)
+
+ // 부모 스레드 - 클라이언트의 연결 요청 수락 담당
+ // 자식 스레드 - 연결된 소켓에 대한 IO 처리 담당
+
+
+ .channel(NioServerSocketChannel.class) // 서버 소켓(부모 스레드)이 사용할 네트워크 입출력 모드 설정, NIOServer~ - 논블로킹 모드
+
+ // ChannelInitializer - 클라이언트로부터 연결된 채널이 초기화될 때 수행할 동작이 지정된 클래스
+ .childHandler(new ChannelInitializer() {
+
+ @Override
+ protected void initChannel(SocketChannel socketChannel) throws Exception { // 채널 초기화 시 수행할 동작
+ ChannelPipeline pipeline = socketChannel.pipeline(); // 채널 파이프라인 객체 생성
+ pipeline.addLast(new EchoServerHandler()); // 채널 파이프라인에 EchoServerHandler 등록
+ // EchoServerHandler - 클라이언트의 연결이 생성되었을 때 수행할 데이터 처리 로직 담당
+ }
+ });
+
+ // ChannelFuture를 통해 비동기 메서드의 처리 결과를 확인
+ // sync(): ChannelFuture 객체의 요청이 완료될 때까지 대기
+ ChannelFuture future = bootstrap.bind(8888).sync();
+ future.channel().closeFuture().sync();
+
+ } finally {
+ workerGroup.shutdownGracefully();
+ bossGroup.shutdownGracefully();
+ }
+ }
+}
diff --git a/week14/src-backend-java-nio/step03_netty/src/main/java/dev/netty/step01_hello_netty/EchoServerHandler.java b/week14/src-backend-java-nio/step03_netty/src/main/java/dev/netty/step01_hello_netty/EchoServerHandler.java
new file mode 100644
index 0000000..2be3c8c
--- /dev/null
+++ b/week14/src-backend-java-nio/step03_netty/src/main/java/dev/netty/step01_hello_netty/EchoServerHandler.java
@@ -0,0 +1,37 @@
+package dev.netty.step01_hello_netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+
+import java.nio.charset.Charset;
+
+public class EchoServerHandler extends ChannelInboundHandlerAdapter {
+
+ /*
+ 서버 입장에서 채널을 통해 읽기 이벤트가 발생했을 때, 처리할 로직을 작성하는 부분
+ */
+ @Override
+ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+ // 채널을 통해 버퍼에서 읽은 메시지를 문자열 타입으로 변환
+ String readMessage = ((ByteBuf) msg).toString(Charset.defaultCharset());
+
+ StringBuilder builder = new StringBuilder();
+ builder.append("수신한 문자열 [");
+ builder.append(readMessage);
+ builder.append("]");
+ System.out.println(builder.toString());
+
+ ctx.write(msg);
+ }
+
+ @Override
+ public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
+ ctx.flush();
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+ // super.exceptionCaught(ctx, cause);
+ }
+}
diff --git a/week14/src-backend-java-nio/step03_netty/src/test/java/dev/netty/AppTest.java b/week14/src-backend-java-nio/step03_netty/src/test/java/dev/netty/AppTest.java
new file mode 100644
index 0000000..cd60932
--- /dev/null
+++ b/week14/src-backend-java-nio/step03_netty/src/test/java/dev/netty/AppTest.java
@@ -0,0 +1,38 @@
+package dev.netty;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Unit test for simple App.
+ */
+public class AppTest
+ extends TestCase
+{
+ /**
+ * Create the test case
+ *
+ * @param testName name of the test case
+ */
+ public AppTest( String testName )
+ {
+ super( testName );
+ }
+
+ /**
+ * @return the suite of tests being tested
+ */
+ public static Test suite()
+ {
+ return new TestSuite( AppTest.class );
+ }
+
+ /**
+ * Rigourous Test :-)
+ */
+ public void testApp()
+ {
+ assertTrue( true );
+ }
+}