티스토리 뷰


이번 포스팅에서는

리듬게임에서 클라이언트와 서버의

데이터 교환을 위한 

서버와 클라이언트의 큰 틀을 

설계하겠습니다.


=================================================================================


Rhythm Game : Server & Client






1. Rhythm Game : Server & Client?


 기본적인 게임에 입장하기 위해서는 로그인, 방입장, 초대, 강퇴 등 여러가지 기능이 존재하게 됩니다. 이러한 기능들의 공통점은 접속하는 특정 사용자의 동작에 대한 결과를 다른 유저도 알 수 있어야 한다는 것입니다.

 예를 들어 어떠한 유저가 방을 생성한다면 다른 유저들에게는 실시간으로 생성된 방이 보여야 한다는 것이죠.


 이러한 기능을 구현하기 위해서는 특정 유저의 행동에 대한 결과를 한 곳에서 받아 다른 유저에게 뿌려주어야 한다는 것입니다. 때문에 사용자의 동작을 전달받아 처리하고 그에 따른 결과를 다른 유저에게 뿌려주기 위한 Server와 사용자가 사용하기 위한 Client를 따로 구현해야합니다.


 기본적으로 특정 사용자의 동작에 대한 결과를 다시 전달받기 위해 TCP를 이용하고 동시에 UDP도 구현하여 서버로부터 언제 올지 모르는 데이터에 대한 처리도 할 수 있도록 설계하겠습니다.


[TCP와 UDP]


쉽게 말하자면 초록색의 UDP는 서버에서 일방적으로 클라이언트에게 전송하기 위한 것(1:n)이고, 파랑색의 TCP는 서버와 특정 클라이언트와 1:1로 통신하기 위한 것입니다. 





2. Rhythm Game : Server?



1) ServerStart.java

 서버를 시작하기 위한 클래스입니다. 클라이언트가 서버와 연결을 시도하고 성공한다면 해당 socket을 인자로 해당 유저에 대한 쓰레드를 생성합니다.

PersonalServer는 해당 쓰레드를 상속하고 있고 각 유저마다의 연결을 개별적으로 관리하여 멀티쓰레드를 이용한 여러 유저간의 데이터교환을 가능하게 합니다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ServerStart {
    public static void 
    main(String[] args) {
        
        try {
            ServerSocket server = new ServerSocket(56789);
            System.out.println("서버시작");
            while(!server.isClosed()) {
                Socket socket = server.accept();
                Thread p = new PersonalServer(socket);
 
                System.out.println("서버 접속");
            }
            server.close();
        }catch(IOException e) {
            System.out.println("[server] network error "  + e.toString()); 
        }
    }
}

cs




2) PersonalServer.java

 PersonalServer 클래스에서는 사용자가 서버에게 요청했을 때의 요청에 따른 처리를 해주기 위한 기능이 정의되어 있습니다.

또한 static으로 sendAlramTo () 형태로 경우에 따라 메소드 3개를 정의하여 UDP를 통해 클라이언트로 데이터 전송할 수 있도록 했습니다.

그리고 쓰레드를 통해 실질적으로 동작하는 run() 메소드에서는 TCP를 통한 데이터의 전송이 이루어지는 부분으로 '#'으로 구분하여 클라이언트의 동작을 판단하고 그에 따른 동작을 처리하도록 설계했습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
public class PersonalServer extends Thread {
    
    // static ================================================================
    static UserAccountPool accountPool;     // 전체 유저와 현재 접속중인 유저 관리
    static DatagramSocket ds;                // UDP를 위한 소켓
    static List<Room> rooms;                // 생성된 방 관리
     
    static {
        accountPool = new UserAccountPool();
        rooms = new ArrayList<>();
        try {
            ds = new DatagramSocket(56789);
        }catch(IOException e) {
            System.out.println("alramSocket create failed.. " + e.toString());
        }
    }
    
    static void sendAlramToAll(String alram) {            // 모든 유저에게 UDP전송
        DatagramPacket dp = new DatagramPacket(alram.getBytes(), alram.getBytes().length);
        for(Account a : accountPool.getCurrentUser()) {
            dp.setSocketAddress(a.getSocketAddress());
            try {
                System.out.println("server UDP send");
                
                ds.send(dp);
            }catch(IOException e) {
                System.out.println("[server-Thread] send alarm failed .. " + e.toString());
            }
        }
    }    
    
    static void sendAlramToUser(SocketAddress sa, String alram) {        // 특정 유저에게 UDP 전송
        DatagramPacket dp = new DatagramPacket(alram.getBytes(), alram.getBytes().length);
        dp.setSocketAddress(sa);
        try {
            System.out.println("server UDP send");
            ds.send(dp);
        }catch(IOException e) {
            System.out.println("[server-Thread] send alarm failed .. " + e.toString());
        }
        
    }
    
    static void sendAlramToUsers(Room r, String alram) {            // 방에 있는 사람에게 UDP 전송
        DatagramPacket dp = new DatagramPacket(alram.getBytes(), alram.getBytes().length);
    
        for(Account a : r.getJoiner()) {
            SocketAddress sa = a.getSocketAddress();
            dp.setSocketAddress(sa);
            try {
                System.out.println("server UDP send");
                System.out.println("txt"+alram);
                ds.send(dp);
            }catch(IOException e) {
                System.out.println("[server-Thread] send alarm failed .. " + e.toString());
            }
        }
    }
    
    //=========================================================================================
    
    private Socket socket;
    private ObjectOutputStream oos;
    private ObjectInputStream ois;
    private Account user;        //  현재 계정 객체 저장
    
    // 생성자 ================================
    public PersonalServer(Socket socket) {
        this.socket = socket;
        try {
            oos = new ObjectOutputStream(socket.getOutputStream());
            ois = new ObjectInputStream(socket.getInputStream());
        }catch(IOException e) {}
    }
    //========================================
    @Override
    public void run() {
        String[] command = null;
        while(socket.isConnected()) {
            String received;
            try {
                received = (String)ois.readObject();
            }catch(IOException | ClassNotFoundException e) {    // 비정상 종료시
                System.out.println("[server] 비정상 종료");
            }
            
            System.out.println("[server] received : " + received);
            command = received.split("#");
            Object resp = null;
            System.out.println(command[0]);
            switch(command[0]) {
                
                // 클라이언트의 요청에 따른 처리
                    
            }
        }
    }
    
    // TCP를 이용한 클라이언트에게 데이터전송
    private void sendToClient(Object resp) {
        try {
            oos.reset();    
            oos.writeObject(resp);
            System.out.println(resp);
        }catch(IOException e) {
            System.out.println("server write fail.. " + e.toString());
        }
    }
    
    
}
 
cs




 3) UserAccountPool.java

 UserAccountPool 클래스에서는 사용자 계정을 제어하기 위한 기능이 정의되어있습니다.

회원가입, 로그인, 로그아웃했을 때의 계정들의 상태를 관리하게 될 클래스입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class UserAccountPool {
    private Map<String,Account> accountMap;        // 유저의 계정 보관
    private Set<Account> currentUser;            // 현재 접속중인 유저
    private File address;                        
    
    // 생성자 ==========================================================
    public UserAccountPool() {
        
        // 서버에 저장된 계정 파일 호출 =====================================
       
 
        // ============================================================
        
        currentUser = new HashSet<>();
    }
    // ================================================================
    
 
    // User의 정보를 제어할 메서드 정의
    
    
    // Getter and Setter ========================================
    public Map<String, Account> getAccountMap() {
        return accountMap;
    }
 
    public void setAccountMap(Map<String, Account> accountMap) {
        this.accountMap = accountMap;
    }
 
    public Set<Account> getCurrentUser() {
        return currentUser;
    }
 
    public void setCurrentUser(Set<Account> currentUser) {
        this.currentUser = currentUser;
    }
 
    public File getAddress() {
        return address;
    }
 
    public void setAddress(File address) {
        this.address = address;
    }
    //============================================================
}
cs






3. Rhythm Game : Client?

 

 서버측과 유사하게 클라이언트에서도 UDP를 통해서는 언제든지 데이터를 받을 수 있도록 설계하고 TCP를 통해서 서버로 전송할 때에는 메소드를 통해 원하는 요청을 '#'으로 구분하여 데이터를 전송하도록 설계합니다.



1) ClientNetwork.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class ClientNetwork extends Thread {
    private Socket soc; // 핵심 연결 소켓
    private ObjectOutputStream oos = null;
    private ObjectInputStream ois = null;
 
    private DatagramSocket ds; // 서브 소켓(receive용)
    private ClientUI ui;
    private Account user;
    private String nick;    // user을 저장하기 위한 nick
 
    public ClientNetwork(ClientUI c) {
        this.ui = c;
        System.out.println("연결중");
        try {
            soc = new Socket(c.ip, 56789);
            System.out.println("??");
            oos = new ObjectOutputStream(soc.getOutputStream());
            ois = new ObjectInputStream(soc.getInputStream());
            System.out.println("[client] connected to server");
            ds = new DatagramSocket(soc.getLocalPort()); // // TCP와 UDP는 같은 포트로 사용할 수 있음.
 
        } catch (IOException e) {
            System.out.println("[client] network error " + e.toString());
        }
        start();
    }
 
    @Override
    public void run() {
        while (!ds.isClosed()) {
            DatagramPacket dp = new DatagramPacket(new byte[2048], 2048);
            try {
                ds.receive(dp);
                System.out.println("client UDP received");
                String data = new String(dp.getData(), 0, dp.getLength());
                System.out.println(data);
                String[] str = data.split("#");
                switch (str[0]) {
                    //  서버의 UDP 데이터 전송에 대한 값에 따라 처리
                }
            } catch (IOException e) {
                System.out.println("dp failed .. " + e.toString());
                ds.close();
                break;
            }
 
        }
    }
    
    // ==============================================================================
 
    // 클라이언트에서 서버로 요청할 데이터를 정의
    
}
cs





이렇게 네트워크 부분을 큰 틀로 구성한 후 각 기능에 따라 서버와 클라이언트에서 동작할 기능을 정의하면 원하는 기능을 쉽게 설계할 수 있습니다.

클라이언트에서의 UDP와 TCP의 기능을 정말 간단하게 말하자면 UDP는 서버로부터 데이터를 받을 때만 사용되고, TCP는 클라이언트가 먼저 서버로 보낼 때 사용됩니다.











공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함