Archive

Posts Tagged ‘网络’

互联网技术比游戏后端技术领先十年吗?

May 20th, 2023 2 comments

最近时间线上又起了一场不大不小的论战,做互联网的人觉得游戏服务端发展很慢,同时互联网技术日新月异,似乎觉得互联网技术领先了游戏后端技术十年,这个结论显然是武断的,几位朋友也已经驳斥的很充分了,游戏服务端的同学实属没必要和这个互联网的人一般见识,本来就此打住也还挺好。

但最近两天事情似乎正在悄悄起变化,时间线上一直看到不停的有人跳出来,清一色的全在说互联网简单,什么做个电商不过就是 CRUD 的话也出来了,看的我也大跌眼镜,过犹不及吧。

今天更是又刷到有几位不管不顾就说什么游戏服务端领先互联网十年什么的,似乎这又要成为了另外一个极端了,那么有几点情况是不是也请正视一下:

1)游戏服务端足够复杂,但是发展太慢,祖传代码修修补补跑个十多年的不要太多。能用固然是好事,但没有新观念的引入,导致可用性和开发效率一直没有太多提升。

2)各自闭门造车,没有形成行业标准与合力,这个项目的代码,很难在另一个项目共享,相互之间缺少支持和协同。

3)互联网后端随便拎出一个服务来(包括各种 C/C++ 基建)大概率都没有游戏服务端复杂,但最近十年日新月异,形成了很强的互相组合互相增强的态势。

我上面指的是互联网基建项目,不是互联网 CRUD,互联网近十年的发展,让其整体可用性,效能,开发效率,都上了很多个台阶,不应一味忽视。

如果继续觉得游戏服务端领先互联网十年可以直接右转了,开放心态的话我也可以多聊一些(点击下方 more 阅读更多):

Read more…

Categories: 游戏开发, 网络编程 Tags: , ,

网游通信协议如何防止封包篡改?

February 20th, 2022 1 comment

第一层:协议非对称加密交换密钥,对称加密传输内容,保护好服务端私钥,防止中间人攻击。流式加密,同样包发两次内容不一样。

第二层:不用标准序列化工具如 protocolbuf,用修改版或者自己实现的。

第三层:客户端加密加壳防止调试和注入,程序签名防止篡改二进制。

第四层:重要代码放虚拟机或者脚本里运行(脚本字节码需需改),一般黑客主要分析反汇编,你复杂逻辑多套几层他就晕了。

第五层:关键数据不落内存,一律使用 getxx,setxx 之类的接口,后面将真实数据经过变换以后才落内存。

第六层:守护进程动态跟踪监控情况。

第七层:决定性逻辑永远放在服务端。

第八层:服务端定期校验消息合理性,比如十秒内最大的移动步长是多少,实际发上来的合理不合理,不合理就踢掉,比如按键点击频率是否超过正常人。

第九层:不定期弹出反外挂答题,答正确奖励经验,错误就掉线。

第十层:必须要放在客户端计算的内容将输入和结果 hash 同步给其他客户端验算,不对就踢掉。

第十一层:当检测到客户端触碰到某规则不要急着踢掉它,而是有概率被踢掉,还要随机几秒踢掉,这样黑客发现一会这里断一会那里断,就蒙圈了。

第十二层:发现某黑客/外挂工具利用某漏洞破解了游戏,先看影响大不大,再看他挣不挣钱,影响一般又不挣钱的话可以先养着他,等他挣钱了用户多了,大型活动之前,一条指令就把它封了,用户退款都可以弄得他爬不起来。

。。。。。

手机打字慢,先写这些,没有绝对安全,就是合理的策略加攻防成本。

Categories: 游戏开发 Tags:

新版瑞士军刀:socat

January 31st, 2021 No comments

我在《用好你的瑞士军刀:netcat》中介绍过 nc 和它的几个实现(bsd, gnu, nmap),netcat 还有一个最重要的变种 socat (socket cat),值得花一篇完整的文章介绍一下,它不仅语义统一,功能灵活,除了完成 nc 能完成的所有任务外,还有很多实用的用法:

基本命令就是:

socat [参数]  <地址1>  <地址2>

使用 socat 需要提供两个地址,然后 socat 做的事情就是把这两个地址的数据流串起来,把第左边地址的输出数据传给右边,同时又把右边输出的数据传到左边。

最简单的地址就是一个减号“-”,代表标准输入输出,而在命令行输入:

socat - -              # 把标准输入和标准输出对接,输入什么显示什么

就会对接标准输入和标准输出,你键盘敲什么屏幕上就显示什么,类似无参数的 cat 命令。除了减号地址外,socat 还支持:TCP, TCP-LISTEN, UDP, UDP-LISTEN, OPEN, EXEC, SOCKS, PROXY 等多种地址,用于端口监听、链接,文件和进程读写,代理桥接等等。

因此使用 socat 其实就是学习各类地址的定义及搭配方法,我们继续以实用例子开始。

网络测试

这个类似 nc 的连通性测试,两台主机到底网络能否联通:

socat - TCP-LISTEN:8080               # 终端1 上启动 server 监听 TCP
socat - TCP:localhost:8080            # 终端2 上启动 client 链接 TCP

在终端 1 上输入第一行命令作为服务端,并在终端 2 上输入第二行命令作为客户端去链接。

联通后在终端2上随便输入点什么,就能显示在终端1上,反之亦然,因为两条命令都是把标准输入输出和网络串起来,因此把两个地址交换一下也是等价的:

socat TCP-LISTEN:8080 -               # 终端1 上启动 server 监听 TCP
socat TCP:localhost:8080 -            # 终端2 上启动 client 链接 TCP

因为 socat 就是把左右两个地址的输入输出接在一起,因此颠倒左右两个地址影响不大,除非前面指明 -u 或者 -U 显示指明数据“从左到右”还是“从右到左”。

同 netcat 一样,如果客户端结束的话,服务端也会结束,但是 socat 还可以加额外参数:

socat - TCP-LISTEN:8080,fork,reuseaddr      # 终端1 上启动 server
socat - TCP:localhost:8080                  # 终端2 上启动 client

服务端在 TCP-LISTEN 地址后面加了 fork 的参数后,就能同时应答多个链接过来的客户端,每个客户端会 fork 一个进程出来进行通信,加上 reuseaddr 可以防止链接没断开玩无法监听的问题。

刚才也说了使用 socat 主要就是学习描述各种地址,那么想测试 UDP 的话修改一下就行:

socat - UDP-LISTEN:8080               # 终端1 上启动 server 监听 UDP
socat - UDP:localhost:8080            # 终端2 上启动 client 链接 UDP

即可进行测试。

端口转发

在主机上监听一个 8080 端口,将 8080 端口所有流量转发给远程机器的 80 端口:

socat TCP-LISTEN:8080,fork,reuseaddr  TCP:192.168.1.3:80

那么连到这台机器上 8080 端口的所有链接,相当于链接了 192.168.1.3 这台机器的 80 端口,命令中交换左右两个地址一样是等价的。

(点击 Read more 展开)

Read more…

Categories: 网络编程 Tags:

支持 Win10 的网络环境模拟(丢包,延迟,带宽)

April 13th, 2020 No comments

升级 Windows 10 以后,原来各种网络模拟软件都挂掉了,目前能用的就是只有 clumsy

唯一问题是不支持模拟带宽,那么平时要模拟一些糟糕的网络情况的话,是不太方便的,而开虚拟机用 Linux tc 或者设置个远程 linux 网关又很蛋疼,于是我顺便给他加了个带宽模拟功能:

注意最下面的 “Bandwidth” 选项,打上勾的话,就能顺利限速了,注意上面的 Filtering 需要填写正确的 WinDivert 规则。

注意,统计包大小时用的是整个 IP 包的大小(包括各种协议头),所以你设置成 500 KB/s 的话,实际按 tcp 计算的下载速率会略小。

二进制下载:

想自己检查自己编译的话:

欢迎 PR。

Categories: 网络编程 Tags:

用好你的瑞士军刀/netcat

September 24th, 2019 No comments

Netcat 号称 TCP/IP 的瑞士军刀并非浪得虚名,以体积小(可执行 200KB)功能灵活而著称,在各大发行版中都默认安装,你可以用它来做很多网络相关的工作,熟练使用它可以不依靠其他工具做一些很有用的事情。

最初作者是叫做“霍比特人”的网友 Hobbit hobbit@avian.org 于 1995 年在 UNIX 上以源代码的形式发布,Posix 版本的 netcat 主要有 GNU 版本的 netcat 和 OpenBSD 的 netcat 两者都可以在 debian/ubuntu 下面安装,但是 Windows 下面只有 GNU 版本的 port。

不管是程序员还是运维,熟悉这个命令都可以让很多工作事半功倍,然而网上基本 90% 的 netcat 文章说的都是老版本的 OpenBSD 的 netcat,已经没法在主流 linux 上使用了,所以我们先要检查版本:

在 debian/ubuntu 下面:

readlink -f $(which nc)

看看,结果会有两种:

  • /bin/nc.traditional: 默认 GNU 基础版本,一般系统自带。
  • /bin/nc.openbsd: openbsd 版本,强大很多。

都可以用 apt-get install nc-traditional 或者 apt-get install nc-openbsd 来选择安装。不管是 gnu 版本还是 openbsd 版本,都有新老的区别,主要是传送文件时 stdin 发生 EOF 了,老版本会自动断开,而新的 gnu/openbsd 还会一直连着,两年前 debian jessie 时统一升过级,导致网上的所有教程几乎同时失效。

下面主要以最新的 GNU 版本为主同时对照更强大的 openbsd 版本进行说明。

端口测试

你在服务器 A主机(192.168.1.2) 上面 8080 端口启动了一个服务,有没有通用的方法检测服务的 TCP 端口是否启动成功?或者在 B 主机上能不能正常访问该端口?

进一步,如果而 A 主机上用 netstat -an 发现端口成功监听了,你在 B 主机上的客户端却无法访问,那么到底是服务错误还是网络无法到达呢?我们当然可以在 B 主机上用 telnet 探测一下:

telnet 192.168.1.2 8080

但 telnet 并不是专门做这事情的,还需要额外安装,所以我们在 B 主机上用 netcat:

nc -vz 192.168.1.2 8080

即可,v 的意思是显示多点信息(verbose),z 代表不发送数据。那么如果 B 主机连不上 A 主机的 8080 端口,此时你就该检查网络和安全设置了,如果连的上那么再去查服务日志去。

nc 命令后面的 8080 可以写成一个范围进行扫描:

nc -v -v -w3 -z 192.168.1.2 8080-8083

两次 -v 是让它报告更详细的内容,-w3 是设置扫描超时时间为 3 秒。

传输测试

你在配置 iptable 或者安全组策略,禁止了所有端口,但是仅仅开放了 8080 端口,你想测试一下该设置成功与否怎么测试?安装个 nginx 改下端口,外面再用 chrome 访问下或者 telnet/curl 测试下??还是 python -m 启动简单 http 服务 ?其实不用那么麻烦,在需要测试的 A 主机上:

nc -l -p 8080

这样就监听了 8080 端口,然后在 B 主机上连接过去:

nc 192.168.1.2 8080

两边就可以会话了,随便输入点什么按回车,另外一边应该会显示出来,注意,openbsd 版本 netcat 用了 -l 以后可以省略 -p 参数,写做:nc -l 8080 ,但在 GNU netcat 下面无法运行,所以既然推荐写法是加上 -p 参数,两个版本都通用。

老版本的 nc 只要 CTRL+D 发送 EOF 就会断开,新版本一律要 CTRL+C 结束,不管是服务端还是客户端只要任意一边断开了,另一端也就结束了,但是 openbsd 版本的 nc 可以加一个 -k 参数让服务端持续工作。

那么你就可以先用 nc 监听 8080 端口,再远端检查可用,然后又再次随便监听个 8081 端口,远端检测不可用,说明你的安全策略配置成功了,完全不用安装任何累赘的服务。

(点击 Read more 展开)

Read more…

Categories: 网络编程 Tags:

如何在高丢包率的链路上建立低延迟连接?

May 20th, 2016 No comments

这是其实通信领域的话题了,低延迟传输有上百种优化方式,上面说的那些冗余码只是很小一部分,不考虑信道容量的冗余编码系统都是在耍流氓,不用等到同信道内跑两套这样的协议你才会发现问题,一套协议再接近信道带宽容量限制时,就会出现指数上升的丢包率,所以不考虑带宽检测的冗余法就是一个残次品。

要系统的解决低延迟传输问题,需要同时在传输层,协议层,路由层,应用层几个方面着手:

传输层带外冗余:弱智重复法

设你要发送的数据为 x1-xn,你实际发送出去的包为 p1-pn,那么比如 Pn = [Xn, Xn-1, Xn-2],重复前面出现过的 1-2个数据,丢包了你可以随时恢复出来。

传输层带外冗余:异或法

每发四个包 x1-x4,你多发一个冗余包,内容为前面四个包的异或: R = x1 ^ x2 ^ x3 ^ x4,那么本组数据五个数据包(x1-x4, R)中任意丢失一个,都可以从其他四个异或得到。当然,不一定要四个包冗余一个,你可以根据情况和丢包率,两个包或者三个包冗余一个。

传输层带外冗余:解方程法

把每个数据包看成一个整数,要发送 x1-x4 四个包,并不直接发送x,而是将他们进行线性运算得到 y1-y7,然后发送出去:

A1 * x1 + B1 * x2 + C1 * x3 + D1 * x4 = y1   收到
A2 * x1 + B2 * x2 + C2 * x3 + D2 * x4 = y2   收到
A3 * x1 + B3 * x2 + C3 * x3 + D3 * x4 = y3   丢失
A4 * x1 + B4 * x2 + C4 * x3 + D4 * x4 = y4   收到
A5 * x1 + B5 * x2 + C5 * x3 + D5 * x4 = y5   丢失
A6 * x1 + B6 * x2 + C6 * x3 + D6 * x4 = y6   收到
A7 * x1 + B7 * x2 + C7 * x3 + D7 * x4 = y7   丢失

接收方收到一组数据以后,发现y3, y5, y7丢失,得到:

A1 * x1 + B1 * x2 + C1 * x3 + D1 * x4 = y1   
A2 * x1 + B2 * x2 + C2 * x3 + D2 * x4 = y2   
A4 * x1 + B4 * x2 + C4 * x3 + D4 * x4 = y4   
A6 * x1 + B6 * x2 + C6 * x3 + D6 * x4 = y6   

如果 y1-y7 在发送的过程中丢失了三个:y3, y5, y7 观察等式。变量任然有4个,参数也任然有四个A1, A2, A4, A6, B?, C?, D? 四个变量,四个等式,那么我们可以通过解方程求出x1-x4,这就是一个矩阵运算和求逆的过程,也就是俗称的 Read-Solomon 冗余码的基本原理(PS: shorthair/long hair 两个库写的很烂呀),实际传输时需要根据网络质量来选择每组数据和冗余包的比例。

传输层网络评估

需要一套比较强大的系统,实时评估当前网络质量(RTT, 丢包率,抖动值,可用带宽),为协议决策提供参考,比如这时一个带宽很大,延迟却很高的信道呢?还是带宽很小,延迟也很小的信道呢?不同的情况对应不同的策略,当前丢包是常规丢包?震荡性丢包?还是接近信道限制出现无可挽回的丢包?再根据当前协议出于什么情况?交互模式还是单向传输模式,来给出最佳的传输策略。不考虑这些情况的协议,都是比较弱智的(比如楼上提到的几种)。

协议层决策模型

根据不同的情况,来推导不同的数据包丢失后,对整体协议的影响,从而通过马科夫决策过程,来找到哪个包丢失的代价最大,以此来指导冗余包的生成过程:

Read more…

Categories: 网络编程 Tags: ,

KCP同 UDT/ENET的性能比较

February 18th, 2016 9 comments

如果不丢包那么 KCP(https://github.com/skywind3000/kcp)和 TCP性能差不多,KCP不会有任何优势,但是网络会卡,造成卡的原因就是丢包和抖动,有同学在内网这样好的环境下没有用任何丢包模拟直接跑,跑出来的数据是差不多的,但是放到公网上,放到3G/4G网络情况下,差距就很明显了,公网在高峰期有平均接近10%的丢包,wifi/3g/4g下更糟糕,这正是造成各种网络卡顿的元凶。

感谢 asio-kcp 的作者 zhangyuan 对 KCP 与 enet, udt做过的一次横向评测,结论如下:

  • ASIO-KCP has good performace in wifi and phone network(3G, 4G).
  • Extra using 20% ~ 50% network flow for speed improvement.
  • The kcp is the first choice for realtime pvp game.
  • The lag is less than 1 second when network lag happen. 3 times better than enet when lag happen.
  • The enet is a good choice if your game allow 2 second lag.
  • UDT is a bad idea. It always sink into badly situation of more than serval seconds lag. And the recovery is not expected.
  • enet has the problem of lack of doc. And it has lots of functions that you may intrest. kcp’s doc is chinese. Good thing is the function detail which is writen in code is english. And you can use asio_kcp which is a good wrap.
  • The kcp is a simple thing. You will write more code if you want more feature.
  • UDT has a perfect doc. UDT may has more bug than others as I feeling.

具体见:横向比较这里。截取一段在网络糟糕时,asio-kcp/enet的延迟数据:

worst network lag happen:
asio: 10:51.21
291  295   269   268   231   195   249   230   225   204

enet: 10:51.21
1563   1520    1470    1482    1438    1454    1412    1637    1588    1540

我当年主要测试了 KCP和 TCP/UDT的比较,扫了一眼 libenet觉得协议实现中规中矩,缺乏很多现代传输协议的技术,所以并没有详细测试。而 asio-kcp的作者同时给出了KCP/enet/udt三者的详细比较,为更多犹豫选择使用那一套的人提供了更多指引。

Categories: 网络编程 Tags: ,

网络程序计时器通常用啥实现?

July 26th, 2015 No comments

通常来讲,就是利用 select 的空余时间,来进行时钟检查,不管是 select / poll / epoll/ kevent,以下统称 select,它有一个等待时间作为参数,即没有事件时,最多 wait 多少时间,我们把这个作为网络库的基准频率,比如 10MS,或者 20MS, 25MS, 50MS,都是常用的几个值。

就是说网络库调用 select 等待事件时如果没有事件,那么最长等待 10MS 就返回了,这时再处理完所有网络事件后,就可以来处理时钟数据了。事件处理函数就是这样:

def update_events(milisec = 10):
    result = selector.select(milisec)
    for fd, event in result:
        do something with socket event
    current = time.time()
    update_timer(current)

while 1:
    WAIT_MILLISEC = 10
    update_events(WAIT_MILLISEC)

关键就是这个两次 select 中间 update_timer 的任务:集合中检查需要唤醒的时钟,并且调用它们的回调函数,来驱动整个服务器的时钟运行,以最简单的扫描法为例:

def update_timer (current):
    for timer in available_timers:
         while current >= timer.expires:
             timer.callback(current)
             timer.expires += timer.period

available_timers 记录着当前可用的所有 timer 的集合,expires 是他们需要被触发的时间,如果当前时间大于等于这个 expires,认为该 timer 需要被触发到。注意 timer.expires 更新的时候是 += 周期,而不是 = current + 周期,后者会导致误差积累,长时间运行后偏差越来越大。同时这里需要 while,因为可能跨越两个以上周期,当然只运行一次的 timer 就不需要了,这里只是简化下。

比如 libevent 里面的主循环 event_base_loop 每次 select 完毕后就调用一次 timeout_process。

这就是 Timer 调度的基本原理。

可能你会发现每次 select 结束都要扫描整个 available_timers 集合,是一个非常费时间的事情,那么首先想到的就是优先队列了:将 Timer 节点按照 expires 的先后顺序,将最快要发生的超时节点放在前面,每次检测队列头就可以判断是否超时了。

Read more…

Categories: 编程技术, 网络编程 Tags:
Wordpress Social Share Plugin powered by Ultimatelysocial