查看: 12218|回复: 25

[通用发布] Socket网络编程【附RMVA工程】

[复制链接]

宛若

梦石
0
星屑
1888
在线时间
529 小时
注册时间
2007-8-19
回帖
1466

极短24参与开拓者

发表于 2014-2-15 00:06:27 | 显示全部楼层 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有账号?注册会员

×
本帖最后由 逸豫 于 2014-2-15 00:16 编辑

啊,没错我知道的今天是情人节与元宵节,在这种与我无关的日子里还在干这种活真是对不起了

纯Ruby实现的Socket网络通信
稍微封装了一下,采用了非阻塞的方式,注意每帧调用update

使用方法

[pre lang="ruby" line="1"]#使用前请先调用
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[/pre]

以上脚本仅供参考,并未经测试

一些吐槽
用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

核心脚本
[pre lang="ruby" line="1"]#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[/pre]

评分

参与人数 6星屑 +1210 收起 理由
有丘直方 + 10 精品文章
joe5491 + 64 用C++搞dll的路過
IamI + 220 塞糖
柳之一 + 220 我很赞同
Sion + 666 谢谢分享
晴兰 + 30 @fdset = [1, @socket].pack(&quot;LL&quot

查看全部评分

[url=http://rpg.blue/thread-219730-1-1.html]http://unhero.sinaapp.com/wi.php[/url]
[color=Red]如你所见这是个死坑,没错这就是打我的脸用的[/color]
头像被屏蔽
梦石
0
星屑
653
在线时间
3774 小时
注册时间
2011-2-26
回帖
1577

开拓者

发表于 2014-2-15 01:32:20 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽
签名被屏蔽
回复

使用道具 举报

梦石
0
星屑
266
在线时间
2355 小时
注册时间
2009-3-13
回帖
2257

贵宾

发表于 2014-2-15 10:10:09 | 显示全部楼层
谢谢分享。
回复

使用道具 举报

梦石
0
星屑
752
在线时间
800 小时
注册时间
2011-10-20
回帖
2257

开拓者

发表于 2014-2-15 10:26:24 | 显示全部楼层
好东西尤物收藏啦!
合并消息如果后台太久会不会一次性卡掉!?
能否有个消息接受底栏发黄光。。。。。
另外不知是不是wlan同一个连不上是ip,貌似要用内网ip才行,(以前测试的。。)
欢迎点此进入我的egames.wink.ws,有RMQQ堂等

[url=http://rpg.blue/thread-317273-1-1.html]短篇八-赶选

http://yun.baidu.com/share/link?shareid=2158225779&uk=169642147&third=0


历险?ARPG?赢回你的疆域?新的战斗模式?…………点击这里:[宋乱贼狂 for QQ堂]
http://rpg.blue/group-368-1.html
programing ....?
[url=http://rpg.blue/thrd-234658-1-1.html]
回复

使用道具 举报

宛若

梦石
0
星屑
1888
在线时间
529 小时
注册时间
2007-8-19
回帖
1466

极短24参与开拓者

 楼主| 发表于 2014-2-17 21:35:04 | 显示全部楼层
晴兰 发表于 2014-2-15 01:32
看到小瞳前辈这个帖子很是兴奋,于是想和其他人分享和介绍一下感觉,如有冒犯请见谅和不吝指正:

前辈的这 ...

1、叫我小瞳的人都是我前辈
2、HTTP应该有更加方便的库可以使用(网络编程了解不深,只用过Ruby的标准库,不能提供更详细的信息
3、DLL什么的虽然看不懂但是好高端的样子,我之所以想用纯Ruby写Win32API调用就是不熟悉DLL的写法……否则的话用WSAEventSelect搭配新的窗口过程效率也会更高
4、RM是单线程运行,select的时候会卡住游戏,也可能会有人希望在等待链接的时候自己去转个菊花什么的……所以用阻塞式的方法总觉得不太舒服……
[url=http://rpg.blue/thread-219730-1-1.html]http://unhero.sinaapp.com/wi.php[/url]
[color=Red]如你所见这是个死坑,没错这就是打我的脸用的[/color]
回复

使用道具 举报

头像被屏蔽
梦石
0
星屑
653
在线时间
3774 小时
注册时间
2011-2-26
回帖
1577

开拓者

发表于 2014-2-18 00:29:29 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽
签名被屏蔽
回复

使用道具 举报

梦石
0
星屑
50
在线时间
87 小时
注册时间
2013-2-21
回帖
25
发表于 2014-4-15 16:51:53 | 显示全部楼层
好帖,喜欢,真心觉得能联机共斗才是RM的道路
回复

使用道具 举报

梦石
0
星屑
3703
在线时间
3081 小时
注册时间
2011-11-17
回帖
943
发表于 2014-4-19 12:56:10 | 显示全部楼层
前段时间也在用scoket 关于发包收包  我用到的情况是 如果包的大小超1024 会被路由器截断 并且 并不是按1024截断 比如1400的包 可能会被截成 800的 和600发过来 所以我在包尾巴上都加上个结束符字符串判定的 ,这样便于拼接包
其实数据还是阻塞性的好 这样不会出现连续send的情况  主线程跑个渲染 用个图片转圈圈 主线程里加个update 不断判断某个bool 变量的变化 scoket的那个线程完成通讯后变更那个bool变量的值 让主线程继续跑下去
回复

使用道具 举报

梦石
0
星屑
110
在线时间
81 小时
注册时间
2013-9-18
回帖
47
发表于 2014-4-28 12:33:24 | 显示全部楼层
不是说有va工程吗?怎么没有了?
回复

使用道具 举报

梦石
0
星屑
388
在线时间
139 小时
注册时间
2012-2-8
回帖
35
发表于 2014-6-24 22:25:08 | 显示全部楼层
收藏。 表示对网络化有兴趣
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.

在本版发帖返回顶部