mini-pms 34a
9 minute read
Server
01) 간단한 메시지를 송수신 할 수 있도록 한다.
public class ServerApp {
public static void main(String[] args) {
// 8888 포트에서 클라이언트를 기다리는 서버 소켓을 생성한다.
try (ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("서버 실행 중");
// 클라이언트가 연결된 소켓을 리턴 받는다.
// 소켓을 통해 문자열 메시지를 입출력 할 수 있도록 스트림 객체를 준비한다.
try (Socket socket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream());
) {
// 클라이언트가 보낸 메시지를 읽고, 다시 클라이언트에게 리턴해준다.
String request = in.readLine();
out.println(request);
out.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
02) 사용자가 quit 명령을 입력할 때까지 반복한다.
public class ServerApp {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("서버 실행 중");
try (Socket socket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream());
) {
// while 문을 사용하여 반복문을 돌려 서버가 종료되지 않도록 한다.
while (true) {
String request = in.readLine();
out.println(request);
out.flush();
// quit 명령어를 입력받으면 종료하게 만든다.
if (request.equalsIgnoreCase("quit")) {
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
03) 응답의 끝에는 빈 줄을 보내도록 응답 프로토콜을 정의한다.
public class ServerApp {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("서버 실행 중");
try (Socket socket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream());
) {
while (true) {
String request = in.readLine();
// 응답 프로토콜을 정의한 메서드를 실행한다.
sendResponse(out, request);
// quit 명령어를 입력받으면 종료하게 만든다.
if (request.equalsIgnoreCase("quit")) {
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 응답의 끝에는 빈 줄을 보내도록 응답 프로토콜을 정의한다.
private static void sendResponse(PrintWriter out, String message) {
out.println(message);
out.println();
out.flush();
}
}
04) 다중 클라이언트 요청 처리 - 접속 순서대로 처리함
- 클라이언트 요청이 끊어지면 다음 클라이언트와 연결하는 것을 반복한다.
public class ServerApp {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("서버 실행 중");
// 클라이언트의 요청이 있을 때까지 대기하는 것을 반복한다.
while (true) {
handleClient(serverSocket.accept());
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static void sendResponse(PrintWriter out, String message) {
out.println(message);
out.println();
out.flush();
}
// 클라이언트 연결이 끊어지면 다음 클라이언트와 연결하는 것을 반복한다.
private static void handleClient(Socket clientSocket) {
try (Socket socket = clientSocket;
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream());
) {
while (true) {
String request = in.readLine();
sendResponse(out, request);
if (request.equalsIgnoreCase("quit")) {
break;
}
}
} catch (Exception e) {
System.out.println("클라이언트와의 통신 오류");
}
}
}
- 클라이언트가 접속하거나 연결을 끊으면 로그를 남기도록 한다.
public class ServerApp {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("서버 실행 중");
while (true) {
handleClient(serverSocket.accept());
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static void sendResponse(PrintWriter out, String message) {
out.println(message);
out.println();
out.flush();
}
private static void handleClient(Socket clientSocket) {
// 클라이언트가 접속하면 로그를 남긴다.
InetAddress address = clientSocket.getInetAddress();
System.out.printf("클라이언트(%s)가 연결되었습니다.\n",
address.getHostAddress());
try (Socket socket = clientSocket;
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream());
) {
while (true) {
String request = in.readLine();
sendResponse(out, request);
if (request.equalsIgnoreCase("quit")) {
break;
}
}
} catch (Exception e) {
System.out.println("클라이언트와의 통신 오류");
}
// 클라이언트가 연결을 끊으면 로그를 남긴다.
System.out.printf("클라이언트(%s)와의 연결을 끊었습니다.\n",
address.getHostAddress());
}
}
05) 다중 클라이언트의 동시 접속 처리
- 클라이언트의 요청 처리를 별도의 스레드로 분리한다.
ClientHandler 클래스 추가
- Runnable 인터페이스를 구현한다.
- 클라이언트의 요청 처리를 담당한다.
- ServerApp 에서 해당 코드를 가져온다.
ServerApp 클래스 변경
- 클라이언트 요청 처리를 ClientHandler에게 맡긴다.
// 클라이언트 요청을 처리하는 일을 한다.
public class ClientHandler implements Runnable {
Socket socket;
// ClientHandler의 내장 변수 socket에 클라이언트가 접속한 소켓을 저장한다.
public ClientHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
InetAddress address = this.socket.getInetAddress();
System.out.printf("클라이언트(%s)가 연결되었습니다.\n",
address.getHostAddress());
// this.socket으로 하면 try 문을 떠날 때 close(); 가 자동으로 호출된다.
try (Socket socket = this.socket;
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream());
) {
while (true) {
String request = in.readLine();
sendResponse(out, request);
if (request.equalsIgnoreCase("quit")) {
break;
}
}
} catch (Exception e) {
System.out.println("클라이언트와의 통신 오류");
}
System.out.printf("클라이언트(%s)와의 연결을 끊었습니다.\n",
address.getHostAddress());
}
private static void sendResponse(PrintWriter out, String message) {
out.println(message);
out.println();
out.flush();
}
}
public class ServerApp {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("서버 실행 중");
// 스레드를 생성해서 메인 스레드와 클라이언트의 요청을 수행하는
// 클라이언트 핸들러 스레드를 분리해서 실행한다.
// 이렇게 하면 클라이언트가 접속할 때마다
// 새로운 스레드를 실행하기 때문에 다중 클라이언트의 동시 접속을 처리할 수 있다.
while (true) {
// ClientHandler clientHandler = new ClientHandler(serverSocket.accept());
// Thread t = new Thread(clientHandler);
// t.start();
new Thread(new ClientHandler(serverSocket.accept())).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
- ClientHandler 클래스를 중첩 클래스로 만든다.
public class ServerApp {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("서버 실행 중");
while (true) {
new Thread(new ClientHandler(serverSocket.accept())).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
// ClientHandler 클래스를 ServerApp의 스태틱 중첩 클래스로 선언한다.
// 다른 클래스가 사용할 일이 없기 때문에 private로 선언한다.
// 바깥 클래스의 특정 인스턴스 멤버를 사용할 일이 없기 때문에
// static nested class로 선언한다.
private static class ClientHandler implements Runnable {
Socket socket;
public ClientHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
InetAddress address = this.socket.getInetAddress();
System.out.printf("클라이언트(%s)가 연결되었습니다.\n",
address.getHostAddress());
try (Socket socket = this.socket;
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream());
) {
while (true) {
String request = in.readLine();
sendResponse(out, request);
if (request.equalsIgnoreCase("quit")) {
break;
}
}
} catch (Exception e) {
System.out.println("클라이언트와의 통신 오류");
}
System.out.printf("클라이언트(%s)와의 연결을 끊었습니다.\n",
address.getHostAddress());
}
private static void sendResponse(PrintWriter out, String message) {
out.println(message);
out.println();
out.flush();
}
}
}
- 익명 클래스로 만든다.
- 상속 + 내부 클래스
- 클래스의 선언과 객체의 생성을 동시에 하기 때문에 단 한번만 사용될 수 있고 오직 하나의 객체만을 생성할 수 있는 일회용 클래스이다.
- 재사용을 안할 것 같은 클래스를 익명 클래스로 만든다.
public class ServerApp {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("서버 실행 중");
while (true) {
Socket clientSocket = serverSocket.accept();
// 익명 클래스 문법 사용
new Thread(new Runnable() {
@Override
public void run() {
InetAddress address = clientSocket.getInetAddress();
System.out.printf("클라이언트(%s)가 연결되었습니다.\n",
address.getHostAddress());
try (Socket socket = clientSocket;
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream());
) {
while (true) {
String request = in.readLine();
sendResponse(out, request);
if (request.equalsIgnoreCase("quit")) {
break;
}
}
} catch (Exception e) {
System.out.println("클라이언트와의 통신 오류");
}
System.out.printf("클라이언트(%s)와의 연결을 끊었습니다.\n",
address.getHostAddress());
}
private void sendResponse(PrintWriter out, String message) {
out.println(message);
out.println();
out.flush();
}
}).start();
new Thread(new ClientHandler(serverSocket.accept())).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 익명 클래스를 람다 문법으로 정의한다.
- 코드가 여러 블록에 중첩되면 될수록 들여쓰기를 하면서 코드를 읽기가 불편해진다.
- 익명 클래스의 코드를 바깥 클래스의 멤버로 만든 후 그 바깥 클래스의 멤버를 호출한다.
- 만약 익명 클래스가 메서드 한개짜리 인터페이스를 구현하고 그 코드도 간단하다면 람다 문법을 사용하면 간편하게 할 수 있다.
public class ServerApp {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("서버 실행 중");
while (true) {
// 익명 클래스의 바깥 클래스의 멤버를 사용
Socket clientSocket = serverSocket.accept();
// 람다 문법 적용
// new Thread(new Runnable() {
// @Override
// public void run() {
// handleClient(clientSocket);
// }
// }).start();
new Thread(() -> handleClient(clientSocket)).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static void handleClient(Socket clientSocket) {
InetAddress address = clientSocket.getInetAddress();
System.out.printf("클라이언트(%s)가 연결되었습니다.\n",
address.getHostAddress());
try (Socket socket = clientSocket;
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream());
) {
while (true) {
String request = in.readLine();
sendResponse(out, request);
if (request.equalsIgnoreCase("quit")) {
break;
}
}
} catch (Exception e) {
System.out.println("클라이언트와의 통신 오류");
}
System.out.printf("클라이언트(%s)와의 연결을 끊었습니다.\n",
address.getHostAddress());
}
private static void sendResponse(PrintWriter out, String message) {
out.println(message);
out.println();
out.flush();
}
}
Client
01) 간단한 메시지를 송수신 할 수 있도록 한다.
public class ClientApp {
public static void main(String[] args) {
// 서버주소 : localhost
// 서버포트 : 8888
try (Socket socket = new Socket("localhost", 8888);
PrintWriter out = new PrintWriter(socket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
out.println("Hello");
out.flush();
String response = in.readLine();
System.out.println(response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
02) 사용자가 quit 명령을 입력할 때까지 반복한다.
- com.eomcs.util 에 Prompt 클래스를 만든다.
public class Prompt {
static Scanner keyboardScan = new Scanner(System.in);
// 다른 패키지에서 메서드를 호출할 수 있도록 사용 범위를 public 으로 공개한다.
public static String inputString(String title) {
System.out.print(title);
return keyboardScan.nextLine();
}
public static int inputInt(String title) {
return Integer.parseInt(inputString(title));
}
public static Date inputDate(String title) {
return Date.valueOf(inputString(title));
}
// 프롬프트의 사용이 모두 끝났으면
// 이 메서드를 호출하여 System.in 입력 스트림 자원을 해제하도록 한다.
public static void close() {
keyboardScan.close();
}
}
- ClientApp 에서 Prompt 를 사용해 명령을 입력받도록 한다.
public class ClientApp {
public static void main(String[] args) {
try (Socket socket = new Socket("localhost", 8888);
PrintWriter out = new PrintWriter(socket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
// 명령어를 입력받도록 만든다.
String input = Prompt.inputString("명령> ");
out.println(input);
out.flush();
String response = in.readLine();
System.out.println(response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
- quit 명령어를 입력받으면 종료하게 만든다.
public class ClientApp {
public static void main(String[] args) {
// 서버주소 : localhost
// 서버포트 : 8888
try (Socket socket = new Socket("localhost", 8888);
PrintWriter out = new PrintWriter(socket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
// quit 명령어를 입력받으면 종료하게 만든다.
while (true) {
String input = Prompt.inputString("명령> ");
out.println(input);
out.flush();
String response = in.readLine();
System.out.println(response);
if (input.equalsIgnoreCase("quit")) {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
03) 서버가 빈 줄을 보낼 때까지 응답을 읽는다.
public class ClientApp {
public static void main(String[] args) {
// 서버주소 : localhost
// 서버포트 : 8888
try (Socket socket = new Socket("localhost", 8888);
PrintWriter out = new PrintWriter(socket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
// quit 명령어를 입력받으면 종료하게 만든다.
while (true) {
String input = Prompt.inputString("명령> ");
out.println(input);
out.flush();
// 응답을 읽는 메서드를 호출한다.
receiveResponse(in);
if (input.equalsIgnoreCase("quit")) {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 서버가 빈 줄을 보낼 때까지 응답을 읽는 메서드를 정의한다.
private static void receiveResponse(BufferedReader in) throws Exception {
while (true) {
String response = in.readLine();
if (response.length() == 0) {
break;
}
System.out.println(response);
}
}
}
04) 다중 클라이언트 요청 처리 - 접속 순서대로 서버에서 리턴받음
- 애플리케이션 아규먼트를 통해 서버의 주소와 포트 번호를 입력 받는다.
- 다른 서버에 접속하기 위해서 주소와 포트 번호를 알아야 한다.
public class ClientApp {
public static void main(String[] args) {
// 애플리케이션 아규먼트를 통해 서버의 주소와 포트 번호를 입력 받는다.
if (args.length != 2) {
System.out.println("프로그램 사용법");
System.out.println("java -cp ... ClientApp 서버주소 포트번호");
// JVM 강제 종료 - 정상종료
// 비정상 종료일 때는 종료 상태값이 1이 된다.
System.exit(0);
}
try (Socket socket = new Socket("localhost", 8888);
PrintWriter out = new PrintWriter(socket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
while (true) {
String input = Prompt.inputString("명령> ");
out.println(input);
out.flush();
receiveResponse(in);
if (input.equalsIgnoreCase("quit")) {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static void receiveResponse(BufferedReader in) throws Exception {
while (true) {
String response = in.readLine();
if (response.length() == 0) {
break;
}
System.out.println(response);
}
}
}
05) 04와 동일