PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Serverklasse für den RPG-Maker XP



Cornix
25.04.2011, 17:16
Guten Tag,
ich weis dies wird sehr oft gefragt und die Antworten sind meistens vorhersehbar doch ich wage es einmal dennoch zu fragen.

Ich habe mich in den letzten zwei Tagen sehr viel mit der Fragestellung beschäftigt wie ich ein Multiplayer Projekt mit dem RPG-Maker XP / VX erarbeiten kann.

Ich stieß auf lediglich ein einziges vielversprechendes Ergebniss im Internet: http://nrpgxpfiles.webs.com/
Zusammenfassend gesagt sei, dass hier ein kompletter Client für ein Massive-Multiplayer-Online-RPG mit dem RPG-Maker XP erstellt wird; der Server hingegen wird extern betrieben. Verständlicherweise.

Allerdings will ich kein MMORPG erstellen, gott bewahre, so utopisch sind meine Fantasien nicht.
Aber ich würde gerne ein kleines einfaches RTS-Spiel von mir für zwei Spieler umkonzipieren. Es handelt sich dabei um nichts allzu aufwändiges, Performance und Datenübertragungsmengen sollten keine nennenswerte Rolle spielen.

Was ich nun brauche ist keine genaue Anleitung wie soetwas aufgebaut wird, das schaffe ich wahrscheinlich. Ich brauche nur die nötigen Klassen, sprich die Schnittstellen zu den WindowsAPI's um eine Verbindung zwischen zwei Computern herzustellen.

Unter den oben verlinkten Scripten für den Client ließen sich folgende Zeilen extrahieren:


module Win32

#----------------------------------------------------------------------------
# ● Retrieves data from a pointer.
#----------------------------------------------------------------------------
def copymem(len)
buf = "\0" * len
Win32API.new('kernel32', 'RtlMoveMemory', 'ppl', '').call(buf, self, len)
buf
end

end

# Extends the numeric class.
class Numeric
include Win32
end

# Extends the string class.
class String
include Win32
end

#==============================================================================
# ** Module Winsock - Maps out the functions held in the Winsock DLL.
#------------------------------------------------------------------------------
# Author Ruby
# Version 1.8.1
#==============================================================================

module Winsock

DLL = 'ws2_32'

#----------------------------------------------------------------------------
# * Accept Connection
#----------------------------------------------------------------------------
def self.accept(*args)
Win32API.new(DLL, 'accept', 'ppl', 'l').call(*args)
end
#----------------------------------------------------------------------------
# * Bind
#----------------------------------------------------------------------------
def self.bind(*args)
Win32API.new(DLL, 'bind', 'ppl', 'l').call(*args)
end
#----------------------------------------------------------------------------
# * Close Socket
#----------------------------------------------------------------------------
def self.closesocket(*args)
Win32API.new(DLL, 'closesocket', 'p', 'l').call(*args)
end
#----------------------------------------------------------------------------
# * Connect
#----------------------------------------------------------------------------
def self.connect(*args)
Win32API.new(DLL, 'connect', 'ppl', 'l').call(*args)
end
#----------------------------------------------------------------------------
# * Get host (Using Adress)
#----------------------------------------------------------------------------
def self.gethostbyaddr(*args)
Win32API.new(DLL, 'gethostbyaddr', 'pll', 'l').call(*args)
end
#----------------------------------------------------------------------------
# * Get host (Using Name)
#----------------------------------------------------------------------------
def self.gethostbyname(*args)
Win32API.new(DLL, 'gethostbyname', 'p', 'l').call(*args)
end
#----------------------------------------------------------------------------
# * Get host's Name
#----------------------------------------------------------------------------
def self.gethostname(*args)
Win32API.new(DLL, 'gethostname', 'pl', '').call(*args)
end
#----------------------------------------------------------------------------
# * Get Server (Using Name)
#----------------------------------------------------------------------------
def self.getservbyname(*args)
Win32API.new(DLL, 'getservbyname', 'pp', 'p').call(*args)
end
#----------------------------------------------------------------------------
# * HT OnL
#----------------------------------------------------------------------------
def self.htonl(*args)
Win32API.new(DLL, 'htonl', 'l', 'l').call(*args)
end
#----------------------------------------------------------------------------
# * HT OnS
#----------------------------------------------------------------------------
def self.htons(*args)
Win32API.new(DLL, 'htons', 'l', 'l').call(*args)
end
#----------------------------------------------------------------------------
# * Inet Adress
#----------------------------------------------------------------------------
def self.inet_addr(*args)
Win32API.new(DLL, 'inet_addr', 'p', 'l').call(*args)
end
#----------------------------------------------------------------------------
# * Inet NtOA
#----------------------------------------------------------------------------
def self.inet_ntoa(*args)
Win32API.new(DLL, 'inet_ntoa', 'l', 'p').call(*args)
end
#----------------------------------------------------------------------------
# * Listen
#----------------------------------------------------------------------------
def self.listen(*args)
Win32API.new(DLL, 'listen', 'pl', 'l').call(*args)
end
#----------------------------------------------------------------------------
# * Recieve
#----------------------------------------------------------------------------
def self.recv(*args)
Win32API.new(DLL, 'recv', 'ppll', 'l').call(*args)
end
#----------------------------------------------------------------------------
# * Select
#----------------------------------------------------------------------------
def self.select(*args)
Win32API.new(DLL, 'select', 'lpppp', 'l').call(*args)
end
#----------------------------------------------------------------------------
# * Send
#----------------------------------------------------------------------------
def self.send(*args)
Win32API.new(DLL, 'send', 'ppll', 'l').call(*args)
end
#----------------------------------------------------------------------------
# * Set Socket Options
#----------------------------------------------------------------------------
def self.setsockopt(*args)
Win32API.new(DLL, 'setsockopt', 'pllpl', 'l').call(*args)
end
#----------------------------------------------------------------------------
# * Shutdown
#----------------------------------------------------------------------------
def self.shutdown(*args)
Win32API.new(DLL, 'shutdown', 'pl', 'l').call(*args)
end
#----------------------------------------------------------------------------
# * Socket
#----------------------------------------------------------------------------
def self.socket(*args)
Win32API.new(DLL, 'socket', 'lll', 'l').call(*args)
end
#----------------------------------------------------------------------------
# * Get Last Error
#----------------------------------------------------------------------------
def self.WSAGetLastError(*args)
Win32API.new(DLL, 'WSAGetLastError', '', 'l').call(*args)
end

end

#==============================================================================
# ** Socket - Creates and manages sockets.
#------------------------------------------------------------------------------
# Author Ruby
# Version 1.8.1
#==============================================================================

class Socket

#----------------------------------------------------------------------------
# ● Constants
#----------------------------------------------------------------------------
AF_UNSPEC = 0
AF_UNIX = 1
AF_INET = 2
AF_IPX = 6
AF_APPLETALK = 16

PF_UNSPEC = 0
PF_UNIX = 1
PF_INET = 2
PF_IPX = 6
PF_APPLETALK = 16

SOCK_STREAM = 1
SOCK_DGRAM = 2
SOCK_RAW = 3
SOCK_RDM = 4
SOCK_SEQPACKET = 5

IPPROTO_IP = 0
IPPROTO_ICMP = 1
IPPROTO_IGMP = 2
IPPROTO_GGP = 3
IPPROTO_TCP = 6
IPPROTO_PUP = 12
IPPROTO_UDP = 17
IPPROTO_IDP = 22
IPPROTO_ND = 77
IPPROTO_RAW = 255
IPPROTO_MAX = 256

SOL_SOCKET = 65535

SO_DEBUG = 1
SO_REUSEADDR = 4
SO_KEEPALIVE = 8
SO_DONTROUTE = 16
SO_BROADCAST = 32
SO_LINGER = 128
SO_OOBINLINE = 256
SO_RCVLOWAT = 4100
SO_SNDTIMEO = 4101
SO_RCVTIMEO = 4102
SO_ERROR = 4103
SO_TYPE = 4104
SO_SNDBUF = 4097
SO_RCVBUF = 4098
SO_SNDLOWAT = 4099

TCP_NODELAY = 1

MSG_OOB = 1
MSG_PEEK = 2
MSG_DONTROUTE = 4

IP_OPTIONS = 1
IP_DEFAULT_MULTICAST_LOOP = 1
IP_DEFAULT_MULTICAST_TTL = 1
IP_MULTICAST_IF = 2
IP_MULTICAST_TTL = 3
IP_MULTICAST_LOOP = 4
IP_ADD_MEMBERSHIP = 5
IP_DROP_MEMBERSHIP = 6
IP_TTL = 7
IP_TOS = 8
IP_MAX_MEMBERSHIPS = 20

EAI_ADDRFAMILY = 1
EAI_AGAIN = 2
EAI_BADFLAGS = 3
EAI_FAIL = 4
EAI_FAMILY = 5
EAI_MEMORY = 6
EAI_NODATA = 7
EAI_NONAME = 8
EAI_SERVICE = 9
EAI_SOCKTYPE = 10
EAI_SYSTEM = 11
EAI_BADHINTS = 12
EAI_PROTOCOL = 13
EAI_MAX = 14

AI_PASSIVE = 1
AI_CANONNAME = 2
AI_NUMERICHOST = 4
AI_MASK = 7
AI_ALL = 256
AI_V4MAPPED_CFG = 512
AI_ADDRCONFIG = 1024
AI_DEFAULT = 1536
AI_V4MAPPED = 2048

#----------------------------------------------------------------------------
# ● Returns the associated IP address for the given hostname.
#----------------------------------------------------------------------------
def self.getaddress(host)
gethostbyname(host)[3].unpack('C4').join('.')
end
#----------------------------------------------------------------------------
# ● Returns the associated IP address for the given hostname.
#----------------------------------------------------------------------------
def self.getservice(serv)
case serv
when Numeric
return serv
when String
return getservbyname(serv)
else
raise 'Please use an interger or string for services.'
end
end
#----------------------------------------------------------------------------
# ● Returns information about the given hostname.
#----------------------------------------------------------------------------
def self.gethostbyname(name)
raise SocketError::ENOASSOCHOST if (ptr = Winsock.gethostbyname(name)) == 0
host = ptr.copymem(16).unpack('iissi')
[host[0].copymem(64).split("\0")[0], [], host[2], host[4].copymem(4).unpack('l')[0].copymem(4)]
end
#----------------------------------------------------------------------------
# ● Returns the user's hostname.
#----------------------------------------------------------------------------
def self.gethostname
buf = "\0" * 256
Winsock.gethostname(buf, 256)
buf.strip
end
#----------------------------------------------------------------------------
# ● Returns information about the given service.
#----------------------------------------------------------------------------
def self.getservbyname(name)
case name
when /echo/i
return 7
when /daytime/i
return 13
when /ftp/i
return 21
when /telnet/i
return 23
when /smtp/i
return 25
when /time/i
return 37
when /http/i
return 80
when /pop/i
return 110
else
raise 'Service not recognized.'
end
end
#----------------------------------------------------------------------------
# ● Creates an INET-sockaddr struct.
#----------------------------------------------------------------------------
def self.sockaddr_in(port, host)
begin
[AF_INET, getservice(port)].pack('sn') + gethostbyname(host)[3] + [].pack('x8')
rescue
end
end
#----------------------------------------------------------------------------
# ● Creates a new socket and connects it to the given host and port.
#----------------------------------------------------------------------------
def self.open(*args)
socket = new(*args)
if block_given?
begin
yield socket
ensure
socket.close
end
end
nil
end
#----------------------------------------------------------------------------
# ● Creates a new socket.
#----------------------------------------------------------------------------
def initialize(domain, type, protocol)
SocketError.check if (@fd = Winsock.socket(domain, type, protocol)) == -1
@fd
end
#----------------------------------------------------------------------------
# ● Accepts incoming connections.
#----------------------------------------------------------------------------
def accept(flags = 0)
buf = "\0" * 16
SocketError.check if Winsock.accept(@fd, buf, flags) == -1
buf
end
#----------------------------------------------------------------------------
# ● Binds a socket to the given sockaddr.
#----------------------------------------------------------------------------
def bind(sockaddr)
SocketError.check if (ret = Winsock.bind(@fd, sockaddr, sockaddr.size)) == -1
ret
end
#----------------------------------------------------------------------------
# ● Closes a socket.
#----------------------------------------------------------------------------
def close
SocketError.check if (ret = Winsock.closesocket(@fd)) == -1
ret
end
#----------------------------------------------------------------------------
# ● Connects a socket to the given sockaddr.
#----------------------------------------------------------------------------
def connect(sockaddr)
SocketError.check if (ret = Winsock.connect(@fd, sockaddr, sockaddr.size)) == -1
ret
end
#----------------------------------------------------------------------------
# ● Listens for incoming connections.
#----------------------------------------------------------------------------
def listen(backlog)
SocketError.check if (ret = Winsock.listen(@fd, backlog)) == -1
ret
end
#----------------------------------------------------------------------------
# ● Checks waiting data's status.
#----------------------------------------------------------------------------
def select(timeout)
SocketError.check if (ret = Winsock.select(1, [1, @fd].pack('ll'), 0, 0, [timeout, timeout * 1000000].pack('ll'))) == -1
ret
end
#----------------------------------------------------------------------------
# ● Checks if data is waiting.
#----------------------------------------------------------------------------
def ready?
not select(0) == 0
end
#----------------------------------------------------------------------------
# ● Reads data from socket.
#----------------------------------------------------------------------------
def read(len)
buf = "\0" * len
Win32API.new('msvcrt', '_read', 'lpl', 'l').call(@fd, buf, len)
buf
end
#----------------------------------------------------------------------------
# ● Returns recieved data.
#----------------------------------------------------------------------------
def recv(len, flags = 0)
buf = "\0" * len
SocketError.check if Winsock.recv(@fd, buf, buf.size, flags) == -1
buf
end
#----------------------------------------------------------------------------
# ● Sends data to a host.
#----------------------------------------------------------------------------
def send(data, flags = 0)
SocketError.check if (ret = Winsock.send(@fd, data, data.size, flags)) == -1
ret
end
#----------------------------------------------------------------------------
# ● Writes data to socket.
#----------------------------------------------------------------------------
def write(data)
Win32API.new('msvcrt', '_write', 'lpl', 'l').call(@fd, data, 1)
end

end

#==============================================================================
# ** TCPSocket - Creates and manages TCP sockets.
#------------------------------------------------------------------------------
# Author Ruby
# Version 1.8.1
#==============================================================================

class TCPSocket < Socket

#----------------------------------------------------------------------------
# ● Creates a new socket and connects it to the given host and port.
#----------------------------------------------------------------------------
def self.open(*args)
socket = new(*args)
if block_given?
begin
yield socket
ensure
socket.close
end
end
nil
end
#----------------------------------------------------------------------------
# ● Creates a new socket and connects it to the given host and port.
#----------------------------------------------------------------------------
def initialize(host, port)
super(AF_INET, SOCK_STREAM, IPPROTO_TCP)
connect(Socket.sockaddr_in(port, host))
end

end

#==============================================================================
# ** SocketError
#------------------------------------------------------------------------------
# Default exception class for sockets.
#==============================================================================

class SocketError < StandardError

ENOASSOCHOST = 'getaddrinfo: no address associated with hostname.'

def self.check
errno = Winsock.WSAGetLastError
raise Errno.const_get(Errno.constants.detect { |c| Errno.const_get(c).new.errno == errno })
end

end

Unschwer zu erkennen sind dies die benötigten Klassen um einen TCP-Client zu starten und diesen mit einem Server zu verbinden. Eine Hälfte wäre also gefunden.

Allerdings finde ich keine vergleichbaren Scripte für einen Server. Ich habe tatsächlich Stunden damit verbracht und verschiedene Ansätze verfolgt wie ich zwei Computer nur über den RPG-Maker verbinden kann ohne einen externen Server zu verwenden. Ohne Erfolg versteht sich, sonst würde ich nun nicht hier um Hilfe bitten.

Ich weis nicht in welcher Schwierigkeitsstufe sich diese Bitte bewegt, ich sehe, die Klassen für die Clientverbindung lassen sich in überschaubarem geringen Aufwand erstellen, ich weis auch, dass soetwas mit dem RPG-Maker 2000 (DestinyPatch) möglich ist.

Falls mir also irgendjemand bei diesem Problem helfen kann, sprich eine Schnittstelle zwischen den nötigen WindowsAPI's und dem Ruby Interpreter des Makers bereitstellen so wäre ich sehr sehr dankbar.

Selbstverständlich würde ein Creditseintrag bei jedem Spiel welches die Scripte verwendet gesichert sein und ich würde die Scripte auch bereitwillig jedem Interessenten zur Verfügung stellen so wie sie mir gegeben worden sind.

Danke nocheinmal im Vorraus,
Cornix.

MagicMagor
27.04.2011, 14:24
Allerdings finde ich keine vergleichbaren Scripte für einen Server. Ich habe tatsächlich Stunden damit verbracht und verschiedene Ansätze verfolgt wie ich zwei Computer nur über den RPG-Maker verbinden kann ohne einen externen Server zu verwenden. Ohne Erfolg versteht sich, sonst würde ich nun nicht hier um Hilfe bitten.
Du brauchst keine "vergleichbaren" Skripte weil in den obigen so wie es aussieht schon alles drin ist.
Die Unterscheidung Server/Client ist primär eine inhaltliche, sprich welcher Teil übernimmt welche Aufgaben (Server bietet Dienst an, Client nimmt Dienst in Anspruch), weniger eine technische. Wenn du eine Client-Client Verbindung haben willst, implementierst du in aller Regel Client und Server in einem. Meistens läuft das so, einer der beiden Spieler "eröffnet" das Spiel und fungiert damit als Server, der andere verbindet sich dann zu dessen IP und fungiert als reiner Client.

Die Socket-Klasse ist so gesehen "alles" was du brauchst. Ein Socket ist eine Schnittstelle zwischen Netzwerk und Programm. Du kannst dich über einen Socket mit einem Server verbinden, genauso kannst du mithilfe eines Sockets aber auch auf einkommende Verbindungen warten (listen - horchen) und diese annehmen (accept).
Für beides scheint es in der obigen Klasse Funktionen zu geben, somit hast du eigentlich alles was du brauchst.

Du musst also so vorgehen:
1. Spieler A eröffnet das Spiel
-> A erstellt einen Socket und horcht auf einem fest definiertem Port auf Verbindungen
2. Spieler B verbindet sich zur IP-Adresse von A
-> Socket erstellen und eine Verbindung zu A als Server aufbauen (IP-Addr. von A, und der vorher definierte Port auf dem A horcht)
3. Das Programm von A nimmt mittels accept die Verbindung an.
4. Ab jetzt können beide über diese Sockets Daten versenden und empfangen

Da die Skripte oben aber wohl nur eine sehr rohe Anbindung an die WinSock-DLL von Windows bieten, keine benutzerfreundliche Netzwerkbibliothek, lohnt es sich aber vermutlich ein wenig generell über Netzwerkprogrammierung mit Sockets schlau zu machen.

Cornix
27.04.2011, 14:36
Vielen Dank für die Antwort.
Ich habe bereits Netzwerkspiele programmiert, nur war dies damals mit Java wo ich getrennte Klassen für Server und Clientverbindung zur Verfügung hatte. Ich wusste nicht direkt wie groß der Unterschied zwischen beiden ausfallen würde. Ich habe mir das Programmieren bisher größtenteils selbst beigebracht indem ich das Internet durchstöberte und immer wieder verschiedenes ausprobiert habe.

Wie genau habe ich den Server Socket zu initialisieren? Immerhin scheinen alle Methoden eine Host angabe zu benötigen, der Server sollte lediglich eine Port angaben brauchen nehme ich an.
Könntest du mit diesen Klassen ein kurzes Beispiel dafür geben? Das wäre sehr freundlich von dir, ich lerne immer sehr gerne an Beispielen.

MagicMagor
27.04.2011, 15:29
Ich selbst kenn mich kaum aus mit der Ruby-Klasse oben, oder der Windows-Socket Library.
Vielleicht hilft dir das hier weiter:
Getting started with Winsock (http://msdn.microsoft.com/en-us/library/ms738545.aspx)
Die Funktionen des Winsocks-Moduls scheinen ja 1-1 die Windows-Funktion aufzurufen, entsprechend solltest du wohl hoffentlich damit klar kommen.

Cornix
28.04.2011, 09:48
Vielen Dank für die Adresse.
Ich habe es mir einmal angeschaut, bisher habe ich folgendes zustande gebracht:

PORT = 9999
HOST = "127.0.0.1"


#Server Socket
$s = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, Socket::IPPROTO_TCP)
$ais = Socket.sockaddr_in(PORT, nil)
$s.bind($ais)
$s.listen(5)
print("Server initialisiert")

#Client Socket
$c = Socket.new(Socket::AF_UNSPEC, Socket::SOCK_STREAM, Socket::IPPROTO_TCP)
$aic = Socket.sockaddr_in(PORT, HOST)
$c.connect($aic)
print("Client initialisiert")

#Client Socket des Servers
$client = $s.accept
print("Verbindung initialisiert")
Allerdings kann der Clientsocket derzeit noch keine Verbindung zum Serversockt herstellen, es könnte an meiner Firewall oder dem Router liegen, oder an dem Code. Ersteres kann ich überprüfen, letzteres werde ich alleine wahrscheinlich nicht herausfinden können.
Kann mir jemand hierbei helfen?

makenshi
28.04.2011, 11:03
Mit einem funktionierenden Rubybeispiel direkt kann ich dir nicht behilflich sein.
Allerdings kenne ich Client-Server Anwendungen eher so das es auch wirklich beides gibt.
Einen Client und einen Server.

Wenn ich deinen Code richtig lese, versuchst du erstmal einen einen Socket auf Port 5 aufzumachen.
Das ist schonmal nicht möglich, da die Ports von 0 - 1023 (well known Ports) reserviert sind.
Du kannst also einen Port ab 1024 nutzen.

Die nächste Sache ist halt wie am Anfang erwähnt das du den Server auf den Port horchen lässt und danach direkt im
Serverprogramm den Client initalisiert. An sich müsste es aber so laufen das der Server auf den Sockel horcht und dann
in einer Dauerschleife auf einen Verbindungsversuch wartet. Wenn man nun noch die Anfragen mehrerer Clients bearbeiten will,
müsste man für jede eingehende Verbindung einen Thread öffnen. Für einen einfachen Versuch würde aber erstmal reichen einen eingehenden
Verbindungsversuch anzunehmen.

Als zweites brauchst du dann das Clientprogramm welches sich versucht mit dem Serversocket zu verbinden. Das sollte allerdings ebenfalls ein
externes Programm sein.

Papa Justify
28.04.2011, 12:17
Mit Wireshark deine Netwerk-Aktivität überwachen ist eine gute Idee, wenn du herausfinden willst, ob das funktioniert.

Cornix
28.04.2011, 20:54
@makenshi (http://www.multimediaxis.de/members/2394-makenshi):
Der Port welchen ich verwenden will ist 9999, die 5 als Parameter der .listen Methode gibt, soweit ich verstanden habe, die maximale Anzahl an Verbindungen an welche der Server aufnimmt.

Nur als Information zum oben beschriebenen, der Server wird initialisiert, der Client auch, der Fehler welchen ich erhalte liegt in der Zeile: "$c.connect($aic)", alle vorhergehenden scheinen für den Computer in Ordnung zu sein.