目录

  1. 网游中的网络编程系列1:UDP vs. TCP
  2. 网游中的网络编程2:发送和接收数据包
  3. 网游中的网络编程3:在UDP上建立虚拟连接
  4. TODO

二、在UDP上建立虚拟连接

介绍

UDP是无连接的,一个UDPsocket可以被用做,与任意数量的计算机交换数据包。然而,在多人游戏中,我们只希望在一小部分建立起连接的计算中,交换数据包。

所以,我们需要做的第一步就是:在UDP上让两台计算机,建立起虚拟连接。

但是,首先,我们先深入到底层,弄清楚互联网是如何工作的。

互联网不是一系列的电话线

在2006年, Senator Ted Stevens做了一个互联网历史上,著名的一次演讲:

“The internet is not something that you just dump something on. It’s not a big truck. It’s a series of tubes”

当我第一次使用互联网的是:1995年,我在大学的计算机实验室中,我用Netscape浏览器上网,当时漫无目的的瞎逛。

我当时想:每次连上一个网站,就产生一些“真实的连接”,就像电话线。我十分惊奇,当我每次访问一个新的网站的时候需要花费多少钱?(作者当时认为,每次访问网站都是建立在一条通信线路之上,就像电话线,需要拉线)不会有人找上门,让我付这些线路的费用吧?

当然,这个想法现在看起来很傻。

没有直接的连接

互联中:没有一条通信电缆,直接通信的两台计算机。数据是由IP协议,通过数据包,从一个个电脑传递过来的。(就像传纸条)

一个数据包可能通过几个计算机才能到达目的地。你不能知道准确的传递过程(第一步,第二步。。。),这个过程是会变化的,是根据网络质量决定数据包的下一步走向。你可能发送过两个数据包A和B到同一个地址,它们可能走的是不同的路线。这个也是数据包无序的一个原因。

在Linux和Unix系统上(win可以用‘tracert’),可以使用‘traceroute’指令来查看数据包的传递线路和途径的主机名和IP地址。traceroute请参考

试一下traceroute指令:

traceroute: Warning: baidu.com has multiple addresses; using 220.181.57.217
traceroute to baidu.com (220.181.57.217), 64 hops max, 52 byte packets1  192.168.1.1 (192.168.1.1)  4.727 ms  4.960 ms  4.144 ms2  223.20.160.1 (223.20.160.1)  13.405 ms  6.047 ms  8.561 ms3  218.241.252.185 (218.241.252.185)  4.735 ms  2.130 ms  7.771 ms4  218.241.252.197 (218.241.252.197)  6.849 ms  5.335 ms  4.555 ms5  202.99.1.217 (202.99.1.217)  4.025 ms  13.324 ms  3.761 ms6  * 218.241.244.21 (218.241.244.21)  8.492 ms218.241.244.9 (218.241.244.9)  5.389 ms7  218.241.244.33 (218.241.244.33)  6.699 ms  4.851 ms  5.386 ms8  * * *

注意:第八行是因为有ICMP防火墙,请求被拒绝了,所以没有探测出目的ip地址。

这个过程就能诠释:没有直接的连接。

如何收到数据包

正如第一篇文章,举的那个简单的例子:收到数据包,就像在一个房间,人们手递手传纸条。

互联网是网络的网络(网络的集合)。当然我们不仅在一个小房子中传递信件,我们能把它传到世界各地。

最好的例子是邮局系统!

当你想要发一封信给别人,你需要把你的信放到邮箱中,同时你会相信会到达收件人的手上。信件怎么到的,你不需要关心,反正到了。总得有人把你的信送到目的地,那么到底是怎么送到的呢?

首先,邮递员不会拿着你的信,直接送到目的地!邮递员拿着你的信,送到当地邮局,让邮局处理。

如果这封信是本地的,本地的邮局会收过来,安排让另外一个邮递员直接送到目的地。但是,如果信的地址不是本地的,那么本地的邮局不会直接把信送到目的地,所以邮局会送到上一级(镇邮局送到市邮局),或者送到临近城市的邮局,如果目的地太远就会送到飞机场。信件的传输方式是用大卡车。

我们来看一个例子:假定一封信,从洛杉矶寄到北京,本地邮局接收到信,然后发现是国际信件,就直接送到洛杉矶的邮件中心。这封信,确认收件的地址无误,就安排到下一班飞机飞往北京的航班。

飞机着陆在北京,北京的邮件系统肯定是和洛杉矶的邮件系统不一样。北京的邮件中心收到这封信后,就送到具体的区级的当地邮局,最终,这封信会通过一个邮递员直接送到收件人的手里。

就像邮局系统,通过地址传递信件一样。网络传递数据包是通过IP地址。传递数据包的细节和路径选择是非常复杂的,但是基本思想:每个路由器都是一台计算机,由路由表决定数据下一步走的地址。(这部分我省略了一些路由和路由表的部分,我没有看懂,后面研究明白,回来再补全。现在不影响后面的阅读)

编辑路由表的工作是网络管理员的工作,不是我们这些程序员关心的问题(还好?)。但是,如果你想了解更多关于这方面的知识,可以看看下面这些文章:

  • ars technica:讲网络如何交换数据包和传输的细节
  • routing tables
  • border gateway protocol

虚拟连接

现在回到连接的话题上。

如果你使用TCP socket,你知道它是面向连接的,看起来像一个‘连接’。但是TCP是建立在IP协议上的,而IP协议只是数据包在计算机之间传递(并没有连接的概念),所以TCP的连接概念一定是:虚拟连接。

如果TCP可以建立再IP上建立虚拟的连接,那么我们也能在UDP上实现虚拟连接。

让我们定义虚拟连接:两台计算机间传输UDP数据包以固定的速度,如每秒10个包。只要数据包传输流畅,我们就认为:两个计算机建立起了虚拟连接。

连接分为两部分:

  • 监听计算机连入,我们称这个计算机为‘服务器’。
  • 通过IP地址和端口连接服务器的计算机,我们成为‘客户端’。

我们把场景设定为(先设定简单的场景,一点点来):不论何时,我们只允许一个客户端连接服务器。同时,我们假定服务器的IP地址不变,客户端是直接连接服务器。后面的文章再说支持多个客户端连接的例子等,现在先现实我们限定条件下,简单的虚拟连接,这样可以更好的理解虚拟连接。

协议id

UDP是无连接的,UDP socket会接收任意计算机发来的数据包。

我们将限定:服务器只从客户端接收数据包,客户端只给服务器发送数据包(一对一)。我们不能通过地址过滤数据包,因为服务器不知道客户端的地址(python中socket可以通过recvfrom方法得到地址)。所以我们在每个UDP数据包加一个‘头信息’,由32位protocol id组成:

[uint protocol id]
(packet data...)

protocol id只是一些唯一的数字。如果数据包的protocol id不能匹配我们的protocol id,数据包就被忽略。如果protocol id匹配,我们就接收packet data。

你只需要选择唯一的数字,可以用hash你的游戏名字和协议版本数字。你也可以用任何信息当做protocol id,需要保证protocol id的唯一性,因为这个protocol id是我们连接协议的基础。

检测连接

现在我们需要一个检查连接的方法。

当然我们可以做一些复杂的握手,此过程需要发送和接收多个UDP数据包。或许客户端‘请求连接’数据包,发送服务器,服务器响应返回给客户端‘连接接受’,或者如果客户端请求与,已经和其他客户端建立起连接的服务器,建立连接,则服务器就会返回给客户端‘忙碌中’。

或者,我们可以让服务器检查接收到的第一个数据包的protocol id是否正确,然后考虑是否建立连接。

客户端假定与服务器建立起连接,然后给服务器发送数据包。当服务器接受到客户端发来的第一个数据包,就记下该客户端的IP地址和端口号,最后,返回响应数据包。

客户端已经知道服务器的地址和端口。所以,当客户端接受数据包,客户端会过滤掉任何不是服务器地址的请求。同样,服务器接收到客户端的第一个数据包,通过recvfrom方法,能获取到客户端的IP地址和端口。所以服务器也可以忽略不来自指定客户端的任何数据包。

我们可以使用这个简洁的方式,因为我们只需要在两台计算机之间建立连接。在后面的文章中,我们会升级我们的连接系统,用于支持两个以上的计算机连接,并且使得连接更加健壮。

(就是与特定ip地址和端口的计算机进行传输数据)

检测断开连接

我们如何检测断开连接?

如果一个连接被定为接收数据包,那么断开连接就可以定义为不接收数据包。

为了查明我们没有接受数据包,服务器和客户端两边都计算:从上一次接收到数据包的开始,到下一个接收到数据包的时间。(也就是所谓的‘超时时间’)

每次如果我们接收到数据包,就重置计时器(’超时时间’清零)。如果计时器超过设定的值,侧连接‘超时’,我们就是断开连接(不再限制连接客户端的IP和端口)。

这也是一种优雅的方式用来处理,第二个客户端请求已建立连接的服务器的情况。建立起连接的服务器不会接收来自其他客户端的数据包,所以第二个客户端接收不到服务器响应的数据包,所以第二个客户端连接超时并处于断开连接的状态。

总结

这些就是建立虚拟连接的过程:建立连接,过滤不是来自连接的计算机的数据包,检查断开连接,设定超时。

我们的建立的连接跟其他TCP连接一样,稳定的UDP数据包传输是多人动作游戏的基础。

目前为止,已经在UDP上建立虚拟的连接,你就可以使用它来进行多人游戏中的,client/server模式下的数据传输,来替代TCP。

python实现

还是推荐看,英文原文中的源代码

看完理论部分,下面我就用python根据上述原理实现:我写的UDP上实现虚拟连接只做了两件事:每次只能有一个socket和server进行通信;如果在一段时间无数据传输,则注销掉原来的连接,允许建立新的连接。

注:下面代码中很多细节没有处理,仅供大家参考。

  • protocol id:我打算用时间戳hash一个字符串
  • 监测断开连接:通过settimeout()方法,捕获socket.timeout异常

test_server.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#
#   Author  :   XueWeiHan
#   E-mail  :   595666367@qq.com
#   Date    :   16/5/11 下午3:54
#   Desc    :   serverimport socket
import timeUDP_IP = ''
UDP_PORT = 5000
_ID = []  # 存储建立连接的protocol_id
_IP = None  # 存储建立连接的IP和端口
TIME_OUT = 2  # 超时时间(s)sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((UDP_IP, UDP_PORT))
sock.settimeout(TIME_OUT)def check_protocol_id(protocol_id, _ID):'''检测protocol_id'''if _ID:if protocol_id in _ID:return Trueelse:return Falseelse:_ID.append(protocol_id)return Trueprint '准备接收内容。'
while 1:try:response = ''data, addr = sock.recvfrom(1024)  # 缓冲区大小为1024bytesprotocol_id, data = data.split('|')if _IP:if _IP == addr:response = '建立连接'print '从{ip}:{port},接收到内容:{data}'.format(ip=addr[0],port=addr[1], data=data)else:response = '无法建立连接'else:if check_protocol_id(protocol_id, _ID):_IP = addrresponse = '建立连接'print '从{ip}:{port},接收到内容:{data}'.format(ip=addr[0],port=addr[1], data=data)else:response = '无法建立连接'# 返回响应数据包给客户端sock.sendto(response, addr)except socket.timeout:print '连接超时,注销连接,其他socket可以连入'_IP = None_ID = []

test_client.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#
#   Author  :   XueWeiHan
#   E-mail  :   595666367@qq.com
#   Date    :   16/5/11 下午3:54
#   Desc    :   client
import socket
import time
import hashlibUDP_IP = ''
UDP_PORT = 5000
MESSAGE = 'Hello, world!'
TIME_OUT = 3print 'UDP 目标IP:', UDP_IP
print 'UDP 目标端口:', UDP_PORT
print '发送的内容:', MESSAGEclass Udp(object):def __init__(self):self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)self.server_addr = None# 设置超时时间self.socket.settimeout(TIME_OUT)@propertydef protocol_id(self):hash = hashlib.md5(str(time.time()) + 'xueweihan')return hash.hexdigest()def send_mesaage(self):# 这里只简单用|分割protocol_id和发送内容message = self.protocol_id +'|'+ MESSAGEself.socket.sendto(message, (UDP_IP, UDP_PORT))def get_message(self):data, addr = self.socket.recvfrom(1024)if self.server_addr:# 客户端也只接收建立连接的服务端的数据包if self.server_addr == addr:return dataelse:return Noneelse:self.server_addr = addrreturn datas1 = Udp()
for i in range(2):try:s1.send_mesaage()print s1.get_message()except socket.timeout:print '连接超时'# 清除原来建立连接的数据s1.server_addr = None        s2 = Udp()
for i in range(2):
# 此时是无法建立连接的,因为上一个连接还没有销毁try:s2.send_mesaage()print s2.get_message()except socket.timeout:print '连接超时'# 清除原来建立连接的数据s2.server_addr = None# 暂停2秒,等待服务器注销上一次的连接
time.sleep(2)
s3 = Udp()
for i in range(2):
# 此时是可以建立连接的,因为上面连接以超时try:s3.send_mesaage()print s3.get_message()except socket.timeout:print '连接超时'# 清除原来建立连接的数据s3.server_addr = None

上面代码还有很多不足的地方(TODO:超时,部分有问题),所以仅供参考。所有代码都在github上,代码运行效果如下:

网游中的网络编程3:在UDP上建立虚拟连接相关推荐

  1. 网游中的网络编程系列1:UDP vs. TCP

    原文:UDP vs. TCP,作者是Glenn Fiedler,专注于游戏网络编程相关工作多年. 目录 网游中的网络编程系列1:UDP vs. TCP 网游中的网络编程2:发送和接收数据包 网游中的网 ...

  2. 网游中针对网络延迟的优化

    在网络游戏中,由于延迟或者网络状况的抖动,可能会将客户端效果产生一定的扭曲和卡顿.影响玩家体验以及进一步的操作.要降低因为延迟带来的体验问题,有两种策略,一种为客户端预测,一种为延迟补偿. 1.客户端 ...

  3. java 中的网络编程(Socket、TCP三次握手四次挥手、TCP/UDP/URL)

    文章目录 前言 一.网络编程概述 二.网络通信要素概述 1.如何实现网络中的主机互相通信 2.网络通信协议 3.IP和端口号 4.InetAddress类 5.网络协议 6.TCP/IP协议簇 7.T ...

  4. 学习Java第十九天(一):1、Java中的网络编程

    1.Java中的网络编程 什么是网络? 在计算机领域中网络是信息传输,接收共享的虚拟平台,通过它把各个点,面,体的联系到一起,从而实现这些资源的共享. 作用:信息传输,资源共享 有点:资源共享免费 O ...

  5. 3D网游中人物的碰撞后反应控制

    人物移动控制是单机和网游中比较重要的部分,但前单机游戏使用动力学以及IK动画等已经达到了非常逼真的地步,在大型网络游戏中这样的物理模拟同步是很实现的,因此在目前多数网游中仍旧是采取使用一个包围体(盒子 ...

  6. 网络游戏中的网络编程

    谈谈网络游戏中的"网络"编程 ????近段时间总是有不少人问我关于完成端口模型的一些资料,很多时候其实我很郁闷,为什么大家会选择使用完成端口呢?或者说很多时候他们竟为了使用完成端口 ...

  7. Java中的网络编程类(TCPUDP)

    Java中的网络编程类 n Java.net包 – TCP协议 URL URLConnection Socket ServerSocket – UDP协议 DatagramPacket Datagra ...

  8. Android中的网络编程-黄俊东-专题视频课程

    Android中的网络编程-1902人已学习 课程介绍         剔除繁杂的理论,注重实践,深入浅出讲解Android中的网络编程 课程收益     每一个学员都能系统地掌握Android中的网 ...

  9. 第78节:Java中的网络编程(上)

    第78节:Java中的网络编程(上) 前言 网络编程涉及ip,端口,协议,tcp和udp的了解,和对socket通信的网络细节. 网络编程 OSI开放系统互连 网络编程指IO加网络 TCP/IP模型: ...

  10. 21天学会Java之(Java SE第十三篇):网络编程、TCP/UDP通信

    如今,计算机已经成为人们学习.工作.生活必不可少的工具.人们利用计算机可以和亲朋好友在网上聊天,玩网游或发邮件等,这些功能的实现都离不开计算机网络.计算机网络实现了不同计算机之间的通信,而这些必须依靠 ...

最新文章

  1. excel模糊匹配两列文字_Excel快速画出美观饼图
  2. 类variant解剖
  3. 关于Webview如何自动登录保存登录信息
  4. spring boot 中启用 https
  5. HTML5-寻路跟踪
  6. 8.对Hello World程序的深入
  7. Mac下Git项目使用的.gitignore文件
  8. 编程之美——数字哑谜
  9. jQuery获取所选单选按钮的值
  10. ScrumChina 2008 上海之行
  11. C - Bone Collector(背包问题)(01背包)
  12. freeswitch安装1.6
  13. 最齐全的骨头VRay材质球素材,速来收藏
  14. nginx的介绍和安装
  15. playsound playsound.PlaysoundException: Error 263 for command: open ./dataSet/warn.wav 指定的设备未打开,或不被
  16. APISpace 绕口令API接口 免费好用
  17. 安卓8.0 android系统,谷歌安卓8.0重磅来袭!国产定制系统有必要马上适配吗?
  18. 帝国cms php序号,帝国cms输出数字排序序号的列表
  19. 因上努力,果上随缘。
  20. Python Tkinter ListBox添加滚动条Scrollbar

热门文章

  1. linux系统交换分区的文件格式是,【简答题】Red Hat Linux中,交换分区的文件系统类型是什么,光盘文件的文件系统类型是什么?...
  2. 粒子群算法的惩罚函数的c语言实现,粒子群算法结合惩罚函数用于桥式起重机主梁优化.pdf...
  3. vue 通信PHP,Vue组件通信(详细教程)
  4. 回文数c语言构造思路,基础练习 回文数 C语言
  5. java图书购买系统的项目_JAVA入门第三季项目(图书查找系统)
  6. ajax如何获取复选框中的值_Web自动化测试:页面元素信息(属性)的获取
  7. 深入理解for循环(执行顺序)
  8. mongodb 系列 ~ mongo的两种引擎对比
  9. 杨晓峰-Java核心技术-9 HashMap Hashtable TreeMap MD
  10. HashMap、LinkedHashMap、HashTable、HashSet笔记