赞 | 7 |
VIP | 20 |
好人卡 | 0 |
积分 | 16 |
经验 | 11472 |
最后登录 | 2024-7-10 |
在线时间 | 526 小时 |
Lv3.寻梦者 宛若
- 梦石
- 0
- 星屑
- 1568
- 在线时间
- 526 小时
- 注册时间
- 2007-8-19
- 帖子
- 1493
|
加入我们,或者,欢迎回来。
您需要 登录 才可以下载或查看,没有帐号?注册会员
x
本帖最后由 逸豫 于 2014-2-15 00:16 编辑
啊,没错我知道的今天是情人节与元宵节,在这种与我无关的日子里还在干这种活真是对不起了
纯Ruby实现的Socket网络通信
稍微封装了一下,采用了非阻塞的方式,注意每帧调用update
使用方法
#使用前请先调用 NET.init #创建Socket类 socket = NET::Socket.new #连接 socket.connect("127.0.0.1",20001) #每帧刷新 loop do Graphics.update Input.update socket.update #接收到消息 if socket.recv socket.send(socket.recv) end end #另附 #在port端口上进行监听 socket.listen(port) #获取监听得到的客户端socket socket.accepted_sockets.each do |cs| #可以直接操纵 cs.send("welcome") end #是否已经连接,因为使用了非阻塞的方法,从调用connect方法到实际连接上有延迟 socket.connected? #是否有错误 socket.error? #错误码可以通过以下代码获取,该错误码与WSAGetLastError获取的错误码一致 socket.errcode #是否有内容需要发送 socket.sending? #是否在监听状态 socket.listening? #停止监听(仅仅是不调用accept方法而已) socket.stop_listening #继续监听 socket.continue_listening
#使用前请先调用
NET.init
#创建Socket类
socket = NET::Socket.new
#连接
socket.connect("127.0.0.1",20001)
#每帧刷新
loop do
Graphics.update
Input.update
socket.update
#接收到消息
if socket.recv
socket.send(socket.recv)
end
end
#另附
#在port端口上进行监听
socket.listen(port)
#获取监听得到的客户端socket
socket.accepted_sockets.each do |cs|
#可以直接操纵
cs.send("welcome")
end
#是否已经连接,因为使用了非阻塞的方法,从调用connect方法到实际连接上有延迟
socket.connected?
#是否有错误
socket.error?
#错误码可以通过以下代码获取,该错误码与WSAGetLastError获取的错误码一致
socket.errcode
#是否有内容需要发送
socket.sending?
#是否在监听状态
socket.listening?
#停止监听(仅仅是不调用accept方法而已)
socket.stop_listening
#继续监听
socket.continue_listening
以上脚本仅供参考,并未经测试
一些吐槽
用Ruby操作Win32API就好像在一堆void指针上跳舞
一开始看错了结构体定义导致了一个需要400字节长度的结构体指针的参数被我直接传送了一个14字节长的字符串……而且在win7下竟然表现良好,在WINXP下也总是在奇怪的地方报内存错误,在没调试器的情况下DEBUG真是恶心(其实有也是一样恶心就是了……
没使用select,因为select还要自己设计那啥结构,我懒……
在一条消息还没发出去之前就继续send会导致两条消息被合成一条发过去,具体可以在测试DEMO的时候频繁按下某键看结果
因为RM的限制Graphics.update会在游戏处于后台的时候卡住游戏,所以当游戏处于后台时socket操作全部被暂停,积下没有收到的信息会在一次recv过程中合并成一条消息接收(后台运行可破之
具体代码可以参考DEMO
顺便一提核心脚本在DEMO脚本页的第一项……忘了写名字
顺便用vLan和别人联机的时候死活收不到消息,明明用家里的两台机子一台做主另一台挂上德国的VPN通过vLan去连都能连上的……
DEMO的使用方法是在连接以后按下游戏中能够按下的按键(ZXC上下左右之类),然后另一端会收到按键被按下的消息
一些截图
DEMO下载
https://dl.dropboxusercontent.com/u/322483/socket.7z
核心脚本
#encoding: utf-8 module NET #USER_DEFINED_CONST MAX_BUFFER = 4096 #const WSADESCRIPTION_LEN = 256 WSASYS_STATUS_LEN = 128 AF_INET = 2 SOCK_STREAM = 1 IPPROTO_TCP = 6 INVALID_SOCKET = ~0 SOCKET_ERROR = -1 INADDR_ANY = 0x00000000 SOMAXCONN = 0x7fffffff FIONBIO = 2147772030 EINPROGRESS = 10036 EISCONN = 10056 EWOULDBLOCK = 10035 EINVAL = 10022 #win32api API_WSAStartup = Win32API.new('ws2_32','WSAStartup','ip','i') API_WSACleanup = Win32API.new('ws2_32','WSACleanup','v','i') API_socket = Win32API.new('ws2_32','socket','iii','i') API_htons = Win32API.new('ws2_32','htons','i','i') API_ntohs = Win32API.new('ws2_32','ntohs','i','i') API_htonl = Win32API.new('ws2_32','htonl','L','L') API_inet_addr = Win32API.new('ws2_32','inet_addr','p','L') API_inet_ntoa = Win32API.new('ws2_32','inet_ntoa','L','p') API_ioctlsocket = Win32API.new('ws2_32','ioctlsocket','iip','i') API_bind = Win32API.new('ws2_32','bind','ipi','i') API_listen = Win32API.new('ws2_32','listen','ii','i') API_accept = Win32API.new('ws2_32','accept','ipp','i') API_connect = Win32API.new('ws2_32','connect','ipi','i') API_recv = Win32API.new('ws2_32','recv','ipii','i') API_send = Win32API.new('ws2_32','send','ipii','i') API_closesocket = Win32API.new('ws2_32','closesocket','i','i') API_WSAGetLastError = Win32API.new('ws2_32','WSAGetLastError','v','i') def self.init #最后两个char是为了内存对齐(byte alignment)理论上说并非必须 wsdata_types = "SSc#{WSADESCRIPTION_LEN+1}c#{WSASYS_STATUS_LEN+1}SSLcc" wsadata = ([0]*(WSADESCRIPTION_LEN+WSASYS_STATUS_LEN+9)).pack(wsdata_types) version = [2,2].pack("CC").unpack("S")[0] if API_WSAStartup.call(version,wsadata) != 0 false else @inited = true end end def self.inited? return @inited end class Socket attr :state attr :errcode attr :accepted_sockets def initialize @state = :not_ready @errcode = 0 @accepted_sockets = [] return false unless NET.inited? @socket = API_socket.call(AF_INET,SOCK_STREAM,IPPROTO_TCP) if @socket == INVALID_SOCKET return false end flag = [1].pack('L') if API_ioctlsocket.call(@socket,FIONBIO,flag) == SOCKET_ERROR return false end @state = :ready end def connected? return @state == :connected end def error? return @state == :error end def sending? return @state == :sending end def listening? return @state == :listening end def update @recv = nil @accepted_sockets.each{ |cs| cs.update } case @state when :connecting if @connect_info.nil? @state = :ready else if API_connect.call(@socket,@connect_info,@connect_info.size) == SOCKET_ERROR ref = API_WSAGetLastError.call if ref == EISCONN @state = :connected elsif ref != EWOULDBLOCK && ref != EINPROGRESS && ref != EINVAL puts "Error occured when connecting" @state = :error @errcode = ref end p ref else @state = :connected end end when :connected @buf = "\0"*MAX_BUFFER if @buf.nil? if (response_size = API_recv.call(@socket,@buf,@buf.size,0)) == SOCKET_ERROR ref = API_WSAGetLastError.call if(ref != EWOULDBLOCK) @state = :error @errcode = ref end else @recv = @buf[0...response_size] end when :sending if API_send.call(@socket,@message,@message.size,0) == SOCKET_ERROR ref = API_WSAGetLastError.call if ref != EWOULDBLOCK @state = :error @errcode = ref puts "Error occured when sending" end else @state = :connected end when :listening @ci = '\0'*16 if @ci.nil? len = [@ci.size].pack('i') if (cs = API_accept.call(@socket,@ci,len)) == SOCKET_ERROR ref = API_WSAGetLastError.call if ref != EWOULDBLOCK @state = :error @errcode = ref end else @accepted_sockets.push NET::ClientSocket.new({:socket => cs, :client_info => @ci}) end end end def connect(ip,port) p ip,port.to_i return false if @state != :ready sockaddr_in = [AF_INET,API_htons.call(port.to_i),API_inet_addr.call(ip)] sockaddr_in += ([0]*8) @connect_info = sockaddr_in.pack('sSLc*8') @state = :connecting end def send(message) return false if @state != :connected && @state != :sending @message = message if API_send.call(@socket,@message,@message.size,0) == SOCKET_ERROR ref = API_WSAGetLastError.call if ref != EWOULDBLOCK puts "Error occured when send" @state = :error @errcode = ref else @state = :sending end else @state = :connected end end def recv return false if @state != :connected return @recv end def listen(port) return if @state != :ready #server info sockaddr_in_server = [AF_INET,API_htons.call(port),API_htonl.call(INADDR_ANY)] sockaddr_in_server += ([0]*8); server_info = sockaddr_in_server.pack('sSLc*8') #bind if API_bind.call(@socket,server_info,server_info.size) == SOCKET_ERROR return false end #listen if API_listen.call(@socket,SOMAXCONN) == SOCKET_ERROR return false end @state = :listening end def stop_listening @state = :listening_stoped end def continue_listening @state = :listening end end class ClientSocket < Socket attr :ip attr :port def initialize(params) @state = :not_ready @accepted_sockets = [] return false unless NET.inited? @state = :connected @socket = params[:socket] @connect_info = params[:client_info] info = @connect_info.unpack('sSLc*8') @ip = API_inet_ntoa.call(info[2]) @port = API_ntohs.call(info[1]) end end end
#encoding: utf-8
module NET
#USER_DEFINED_CONST
MAX_BUFFER = 4096
#const
WSADESCRIPTION_LEN = 256
WSASYS_STATUS_LEN = 128
AF_INET = 2
SOCK_STREAM = 1
IPPROTO_TCP = 6
INVALID_SOCKET = ~0
SOCKET_ERROR = -1
INADDR_ANY = 0x00000000
SOMAXCONN = 0x7fffffff
FIONBIO = 2147772030
EINPROGRESS = 10036
EISCONN = 10056
EWOULDBLOCK = 10035
EINVAL = 10022
#win32api
API_WSAStartup = Win32API.new('ws2_32','WSAStartup','ip','i')
API_WSACleanup = Win32API.new('ws2_32','WSACleanup','v','i')
API_socket = Win32API.new('ws2_32','socket','iii','i')
API_htons = Win32API.new('ws2_32','htons','i','i')
API_ntohs = Win32API.new('ws2_32','ntohs','i','i')
API_htonl = Win32API.new('ws2_32','htonl','L','L')
API_inet_addr = Win32API.new('ws2_32','inet_addr','p','L')
API_inet_ntoa = Win32API.new('ws2_32','inet_ntoa','L','p')
API_ioctlsocket = Win32API.new('ws2_32','ioctlsocket','iip','i')
API_bind = Win32API.new('ws2_32','bind','ipi','i')
API_listen = Win32API.new('ws2_32','listen','ii','i')
API_accept = Win32API.new('ws2_32','accept','ipp','i')
API_connect = Win32API.new('ws2_32','connect','ipi','i')
API_recv = Win32API.new('ws2_32','recv','ipii','i')
API_send = Win32API.new('ws2_32','send','ipii','i')
API_closesocket = Win32API.new('ws2_32','closesocket','i','i')
API_WSAGetLastError = Win32API.new('ws2_32','WSAGetLastError','v','i')
def self.init
#最后两个char是为了内存对齐(byte alignment)理论上说并非必须
wsdata_types = "SSc#{WSADESCRIPTION_LEN+1}c#{WSASYS_STATUS_LEN+1}SSLcc"
wsadata = ([0]*(WSADESCRIPTION_LEN+WSASYS_STATUS_LEN+9)).pack(wsdata_types)
version = [2,2].pack("CC").unpack("S")[0]
if API_WSAStartup.call(version,wsadata) != 0
false
else
@inited = true
end
end
def self.inited?
return @inited
end
class Socket
attr :state
attr :errcode
attr :accepted_sockets
def initialize
@state = :not_ready
@errcode = 0
@accepted_sockets = []
return false unless NET.inited?
@socket = API_socket.call(AF_INET,SOCK_STREAM,IPPROTO_TCP)
if @socket == INVALID_SOCKET
return false
end
flag = [1].pack('L')
if API_ioctlsocket.call(@socket,FIONBIO,flag) == SOCKET_ERROR
return false
end
@state = :ready
end
def connected?
return @state == :connected
end
def error?
return @state == :error
end
def sending?
return @state == :sending
end
def listening?
return @state == :listening
end
def update
@recv = nil
@accepted_sockets.each{ |cs| cs.update }
case @state
when :connecting
if @connect_info.nil?
@state = :ready
else
if API_connect.call(@socket,@connect_info,@connect_info.size) == SOCKET_ERROR
ref = API_WSAGetLastError.call
if ref == EISCONN
@state = :connected
elsif ref != EWOULDBLOCK && ref != EINPROGRESS && ref != EINVAL
puts "Error occured when connecting"
@state = :error
@errcode = ref
end
p ref
else
@state = :connected
end
end
when :connected
@buf = "\0"*MAX_BUFFER if @buf.nil?
if (response_size = API_recv.call(@socket,@buf,@buf.size,0)) == SOCKET_ERROR
ref = API_WSAGetLastError.call
if(ref != EWOULDBLOCK)
@state = :error
@errcode = ref
end
else
@recv = @buf[0...response_size]
end
when :sending
if API_send.call(@socket,@message,@message.size,0) == SOCKET_ERROR
ref = API_WSAGetLastError.call
if ref != EWOULDBLOCK
@state = :error
@errcode = ref
puts "Error occured when sending"
end
else
@state = :connected
end
when :listening
@ci = '\0'*16 if @ci.nil?
len = [@ci.size].pack('i')
if (cs = API_accept.call(@socket,@ci,len)) == SOCKET_ERROR
ref = API_WSAGetLastError.call
if ref != EWOULDBLOCK
@state = :error
@errcode = ref
end
else
@accepted_sockets.push NET::ClientSocket.new({:socket => cs,
:client_info => @ci})
end
end
end
def connect(ip,port)
p ip,port.to_i
return false if @state != :ready
sockaddr_in = [AF_INET,API_htons.call(port.to_i),API_inet_addr.call(ip)]
sockaddr_in += ([0]*8)
@connect_info = sockaddr_in.pack('sSLc*8')
@state = :connecting
end
def send(message)
return false if @state != :connected && @state != :sending
@message = message
if API_send.call(@socket,@message,@message.size,0) == SOCKET_ERROR
ref = API_WSAGetLastError.call
if ref != EWOULDBLOCK
puts "Error occured when send"
@state = :error
@errcode = ref
else
@state = :sending
end
else
@state = :connected
end
end
def recv
return false if @state != :connected
return @recv
end
def listen(port)
return if @state != :ready
#server info
sockaddr_in_server = [AF_INET,API_htons.call(port),API_htonl.call(INADDR_ANY)]
sockaddr_in_server += ([0]*8);
server_info = sockaddr_in_server.pack('sSLc*8')
#bind
if API_bind.call(@socket,server_info,server_info.size) == SOCKET_ERROR
return false
end
#listen
if API_listen.call(@socket,SOMAXCONN) == SOCKET_ERROR
return false
end
@state = :listening
end
def stop_listening
@state = :listening_stoped
end
def continue_listening
@state = :listening
end
end
class ClientSocket < Socket
attr :ip
attr :port
def initialize(params)
@state = :not_ready
@accepted_sockets = []
return false unless NET.inited?
@state = :connected
@socket = params[:socket]
@connect_info = params[:client_info]
info = @connect_info.unpack('sSLc*8')
@ip = API_inet_ntoa.call(info[2])
@port = API_ntohs.call(info[1])
end
end
end
|
评分
-
查看全部评分
|