본문 바로가기

Infomation

[JAVA / 네트워크] 멀티룸 구조에 대하여(게임&채팅 방 여러개)

출처 : http://gompangs.tistory.com/50


멀티 룸 구조라는 것에 대해 이해는 하고 있지만 구현에 애를 먹었던 기억이 있다.


이는 채팅 이라던지, 게임에 통용되는 개념으로


하나의 방을 가지는 게임과 채팅이라면 상관은 없으나, 여러개의 방을 가져야하는 프로그램에서는 구현을 해주어야 한다.


일단 접속하는 각각의 유저마다 소켓을 가지고 있을 것이다.


서버에서는 여러개의 소켓을 생성하여 클라이언트를 할당하게 되고,


그 소켓을 방(Room) 개념에 맞게 분배해주면 간단하게 구현이 된다.


예를 들어 메인 클래스가 있다고 했을 때 멀티룸 구조를 위해서 두개의 클래스가 더 필요하게 된다.


편의상 간단하게


RoomManager, GameRoom


이라는 이름으로 정한다고 가정하자.


구조는 다음과 같다.


그림이 많이 심플하긴한데.. 전달하고자 하는 의미는 전달되었으리라 믿음.


클래스는 기본적으로 총 3개가 필요하다


게임서버를 기준으로 잡았을 때(채팅서버나 게임서버나 사실 그냥 이름만 다를뿐)


RoomManager

GameRoom

GameUser


이정도가 필요하겠다.


각 클래스의 역할은 다음과 같다.


RoomManager : Room의 생성/삭제를 관리하는 클래스이다. Room을 여러개 가질 수 있음

GameRoom : 게임 내의 로직(게임 진행 관련)을 처리하기 위한 클래스이다.  GameUser를 여러개 가질 수 있음

GameUser : 클라이언트의 고유한 정보(닉네임, 아이템, 플레이어 정보 등)를 가지는 클래스이며, 중요한 것은 "소켓" 을 가지고 있어야 한다.


예를 들어 A, B , C라는 방이 있을 때


각 방에서 일어나는 일은 다른 방에서 알 필요가 없다.


A에서 두 사용자가 채팅을 하고 있을 때 A방에 속한 사용자들 끼리만 데이터를 주고받으면 된다.


이 때 채팅의 단계는 다음과 같이 이루어진다.



(유저A , 유저B, 유저 C)가 룸 A에 속해있다.


유저 A : Hello World 전송하고자 함


유저 A -> 서버로 Hello world 문자열 송신 -> 서버에서는 A가 속한 룸 클래스에 그 정보를 전달하게 됨 -> 전달받은 문자열을 A룸에 속한 사용자들에게 Broadcast(유저들이 가지고 있는 소켓 정보를 참조하면 바로 보낼 수 있다)


이런식으로 구성하게 된다면 각 방에 속한 사용자들 끼리만 데이터를 주고 받을 수 있다.


클래스를 직접 작성해서 올리는 건 다음에 하도록하고..


간단하게 이런식이면 될거같다


RoomManager


List<GameRoom> roomList;


로 게임룸의 리스트를 만들고 관리하며 방을 생성하고 지우는 함수를 구현한다.


GameRoom


List<GameUser> userList;

로 유저 리스트를 관리하고 유저가 나갔을 때 리스트에서 빼고, 접속했을 때 리스트에 넣어주면 된다.


GameUser


이 클래스에는 로그인 정보 및 닉네임 등을 가지고 있으면 된다. 게임이라면 아이템 정보들이, 채팅이라면 부가적인 채팅관련 변수들이 존재


 



그래서 서버에서 유저가 접속해서 방에 들어가는 과정을 간략하게  나타내본다면


--메인클래스--


Socket Accept 이후


Socket 객체를 GameUser 클래스로 생성을 해준다.


GameUser user = new GameUser(socket);


이후 사용자가 원하는 방에 입장하고자 입장처리를 요청하면


user.enterRoom(room); 이런식으로 룸에 입장처리를 한다.


enterRoom의 함수에는


void enterRoom(GameRoom room){

this.gameRoom = room;

}


이런식으로 GameUser가 가지고 있는 GameRoom객체를 복사해준다.


혹은 새로운 방을 생성하고자 하는 요청을 받는다면


--메인클래스--

RoomManager roomManger = new RoomManager(); // 클래스 시작 시 한번만 생성해야 한다.


GameUser user = new GameUser(socket);

GameRoom room = new GameRoom();


user.enterRoom(room);

room.enterUser(user);


roomManager.createRoom(room);


이런식으로 만들면 될 것 같다.


중요한점은 Room <-> User 간 서로 상호참조를 하고 있어야 한다는 점이고,

RoomManager는 Room의 리스트를,

GameRoom은 User의 리스트를,


가지고 있어야 한다는 것이다.


예제코드를 보도록 하자.


이런식으로 간단하게 세가지 케이스를 대상으로 샘플코드를 만들어봤다.


위에 언급한대로, Room과 User간 서로 상호참조를 하고 있으면 쉽게 유저와 룸에 접근할 수가 있다.


샘플 프로젝트의 구성은 네개의 클래스로 구성을 했다.



이런식으로 역시 한 패키지 내에 묶어서 작성을 하면 된다.


매우 기초적인 기능만 담았기 때문에 필요한 기능이 있다면 추가해서 사용하면 될 것 같다


이제 게임룸 구조, 프로토콜 구조에 대한 포스팅을 완료했기 때문에


추후에 작성될 포스팅은 "비동기 JAVA 네트워크" 에 대해 포스팅 하고자 한다.


그리고 최종적으로 앞선 포스팅들을 모두 이용한 간단한 샘플 네트워크 프로그램을 만드는 것이 목적이다.




접기


/*
 * Author : Gompang
 * Desc : 네트워크 게임에서 사용되는(채팅도 포함) 방 개념 클래스
 * Blog : http://gompangs.tistory.com/
 */
package GameRoomPkg;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class GameRoom {

	List<GameUser> userList;
	GameUser roomOwner; // 방장
	String roomName; // 방 이름

	public GameRoom() { // 아무도 없는 방을 생성할 때
		userList = new ArrayList<GameUser>();
	}

	public GameRoom(GameUser _user) { // 유저가 방을 만들때
		userList = new ArrayList<GameUser>();
		userList.add(_user); // 유저를 추가시킨 후
		this.roomOwner = _user; // 방장을 유저로 만든다.
	}

	public GameRoom(List<GameUser> _userList) { // 유저 리스트가 방을 생성할
		this.userList = _userList; // 유저리스트 복사
		this.roomOwner = userList.get(0); // 첫번째 유저를 방장으로 설정
	}

	public void EnterRoom(GameUser _user) {
		userList.add(_user);
	}

	public void ExitRoom(GameUser _user) {
		userList.remove(_user);

		if (userList.size() < 1) { // 모든 인원이 다 방을 나갔다면
			RoomManager.RemoveRoom(this); // 이 방을 제거한다.
			return;
		}

		if (userList.size() < 2) { // 방에 남은 인원이 1명 이하라
			this.roomOwner = userList.get(0); // 리스트의 첫번째 유저가 방장이 된다.
			return;
		}

	}

	// 게임 로직

	@SuppressWarnings("unused")
	public void Broadcast(byte[] data) {
		for (GameUser user : userList) { // 방에 속한 유저의 수만큼 반복
			// 각 유저에게 데이터를 전송하는 메서드 호출~
			// ex) user.SendData(data);
			
//			try {
//				user.sock.getOutputStream().write(data); // 이런식으로 바이트배열을 보낸다.
//			} catch (IOException e) {
//				// TODO Auto-generated catch block
//				e.printStackTrace();
//			}
		}
	}

	public void SetOwner(GameUser _user) {
		this.roomOwner = _user; // 특정 사용자를 방장으로 변경한다.
	}

	public void SetRoomName(String _name) { // 방 이름을 설정
		this.roomName = _name;
	}
	
	public GameUser GetUserByNickName(String _nickName){ // 닉네임을 통해서 방에 속한 유저를 리턴함
		
		for(GameUser user : userList){
			if(user.nickName.equals(_nickName)){
				return user; // 유저를 찾았다면
			}
		}
		return null; // 찾는 유저가 없다면
	}

	public String GetRoomName() { // 방 이름을 가져옴
		return roomName;
	}

	public int GetUserSize() { // 유저의 수를 리턴
		return userList.size();
	}

	public GameUser GetOwner() { // 방장을 리턴
		return roomOwner;
	}
}

접기


접기


/*
 * Author : Gompang
 * Desc : 네트워크 게임에서 사용되는(채팅도 포함) 방 개념 클래스
 * Blog : http://gompangs.tistory.com/
 */
package GameRoomPkg;

import java.net.Socket;

// 실제로 게임을 플레이하는 유저의 클래스이다.

public class GameUser {

	GameRoom room; // 유저가 속한 룸이다.
	Socket sock;
	String nickName;
	int uid;

	// 게임에 관련된 변수 설정
	// ...
	//
	PlayerGameInfo.Location playerLocation; // 게임 정보
	PlayerGameInfo.Status playerStatus; // 게임 정보

	public GameUser() { // 아무런 정보가 없는 깡통 유저를 만들 때

	}

	public GameUser(String _nickName) { // 닉네임 정보만 가지고 생성
		this.nickName = _nickName;
	}

	public GameUser(int _uid, String _nickName) { // UID, 닉네임 정보를 가지고 생성
		this.uid = _uid;
		this.nickName = _nickName;
	}

	public void EnterRoom(GameRoom _room) {
		_room.EnterRoom(this); // 룸에 입장시킨 후
		this.room = _room; // 유저가 속한 방을 룸으로 변경한다.(중요)		
	}

	public void SetPlayerStatus(PlayerGameInfo.Status _status) { // 유저의 상태를 설정
		this.playerStatus = _status;
	}

	public void SetPlayerLocation(PlayerGameInfo.Location _location) { // 유저의 위치를 설정
		this.playerLocation = _location;
	}
}

접기


접기


/*
 * Author : Gompang
 * Desc : 네트워크 게임에서 사용되는(채팅도 포함) 방 개념 클래스
 * Blog : http://gompangs.tistory.com/
 */
package GameRoomPkg;

// 유저의 상태 및 현재 위치하고 있는 장소를 지정하기 위한 Enum Class
public class PlayerGameInfo {

	static enum Location {
		MAP_1, MAP_2, MAP_3, MAP_4, MAP_5
	};

	static enum Status {
		IDLE, BATTLE, DEAD
	};
}

접기


접기


/*
 * Author : Gompang
 * Desc : 네트워크 게임에서 사용되는(채팅도 포함) 방 개념 클래스
 * Blog : http://gompangs.tistory.com/
 */
package GameRoomPkg;

import java.util.ArrayList;
import java.util.List;

public class RoomManager {

	static List<GameRoom> roomList; // 방의 리스트
	
	public RoomManager(){
		roomList = new ArrayList<GameRoom>();
	}
	
	public GameRoom CreateRoom(){ // 룸을 새로 생성(빈 방)
		GameRoom room = new GameRoom();
		roomList.add(room);
		System.out.println("Room Created!");
		return room;
	}
	
	public GameRoom CreateRoom(GameUser _owner){ // 유저가 방을 생성할 때 사용(유저가 방장으로 들어감)
		GameRoom room = new GameRoom(_owner);
		roomList.add(room);
		System.out.println("Room Created!");
		return room;
	}
	
	public GameRoom CreateRoom(List<GameUser> _userList){
		GameRoom room = new GameRoom(_userList);
		roomList.add(room);
		System.out.println("Room Created!");
		return room;
	}
	
	public static void RemoveRoom(GameRoom _room){
		roomList.remove(_room); // 전달받은 룸을 제거한다.
		System.out.println("Room Deleted!");
	}
	
	public static int RoomCount(){ return roomList.size();} // 룸의 크기를 리턴해
}

접기