设为首页收藏本站|繁體中文

Project1

 找回密码
 注册会员
搜索
查看: 10105|回复: 25
打印 上一主题 下一主题

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

[复制链接]

Lv3.寻梦者

宛若

梦石
0
星屑
1558
在线时间
526 小时
注册时间
2007-8-19
帖子
1493

极短24参与开拓者

跳转到指定楼层
1
发表于 2014-2-15 00:06:27 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式

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

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

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

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

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

使用方法

RUBY 代码复制
  1. #使用前请先调用
  2. NET.init
  3. #创建Socket类
  4. socket = NET::Socket.new
  5. #连接
  6. socket.connect("127.0.0.1",20001)
  7. #每帧刷新
  8. loop do
  9.   Graphics.update
  10.   Input.update
  11.   socket.update
  12.   #接收到消息
  13.   if socket.recv
  14.     socket.send(socket.recv)
  15.   end
  16. end
  17.  
  18. #另附
  19. #在port端口上进行监听
  20. socket.listen(port)
  21. #获取监听得到的客户端socket
  22. socket.accepted_sockets.each do |cs|
  23.   #可以直接操纵
  24.   cs.send("welcome")
  25. end
  26. #是否已经连接,因为使用了非阻塞的方法,从调用connect方法到实际连接上有延迟
  27. socket.connected?
  28. #是否有错误
  29. socket.error?
  30. #错误码可以通过以下代码获取,该错误码与WSAGetLastError获取的错误码一致
  31. socket.errcode
  32. #是否有内容需要发送
  33. socket.sending?
  34. #是否在监听状态
  35. socket.listening?
  36. #停止监听(仅仅是不调用accept方法而已)
  37. socket.stop_listening
  38. #继续监听
  39. 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

核心脚本
RUBY 代码复制
  1. #encoding: utf-8
  2. module NET
  3.   #USER_DEFINED_CONST
  4.   MAX_BUFFER = 4096
  5.  
  6.   #const
  7.   WSADESCRIPTION_LEN = 256
  8.   WSASYS_STATUS_LEN = 128
  9.   AF_INET = 2
  10.   SOCK_STREAM = 1
  11.   IPPROTO_TCP = 6
  12.   INVALID_SOCKET = ~0
  13.   SOCKET_ERROR = -1
  14.   INADDR_ANY = 0x00000000
  15.   SOMAXCONN = 0x7fffffff
  16.   FIONBIO = 2147772030
  17.   EINPROGRESS = 10036
  18.   EISCONN = 10056
  19.   EWOULDBLOCK = 10035
  20.   EINVAL = 10022
  21.  
  22.   #win32api
  23.   API_WSAStartup = Win32API.new('ws2_32','WSAStartup','ip','i')
  24.   API_WSACleanup = Win32API.new('ws2_32','WSACleanup','v','i')
  25.   API_socket = Win32API.new('ws2_32','socket','iii','i')
  26.   API_htons = Win32API.new('ws2_32','htons','i','i')
  27.   API_ntohs = Win32API.new('ws2_32','ntohs','i','i')
  28.   API_htonl = Win32API.new('ws2_32','htonl','L','L')
  29.   API_inet_addr = Win32API.new('ws2_32','inet_addr','p','L')
  30.   API_inet_ntoa = Win32API.new('ws2_32','inet_ntoa','L','p')
  31.   API_ioctlsocket = Win32API.new('ws2_32','ioctlsocket','iip','i')
  32.   API_bind = Win32API.new('ws2_32','bind','ipi','i')
  33.   API_listen = Win32API.new('ws2_32','listen','ii','i')
  34.   API_accept = Win32API.new('ws2_32','accept','ipp','i')
  35.   API_connect = Win32API.new('ws2_32','connect','ipi','i')
  36.   API_recv = Win32API.new('ws2_32','recv','ipii','i')
  37.   API_send = Win32API.new('ws2_32','send','ipii','i')
  38.   API_closesocket = Win32API.new('ws2_32','closesocket','i','i')
  39.   API_WSAGetLastError = Win32API.new('ws2_32','WSAGetLastError','v','i')
  40.  
  41.   def self.init
  42.     #最后两个char是为了内存对齐(byte alignment)理论上说并非必须
  43.     wsdata_types = "SSc#{WSADESCRIPTION_LEN+1}c#{WSASYS_STATUS_LEN+1}SSLcc"
  44.     wsadata = ([0]*(WSADESCRIPTION_LEN+WSASYS_STATUS_LEN+9)).pack(wsdata_types)
  45.     version = [2,2].pack("CC").unpack("S")[0]
  46.  
  47.     if API_WSAStartup.call(version,wsadata) != 0
  48.       false
  49.     else
  50.       @inited = true
  51.     end
  52.   end
  53.  
  54.   def self.inited?
  55.     return @inited
  56.   end
  57.  
  58.   class Socket
  59.     attr :state
  60.     attr :errcode
  61.     attr :accepted_sockets
  62.     def initialize
  63.       @state = :not_ready
  64.       @errcode = 0
  65.       @accepted_sockets = []
  66.       return false unless NET.inited?
  67.  
  68.       @socket = API_socket.call(AF_INET,SOCK_STREAM,IPPROTO_TCP)
  69.       if @socket == INVALID_SOCKET
  70.         return false
  71.       end
  72.  
  73.       flag = [1].pack('L')
  74.       if API_ioctlsocket.call(@socket,FIONBIO,flag) == SOCKET_ERROR
  75.         return false
  76.       end
  77.  
  78.       @state = :ready
  79.     end
  80.  
  81.     def connected?
  82.       return @state == :connected
  83.     end
  84.  
  85.     def error?
  86.       return @state == :error
  87.     end
  88.  
  89.     def sending?
  90.       return @state == :sending
  91.     end
  92.  
  93.     def listening?
  94.       return @state == :listening
  95.     end
  96.  
  97.     def update
  98.       @recv = nil
  99.       @accepted_sockets.each{ |cs| cs.update }
  100.       case @state
  101.       when :connecting
  102.         if @connect_info.nil?
  103.           @state = :ready
  104.         else
  105.           if API_connect.call(@socket,@connect_info,@connect_info.size) == SOCKET_ERROR
  106.             ref = API_WSAGetLastError.call
  107.             if ref == EISCONN
  108.               @state = :connected
  109.             elsif ref != EWOULDBLOCK && ref != EINPROGRESS && ref != EINVAL
  110.               puts "Error occured when connecting"
  111.               @state = :error
  112.               @errcode = ref
  113.             end
  114.             p ref
  115.           else
  116.             @state = :connected
  117.           end
  118.         end
  119.  
  120.       when :connected
  121.         @buf = "\0"*MAX_BUFFER if @buf.nil?
  122.         if (response_size = API_recv.call(@socket,@buf,@buf.size,0)) == SOCKET_ERROR
  123.           ref = API_WSAGetLastError.call
  124.           if(ref != EWOULDBLOCK)
  125.             @state = :error
  126.             @errcode = ref
  127.           end
  128.         else
  129.           @recv = @buf[0...response_size]
  130.         end
  131.  
  132.       when :sending
  133.         if API_send.call(@socket,@message,@message.size,0) == SOCKET_ERROR
  134.           ref = API_WSAGetLastError.call
  135.           if ref != EWOULDBLOCK
  136.             @state = :error
  137.             @errcode = ref
  138.             puts "Error occured when sending"
  139.           end
  140.         else
  141.           @state = :connected
  142.         end
  143.  
  144.       when :listening
  145.         @ci = '\0'*16 if @ci.nil?
  146.         len = [@ci.size].pack('i')
  147.         if (cs = API_accept.call(@socket,@ci,len)) == SOCKET_ERROR
  148.           ref = API_WSAGetLastError.call
  149.           if ref != EWOULDBLOCK
  150.             @state = :error
  151.             @errcode = ref
  152.           end
  153.         else
  154.           @accepted_sockets.push NET::ClientSocket.new({:socket => cs,
  155.                                                         :client_info => @ci})
  156.         end
  157.       end
  158.  
  159.  
  160.     end
  161.  
  162.     def connect(ip,port)
  163.       p ip,port.to_i
  164.       return false if @state != :ready
  165.  
  166.       sockaddr_in = [AF_INET,API_htons.call(port.to_i),API_inet_addr.call(ip)]
  167.       sockaddr_in += ([0]*8)
  168.       @connect_info = sockaddr_in.pack('sSLc*8')
  169.       @state = :connecting
  170.  
  171.     end
  172.  
  173.     def send(message)
  174.       return false if @state != :connected && @state != :sending
  175.       @message = message
  176.       if API_send.call(@socket,@message,@message.size,0) == SOCKET_ERROR
  177.         ref = API_WSAGetLastError.call
  178.         if ref != EWOULDBLOCK
  179.           puts "Error occured when send"
  180.           @state = :error
  181.           @errcode = ref
  182.         else
  183.           @state = :sending
  184.         end
  185.       else
  186.         @state = :connected
  187.       end
  188.     end
  189.  
  190.     def recv
  191.       return false if @state != :connected
  192.       return @recv
  193.     end
  194.  
  195.     def listen(port)
  196.       return if @state != :ready
  197.       #server info
  198.       sockaddr_in_server = [AF_INET,API_htons.call(port),API_htonl.call(INADDR_ANY)]
  199.       sockaddr_in_server += ([0]*8);
  200.       server_info = sockaddr_in_server.pack('sSLc*8')
  201.  
  202.       #bind
  203.       if API_bind.call(@socket,server_info,server_info.size) == SOCKET_ERROR
  204.         return false
  205.       end
  206.  
  207.       #listen
  208.       if API_listen.call(@socket,SOMAXCONN) == SOCKET_ERROR
  209.         return false
  210.       end
  211.  
  212.       @state = :listening
  213.  
  214.     end
  215.  
  216.     def stop_listening
  217.       @state = :listening_stoped
  218.     end
  219.  
  220.     def continue_listening
  221.       @state = :listening
  222.     end
  223.  
  224.   end
  225.  
  226.   class ClientSocket < Socket
  227.     attr :ip
  228.     attr :port
  229.     def initialize(params)
  230.       @state = :not_ready
  231.       @accepted_sockets = []
  232.       return false unless NET.inited?
  233.       @state = :connected
  234.       @socket = params[:socket]
  235.       @connect_info = params[:client_info]
  236.       info = @connect_info.unpack('sSLc*8')
  237.       @ip = API_inet_ntoa.call(info[2])
  238.       @port = API_ntohs.call(info[1])
  239.     end
  240.   end
  241.  
  242. end

评分

参与人数 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]
头像被屏蔽

Lv2.观梦者 (禁止发言)

梦石
0
星屑
653
在线时间
3774 小时
注册时间
2011-2-26
帖子
1839

开拓者

2
发表于 2014-2-15 01:32:20 | 只看该作者
提示: 作者被禁止或删除 内容自动屏蔽
签名被屏蔽
回复 支持 反对

使用道具 举报

Lv2.观梦者 (暗夜天使)

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

贵宾

3
发表于 2014-2-15 10:10:09 | 只看该作者
谢谢分享。
回复 支持 反对

使用道具 举报

Lv2.观梦者

梦石
0
星屑
681
在线时间
791 小时
注册时间
2011-10-20
帖子
2394

开拓者

4
发表于 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]
回复 支持 反对

使用道具 举报

Lv3.寻梦者

宛若

梦石
0
星屑
1558
在线时间
526 小时
注册时间
2007-8-19
帖子
1493

极短24参与开拓者

5
 楼主| 发表于 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]
回复 支持 反对

使用道具 举报

头像被屏蔽

Lv2.观梦者 (禁止发言)

梦石
0
星屑
653
在线时间
3774 小时
注册时间
2011-2-26
帖子
1839

开拓者

6
发表于 2014-2-18 00:29:29 | 只看该作者
提示: 作者被禁止或删除 内容自动屏蔽
签名被屏蔽
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
50
在线时间
87 小时
注册时间
2013-2-21
帖子
31
7
发表于 2014-4-15 16:51:53 | 只看该作者
好帖,喜欢,真心觉得能联机共斗才是RM的道路
回复 支持 反对

使用道具 举报

Lv3.寻梦者

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

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
110
在线时间
81 小时
注册时间
2013-9-18
帖子
68
9
发表于 2014-4-28 12:33:24 | 只看该作者
不是说有va工程吗?怎么没有了?
回复 支持 反对

使用道具 举报

Lv2.观梦者

梦石
0
星屑
378
在线时间
139 小时
注册时间
2012-2-8
帖子
39
10
发表于 2014-6-24 22:25:08 | 只看该作者
收藏。 表示对网络化有兴趣
回复 支持 反对

使用道具 举报

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

本版积分规则

拿上你的纸笔,建造一个属于你的梦想世界,加入吧。
 注册会员
找回密码

站长信箱:[email protected]|手机版|小黑屋|无图版|Project1游戏制作

GMT+8, 2024-4-25 12:03

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表