학습목표 : 유니티에서 웹서버로 요청(request) 보내기
요청 내용: 방 목록, 플레이어 정보 등...
웹서버 역할: DB에서 방 목록, 플레이어 정보등을 DB에서 가져와서 유니티로 응답함
웹서버를 사용하는 이유:
1. 데이터 저장,관리에 용이함.
2. 만들어두면 미러(Mirror)에서 로비, 방리스트, 계정 로그인 및 저장등을 간단하게 구현할 수 있음
3. 나중에 게임홈페이지도 외부에 만들어서 연동할 수 있음
4. JSP공부겸 병행
DBMS(오라클)에서 방 테이블 생성
CREATE TABLE ROOMS (
ROOM_ID NUMBER GENERATED AS IDENTITY PRIMARY KEY,
ROOM_NAME VARCHAR2(100) NOT NULL,
HOST_ADDR VARCHAR2(100) NOT NULL,
PLAYERS NUMBER(2) DEFAULT 1,
LAST_UPDATE DATE DEFAULT SYSDATE
);
방 테이블 : 방ID(기본키), 방이름, 방장 아이피, 플레이어 수, 방 생존신고 요청(?)
방장 아이피랑, 생존신고를 설명하자면,
원래는 단일 서버 아이피주소로 모든 플레이어가 한 서버로 접속하도록 구현할 수 도 있지만(포톤 네트워크처럼)
이러면 모든 네트워크 로직을 서버가 부담하게 됨
이후에 ngrok으로 무료 서버 도메인을 생성할건데, 클라이언트(플레이어) 수가 많을수록 부담이 갈까봐,
방장을 독립적은 서버로 설정하여 부담을 줄이려고함
(단, 방장이 나가면 방이 터짐)

생존신고 기능이란 유령방을 제거하기 위해 만든 기능으로, 살아있는 방은 주기적으로 LAST_UPDATE를
현재 시각으로 업데이트하여, 이후에 코드에서 업데이트 주기가 몇분 이내로 이루어지지않는 방은 자동으로 삭제하도록
만들 예정
- 자바 DTO로 이동

DTO는 DB의 테이블 구조와 똑같음
간단히 속성과 getter, setter로만 이루어져있음
DTO의 목적:말 그대로 DB의 테이블구조를 자바로
가져와서 사용하겠다는 것
-DAO 구성
DAO는 DBMS에서 사용하던 DML (조회, 수정, 삽입, 삭제)을 자바에서 처리하도록 함

여러 서블릿에서 이 DAO의 기능을 사용할때, 매번 DAO myDAO = new DAO () ... 이런식으로 사용하면
비효율적이니 싱글톤으로 만듦


Context는 src/main/webapp/META-INF의 context.xml파일에서 참조할때 사용
웹 애플리케이션의 설정과 자원을 관리함

<Resource /> 자원
name 리소스이름
auth 인증주체 (톰캣 = 컨테이너)
type 자원타입, DAO에서 ctx = javax.sql.DataSource로 사용중
driverClassName DB에서 제공하는 드라이버, 오라클은 odjbc17.jar, 오라클 홈페이지에서 다운받을수있음

username, password 오라클 DBMS에서 사용하던 계정정보
maxTotal 최대 커넥션(연결) 개수( 커질수록 서버 부담도 커짐 )
maxIdle 사용되지 않고 대기할 수 있는 최대 커넥션의 수
ds = (DataSource) ctx.lookup("java:comp/env/jdbc/myoracle");

Context.lookup 메서드의 파라미터는 반드시 리소스의 이름과 같아야함
DataSource를 사용하는 이유:
사실 DataSource를 사용하지 않고도 DB기능을 사용할 수 있는데,
예를들어 DAO에서 searchPlayerInfo(), getRoomList() 등의 메서드가 존재한다면
각 메서드에서 DriverManager로 커넥션을 생성하고 기능을 수행한다음 커넥션을 닫은 과정을 반복해야함
새로운 커넥션을 계속 만들때마다 딜레이가 생길 수 있고, 플레이어가 많다면 그만큼 커넥션이 생성되고 소멸하기를
반복하니까 서버에 부담이 늘어날 수 밖에 없음

하지만 DataSource를 사용하면 미리 커넥션을 만들어두고, 필요할때마다 이 커넥션을 불러다가 재사용하니 효율이 훨씬 좋음
DAO 메서드 로직:
커넥션(Connection) 생성
PreparedStatement 생성
쿼리(sql)를 PreparedStatement에 대입
PreparedStatement를 실행(execute)
실행에도 두 종류가 있음
executeQuery() 와 executeUpdate()
Query는 Select (조회) 전용, 조회결과는 ResultSet으로 반환
Update는 실제 DB에 영향을 주는것 (Update, Delete, Insert)
실행 후에 ResultSet, PreparedStatement, Connection을 닫아야함
방생성 메서드 예시
public int insertRoom(Room room) {
Connection conn = null;
PreparedStatement pstmt = null;
String sql = "INSERT INTO ROOMS ( ROOM_NAME, HOST_ADDR, PLAYERS, LAST_UPDATE)"
+ "VALUES ( ?, ?, 1, SYSDATE)";
try {
conn = ds.getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, room.getRoomName());
pstmt.setString(2, room.getHostAddress());
return pstmt.executeUpdate();
}catch(Exception e) {
e.printStackTrace();
}finally {
closeAll(conn,pstmt, null); // 따로 만든 메서드임
}
return -1;
}
sql문 안에 ? 라고 적혀있는 부분은 try문 안에있는 PreparedStatement.setString에서 대입함
첫번째 ?는 setStirng(1, "내용")
두번째 ?는 setString(2, "내용")
executeUpdate()로 실행하고 닫기
private void closeAll(Connection conn, PreparedStatement pstmt, ResultSet rs) {
try {
if (rs != null) rs.close();
if(pstmt != null) pstmt.close();
if(conn != null) conn.close();
}catch(Exception e) {
e.printStackTrace();
}
}
방 목록 가져오기 메서드(SELECT 문)
public List<Room> getRoomList(){
deleteExpiredRooms(); //따로 만든 메서드임
List<Room> list = new ArrayList<>();
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
String sql = "SELECT * FROM ROOMS ORDER BY ROOM_ID DESC";
try {
conn = ds.getConnection();
pstmt = conn.prepareStatement(sql);
rs = pstmt.executeQuery();
while(rs.next()) {
Room room = new Room();
room.setRoomId(rs.getInt("ROOM_ID"));
room.setRoomName(rs.getString("ROOM_NAME"));
room.setHostAddress(rs.getString("HOST_ADDR"));
room.setCurrentUserCount(rs.getInt("PLAYERS"));
list.add(room);
}
}catch(Exception e) {
e.printStackTrace();
}finally { closeAll(conn, pstmt, rs); }
return list;
}
Insert문과 비슷하지만 executeQuery는 ResultSet으로 반환함
당연히 반환되는 ResultSet의 값은 여러개일 수 있으니 while문으로 처리
rs.next() : result의 다음 행이 존재하면 true반환
방 제거도 insert와 비슷한 원리고, 오래된 방 제거하는 메서드는 다음과 같음
private void deleteExpiredRooms() {
Connection conn = null;
PreparedStatement pstmt = null;
String sql = "DELETE FROM ROOMS WHERE LAST_UPDATE < (SYSDATE - INTERVAL '2' MINUTE)";
try {
conn = ds.getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.executeUpdate();
}catch(Exception e) {e.printStackTrace();}
finally {
closeAll(conn,pstmt, null);
}
}
다음은 LAST_UPDATE값을 새로고침(생존신고)하는 메서드
public void updateHeartbeat(int roomId) {
Connection conn = null;
PreparedStatement pstmt = null;
String sql = "UPDATE ROOMS SET LAST_UPDATE = SYSDATE WHERE ROOM_ID = ?";
try {
conn = ds.getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, roomId);
pstmt.executeUpdate();
}catch(Exception e) {e.printStackTrace();}
finally {closeAll(conn,pstmt, null); }
}
지금까지 DTO와 DAO를 작성했다면 자바에서 남은건 서블릿 뿐.
서블릿이란 웹 애플리케이션에서 클라이언트의 요청을 받아 처리하고 응답으로 반환함
지금까지의 내용을 종합해보면
DTO : 테이블
DAO : DB에서 사용하던 쿼리를 쓰기좋게 메서드로 정리
서블릿 : 클라이언트-웹서버 통신
다음은 방생성 서블릿:
@WebServlet("/createRoom")
public class RoomCreateServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("application/json;charset=UTF-8");
String roomName = request.getParameter("roomName");
String hostAddr = request.getParameter("hostAddr");
Room room = new Room();
room.setRoomName(roomName);
room.setHostAddress(hostAddr);
RoomDAO dao = RoomDAO.getInstance();
int result = dao.insertRoom(room);
PrintWriter out = response.getWriter();
if(result > 0) {
out.print(new Gson().toJson("success"));
}else {
out.print(new Gson().toJson("fail"));
}
}
}

https://프로젝트이름/createRoom 이라는 사이트 주소로
들어가면 아래 클래스(서블릿) 실행한다는 뜻
서블릿을 사용하려면 HttpServlet을 상속받아야하고
doPost()메서드나 doGet()메서드를 작성할 수 있음
Post는 전송, Get은 가져오기 같은 느낌
여기서 클라이언트가 /createRoom 으로 들어갈때, 서버 입장에서는 이것은 요청(request)고,
방이름이랑 방장의 아이피주소 데이터를 같이 가지고 가는데 이러한 데이터는 파라미터다.
이 결과값을 클라이언트한테 반환할때는 응답(response)
이후 DTO에 위 데이터를 집어넣고 DAO의 방 만들기 메서드 호출
참고로 유니티 - 웹서버 간의 데이터 전송 타입은 JSON으로 할 예정이고
JSON을 사용할거면 손코딩으로 JSON형식으로 변환하던지, GSON이나 JACKSON라이브러리 사용은 개인취향에
맞춰서 사용하면 된다.
스프링부트는 JACKSON 라이브러리를 기본적으로 가지고있는걸로 알고있고, 글쓴이는 이클립스에서
GSON라이브러리를 따로 다운받아 사용중이다.


이런느낌으로 각 서블릿을 만들어서 사용하면 된다.
웹서버를 개발할때 POSTMAN을 사용하면 테스트할때 용이하다.


POSTMAN으로 실행결과에 문제가 없다면 유니티로 넘어갈 차례다.
포스트맨에서 했던 것처럼
UnityWebRequest.Get타입으로 요청보내기
yield return SendWebRequest() 응답이 올때까지 대기
www.result 통신 성공여부 체크
downloadHandler.text : response.getWriter().print()로 보낸 JSON값
실행결과

| [Mirror] #3 유니티로 데이터파일 직렬화/역직렬화 (0) | 2026.04.07 |
|---|---|
| [Mirror] #1 초기세팅 (0) | 2026.04.03 |