이전에 다룬 TCP, UDP는 서버-클라이언트가 1:1로 통신을 했습니다. 하지만 현재의 서버-클라이언트 서비스는 한대의 서버가 여러대의 클라이언트와 동시에 통신을 수행합니다. 기존 포스트의 서버는 하나의 요청을 받으면 그에 대한 처리를 하고 응답을 보낸 뒤 다음 요청을 받아들입니다. 따라서 여러대가 동시에 요청을 하게되면 요청 순서대로 처리하는 동안 클라이언트는 대기하는 시간이 발생하게 됩니다.
다수의 요청을 동시에 처리하기 위해서 요청마다 별개의 스레드를 생성하고 처리하는 방식을 사용해야합니다. 이때 스레드가 너무 많이 늘어나는 것도 성능 저하의 원인이 되기 때문에 스레드풀을 사용해서 늘어날 수 있는 최대 스레드의 수를 제한하는 것이 좋습니다.
다음 코드는 기존의 TCP 서버 코드를 ExecutorService
로 스레드풀을 생성하고 각 요청을 별개의 스레드로 관리하는 코드로 개선한 것입니다.
스레드풀 생성, 스레드풀 종료, 작업 큐에 작업 삽입 외에 코드 변화는 거의 없고 변화가 생긴 부분에 대해서만 주석을 달아놓았습니다.
public class TCPServer {
private static ServerSocket serverSocket = null;
//최대 10개의 스레드를 생성할 수 있는 스레드풀 정의
private static ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void main(String[] args) {
System.out.println("* 서버 종료는 q 입력 *");
startTCPServer();
Scanner scanner = new Scanner(System.in);
while (true) {
String input = scanner.nextLine();
if (input.equals("q")) {
break;
}
}
scanner.close();
stopTCPServer();
}
public static void startTCPServer() {
Thread thread = new Thread() {
@Override
public void run() {
try {
serverSocket = new ServerSocket(50001);
System.out.println(serverSocket.getLocalPort() + "번 포트에서 서버 실행");
while (true) {
System.out.println("서버 연결 요청 대기중");
Socket socket = serverSocket.accept();
//작업 큐에 작업 삽입
executorService.execute(() -> {
try {
InetSocketAddress inetSocketAddress =
(InetSocketAddress) socket.getRemoteSocketAddress();
System.out.println(inetSocketAddress.getHostName()
+ "의 연결 요청 수락");
DataInputStream dis = new DataInputStream(socket.getInputStream());
String data = dis.readUTF();
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeUTF(data);
dos.flush();
System.out.println("받은 데이터 클라이언트로 재전송, 데이터: " + data);
socket.close();
System.out.println(inetSocketAddress.getHostName() + "의 연결 해제");
}
catch (IOException e) {
System.err.println(e);
}
});
}
}
catch (IOException e) {
System.err.println(e);
}
}
};
thread.start();
}
public static void stopTCPServer() {
try {
serverSocket.close();
executorService.shutdownNow(); //스레드풀 종료
System.out.println("서버 종료");
}
catch (IOException e) {
System.err.println(e);
}
}
}
이번엔 UDP 서버가 동시 요청을 처리할 수 있도록 개선한 코드입니다. 마찬가지로 스레드풀 생성, 스레드풀 종료, 작업 큐에 작업 삽입 외엔 변화한 부분은 없습니다.
public class UDPServer {
private static DatagramSocket datagramSocket = null;
//최대 10개의 스레드를 생성할 수 있는 스레드풀 정의
private static ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void main(String[] args) {
System.out.println("* 서버 종료는 q 입력 *");
startServer();
Scanner scanner = new Scanner(System.in);
while (true) {
String key = scanner.nextLine();
if (key.equals("q")) {
break;
}
}
scanner.close();
stopServer();
}
public static void startServer() {
Thread thread = new Thread() {
@Override
public void run() {
try {
datagramSocket = new DatagramSocket(50001);
System.out.println(datagramSocket.getLocalPort() + "번 포트에서 서버 실행");
while (true) {
DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024);
datagramSocket.receive(receivePacket);
//작업 큐에 작업 삽입
executorService.execute(() -> {
try {
String fromClientData =
new String(
receivePacket.getData(), 0,
receivePacket.getLength(), "UTF-8"
);
SocketAddress socketAddress = receivePacket.getSocketAddress();
String data = "서버가 보내는 데이터: " + fromClientData;
byte[] bytes = data.getBytes("UTF-8");
DatagramPacket sendPacket =
new DatagramPacket(bytes, 0, bytes.length, socketAddress);
datagramSocket.send(sendPacket);
}
catch (IOException e) {
System.err.println(e);
}
});
}
}
catch (IOException e) {
System.err.println(e);
}
}
};
thread.start();
}
public static void stopServer() {
datagramSocket.close();
executorService.shutdownNow(); //스레드풀 종료
System.out.println("서버 종료");
}
}