티스토리 뷰

어제 Socket을 사용한 간단한 서버 - 클라이언트 간 채팅 프로그램을 구현해봤다.

이번엔 단순한 채팅이 아닌, 문제 은행을 저장해놓고 문제에 해당하는 정답을 클라이언트로부터 받아 서버에 저장 후, 정답 여부를 판단하여 다시 클라이언트에 보내주는 양방향 문제 풀이 프로그램을 구현해봤다.

 

원리는 간단하다. 데이터를 좇아가기가 조금 복잡해졌을 뿐...


우선, 입력받은 답안에 따라 상태를 바꿔가며 문제를 출제해 줄 프로토콜을 구성해야 한다.

즉, 문제 배열과 정답 배열을 가지고 있다가, 서버에서 입력받은 답안이 정답과 같은 지 확인한 그 결과에 따른 출력을 내보내게끔 구성해야 한다.

 

○QuizProtocol.java

package com.choonham;

public class QuizProtocol {
	/*
	 * 1. 서버 요청이 있을 경우 문제를 보내주고, 
     * 2. 서버로부터 전달 받은 답을 확인 후, 그 결과를 서버에 전송.
	 */
	// 상수 선언(상태 및 문항 수), 0:대기, 1:문제 진행 중, 2: 답 제출, 3: 제공 문항 수
	private static final int WAITING = 0;
	private static final int PROBLEM = 1;
	private static final int ANSWER = 2;
	private static final int NUMOFPRO = 5;
	// 상태를 저장하는 변수(대기, 문제, 정답, 제공 문항 수)
	private int state = WAITING;
	// 현재 문항 정보를 저장할 변수
	private int currentProblem = 0;
	// 문제 배열과 정답 배열
	private String[] problems = { "20x20?", "뭉치의 견종은?", "가장 유익한 TIL 블로그는? ", "당신이 지금부터 해야할 일은?", "저녁은?" };
	private String[] answers = { "400", "푸들", "choonham's TIL", "코딩테스트 풀이", "치킨 제발"};

	public QuizProtocol() {
		// TODO Auto-generated constructor stub
	}

	// 문제를 제공하고 정답을 확인해주는 전반적인 프로세스 메서드
	public String process(String theInput) {
		String theOutput = null; // 결과를 서버에 보내기 위한 변수

		if (state == WAITING) {
			theOutput = "퀴즈를 시작합니다.(y/n)";  	// 초기 대기 상태일 경우, client에게 보여질 내용
			state = PROBLEM;
		} else if (state == PROBLEM) {     // client가 계속 문제를 진행할 것인지 여부
			if (theInput.equalsIgnoreCase("y")) {  
				theOutput = problems[currentProblem % NUMOFPRO]; // 문제 제공
				state = ANSWER;
			} else {
				state = WAITING;
				theOutput = "당신은 패배자입니까?";
			}
		} else if (state == ANSWER) {
			if (theInput.equalsIgnoreCase(answers[currentProblem % NUMOFPRO])) {
				theOutput = "성공, 당신은 했습니다. (도전 계속? y, 시시 n)"; 		// 답이 제출되었을 때
				state = PROBLEM;
			} else {
				theOutput = "실패, 빡대가리 당신입니다. (꼬우면 y, 인정하면 n)";
				state = PROBLEM;
			}
			currentProblem++;
		}

		return theOutput;
	}

}

 


다음으로, Socket을 생성하고, 연결을 유지할  Server를 구성한다. 이때, 서버는 클라이언트의 입력 값을 Socket으로부터 가져와서 QuizProtocol에 전달하고 그 출력 값을 다시 받아, Socket에 올리는 역할을 한다.

 

○QuizServer.java

package com.choonham;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class QuizServer {
	/*
	 * 1. 클라이언트 요청 시, 문제 은행으로부터 추출된 문제를 클라이언트에 전송,
	 * 
	 * 2. 클라이언트로부터 전송받은 답을 문제은행에 제출.
	 * 
	 * 3. 문제은행이 보내준 결과를 다시 클라이언트에게 전달.
	 */

	public static void main(String[] args) throws IOException {
		// main메서드는 다른 클래스가 호출하지 않으므로 throws를 사용해도 상관이 없으나, 다른 경우에는 throws를 건 객체를 사용하는
		// 객체가 또 다시 예외 처리를 해야한다.
			
		// 클라이언트와 통신 준비 시작
		ServerSocket serverSocket = null;
		try {
			serverSocket = new ServerSocket(5555);
			
		} catch (IOException e) {
			System.out.println("다음 포트에 연결할 수 없습니다. : 5555");
			e.printStackTrace();
			System.exit(1); // main()을 완전히 종료
		}
		Socket clientSocket = null;
		try {
			clientSocket = serverSocket.accept();
		} catch (IOException e) {
			System.out.println("accept() 실패");
			System.exit(1);
		}
		// 클라이언트와 통신 준비 종료

		// 클라이언트와 통신 시작

		OutputStream toSocket = clientSocket.getOutputStream();
		PrintWriter out = new PrintWriter(toSocket, true);   //입력 받는 모든 Stream을 Socket에게 보내기 위한 Writer

		InputStream fromSocket = clientSocket.getInputStream();
		InputStreamReader isr = new InputStreamReader(fromSocket);
		BufferedReader in = new BufferedReader(isr);  //Socket에 있는 모든 Stream을 읽어올 Reader

		String inputLine;
		String outputLine;
		
		// 클라이언트와 통신 종료

		// 퀴즈 시작
		QuizProtocol qp = new QuizProtocol();
		outputLine = qp.process(null);
		out.println(outputLine); //초기값 null을 넣고 프로세스 시작
		
		while ((inputLine = in.readLine()) != null) {
			outputLine = qp.process(inputLine);  //소켓에 들어온 값을 프로세스에게 전달
			out.println(outputLine);
			if (outputLine.equals("당신은 패배자입니까?")) {
				break;
			}
		}
		
		// 항상 문은 닫는 습관을 들이자
		out.close();
		in.close();
		clientSocket.close();
		serverSocket.close();
	}

}

마지막으로 콘솔 창에 y/n와 같은 요청이나, 문제를 풀이할 클라이언트 쪽을 설계하면 코드 작성은 끝이다. 

클라이언트도 마찬가지로, Socket에 Stream을 보내고 받기 때문에 80% 정도 서버의 코드와 동일하며, 콘솔 창 입력 Stream을 받아 읽어내는 코드가 추가되었을 뿐이다.

 

○QuizClient.java

package com.choonham;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

public class QuizClient {
/*
  1. 서버로부터 전송 받은 문제에 대한 입력한 답을 서버에 전달

  2. 서버로부터 전송받은 결과를 확인

  3. 문제를 계속 풀 것인지 여부를 결정.
  */
	public static void main(String[] args){
		
		Socket quizSocket = null;
		PrintWriter out = null;
		BufferedReader in = null;
		
		try {
			//서버 접속 및 서버로 전송할 데이터 준비
			quizSocket = new Socket("localhost", 5555);
			OutputStream toSocket = quizSocket.getOutputStream();
			out = new PrintWriter(toSocket, true);
			
			//서버로부터 전송된 데이터 읽기 준비
			InputStream fromSocket = quizSocket.getInputStream();
			InputStreamReader quizIsr = new InputStreamReader(fromSocket);
			in = new BufferedReader(quizIsr);  //Socket에 있는 Stream을 싸그리 싹싹 Read
			
			InputStreamReader isr = new InputStreamReader(System.in);
			BufferedReader user = new BufferedReader(isr);  //콘솔창 입력 Stream을 싸그리 싹싹 Read
			
			String fromServer;
			String fromUser;
			
			while((fromServer = in.readLine()) != null) {
				System.out.println("서버 =>" + fromServer); //Socket에 있는 Stream을 읽어서 확인
				if(fromServer.equals("당신은 패배자입니까?")) {
					break;
				}
				
				fromUser = user.readLine();
				if(fromUser != null) {  //유저가 입력한 콘솔 Stream을 읽어서 확인
					System.out.println("클라이언트 =>" + fromUser);
					out.println(fromUser);
				}
			}
			out.close();
			in.close();
			quizSocket.close();
		} catch(UnknownHostException e) {
			System.out.println("Localhost에 접근할 수 없습니다.");
			System.exit(1);
		} catch(IOException e) {
			System.out.println("입출력 오류");
			System.exit(1);
		}
	
	}

}

 

 


코드 작성을 끝내면, 꽤나 그럴듯한 문제 풀이 프로그램이 완성된다.

 

○Output:


프로젝트의 Git Hub:

github.com/Choonham/Choonham-2020.03.10-Spring-Class/tree/master/java_io_socket_quiz_db

 

Choonham/Choonham-2020.03.10-Spring-Class

Contribute to Choonham/Choonham-2020.03.10-Spring-Class development by creating an account on GitHub.

github.com

 

 

끝!!!!

 

Comments