GAME MAKER 강좌
KAYAN
◈ 서버 만들기
서버와 클라이언트는 따로 작동해야 하기 때문에, 오브젝트를 2개 추가하고 각각 따로 설정하도록 합니다.
강좌에서는 obj_server(서버 오브젝트), obj_client (클라이언트 오브젝트)로 나누겠습니다.
먼저 서버 오브젝트부터 설정해봅시다.
(▲ 서버 오브젝트)
[Create 이벤트]를 추가하고, 필요한 기능을 추가합니다.
서버에는 클라이언트 정보를 저장할 리스트 구조체와 맵 구조체를 추가합니다.
그리고 서버와 클라이언트간의 통신에 사용할 버퍼를 만들어야 합니다.
★ obj_server 오브젝트 - Create 이벤트 ★ global.select_server = 1; //서버 global.player_max = 30; //채팅 참가 최대 인원 global.socketlist = ds_list_create( ); //접속한 클라이언트 소켓 저장 global.player_map = ds_map_create( ); //클라이언트 세부 정보 저장 global.net_buffer = buffer_create( 256, buffer_grow, 1 ); //통신에 필요한 버퍼 global.local_socket = -1; global.local_server_IP = "127.0.0.1"; //현재 서버 IP. 기본값은 로컬 "127.0.0.1" |
다음은 서버를 생성하는 것입니다.
서버는 network_create_server 함수로 만들 수 있습니다.
◎ network_create_server(type, port, max_client) - type : [소켓 유형 상수] 생성할 서버의 유형 - port : [실수] 서버가 사용할 포트. 0~65535. - max_client :[실수] 한 번에 연결할 수 있는 최대 클라이언트 수 [소켓 유형 상수] - network_socket_tcp : TCP를 사용하여 소켓을 만듭니다. - network_socket_udp : UDP를 사용하여 소켓을 만듭니다. - network_socket_ws : TCP를 사용하여 웹소켓을 만듭니다. - network_socket_wss * : TCP를 사용하여 보안 웹소켓을 만듭니다. |
생성 타입은 TCP, 포트는 0~65535까지 지정할 수 있지만, 게임메이커 예문에서 주로 사용하는 6510으로 설정하겠습니다.
포트는 중요한데, 미리 등록된 포트, 또는, 사용중인 포트(같은 게임메이커 게임이더라도) 경우 서버가 생성되지 않습니다.
※ [참고] 대부분의 유닉스 계열 운영 체제의 경우, 잘 알려진 포트를 열려면 루트 권한이 있어야 합니다.
※ - 0번 ~ 1023번: 잘 알려진 포트 (well-known port)
※ - 1024번 ~ 49151번: 등록된 포트 (registered port)
※ - 49152번 ~ 65535번: 동적 포트 (dynamic port)
※ TCP/UDP의 포트 목록 https://ko.wikipedia.org/wiki/TCP/UDP%EC%9D%98_%ED%8F%AC%ED%8A%B8_%EB%AA%A9%EB%A1%9D
서버 생성에 실패하면 0보다 작은 값이 반환되고 성공하면 서버를 식별하는데 사용할 수 있는 소켓 ID 가 반환됩니다.
★ obj_server 오브젝트 - Create 이벤트 ★ global.server_socket = network_create_server( network_socket_tcp, 6510, global.player_max ); if ( global.server_socket < 0 ){ //서버 생성 실패했을 때 } else{ //서버 생성 성공했을 때 } |
서버 생성에 성공했을 때, 또는, 실패했을 때 각각 이벤트를 구성합니다.
강좌에서는 실패하면, 서버 오브젝트를 파기한 다음, "서버 생성에 실패했습니다" 메시지를 표시할 겁니다.
그리고, 메인 화면으로 돌아가는 겁니다.
서버 생성에 성공하면 "채팅을 시작합니다"라고 채팅글로 추가할 거에요.
★ obj_server 오브젝트 - Create 이벤트 ★ global.server_socket = network_create_server( network_socket_tcp, 6510, global.player_max ); if ( global.server_socket < 0 ){ //서버 생성 실패했을 때 instance_destroy( ); show_message( "서버 생성에 실패했습니다." ); room_goto( room_open ); } else{ //서버 생성 성공했을 때 ds_list_add( global.chat_list, "채팅을 시작합니다." ); } |
현재 사용하려 하는 서버의 IP를 모르기 때문에, 클라이언트에게 알려줄 수 가 없죠.
따라서 서버 IP를 얻기 위해 [네트워크 비동기 이벤트]를 발동시킵니다.
★ obj_server 오브젝트 - Create 이벤트 ★ global.server_socket = network_create_server( network_socket_tcp, 6510, global.player_max ); if ( global.server_socket < 0 ){ //서버 생성 실패했을 때 instance_destroy( ); show_message( "서버 생성에 실패했습니다." ); room_goto( room_open ); } else{ //서버 생성 성공했을 때 ds_list_add( global.chat_list, "채팅을 시작합니다." ); //---------- ▼ 서버 IP 갱신 전용( 로컬 ) global.local_socket = network_create_socket_ext( network_socket_udp, 6511 ); alarm[5] = 1; //---------- ▲ 서버 IP 갱신 전용( 로컬 ) } |
클라이언트 소켓을 만들어 서버로 로컬 접속하면 [네트워크 비동기 이벤트]가 발동됩니다.
(▲ 네트워크 비동기 이벤트)
여기서 현재 서버의 IP를 얻을 수 있습니다.
로컬 접속은 아래 함수로 처리할 수 있습니다.
◎ network_send_broadcast(socket, port, bufferid, size); - socket : [네트워크 소켓 ID] 사용할 소켓의 ID. - port : [Real] 서버가 사용할 포트. - bufferid : [버퍼] 데이터를 가져올 버퍼. - size : [Real] 데이터의 크기(바이트). |
서버 오브젝트의 [Alarm 5 이벤트]를 추가하고, 서버에 접속하도록 합니다.
★ obj_server 오브젝트 - Alarm 5 이벤트 ★ //현재 서버의 네트워크 이벤트 트리거( 서버 IP 갱신 ) buffer_seek( global.net_buffer, buffer_seek_start, 0 ); buffer_write( global.net_buffer , buffer_string, "check" ); //메시지 network_send_broadcast( global.local_socket, 6511, global.net_buffer, buffer_tell( global.net_buffer ) ); |
이제 [네트워크 비동기 이벤트]를 추가하고,
(▲ 네트워크 비동기 이벤트)
async_load 맵에서 IP를 받아오도록 합니다.
★ obj_server 오브젝트 - Async - Networking 이벤트 ★ var n_id = ds_map_find_value( async_load, "id" ); if n_id = global.local_socket{ var _ip = ds_map_find_value( async_load, "ip" ); global.local_server_IP = _ip; exit; } |
다음 [Destroy 이벤트]를 추가하고, 메모리에서 제거해야하는 기능은 파기하도록 합니다.
★ obj_server 오브젝트 - Destroy 이벤트 ★ ds_list_destroy( global.socketlist ); //클라이언트 소켓 목록 파기 ds_map_destroy( global.player_map ); //클라이언트 정보 파기 network_destroy( global.server_socket ); //네트워크 서버 파기 network_destroy( global.local_socket ); //네트워크 소켓 파기( 서버 IP ) buffer_delete( global.net_buffer ); //네트워크용 버퍼 파기 |
마찬가지로 [Game End 이벤트]를 추가하고, 같은 내용을 추가합니다.
★ obj_server 오브젝트 - Game End이벤트 ★ ds_list_destroy( global.socketlist ); //클라이언트 소켓 목록 파기 ds_map_destroy( global.player_map ); //클라이언트 정보 파기 network_destroy( global.server_socket ); //네트워크 서버 파기 network_destroy( global.local_socket ); //네트워크 소켓 파기( 서버 IP ) buffer_delete( global.net_buffer ); //네트워크용 버퍼 파기 |
※[주의] 게임을 종료하거나, 재시작, 또는, 네트워크가 필요 없는 다른 룸으로 이동할 때, 오류를 피하기 위해서
생성된 네트워크 서버나, 소켓은 network_destroy 함수를 사용해 완전히 파기하는 것이 좋습니다.
이제 IP를 받아오는지 테스트 해봅시다.
★ obj_server 오브젝트 - Draw 이벤트 ★ draw_set_font( font0 ); draw_set_halign( fa_left ); draw_set_valign( fa_top ); var t, c; t = "채팅 예제 ( 서버 )"+$" IP:{ global.local_server_IP}"; c = c_black; draw_text_color( 80, 16, t, c, c, c, c, 1 ); |
◈ 클라이언트 연결 설정
다음은 클라이언트 설정입니다.
클라이언트 오브젝트에 [Create 이벤트]를 추가하고, 필요한 기능을 추가합니다.
(▲ 클라이언트 오브젝트)
서버와 마찬가지로 서버와의 통신을 위한 버퍼와, 다른 클라이언트의 정보를 저장할 맵 데이터 구조체를 생성하도록 합니다.
★ obj_client 오브젝트 - Create 이벤트 ★ global.select_server = 0; //클라이언트 global.net_buffer = buffer_create( 256, buffer_grow, 1 ); //버퍼 global.player_map = ds_map_create( ); //클라이언트 정보 |
서버와의 통신을 위해선 소켓을 먼저 만들어야 합니다.
소켓은 아래 함수로 만들 수 있습니다.
◎ network_create_socket(type); - type : [소켓 유형 상수] 생성할 서버의 유형 |
기본적으로 이 함수는 동기식이므로 연결이 이루어질 때까지 게임이 "멈춰" 보일 수 있습니다.
그럴 때는 network_set_config 함수를 사용하여 연결에 대한 시간 초과 값을 설정하는 것이 좋습니다.
★ obj_client 오브젝트 - Create 이벤트 ★ global.select_server = 0; //클라이언트 global.net_buffer = buffer_create( 256, buffer_grow, 1 ); //버퍼 global.player_map = ds_map_create( ); //클라이언트 정보 //---------- ▼ 서버에 접속 global.client_socket = network_create_socket( network_socket_tcp ); //클라이언트 소켓 생성 network_set_config( network_config_connect_timeout, 1000 ); //서버 연결 초과 시간 //서버에 접속 var server = network_connect( global.client_socket , "127.0.0.1", 6510 ); if server < 0{ //연결 실패했을 때 } else{ //연결 되었을 때 } //---------- ▲ 서버에 접속 |
서버에 접속하려면 network_connect 함수로 서버의 IP로 접속하면 됩니다.
◎ network_connect(socket, url, port); - socket :[네트워크 소켓 ID] 사용할 소켓의 ID. - url :[문자열] 연결할 URL 또는 IP(문자열). - port :[실수] 연결할 포트. |
함수는 연결에 실패하면 0보다 작은 값이 반환되고, 성공하면 소켓 ID가 반환됩니다.
연결 성공했을 때와 실패 했을 떄, 각각 이벤트를 구성합니다.
★ obj_client 오브젝트 - Create 이벤트 ★ global.select_server = 0; global.net_buffer = buffer_create( 256, buffer_grow, 1 ); global.player_map = ds_map_create( ); global.client_socket = network_create_socket( network_socket_tcp ); network_set_config( network_config_connect_timeout, 1000 ); var server = network_connect( global.client_socket , "127.0.0.1", 6510 ); if server < 0{ //---------- ▼ 연결 실패 //연결 실패했을 때 instance_destroy( ); show_message( "서버 연결에 실패했습니다." ); room_goto( room_open ); //---------- ▲ 연결 실패 } else{ //---------- ▼ 연결 성공 //연결 되었을 때 ds_list_add( global.chat_list, "서버에 연결 되었습니다!" ); //---------- ▲ 연결 성공 } |
강좌에서는 연결 실패 했을 때는 클라이언트 오브젝트를 파기하고, "서버 연결에 실패했습니다" 메시지를 띄울 것입니다.
그리고 메인화면으로 돌아가는 거죠.
연결 성공했을 때는 "서버에 연결 되었습니다!"를 채팅글로 추가합니다.
클라이언트가 서버에 연결되면 서버에서는 [네트워크 비동기 이벤트]가 발동 됩니다.
서버 오브젝트의 [네트워크 비동기 이벤트]에 서버에 접속한 클라이언트 정보를 처리합니다.
클라이언트의 접속 여부는 async_load 맵의 "type"키로 확인할 수 있습니다.
- network_type_connect : 네트워크 연결로 인해 발생합니다. - network_type_disconnect : 네트워크 연결 해제로 인해 발생합니다. |
클라이언트가 접속했을 때(network_type_connect)는 클라이언트가 접속한 소켓을 리스트에 저장하도록 합니다.
그리고, 클라이언트가 채팅을 나갔을 때(network_type_disconnect)는 해당 소켓을 리스트에서 삭제합니다.
★ obj_server 오브젝트 - Async - Networking 이벤트 ★ if n_id == global.server_socket{ //--------- 클라이언트 연결/해제 체크 var type = ds_map_find_value( async_load, "type" ); switch( type ){ case network_type_connect://현재 소켓에 새로운 클라이언트 접속 //------------- var sock = ds_map_find_value( async_load, "socket" ); //클라이언트 소켓 ds_list_add( global.socketlist, sock ); //접속한 클라이언트 소켓 저장함 //------------- break; case network_type_disconnect://현재 소켓에 클라이언트 연결 종료 //------------- var sock = ds_map_find_value( async_load, "socket" ); //클라이언트 소켓 var pos = ds_list_find_index( global.socketlist, sock ); ds_list_delete( global.socketlist, pos ); //연결 종료된 클라이언트 소켓 삭제 //------------- break; } //--------- 클라이언트 연결/해제 체크 } |
서버와 클라이언트간의 통신할 준비가 끝났습니다.
물론 이것만으로 통신이 잘 되는지 확인할만한게 별로 없습니다.
서버에 클라이언트가 접속할 뿐, 메시지를 주고 받아야 어느정도 확인이 가능하죠.
어째든 여기까지 채팅 오브젝트의 [Create 이벤트]에 서버 오브젝트와 클라이언트 오브젝트를 생성하도록 추가합니다.
★ obj_chat 오브젝트 - Create 이벤트 ★ keyboard_string = ""; //--------- ▼ 서버/클라이언트 생성 if ( global.select_server == 1 ){ instance_create_depth( 0, 0, -100, obj_server ); } else{ instance_create_depth( 0, 0, -100, obj_client ); } //--------- ▲ 서버/클라이언트 생성 |
이렇게 하면 메인화면의 메뉴를 눌러 채팅룸으로 갔을 때, 메뉴 종류에 따라 서버 또는 클라이언트를 활성화하게 될 거에요.
이제부터 테스트는 빌드된 실행 파일(exe)이 필요합니다.
게임메이커는 프로젝트 1개만 실행할 수 있어서, 실행 파일로 빌드하고, 2번 실행해서 하나는 서버,
다른 하나는 클라이언트로 테스트하시면 됩니다.
또한 네트워크 기능을 사용하면 윈도우 방화벽 허용 여부를 묻는데, 방화벽 허용해야 정상적으로 작동합니다.
'GameMaker강좌[GMS2] > 네트워크강좌' 카테고리의 다른 글
[게임메이커 강좌-네트워킹][GMS2] 채팅 프로그램 만들기-4-채팅 메시지 보내기 (0) | 2024.12.24 |
---|---|
[게임메이커 강좌-네트워킹][GMS2] 채팅 프로그램 만들기-3-메시지 송수신 (0) | 2024.12.23 |
[게임메이커 강좌-네트워킹][GMS2] 채팅 프로그램 만들기-1-채팅 기본 구성 (0) | 2024.12.21 |
[게임메이커 강좌-네트워킹][GMS2] 기본 서버/클라이언트 구조 (0) | 2024.12.19 |
[게임메이커 강좌-네트워킹][GMS2] 게임메이커 네트워킹 기능 (1) | 2024.12.19 |
댓글