Gra sieciowa w c++ - pytania teoretyczne.

0

Witajcie,

na wstępie chciałbym zaznaczyć, że jestem początkującym użytkownikiem forum i się przywitać.
Jestem studentem IT i mam do zrobienia na przedmiot 'Programowanie obiektowe' zrobić grę sieciową z wykorzystaniem biblioteki Allegro.

Allegro już kiedyś używałem, w c++ może nie wymiatam, ale proste aplikacje na obiektach jestem w stanie napisać bez problemu.

W przypadku powyższego projektu największym problemem jest brak wiedzy na temat pisania aplikacji sieciowych w c++. Słyszałem o programowaniu socketów, zaczynam się dokształcać w tym temacie.
Interesuje mnie przede wszystkim w jaki sposób należy podejść do zrobienia takiego projektu. Bo napisać samą aplikację od strony użytkownika (np. jakiegoś pacmana) to nie problem. Nie wiem natomiast jak zaimplementować tą "sieciowość". Czy do tego używa się jakichś serwerów dla c++ (coś jak apache dla php z możliwością instalacji na localhoście)? Czy może pisze się taką aplikację w taki sposób, aby u dwóch graczy aplikacje komunikowały się bezpośrednio (choć w tym przypadku jakoś nie widzę tego rozwiązania).

Za wszelkie odpowiedzi z góry serdeczne dzięki.
Łukasz

0

Na Twoim miejscu zainteresowałbym się ICEm albo jakimś innym middleware do komunikacji sieciowej. Sockety też będziesz musiał kiedys przerobić, ale wierz mi, nie chcesz się irytować o to, że "nie działa i nie wiesz czemu". Jeśli będziesz potrzebował pomocy z ICEm, chętnie służę pomocą

0

Rozumiem, że ICE nie wyklucza użycia Allegro do zaimplementowania interfejsu? :)

Może na początek, zanim cokolwiek napiszę, prosiłbym o nakierowanie, jak wygląda struktura takiej aplikacji (czy ICE się winstalowuje) itd. Następnie poczytam dokumentację i w razie jakichś problemów odezwę się... Bo nie wiem nawet od czego zacząć.

0

Google->Beej's guide to network programming

0
Its not me napisał(a)

Google->Beej's guide to network programming

Wielkie dzięki za linka.. :) Sporo mi pomoże.... [browar]

0

Maksymalny skrót:
Masz aplikację robiącą za serwer i aplikację robiącą za klienta. Konfigurujesz parametry połaczenia (ip, protokół, port), rejestrujesz na serwerze obiekty (poprzez przypisanie im unikalnej nazwy). Masz więc czwórkę: ip, protokół, port, nazwa. Teraz z poziomu klienta podłączasz się pod wspomnianą czwórkę, dostając proxy (proxy w sensie wzorca). Może asciirysunek:

Server                                      Client
[obiekt]       <------sieć-----------> [obiektProxy]

obiektproxy ma takie same metody jak obiekt. Wywołując jakąś metodę na obiektproxy, argumenty są jakoś serializowane, przesyłane przez sieć, następnie na serwerze odserializowywane i wywoływana jest metoda. Po skończeniu wywołania rezultat funkcji wraca do klienta.

Różnica dla Ciebie: zamiast kminić jakis protokół przesyłu danych przez sockety, możesz pisac program w sposób czysto obiektowy, a'la
gameBoardPrx.getBishop(0).moveTo(x,y);

ICE zasadniczo wspiera Javę, C++, Pythona, Rubiego i C#

0

Jeśli dobrze zrozumiałem, implementuję obiektowo kod tak jak normalnie, a później przesyłam dane do socketów? Czyli, dla przykładu, mając grę w warcaby implementuję planszę, ruch i po dokonaniu ruchu lokalnie i zaktualizowaniu danych gry są one przesyłane do serwera (oczywiście muszę to sam zaimplementować, żeby działało)?

ruch zawodnika -> wyświetlenie zmian -> zapisanie zmian w danych -> przeslanie -> odczyt przez klienta drugiego zawodnika -> wczytanie -> aktualizacja ekranu u drugiego gracza

0

Pozwolę sobie poilustrować kodem. Będą to fragmenty jednej z laborek na studiach. Źródła są w Javie, ale z powodzeniem można pisać w C++.

Serwer:

public class Server {

    public static void main(String args[]) {
        String port = args[0];
        String host = "tcp"; //innymi słowy - nasłuchuj zewsząd, ktokolwiek będzie się łączył to tcp
        String service = "FTPloth"; //to ten identyfikator, o którym pisałem w poprzednim poście
        String connectionString = host + " -p " + port;

        Ice.Communicator ic = null;
        try {
            ic = Ice.Util.initialize(args); // Weź argumenty z linii poleceń, a nuz będzie coś dla ICE'a


            Ice.ObjectAdapter adapter = ic.createObjectAdapterWithEndpoints(service, connectionString); //tworzymy adapter

             Ice.Object object = new CoreI(); //tworzymy sobie obiekt CoreI();

             adapter.add(object, ic.stringToIdentity("FTPloth")); // i rejestrujemy go jako usługa FTPloth
             
            adapter.activate(); //aktywujemy adapter


            ic.waitForShutdown(); // i czekamy, aż nas user zabije
            
        } catch (Ice.LocalException e) {
            e.printStackTrace();
        } catch (Exception e) {
            System.err.println(e.getMessage());
        }
    }

I klient:

private CorePrx cp; //obiekty proxy, czyli formalnie rezydujące na serwerze
private DirectoryPrx rDir;
/* ... */
public Client(String[] argv) throws FileException, IOException {
        host=argv[0];
        port=argv[1];
        ic = Ice.Util.initialize(argv);

        String connectionString = name + ":tcp -h " + host + " -p " + port;

       
        Ice.ObjectPrx base = ic.stringToProxy(connectionString); //Tutaj się łączymy na podstawie nazwausługi:protokół -h host -p port 

        cp = CorePrxHelper.checkedCast(base); //I rzutujemy obiekt na naszą klasę CorePrx

        if (cp == null) {
            throw new Error("Unsuccessful connection.");
        }
        rDir = cp.getDir(rDirPath); //voila!, cokolwiek zrobimy na kliencie, zostanie zrobione na serwerze!

To, co jest tutaj wystarczy Ci, by nawiązać połączenie. Wywołując metody na obiekcie cp w kliencie, wywołujesz je na serwerze (obiekt klasy CoreI()).

0

To mi dużo rozjaśniło :) Dzięki wielkie.

I jeszcze jedno małe pytanie: jeśli chodzi o postawienie serwera ICE, to się normalnie winstalowuje na kompie, który ma służyć za serwer i następnie kod serwera się wrzuca do któregoś z katalogów ICE (tak jak dla Apache wrzuca się pliki php do htdocs), a klienta normalnie na kompie klienckim odpala...?

0

Nie.
Ice z punktu widzenia programowania to tylko biblioteka (znaczy ma też właśne programy, jak np. IceStorm).

Korzystając z biblioteki piszesz serwer. Tak jak na tym listeningu, czyli Ty musisz wymyślić, co on robi (w naszym wypadku udostępnia obiekt CoreI klientom, u Ciebie będzie to pewnie jakiś chessboard czy coś. Klienci łączą się z tym serwerem i dostają CorePrx, czyli coś, co się zachowuje identycznie jak CoreI. Wywołując metodę na CorePrx, wywołują ją w rzeczywistości na CoreI().

Taki szkic dla Ciebie:

class ChessboardI() extends Chessboard{
  String[][] board; //np. do trzymania informacji, jaki pionek gdzie jest, to równie dobrze zamiast stringa może być obiekt
  void movePiece(String pieceId, int xpos, int ypos) throws NotAllowedMoveException{
    //....
  }
  
  String[][] getState(){
    return board;
  }
  
  //... i co Ci jeszcze przyjdzie do głowy.
}

Klient z drugiej strony łączy się z serwerem, dostaje proxy do serwanta ChessboardI:

CheeseboardPrx board;
//łączenie
gui.display(board.getState()); // wyświetlasz pozycję
// pobierasz ruch usera
board.movePiece(id,x,y);

Czyli rozumiesz, klient działa na obiekcie proxy, który wszystkie wywołania pakuje w sieć i wysyła do serwanta (chessboardI).

0

Albo skorzystaj z gotowej warstwy połączeniowej :
http://www.boost.org/doc/libs/1_42_0/doc/html/boost_asio/examples.html
Na potrzeby gry klient serwer polecam kod chat_server i chat_client. serwer koordynuje wysyłanie komunikatów do każdego klienta - w nim nic nie musisz zmieniać.
Pozostaje ci tylko obsługa przychodzących od serwera komunikatów i wysyłanie nowych komunikatów z klienta do serwera. Serwer po otrzymaniu komunikatu automatycznie zrobi "broadcast" do wszystkich pozostałych podłączonych klientów. Na koniec dołączasz bibliotekę allegro i gotowe.

0

A ja mam jeszcze jedno pytanie. Udało mi się już zaimplementować mechanizm chińczyka dla 4 osób (zrezygnowałem z szachów). Próbuję teraz zrobić mechanizm sieciowy.

Generalnie podszedłem do sprawy nastepująco: mam klasę gamedata przechowującą dane gry na serwerze. W momencie wyboru piona przez gracza następuje aktualizacja danych w klasie gamedata za pomocą algorytmu. I jak zostanie zakończona, to wysyłam zaktualizowane dane do klienta, aby manager rysowania zrobił swoje.

Próbowałem na razie zrobić komunikację klient-serwer na zasadzie wymiany komunikatów (analogicznie jak na stronie tej http://firsthost.nazwa.pl/wordpress/2007/08/01/winsock-tutorial-2-laczenie-z-hostem/ oraz tej http://firsthost.nazwa.pl/wordpress/2007/08/12/winsock-tutorial-3-serwer/. Jednak działa tylko dla statycznego message. Jak chcę wczytać wiadomość z linii poleceń, to nie przesyła niestety.

Nie wiem jak sobie z tym poradzić. Nie wiem, czy dobrze podchodzę do tematu, ale zrobiłem następująco
serwer:

#include <winsock2.h>
#include <cstring>
#include <iostream>

using namespace std;

int main(int argc, char* argv[])
{
	WSADATA		wsaData;
	SOCKADDR_IN saddr;
	SOCKET		sock;
	const char *helloMessage  =  "Czesc Michalek... :)";
	char bufor[1024*20] ={0};
  char message[100] = {0};
  
	WSAStartup( MAKEWORD(2,2), &wsaData );

	sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	memset( (void*)&saddr, 0, sizeof(saddr) );
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(10000);
	saddr.sin_addr.s_addr = htonl(INADDR_ANY);

	if ( bind(sock, (sockaddr*)&saddr, sizeof(saddr)) != SOCKET_ERROR )
	{
		if ( listen(sock, 1) != SOCKET_ERROR )
		{
			for (int i = 0; i < 5; i ++ ) {
				SOCKET client = accept(sock,NULL,NULL);

				send(client, helloMessage, strlen(helloMessage), 0);
				recv(sock,bufor,1024*20,0);
      	cout<<"L:";
   			cin>>message;
   			send(client,message,strlen(message),0);
				closesocket(client);
			}
			
		}
	}

	closesocket(sock);
	WSACleanup();
	system("pause");
	return 0;
}

i klient:

#include <iostream>
#include <winsock2.h>

using namespace std;

u_long resolveHost( const string &host )
{
	LPHOSTENT hostEntry = gethostbyname(host.c_str());

	if ( !hostEntry )
	{
		unsigned int addr = inet_addr( host.c_str() );
		hostEntry = gethostbyaddr((char *)&addr, 4, AF_INET);

		if ( !hostEntry )
		{
			return 0;
		}
	}

	return *((int*)*hostEntry->h_addr_list);
}

int main(int argc, char* argv[])
{
	WSADATA		wsaData;
	SOCKADDR_IN saddr;
	SOCKET		sock;
	char		*http_req = "Witaj serwerze.";
	char		index[1024*20] = { 0 };
  char mess[100];
	WSAStartup( MAKEWORD(2,2), &wsaData );

	sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	saddr.sin_addr.S_un.S_addr = resolveHost ("192.168.1.2");
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(10000);

	if ( connect(sock, (sockaddr*)&saddr, sizeof(sockaddr)) == SOCKET_ERROR )
	{
		/* połączenie się nie powiodło */
		sock = 0;
		return -1;
	}

	send(sock,http_req,strlen(http_req),0);
	recv(sock,index,1024*20,0);
	cout<<"M: ";
	cin>>mess;
	send(sock,mess,strlen(mess),0);

	cout << index << endl;

	closesocket(sock);
	WSACleanup();
	system("pause");
	return 0;
}

I moje pytanie w tym miejscu jest takie:

  1. czy komunikat "Witaj serwerze" powinien pojawić się w oknie serwera? (bo się nie pojawia, pojawia się jedynie Witaj Michalek u klienta)
  2. w jaki sposób zmienić kod, aby wczytany do zmiennej mess komunikat został wysłany na serwer i wyświetlony, i na odwrót - wpisany do zmiennej message na serwerze wyświetlił się u klienta?

Jeśli robię błędy merytoryczne w obu pytaniach proszę mnie naprostować :)

Dzięki z góry za odpowiedź ;]

0

Aha i jeszcze zapomniałem dodać co gdzie mam:
Serwer -> klasa gamedata(wszystkie dane gry), ruch (aktualizacja danych w gamedata + algorytm ruchu), piony itd
Klient -> klasy plansza, rysowanie (manager), kostka.

1 użytkowników online, w tym zalogowanych: 0, gości: 1