19.5 서버의 동시 요청 처리
일반적으로 서버는 다수의 클라이언트와 통신을 한다. accept()와 receive()를 제외한 요청 처리 코드를 별도의 스레드에서 작업하는 것이 좋다. 스레드를 처리할 때 주의할 점은 클라이언트의 폭증으로 인한 서버의 과도한 스레드 생성을 방지해야 한다는 것이다. 그래서 스레드풀(ExecutorService)을 사용하는 것이 바람직하다.
TCP EchoServer 동시 요청 처리
package ch19.sec05.exam01;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class EchoServer {
private static ServerSocket serverSocket = null;
private static ExecutorService executorService = Executors.newFixedThreadPool(10); // 10개의 스레드로 요청을 처리하는 스레드풀 생성
public static void main(String[] args) {
System.out.println("--------------------------------------------------------------------");
System.out.println("서버를 종료하려면 q를 입력하고 Enter 키를 입력하세요.");
System.out.println("--------------------------------------------------------------------");
// TCP 서버 시작
startServer();
// 키보드 입력
Scanner scanner = new Scanner(System.in);
while (true) {
String key = scanner.nextLine();
if (key.toLowerCase().equals("q")) {
break;
}
}
scanner.close();
// TCP 서버 종료
stopServer();
}
public static void startServer() {
// 작업 스레드 정의
Thread thread = new Thread() {
@Override
public void run() {
try {
// ServerSocket 생성 및 Port 바인딩
serverSocket = new ServerSocket(50001);
System.out.println("[서버] 시작됨\n");
// 연결 수락 및 데이터 통신
while (true) {
// 연결 수락
Socket socket = serverSocket.accept();
executorService.execute(() -> {
try {
// 연결된 클라이언트 정보 얻기
InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
System.out.println("[서버] " + isa.getHostName() + "의 연결 요청을 수락함");
// 데이터 받기
DataInputStream dis = new DataInputStream(socket.getInputStream());
String message = dis.readUTF();
// 데이터 보내기
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeUTF(message);
dos.flush();
System.out.println("[서버] 받은 데이터를 다시 보냄: " + message);
// 연결 끊기
socket.close();
System.out.println("[서버] " + isa.getHostName() + "의 연결을 끊음\n");
} catch (IOException e) {
}
});
}
} catch (IOException e) {
System.out.println("[서버] " + e.getMessage());
}
}
};
// 스레드 시작
thread.start();
}
public static void stopServer() {
try {
// ServerSocket을 닫고 Port 언바인딩
serverSocket.close();
executorService.shutdownNow(); // 스레드풀 종료
System.out.println("[서버] 종료됨");
} catch (IOException e1) {}
}
}
UDP NewsServer 동시 요청 처리
package ch19.sec05.exam02;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketAddress;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NewsServer {
private static DatagramSocket datagramSocket = null;
private static ExecutorService executorService = Executors.newFixedThreadPool(10); // 10개의 스레드로 요청을 처리하는 스레드풀 생성
public static void main(String[] args) throws Exception {
System.out.println("--------------------------------------------------------------------");
System.out.println("서버를 종료하려면 q를 입력하고 Enter 키를 입력하세요.");
System.out.println("--------------------------------------------------------------------");
// UDP 서버 시작
startServer();
// 키보드 입력
Scanner scanner = new Scanner(System.in);
while (true) {
String key = scanner.nextLine();
if (key.toLowerCase().equals("q")) {
break;
}
}
scanner.close();
// UDP 서버 종료
stopServer();
}
public static void startServer() {
// 작업 스레드 정의
Thread thread = new Thread() {
@Override
public void run() {
try {
// DatagramSocket 생성 및 Port 바인딩
datagramSocket = new DatagramSocket(50001);
System.out.println("[서버] 시작됨");
while (true) {
// 클라이언트가 구독하고 싶은 뉴스 주제 얻기
DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024);
datagramSocket.receive(receivePacket);
executorService.execute(() -> {
try {
String newsKind = new String(receivePacket.getData(), 0, receivePacket.getLength(), "UTF-8");
// 클라이언트의 IP와 Port 얻기
SocketAddress socketAddress = receivePacket.getSocketAddress();
// 10개의 뉴스를 클라이언트로 전송
for (int i=1; i<=10; i++) {
String data = newsKind + ": 뉴스" + i;
byte[] bytes = data.getBytes("UTF-8");
DatagramPacket sendPacket = new DatagramPacket(bytes, 0, bytes.length, socketAddress);
datagramSocket.send(sendPacket);
}
} catch (Exception e) {
}
});
}
} catch (Exception e) {
System.out.println("[서버] " + e.getMessage());
}
}
};
// 스레드 시작
thread.start();
}
public static void stopServer() {
// DatagramSocket을 닫고 Port 언바인딩
datagramSocket.close();
executorService.shutdownNow(); // 스레드풀 종료
System.out.println("[서버] 종료됨");
}
}
서브목차