Hello! Now, I was reading a little here and there, mostly stood upon this thing and I got pretty fueled: http://lthzelda.wordpress.com/2010/04/28/rm-4-tcp-sockets-in-rpg-maker-vx/
I've always wanted to try out and make a game with some online capabilities. Not an MMO, just connect to a friend, and do something, like battles a-la-RPG style. Akin to Pokémon games I suppose.
So I started messing up with the code in that article. But then I found some nasty stuff I still don't get x'D, and I got pretty confused.
Take into account, I'm using the code the guy... umm... took from RMX-OS. I'll put it here for reference.
#==============================================================================
# ** Module Win32 - Handles numerical based data.
#------------------------------------------------------------------------------
# Author Ruby
# Version 1.8.1
#==============================================================================
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 us 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
I started by getting an error in this line.
SocketError.check if (ret = Winsock.connect(@fd, sockaddr, sockaddr.size)) == -1
Basically, it said that sockaddr was nil, so "size" didn't exist. I got deeper, following the trace...
def self.sockaddr_in(port, host)
begin
[AF_INET, getservice(port)].pack('sn') + gethostbyname(host)[3] + [].pack('x8')
rescue
end
end
I then "removed" the rescue, just to know what error was I getting there.
It sent me to this part:
#----------------------------------------------------------------------------
# ● 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
Was getting something about an invalid UTF-8 string. I read somewhere that Ruby 1.9.2 (the version that RGSS3 uses) strings are compatible with nul-ended strings (C chars and the such), so I just changed this accordingly, no need to cut the null char at the end.
[host[0].copymem(64), [], host[2], host[4].copymem(4).unpack('l')[0].copymem(4)]
But then, it came to a dead end. I don't know what the error I get means x'D. Tried the original code on both XP and VX projects, and none of them seemed to work either.
(http://puu.sh/8untV.png)
This is the error I get on VX/XP.
(http://puu.sh/8unza.png)
And this is the one I get on my RGSS3 XP game (pretty sure the same happens on a VXA project).
I somehow think that both errors are the same, but what changes is the nil handling. But I don't really know.
Again, no pressure. This is a feature I have zillions of seconds to develop. But wanted to try and ask if someone has any idea. Right now, I just have a small headache xD.
Salutations to all of you, ladies and gentlemen.
The main problem is how the C-API handles data. In this the thing is that the C-API actually allocated enough memory for some sort of structure, but it ends up being destroyed so the entire memory block is copied. It might not be the very best way to about things, but it is good (and systematic) enough to make things work well. Long story short: This is needed to ensure the memory stays allocated.
Hmmm... well, that's the kind of things I was afraid of doing wrong. Thanks Blizz.
BTW, I gambled and continued the tutorial ignoring the error. Got a connection working, little hurray for me.
(http://puu.sh/8useB.png)
(Tutorial says "don't use Eclipse better use this and that", but already had Eclipse, so... easier for me).
I want to try making some tests with send and receive. Maybe a tic-tac-toe.
Ummm okay, sorry for doubleposting. It seems my little happiness didn't last for so long :facepalm:. Thing is, since it will be communication from game instance to game instance, I need to set one of them as host and the other as client. But to make the host, I think I need to make the TCPServer class. I've been diving into Ruby's source and other stuff, and I don't understand a shit x'DDD.
I'm sorry, but if anyone could give me a little pointers... I feel as bad as the Power Glove.
I know that this point is really old, but I was looking at the same tutorial as you and getting the same error, I did a bit of googling and by pure luck I found out what the problem was.
It turns in C, errno returns a value of 0 if no error has occurred. To solve this was extremely simple:
def self.check
errno = Winsock.WSAGetLastError
if errno == 0
return
end
constName = Errno.constants.detect {|c| Errno.const_get(c).new.errno == errno }
if constName
raise Errno.const_get(constName)
else
raise "Unknown network error code: #{errno}"
end
end